Restore-UDCData: handle ArchivedData-only backups (no CurrentData.json)

Production case: bay 3207 had ArchivedData\ on the share with full
production records but no CurrentData.json at the bay root. The previous
Restore logic treated CurrentData.json as the marker for "valid backup"
and exited early when absent, so the script silently no-op'd every cycle
even though there was real archive data ready to restore.

Asymmetric with Backup-UDCData.ps1, which already handles missing
CurrentData.json gracefully (it copies whatever exists). Possible causes
of CurrentData.json absence in a backup: source PC had no live UDC
session at backup time (UDC inactive / not recording), backup partially
failed for that one file (no Backup-side log to confirm without rerun).

Either way, an ArchivedData-only backup is still a valid backup.

Behavior change:
- Early-exit only when BOTH CurrentData.json AND ArchivedData\ are
  absent. Otherwise proceed with whatever exists.
- Copy step for CurrentData.json wrapped in srcCurExists guard.
- consumeOk now requires: every present source successfully copied,
  AND at least one thing was actually copied.
- Move-to-migrated wraps CurrentData.json move in Test-Path guard
  (was already guarded for ArchivedData).
- restore.manifest.json gains CurrentDataPresent and ArchivedDataPresent
  booleans so future audits can see which side actually restored.
- UDC relaunch now fires when EITHER copy succeeded (was only on
  CurrentData.json copy).

Verbose logs now distinguish three cases at the early-exit:
- Both absent: "no work to do this cycle" (the 99% path)
- Only ArchivedData\: WARN with explanation, proceed
- Only CurrentData.json: WARN with explanation, proceed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-04-29 14:49:38 -04:00
parent 7be5518fd7
commit 4f4f1f43e8

View File

@@ -163,11 +163,17 @@ Log " CurrentData.json src: $(if ($srcCurExists) { 'present' } else { 'absent'
$srcArcExists = Test-Path -LiteralPath $srcArc $srcArcExists = Test-Path -LiteralPath $srcArc
Log " ArchivedData/ src: $(if ($srcArcExists) { 'present' } else { 'absent' }) - $srcArc" Log " ArchivedData/ src: $(if ($srcArcExists) { 'present' } else { 'absent' }) - $srcArc"
if (-not $srcCurExists) { if (-not $srcCurExists -and -not $srcArcExists) {
Log "No backup waiting for bay $mn - no work to do this cycle." Log "No backup waiting for bay $mn (neither CurrentData.json nor ArchivedData\ at bay root) - no work to do this cycle."
Log 'Exit 0.' Log 'Exit 0.'
exit 0 exit 0
} }
if (-not $srcCurExists) {
Log "Partial backup waiting (ArchivedData\ present, CurrentData.json absent). Will restore ArchivedData\ only. Source PC may have had no live UDC session at backup time, or backup partially failed." 'WARN'
}
if (-not $srcArcExists) {
Log "Partial backup waiting (CurrentData.json present, ArchivedData\ absent). Will restore CurrentData.json only." 'WARN'
}
# -- We have a backup. Restore. ------------------------------------------ # -- We have a backup. Restore. ------------------------------------------
Log "Backup waiting at $bayDir - proceeding with restore" Log "Backup waiting at $bayDir - proceeding with restore"
@@ -203,19 +209,23 @@ if (-not (Test-Path -LiteralPath $UdcDataDir)) {
$localCur = Join-Path $UdcDataDir 'CurrentData.json' $localCur = Join-Path $UdcDataDir 'CurrentData.json'
$localArc = Join-Path $UdcDataDir 'ArchivedData' $localArc = Join-Path $UdcDataDir 'ArchivedData'
# Copy CurrentData.json # Copy CurrentData.json (only if present at source)
$copiedCur = $false $copiedCur = $false
Log "Copying CurrentData.json" if ($srcCurExists) {
Log " src: $srcCur" Log "Copying CurrentData.json"
Log " dst: $localCur" Log " src: $srcCur"
try { Log " dst: $localCur"
Copy-Item -LiteralPath $srcCur -Destination $localCur -Force -ErrorAction Stop try {
$copiedCur = $true Copy-Item -LiteralPath $srcCur -Destination $localCur -Force -ErrorAction Stop
$sz = (Get-Item -LiteralPath $localCur).Length $copiedCur = $true
Log " OK ($sz bytes)" $sz = (Get-Item -LiteralPath $localCur).Length
} catch { Log " OK ($sz bytes)"
Log " FAILED" 'ERROR' } catch {
LogErr $_ Log " FAILED" 'ERROR'
LogErr $_
}
} else {
Log "CurrentData.json not present in backup - skipping that copy step"
} }
# Copy ArchivedData/ # Copy ArchivedData/
@@ -245,9 +255,14 @@ if ($srcArcExists) {
Log "ArchivedData/ not present in backup - skipping that copy step" Log "ArchivedData/ not present in backup - skipping that copy step"
} }
# One-shot consumption: only move backup -> migrated/ if everything required succeeded # One-shot consumption: only consume when every present source has been
$consumeOk = ($copiedCur -and ($copiedArc -or -not $srcArcExists)) # successfully copied. If a source was absent we don't fault on it; if a
Log "consumeOk=$consumeOk (copiedCur=$copiedCur, copiedArc=$copiedArc, srcArcExists=$srcArcExists)" # source was present but copy failed, we leave the live backup for retry.
# Must have copied at least one thing to consume.
$consumeOk = (($copiedCur -or -not $srcCurExists) -and `
($copiedArc -or -not $srcArcExists) -and `
($copiedCur -or $copiedArc))
Log "consumeOk=$consumeOk (copiedCur=$copiedCur, copiedArc=$copiedArc, srcCurExists=$srcCurExists, srcArcExists=$srcArcExists)"
if ($consumeOk) { if ($consumeOk) {
try { try {
@@ -258,8 +273,10 @@ if ($consumeOk) {
if (-not (Test-Path -LiteralPath $migDir)) { New-Item -ItemType Directory -Path $migDir -Force | Out-Null } if (-not (Test-Path -LiteralPath $migDir)) { New-Item -ItemType Directory -Path $migDir -Force | Out-Null }
if (-not (Test-Path -LiteralPath $migStamp)) { New-Item -ItemType Directory -Path $migStamp -Force | Out-Null } if (-not (Test-Path -LiteralPath $migStamp)) { New-Item -ItemType Directory -Path $migStamp -Force | Out-Null }
Move-Item -LiteralPath $srcCur -Destination (Join-Path $migStamp 'CurrentData.json') -Force -ErrorAction Stop if (Test-Path -LiteralPath $srcCur) {
Log " moved CurrentData.json" Move-Item -LiteralPath $srcCur -Destination (Join-Path $migStamp 'CurrentData.json') -Force -ErrorAction Stop
Log " moved CurrentData.json"
}
if (Test-Path -LiteralPath $srcArc) { if (Test-Path -LiteralPath $srcArc) {
Move-Item -LiteralPath $srcArc -Destination (Join-Path $migStamp 'ArchivedData') -Force -ErrorAction Stop Move-Item -LiteralPath $srcArc -Destination (Join-Path $migStamp 'ArchivedData') -Force -ErrorAction Stop
Log " moved ArchivedData/" Log " moved ArchivedData/"
@@ -275,7 +292,9 @@ if ($consumeOk) {
DestinationHostname = $env:COMPUTERNAME DestinationHostname = $env:COMPUTERNAME
DestinationUser = $whoami DestinationUser = $whoami
MachineNumber = $mn MachineNumber = $mn
CurrentDataBytes = (Get-Item -LiteralPath $localCur).Length CurrentDataPresent = $copiedCur
CurrentDataBytes = if ($copiedCur) { (Get-Item -LiteralPath $localCur).Length } else { 0 }
ArchivedDataPresent = $copiedArc
ArchivedDataFiles = $arcFiles ArchivedDataFiles = $arcFiles
ArchivedDataBytes = $arcBytes ArchivedDataBytes = $arcBytes
RestoredVia = 'Restore-UDCData.ps1 (manifest engine, on logon)' RestoredVia = 'Restore-UDCData.ps1 (manifest engine, on logon)'
@@ -295,7 +314,7 @@ if ($consumeOk) {
# Relaunch UDC with the current machine number args. UDC's vendor autostart in # Relaunch UDC with the current machine number args. UDC's vendor autostart in
# HKLM\Run will also fire on the next user logon, so this is belt-and-suspenders # HKLM\Run will also fire on the next user logon, so this is belt-and-suspenders
# for the same-session case (e.g. tech is at the keyboard during the restore). # for the same-session case (e.g. tech is at the keyboard during the restore).
if ((Test-Path -LiteralPath $UdcExePath) -and $copiedCur) { if ((Test-Path -LiteralPath $UdcExePath) -and ($copiedCur -or $copiedArc)) {
Log "Relaunching UDC.exe: `"$Site`" -$mn" Log "Relaunching UDC.exe: `"$Site`" -$mn"
try { try {
Start-Process -FilePath $UdcExePath -ArgumentList @("`"$Site`"", "-$mn") Start-Process -FilePath $UdcExePath -ArgumentList @("`"$Site`"", "-$mn")