Files
pxe-server/playbook/shopfloor-setup/Standard/Restore-UDCData.ps1
cproudlock e169f8d0f5 Standard-Machine: UDC backup/restore use ArchivedData (not ArchiveData)
UDC's per-bay archive directory is C:\ProgramData\UDC\ArchivedData, not
ArchiveData. The previous spelling was a typo introduced when the scripts
were first written; it would have meant Backup-UDCData.ps1 found no archive
content (silent zero-file backups), and Restore-UDCData.ps1 wrote into a
location UDC does not read from.

Path swap is straight string replacement across both scripts plus the .bat
wrapper's usage comment. Manifest field names in backup.manifest.json /
restore.manifest.json (ArchivedDataPresent, ArchivedDataFiles,
ArchivedDataBytes) updated to match.

Update-MachineNumber.ps1's parallel UDC-restore branch (still uncommitted
in a prior workstream) has the same fix in the working tree, captured in
that branch's eventual commit.

The v2 share-staged copy at tsgwp00525-v2\standard-machine\scripts\
Restore-UDCData.ps1 also got the fix and is ready for push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:45:39 -04:00

192 lines
7.9 KiB
PowerShell

# Restore-UDCData.ps1 - Idempotent UDC data restore for the manifest engine.
#
# Triggered by the GE Shopfloor Enforce scheduled task (runs as SYSTEM, every
# user logon + every 5 min). Standard-machine manifest entry uses
# DetectionMethod=Always so this fires every cycle; the script self-decides
# whether there's actually any work to do.
#
# CONTRACT:
# - 99% of cycles: no backup waiting -> exit 0 in ~1 second, no side effects
# - 1 cycle (the one after Backup-UDCData lands a backup for this PC's bay):
# stop UDC, copy CurrentData.json + ArchivedData/ to C:\ProgramData\UDC,
# move consumed backup to <bay>\migrated\<timestamp>\, write
# restore.manifest.json, restart UDC. After this, root is empty so the
# check returns "no backup waiting" again on subsequent cycles.
#
# DESIGNED FOR THE SWAP WORKFLOW:
# New PC gets pre-imaged with real machine number + locked down, sits in
# storage. Days/weeks later, tech runs Backup-UDCData on old PC -> backup
# lands on share. Tech swaps PCs. New PC powers on at the bay -> ShopFloor
# autologon -> manifest engine fires this script -> backup detected ->
# restored -> UDC opens with prior history intact.
#
# Replaces the placeholder->real trigger in Update-MachineNumber.ps1 for the
# pre-imaged-then-swapped case (where the trigger fired at imaging time, before
# the backup existed). Update-MachineNumber.ps1's branch still handles the
# secondary case (tech used 9999 placeholder + sets number at bay) - both
# triggers safely no-op if the other already consumed the backup.
[CmdletBinding()]
param(
[string]$BackupShareRoot = '\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\backup\udc',
[string]$UdcDataDir = 'C:\ProgramData\UDC',
[string]$UdcExePath = 'C:\Program Files\UDC\UDC.exe',
[string]$UdcSettingsPath = 'C:\ProgramData\UDC\udc_settings.json',
[string]$Site = 'West Jefferson'
)
$ErrorActionPreference = 'Stop'
$logDir = 'C:\Logs\UDC'
if (-not (Test-Path $logDir)) {
try { New-Item -Path $logDir -ItemType Directory -Force | Out-Null } catch { $logDir = $env:TEMP }
}
$logFile = Join-Path $logDir 'Restore-UDCData.log'
function Log {
param([string]$Msg, [string]$Level = 'INFO')
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
$line = "[$ts][$Level] $Msg"
Add-Content -LiteralPath $logFile -Value $line
Write-Host $line
}
# --- Resolve local machine number ---
if (-not (Test-Path -LiteralPath $UdcSettingsPath)) {
# No UDC installed yet. Manifest engine will catch up later if/when it lands.
exit 0
}
try {
$json = Get-Content -LiteralPath $UdcSettingsPath -Raw | ConvertFrom-Json
$mn = $json.GeneralSettings.MachineNumber
} catch {
Log "Failed to parse $UdcSettingsPath : $_" 'ERROR'
exit 0
}
if (-not $mn -or $mn -eq '9999' -or $mn -notmatch '^\d+$') {
# Placeholder or invalid - the placeholder->real trigger in
# Update-MachineNumber.ps1 will catch it when the tech sets a real number.
exit 0
}
# --- Probe for a waiting backup ---
$bayDir = Join-Path $BackupShareRoot $mn
$srcCur = Join-Path $bayDir 'CurrentData.json'
$srcArc = Join-Path $bayDir 'ArchivedData'
if (-not (Test-Path -LiteralPath $srcCur)) {
# Most-common path: no backup waiting. Exit silently to keep enforce-cycle
# logs clean. (Manifest engine still records that the entry ran.)
exit 0
}
# We have a backup. From here on, log everything.
Log "==============================================="
Log "UDC data backup detected at $bayDir - restoring."
Log "Hostname: $env:COMPUTERNAME"
Log "Machine number: $mn"
# --- Stop UDC.exe so CurrentData.json isn't locked ---
Get-Process UDC -ErrorAction SilentlyContinue | ForEach-Object {
try {
$_.Kill()
$_.WaitForExit(5000) | Out-Null
Log "Stopped existing UDC.exe (PID $($_.Id))"
} catch {
Log "Could not stop UDC.exe (PID $($_.Id)): $_" 'WARN'
}
}
Start-Sleep -Milliseconds 500
# --- Ensure local UDC data dir exists ---
if (-not (Test-Path -LiteralPath $UdcDataDir)) {
New-Item -ItemType Directory -Path $UdcDataDir -Force | Out-Null
}
$localCur = Join-Path $UdcDataDir 'CurrentData.json'
$localArc = Join-Path $UdcDataDir 'ArchivedData'
# --- Copy CurrentData.json ---
$copiedCur = $false
try {
Copy-Item -LiteralPath $srcCur -Destination $localCur -Force -ErrorAction Stop
$copiedCur = $true
Log "Copied CurrentData.json ($((Get-Item $localCur).Length) bytes)"
} catch {
Log "Copy CurrentData.json failed: $_" 'ERROR'
}
# --- Copy ArchivedData/ ---
$copiedArc = $false
$arcFiles = 0
$arcBytes = 0
if (Test-Path -LiteralPath $srcArc) {
try {
if (Test-Path -LiteralPath $localArc) {
Remove-Item -LiteralPath $localArc -Recurse -Force -ErrorAction SilentlyContinue
}
Copy-Item -LiteralPath $srcArc -Destination $localArc -Recurse -Force -ErrorAction Stop
$arcItems = Get-ChildItem -LiteralPath $localArc -Recurse -File -ErrorAction SilentlyContinue
$arcFiles = $arcItems.Count
$arcBytes = ($arcItems | Measure-Object Length -Sum).Sum
$copiedArc = $true
Log "Copied ArchivedData/ ($arcFiles files, $arcBytes bytes)"
} catch {
Log "Copy ArchivedData/ failed: $_" 'ERROR'
}
}
# --- One-shot consumption: only move backup -> migrated/ if BOTH copies succeeded ---
# (If one failed, leave the live backup in place so the next cycle can retry.)
$consumeOk = ($copiedCur -and ($copiedArc -or -not (Test-Path -LiteralPath $srcArc)))
if ($consumeOk) {
try {
$stamp = Get-Date -Format 'yyyy-MM-ddTHH-mm-ssZ'
$migDir = Join-Path $bayDir 'migrated'
$migStamp = Join-Path $migDir $stamp
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 }
Move-Item -LiteralPath $srcCur -Destination (Join-Path $migStamp 'CurrentData.json') -Force -ErrorAction Stop
if (Test-Path -LiteralPath $srcArc) {
Move-Item -LiteralPath $srcArc -Destination (Join-Path $migStamp 'ArchivedData') -Force -ErrorAction Stop
}
$bakManifest = Join-Path $bayDir 'backup.manifest.json'
if (Test-Path -LiteralPath $bakManifest) {
Move-Item -LiteralPath $bakManifest -Destination (Join-Path $migStamp 'backup.manifest.json') -Force -ErrorAction SilentlyContinue
}
$restoreManifest = [ordered]@{
RestoredAt = (Get-Date -Format 'o')
DestinationHostname = $env:COMPUTERNAME
DestinationUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
MachineNumber = $mn
CurrentDataBytes = (Get-Item $localCur).Length
ArchivedDataFiles = $arcFiles
ArchivedDataBytes = $arcBytes
RestoredVia = 'Restore-UDCData.ps1 (manifest engine, on logon)'
}
$restoreManifest | ConvertTo-Json | Set-Content -Path (Join-Path $migStamp 'restore.manifest.json') -Encoding UTF8
Log "Backup consumed -> migrated\$stamp\"
} catch {
Log "Move-to-migrated failed (data IS restored locally, but live backup remains - next cycle will retry consumption): $_" 'ERROR'
}
} else {
Log "Restore incomplete - leaving live backup at $bayDir for retry next cycle." 'WARN'
}
# --- Relaunch UDC with the current machine number args so it picks up the
# restored CurrentData.json. UDC's vendor autostart in 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). ---
if ((Test-Path -LiteralPath $UdcExePath) -and $copiedCur) {
try {
Start-Process -FilePath $UdcExePath -ArgumentList @("`"$Site`"", "-$mn")
Log "Relaunched UDC.exe with `"$Site`" -$mn"
} catch {
Log "UDC relaunch failed: $_" 'WARN'
}
}
Log "==============================================="
exit 0