diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/09-Setup-CMM.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/09-Setup-CMM.ps1 index fb65998..4fdb3ce 100644 --- a/playbook/shopfloor-setup/gea-shopfloor-cmm/09-Setup-CMM.ps1 +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/09-Setup-CMM.ps1 @@ -321,6 +321,28 @@ if (Test-Path $goCmmKey) { } } +# ============================================================================ +# Step 2.8: Restore this bay's PC-DMIS + goCMM settings from its backup +# ============================================================================ +# Runs AFTER app install + first-run-init (so a restored config is not clobbered +# by PC-DMIS's first-launch defaults) and BEFORE the Step 3 cleanup (the backup +# set lives under $stagingRoot\backups\, deleted by cleanup). Restore-CMM +# self-gates: it skips DODA bays and bays with no staged backup, and restores +# ONLY the config-version PC-DMIS zip. Same-bay restore -> no CommPort clobber. +# Best-effort: Restore-CMM always exits 0, so imaging never fails on a restore. +$restoreScript = Join-Path $PSScriptRoot 'scripts\Restore-CMM.ps1' +if (Test-Path -LiteralPath $restoreScript) { + Write-CMMLog "Running per-bay settings restore (Restore-CMM.ps1)" + try { + & $restoreScript -BackupRoot (Join-Path $stagingRoot 'backups') *>&1 | ForEach-Object { Write-CMMLog " $_" } + Write-CMMLog "Restore-CMM returned $LASTEXITCODE" + } catch { + Write-CMMLog "Restore-CMM threw (non-fatal): $_" 'WARN' + } +} else { + Write-CMMLog "Restore-CMM.ps1 not found at $restoreScript - skipping settings restore" 'WARN' +} + # ============================================================================ # Step 3: Conditional cleanup of the bootstrap staging dir # ============================================================================ diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Restore-CMM.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Restore-CMM.ps1 new file mode 100644 index 0000000..cfb32cb --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Restore-CMM.ps1 @@ -0,0 +1,107 @@ +<# +Restore-CMM.ps1 + +Imaging-time per-bay settings restore. Called by 09-Setup-CMM after app install, +before the C:\CMM-Install cleanup (it reads the staged backup from there). + +Reads the resolved bay identity from C:\Enrollment\cmm\ (written by +resolve-cmm-bay-config.ps1 at the WinPE picker): + cmmid.txt - which staged backup set to use (e.g. CMM3) + version.txt - which PC-DMIS version to restore. A bay's backup holds every + version it ever had (2016.0 + 2019 R2); we restore ONLY the + one the bay-config pins, matched by substring (2019 -> the + '2019 R2' zip, 2016 -> the '2016.0' zip). + doda.txt - 'no' to proceed; anything else SKIPS restore (DODA bays are + handled separately and must not get a settings overlay). + partgroup.txt - optional goCMM Selected Part Group (friendly S:\ form), used + as the authoritative per-bay override even if the backup is stale. + +Finds the backup set at \\ (default C:\CMM-Install\backups, +robocopied there from the PXE share by startnet) and restores via the sibling +Install-PCDMISSettings.ps1 + Install-goCMMSettings.ps1. + +SAME-BAY restore by design (cmmid match), so the backed-up controller CommPort +is this bay's own value - no cross-bay CommPort clobber. (The clobber problem is +only when one bay's settings are pushed onto a different bay.) + +Best-effort: always exits 0. Imaging must never fail because a restore step +could not find a backup or hit a non-fatal error - those are logged. +#> +param( + [string]$EnrollDir = 'C:\Enrollment\cmm', + [string]$BackupRoot = 'C:\CMM-Install\backups', + [string]$TargetUser = 'ShopFloor', + [string]$PartGroupShareRoot = '\\tsgwp00525.wjs.geaerospace.net\SHARED', + [string]$OutDir = 'C:\Logs\CMM' +) +$ErrorActionPreference = 'Continue' +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +New-Item -ItemType Directory -Path $OutDir -Force -EA SilentlyContinue | Out-Null +$ts = Get-Date -Format 'yyyyMMdd-HHmmss' +$log = Join-Path $OutDir "restore-cmm-$ts.log" +function Log($m){ $line = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $m"; Write-Host $line; $line | Out-File -FilePath $log -Append -Encoding ascii } +function ReadTxt($n){ $p = Join-Path $EnrollDir $n; if (Test-Path -LiteralPath $p) { (Get-Content -LiteralPath $p -First 1 -EA 0).Trim() } else { '' } } + +Log "==== CMM settings restore on $env:COMPUTERNAME ====" + +$cmmid = ReadTxt 'cmmid.txt' +$ver = ReadTxt 'version.txt' +$doda = (ReadTxt 'doda.txt').ToLower() +$pgRaw = ReadTxt 'partgroup.txt' + +if (-not $cmmid) { Log "no cmmid.txt (manual CMM id, or not a bay-config bay) - nothing to restore. Skipping."; exit 0 } +if ($doda -eq 'yes') { Log "DODA bay ($cmmid) - skipping settings restore by policy."; exit 0 } + +$bdir = Join-Path $BackupRoot $cmmid +if (-not (Test-Path -LiteralPath $bdir)) { Log "no staged backup at $bdir - skipping (stage it with sync-cmm-backups.sh)."; exit 0 } +Log "bay=$cmmid version=$ver doda=$doda backupDir=$bdir" + +# --- PC-DMIS: restore ONLY the config-version zip --- +# A bay's folder holds every version it ever had (e.g. 2016.0 + 2019 R2). Match +# the VERSION FIELD of the name, not a loose substring: filenames are +# pcdmis_backup___.zip +# A loose "*2026*" would wrongly match a 2019 zip whose TIMESTAMP starts 2026 +# (e.g. 20260612). Anchor on the trailing timestamp so only the real version +# field (2019 -> '2019 R2', 2016 -> '2016.0', 2026 -> '2026.1') matches. +$pcZip = $null +if ($ver) { + $verRx = '^pcdmis_backup_.+_' + [regex]::Escape($ver) + '[^_]*_\d{8}-\d{6}\.zip$' + $pcZip = Get-ChildItem -LiteralPath $bdir -Filter 'pcdmis_backup_*.zip' -File -EA 0 | + Where-Object { $_.Name -match $verRx } | Sort-Object LastWriteTime | Select-Object -Last 1 +} +$pcScript = Join-Path $here 'Install-PCDMISSettings.ps1' +if (-not $pcZip) { + Log "WARN: no PC-DMIS backup zip matching version '$ver' in $bdir - PC-DMIS settings NOT restored." +} elseif (-not (Test-Path -LiteralPath $pcScript)) { + Log "WARN: Install-PCDMISSettings.ps1 not found at $pcScript - PC-DMIS settings NOT restored." +} else { + Log "restoring PC-DMIS $ver from $($pcZip.Name) (TargetUser=$TargetUser)" + try { & $pcScript -BackupPath $pcZip.FullName -TargetUser $TargetUser *>&1 | ForEach-Object { Log " $_" } } + catch { Log " ERROR in Install-PCDMISSettings: $($_.Exception.Message)" } +} + +# --- goCMM: restore the bay's settings; force the authoritative part group --- +$goZip = Get-ChildItem -LiteralPath $bdir -Filter 'gocmm_backup_*.zip' -File -EA 0 | Sort-Object LastWriteTime | Select-Object -Last 1 +$goScript = Join-Path $here 'Install-goCMMSettings.ps1' +if (-not $goZip) { + Log "WARN: no goCMM backup zip in $bdir - goCMM settings NOT restored." +} elseif (-not (Test-Path -LiteralPath $goScript)) { + Log "WARN: Install-goCMMSettings.ps1 not found at $goScript - goCMM settings NOT restored." +} else { + # partgroup.txt is the friendly S:\... form; convert the S: prefix to the UNC + # share root the same way 09-Setup-CMM does, and pass it so the restored + # Selected Part Group matches the CURRENT bay-config even if the backup is stale. + $goArgs = @{ BackupPath = $goZip.FullName } + if ($pgRaw) { + $pg = $pgRaw -replace '(?i)^S:\\', "$PartGroupShareRoot\" + $goArgs.SelectedPartGroup = $pg + Log "restoring goCMM from $($goZip.Name) (SelectedPartGroup=$pg)" + } else { + Log "restoring goCMM from $($goZip.Name) (no partgroup override - using backup's own value)" + } + try { & $goScript @goArgs *>&1 | ForEach-Object { Log " $_" } } + catch { Log " ERROR in Install-goCMMSettings: $($_.Exception.Message)" } +} + +Log "==== restore complete for $cmmid ====" +exit 0 diff --git a/playbook/startnet.cmd b/playbook/startnet.cmd index 3390d81..20ccc72 100644 --- a/playbook/startnet.cmd +++ b/playbook/startnet.cmd @@ -181,8 +181,8 @@ REM --- PPKG configuration (constructed at menu time, see docs) --- REM Vendor ships one source PPKG; we construct the BPRT-tagged filename REM by filling in Office, Region, Expiry, Version on the target copy. REM Update SOURCE_PPKG + PPKG_VER when a new PPKG is released. -set SOURCE_PPKG=GCCH_Prod_SFLD_v4.14.ppkg -set PPKG_VER=v4.14 +set SOURCE_PPKG=GCCH_Prod_SFLD_v4.16.ppkg +set PPKG_VER=v4.16 set PPKG_EXP=20260831 set REGION=US @@ -524,13 +524,24 @@ REM unified GE-Enforce dispatcher takes over from the share for ongoing updates. if /i not "%PCTYPE%"=="gea-shopfloor-cmm" goto skip_cmm_stage if exist "Y:\installers-post\cmm\cmm-manifest.json" ( mkdir W:\CMM-Install 2>NUL - robocopy "Y:\installers-post\cmm" "W:\CMM-Install" /E /MT:16 /R:1 /W:1 /NFL /NDL /LOG+:"%STAGELOG%" + REM /XD the backups tree: it holds EVERY bay's settings backup (some 240 MB + REM each). Copying all of it to every imaged CMM would waste GBs. The bulk + REM copy excludes it; the selected bay's backup is staged separately below. + robocopy "Y:\installers-post\cmm" "W:\CMM-Install" /E /XD "Y:\installers-post\cmm\backups" /MT:16 /R:1 /W:1 /NFL /NDL /LOG+:"%STAGELOG%" if errorlevel 8 echo WARNING: cmm robocopy exit %ERRORLEVEL% echo Staged CMM bootstrap to W:\CMM-Install. echo [%TIME%] Staged CMM bootstrap >> "%STAGELOG%" ) else ( echo WARNING: Y:\cmm-installers not found - CMM PC cannot install Hexagon apps at imaging time. ) +REM Stage ONLY the selected bay's settings backup. 09-Setup-CMM's Restore-CMM +REM reads W:\CMM-Install\backups\%CMMID%\ to restore PC-DMIS + goCMM settings. +if not defined CMMID goto skip_cmm_stage +if exist "Y:\installers-post\cmm\backups\%CMMID%" ( + robocopy "Y:\installers-post\cmm\backups\%CMMID%" "W:\CMM-Install\backups\%CMMID%" /E /MT:16 /R:1 /W:1 /NFL /NDL /LOG+:"%STAGELOG%" + echo Staged %CMMID% settings backup for restore. + echo [%TIME%] Staged %CMMID% backup >> "%STAGELOG%" +) :skip_cmm_stage REM --- Stage Keyence per-model bootstrap bundle (Keyence PCs only) --- diff --git a/playbook/sync-cmm-backups.sh b/playbook/sync-cmm-backups.sh index ff20368..ff11aa6 100755 --- a/playbook/sync-cmm-backups.sh +++ b/playbook/sync-cmm-backups.sh @@ -5,7 +5,7 @@ # # Source layout - one folder per CMM, named by the cmm_id in cmm-bay-config.csv, # produced by Backup-CMM.ps1 and mirrored here from the live bays: -# /home/camp/pxe-images/cmm-bk// +# /home/camp/pxe-images/cmm/backups// # gocmm_backup__.zip # pcdmis_backup___.zip (one per installed PC-DMIS version) # cmm-backup-index.json @@ -23,7 +23,7 @@ set -euo pipefail PXE_HOST="${PXE_HOST:-172.16.9.1}" PXE_USER="${PXE_USER:-pxe}" PXE_PASS="${PXE_PASS:-pxe}" -BACKUP_SRC_DIR="${CMM_BACKUP_DIR:-/home/camp/pxe-images/cmm-bk}" +BACKUP_SRC_DIR="${CMM_BACKUP_DIR:-/home/camp/pxe-images/cmm/backups}" REMOTE_BASE="/srv/samba/enrollment/installers-post/cmm/backups" ONLY_ID="${CMM_ID:-}"