Private/Invoke-KadenRepairResume.ps1

function Invoke-KadenRepairResume {
    <#
    .SYNOPSIS
        Finish an interrupted update using only the journal's recorded payload.
    .DESCRIPTION
        Refuses when the installed module version or payload digest differs. It does not copy a newer payload in place of the recorded one.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ChildRoot,
        [Parameter(Mandatory = $true)]
        $OpenJournal
    )

    $journal = $OpenJournal.Journal
    if ($OpenJournal.Broken -or -not $journal) {
        throw 'KDN-TXN-MATCHING-PAYLOAD-MISSING: The interrupted journal cannot be read, so resume will not apply a different payload.'
    }

    $payloadRoot = Get-KadenPayloadRoot
    $manifestPath = Join-Path $payloadRoot 'kaden-managed-files.json'
    $manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
    $installedVersion = [string](Get-Module -Name Kaden -ErrorAction SilentlyContinue).Version
    if ([string]::IsNullOrWhiteSpace($installedVersion)) {
        $psd1 = Join-Path $script:KadenModuleRoot 'Kaden.psd1'
        $installedVersion = [string](Import-PowerShellDataFile -Path $psd1).ModuleVersion
    }
    $installedDigest = [string]$manifest.payloadDigest
    if ([string]$journal.payloadVersion -ne $installedVersion -or [string]$journal.payloadDigest -ne $installedDigest) {
        throw 'KDN-TXN-MATCHING-PAYLOAD-MISSING: The installed module does not contain the recorded payload version and digest. Resume will not apply a different payload.'
    }

    $operations = @($journal.operations)
    $sources = @()
    foreach ($op in $operations) {
        if ([string]$op.verificationStatus -eq 'verified' -and [string]$op.replacementStatus -eq 'complete') {
            $destPath = Resolve-KadenContainedPath -Root $ChildRoot -RelativePath ([string]$op.destination)
            $onDisk = (Test-Path -LiteralPath $destPath) -and ((Get-KadenFileDigest -LiteralPath $destPath) -eq [string]$op.expectedDigest)
            if ($onDisk) {
                $sources += $null
                continue
            }
        }
        $source = $null
        if ($op.stagedPath -and (Test-Path -LiteralPath ([string]$op.stagedPath))) {
            $source = [string]$op.stagedPath
        }
        else {
            $entry = @($manifest.files | Where-Object { [string]$_.destination -eq [string]$op.destination } | Select-Object -First 1)
            if ($entry.Count -lt 1) {
                throw 'KDN-TXN-MATCHING-PAYLOAD-MISSING: The recorded managed file is not in the installed payload. Resume will not apply a different payload.'
            }
            $source = Resolve-KadenContainedPath -Root $payloadRoot -RelativePath ([string]$entry[0].source)
        }
        $digest = Get-KadenFileDigest -LiteralPath $source
        if ($digest -ne [string]$op.expectedDigest) {
            throw 'KDN-TXN-MATCHING-PAYLOAD-MISSING: The recorded file digest is not in the installed payload. Resume will not apply a different payload.'
        }
        $sources += $source
    }

    $copiedIndex = @()
    try {
        for ($i = 0; $i -lt $operations.Count; $i++) {
            if (-not $sources[$i]) { continue }
            $op = $operations[$i]
            $destPath = Resolve-KadenContainedPath -Root $ChildRoot -RelativePath ([string]$op.destination)
            $destDir = Split-Path -Parent $destPath
            if (-not (Test-Path -LiteralPath $destDir)) {
                New-Item -ItemType Directory -Path $destDir -Force | Out-Null
            }
            Copy-Item -LiteralPath $sources[$i] -Destination $destPath -Force
            $copiedIndex += $i
            $actual = Get-KadenFileDigest -LiteralPath $destPath
            if ($actual -ne [string]$op.expectedDigest) {
                throw "KDN-TXN-PREFLIGHT-FAILED: digest mismatch after resuming '$($op.destination)'."
            }
            $op.replacementStatus = 'complete'
            $op.verificationStatus = 'verified'
        }

        $projectId = $null
        if ($journal.stateBefore -and $journal.stateBefore.projectId) {
            $projectId = [string]$journal.stateBefore.projectId
        }
        Write-KadenSharedState -ChildRoot $ChildRoot -ManagedContentVersion ([string]$journal.payloadVersion) -ProjectId $projectId -InstallationStatus 'complete'
        Complete-KadenOpenJournal -OpenJournal $OpenJournal -Operations $operations
    }
    catch {
        foreach ($copied in @($copiedIndex)) {
            $operations[$copied].replacementStatus = 'complete'
        }
        if (@($copiedIndex).Count -gt 0) {
            Restore-KadenManagedFile -ChildRoot $ChildRoot -Operations $operations
        }
        throw
    }
}