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 } } |