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>
213 lines
8.5 KiB
PowerShell
213 lines
8.5 KiB
PowerShell
# Backup-UDCData.ps1 - Capture UDC's CurrentData.json + ArchivedData/ to the
|
|
# SFLD share, keyed by the PC's current machine number. Runs LOCALLY on the
|
|
# old PC before retirement; the new PC restores during its first
|
|
# placeholder-to-real machine-number assignment via Update-MachineNumber.ps1.
|
|
#
|
|
# DESIGN: backup lives at <share>\<machine>\ root. Presence of CurrentData.json
|
|
# at that root means "available to restore". Restore consumes by moving content
|
|
# into <machine>\migrated\<timestamp>\, leaving the root empty so it can't
|
|
# be replayed.
|
|
#
|
|
# USAGE:
|
|
# - Direct: powershell -NoProfile -ExecutionPolicy Bypass -File Backup-UDCData.ps1
|
|
# - Wrapper: Backup-UDCData.bat (handles the bypass + elevation prompts)
|
|
# - Remote (WinRM): ../powershell/remote-execution/Backup-UDCData-Remote.ps1
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
# Override auto-detection for unusual cases. Defaults to whatever UDC
|
|
# currently has in C:\ProgramData\UDC\udc_settings.json.
|
|
[string]$MachineNumber,
|
|
|
|
# Override the share path. Defaults to the canonical SFLD location.
|
|
[string]$BackupShareRoot = '\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\backup\udc',
|
|
|
|
# Local UDC data root. Default matches the vendor install path.
|
|
[string]$UdcDataDir = 'C:\ProgramData\UDC',
|
|
|
|
# Cred for the SFLD share. If omitted, the current session's identity
|
|
# is used (works for SYSTEM-context runs and for local interactive
|
|
# admin sessions where the user has cached SFLD creds via SFLD-Reg).
|
|
[System.Management.Automation.PSCredential]$Credential,
|
|
|
|
# If the destination already has a backup for this machine, append a
|
|
# timestamped suffix instead of overwriting. Default: overwrite (latest
|
|
# backup wins; the prior one only matters if the new PC didn't restore
|
|
# before this re-backup).
|
|
[switch]$KeepPriorBackup
|
|
)
|
|
|
|
$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 ("Backup-UDCData_$(Get-Date -Format 'yyyyMMdd-HHmmss').log")
|
|
try { Start-Transcript -Path $logFile -Append -Force | Out-Null } catch {}
|
|
|
|
function Log {
|
|
param([string]$Msg, [string]$Level = 'INFO')
|
|
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
|
Write-Host "[$ts][$Level] $Msg"
|
|
}
|
|
|
|
Log "==============================================="
|
|
Log "Backup-UDCData starting"
|
|
Log "Hostname: $env:COMPUTERNAME"
|
|
Log "User: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)"
|
|
Log "UDC data dir: $UdcDataDir"
|
|
Log "Backup share: $BackupShareRoot"
|
|
Log "Log file: $logFile"
|
|
Log "==============================================="
|
|
|
|
# --- Resolve machine number ---
|
|
if (-not $MachineNumber) {
|
|
$settings = Join-Path $UdcDataDir 'udc_settings.json'
|
|
if (-not (Test-Path $settings)) {
|
|
Log "udc_settings.json not found at $settings - cannot auto-detect machine number" 'ERROR'
|
|
Log "Re-run with -MachineNumber <num> to override"
|
|
try { Stop-Transcript | Out-Null } catch {}
|
|
exit 2
|
|
}
|
|
try {
|
|
$json = Get-Content $settings -Raw | ConvertFrom-Json
|
|
$MachineNumber = $json.GeneralSettings.MachineNumber
|
|
} catch {
|
|
Log "Failed to parse udc_settings.json: $_" 'ERROR'
|
|
try { Stop-Transcript | Out-Null } catch {}
|
|
exit 3
|
|
}
|
|
}
|
|
if (-not $MachineNumber -or $MachineNumber -eq '9999' -or $MachineNumber -notmatch '^\d+$') {
|
|
Log "Invalid or placeholder machine number: '$MachineNumber'. Aborting." 'ERROR'
|
|
Log "(Backups for placeholder 9999 would collide across PCs and aren't useful.)"
|
|
try { Stop-Transcript | Out-Null } catch {}
|
|
exit 4
|
|
}
|
|
Log "Resolved machine number: $MachineNumber"
|
|
|
|
# --- Verify source files exist ---
|
|
$srcCurrent = Join-Path $UdcDataDir 'CurrentData.json'
|
|
$srcArchive = Join-Path $UdcDataDir 'ArchivedData'
|
|
|
|
$haveCurrent = Test-Path -LiteralPath $srcCurrent
|
|
$haveArchive = Test-Path -LiteralPath $srcArchive
|
|
|
|
if (-not $haveCurrent -and -not $haveArchive) {
|
|
Log "Neither CurrentData.json nor ArchivedData/ exists under $UdcDataDir. Nothing to back up." 'WARN'
|
|
try { Stop-Transcript | Out-Null } catch {}
|
|
exit 0
|
|
}
|
|
Log "CurrentData.json present: $haveCurrent"
|
|
Log "ArchivedData/ present: $haveArchive"
|
|
|
|
# --- Mount share if credentials supplied (otherwise rely on ambient auth) ---
|
|
$psDrive = $null
|
|
$dest = Join-Path $BackupShareRoot $MachineNumber
|
|
if ($Credential) {
|
|
try {
|
|
$psDrive = New-PSDrive -Name UDCBKP -PSProvider FileSystem -Root $BackupShareRoot -Credential $Credential -ErrorAction Stop
|
|
Log "Share mounted as UDCBKP: with explicit credentials"
|
|
$dest = "UDCBKP:\$MachineNumber"
|
|
} catch {
|
|
Log "Failed to mount $BackupShareRoot with supplied credentials: $_" 'ERROR'
|
|
try { Stop-Transcript | Out-Null } catch {}
|
|
exit 5
|
|
}
|
|
}
|
|
|
|
# --- Create destination dir ---
|
|
try {
|
|
if (-not (Test-Path -LiteralPath $dest)) {
|
|
New-Item -ItemType Directory -Path $dest -Force | Out-Null
|
|
Log "Created destination: $dest"
|
|
}
|
|
} catch {
|
|
Log "Failed to create destination $dest : $_" 'ERROR'
|
|
if ($psDrive) { Remove-PSDrive -Name UDCBKP -Force }
|
|
try { Stop-Transcript | Out-Null } catch {}
|
|
exit 6
|
|
}
|
|
|
|
# --- Optional: rotate prior backup at this bay if -KeepPriorBackup ---
|
|
if ($KeepPriorBackup) {
|
|
$existingMarker = Join-Path $dest 'CurrentData.json'
|
|
if (Test-Path -LiteralPath $existingMarker) {
|
|
$rotateName = Join-Path $dest ('superseded-' + (Get-Date -Format 'yyyyMMdd-HHmmss'))
|
|
New-Item -ItemType Directory -Path $rotateName -Force | Out-Null
|
|
Get-ChildItem $dest -Force | Where-Object { $_.Name -ne 'migrated' -and $_.Name -ne (Split-Path $rotateName -Leaf) } | ForEach-Object {
|
|
Move-Item -LiteralPath $_.FullName -Destination $rotateName -Force
|
|
}
|
|
Log "Prior backup rotated into $rotateName (KeepPriorBackup=true)"
|
|
}
|
|
}
|
|
|
|
# --- Copy CurrentData.json ---
|
|
$copiedCurrent = $false
|
|
$currentBytes = 0
|
|
if ($haveCurrent) {
|
|
try {
|
|
Copy-Item -LiteralPath $srcCurrent -Destination (Join-Path $dest 'CurrentData.json') -Force -ErrorAction Stop
|
|
$currentBytes = (Get-Item -LiteralPath $srcCurrent).Length
|
|
$copiedCurrent = $true
|
|
Log "Copied CurrentData.json ($currentBytes bytes)"
|
|
} catch {
|
|
Log "Failed to copy CurrentData.json: $_" 'ERROR'
|
|
}
|
|
}
|
|
|
|
# --- Copy ArchivedData/ recursively ---
|
|
$copiedArchive = $false
|
|
$archiveBytes = 0
|
|
$archiveFiles = 0
|
|
if ($haveArchive) {
|
|
try {
|
|
$destArchive = Join-Path $dest 'ArchivedData'
|
|
if (Test-Path -LiteralPath $destArchive) {
|
|
Remove-Item -LiteralPath $destArchive -Recurse -Force -ErrorAction Stop
|
|
}
|
|
Copy-Item -LiteralPath $srcArchive -Destination $destArchive -Recurse -Force -ErrorAction Stop
|
|
$copiedArchive = $true
|
|
$archiveItems = Get-ChildItem -LiteralPath $destArchive -Recurse -File -ErrorAction SilentlyContinue
|
|
$archiveBytes = ($archiveItems | Measure-Object Length -Sum).Sum
|
|
$archiveFiles = $archiveItems.Count
|
|
Log "Copied ArchivedData/ ($archiveFiles files, $archiveBytes bytes)"
|
|
} catch {
|
|
Log "Failed to copy ArchivedData/: $_" 'ERROR'
|
|
}
|
|
}
|
|
|
|
# --- Drop a small backup.manifest.json next to the data for forensics ---
|
|
try {
|
|
$manifest = [ordered]@{
|
|
BackedUpAt = (Get-Date -Format 'o')
|
|
SourceHostname = $env:COMPUTERNAME
|
|
SourceUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
MachineNumber = $MachineNumber
|
|
CurrentDataPresent = $copiedCurrent
|
|
CurrentDataBytes = $currentBytes
|
|
ArchivedDataPresent = $copiedArchive
|
|
ArchivedDataFiles = $archiveFiles
|
|
ArchivedDataBytes = $archiveBytes
|
|
}
|
|
$manifest | ConvertTo-Json | Set-Content -Path (Join-Path $dest 'backup.manifest.json') -Encoding UTF8
|
|
Log "Wrote backup.manifest.json"
|
|
} catch {
|
|
Log "Manifest write failed (non-fatal): $_" 'WARN'
|
|
}
|
|
|
|
# --- Tear down mount ---
|
|
if ($psDrive) {
|
|
try { Remove-PSDrive -Name UDCBKP -Force } catch {}
|
|
}
|
|
|
|
Log "==============================================="
|
|
Log "Backup complete:"
|
|
Log " Bay: $MachineNumber"
|
|
Log " Destination: $BackupShareRoot\$MachineNumber\"
|
|
Log " CurrentData.json: $(if ($copiedCurrent) {'OK ('+$currentBytes+' bytes)'} else {'MISSING'})"
|
|
Log " ArchivedData/: $(if ($copiedArchive) {'OK ('+$archiveFiles+' files, '+$archiveBytes+' bytes)'} else {'MISSING'})"
|
|
Log "==============================================="
|
|
try { Stop-Transcript | Out-Null } catch {}
|
|
exit 0
|