Adds the PC-DMIS settings/probe backup-restore set alongside the existing goCMM scripts, plus a single combined CMM backup and the diagnostics built while debugging the live bays: - Backup-PCDMISSettings / Install-PCDMISSettings: capture+restore PC-DMIS registry + data/probe/cal files per installed version (2016/2019/2026). Hardened from real-bay failures: detect install dir via Program Files fallback; capture compens.dat (not just comp.dat) + interfac.dll; identify the controller by hash-matching interfac.dll to its source DLL AND reading the PE OriginalFilename (covers rename-without-copy); EXCLUDE the whole Homepage state (Recent/Favorites/DetailsView) which null-refs PC-DMIS on launch via stale routine paths; restore routes HKCU into the target user's hive (-TargetUser ShopFloor), fails loud on a non-backup path, and applies the legacy->new FQDN rewrite across reg + data files incl .bas. - Backup-CMM: one wrapper running goCMM + PC-DMIS (all versions) into one per-CMM folder + index, for staging on PXE and restore-by-machine-number. - Clear-PCDMISRecent: fixes the Homepage recent-list NullReferenceException crash on an already-broken bay. - pcdmis-probe-debug / Export-PCDMISCrashEvents: diagnostics for the custom-probe-not-showing and crash investigations. - Modify-PCDMISRights / Grant-FullControl: grant the operator the registry + filesystem access PC-DMIS needs under lockdown. - Install-goCMMSettings: add .bas to the FQDN-rewrite include list. Not yet wired into 09-Setup-CMM auto-restore - staging + the gated restore block come next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
195 lines
10 KiB
PowerShell
195 lines
10 KiB
PowerShell
<#
|
|
Install-PCDMISSettings.ps1
|
|
|
|
Restore a PC-DMIS settings/probe backup made by Backup-PCDMISSettings.ps1.
|
|
Imports the registry hive + lays the install/ProgramData/Public/AppData files
|
|
back, grants the operator write on the ProgramData data dir (so custom probes
|
|
can be saved under lockdown), and auto-applies the legacy->new FQDN rewrite.
|
|
|
|
SAME-VERSION restore only (e.g. 2026.1 backup -> 2026.1 bay). To migrate a bay
|
|
to a NEWER PC-DMIS (e.g. 2016 -> 2026), use the 2026 Settings Editor's IMPORT
|
|
feature instead - it remaps the older registry/data layout. This script does a
|
|
verbatim restore and does not remap versions.
|
|
|
|
Run as administrator / at imaging.
|
|
|
|
Params:
|
|
-BackupPath <zip|dir> required
|
|
-ReplaceFrom/-ReplaceTo extra find/replace pair (case-insensitive)
|
|
-NoDefaultRewrite skip the built-in legacy->new FQDN swaps
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory=$true)][string]$BackupPath,
|
|
[string]$TargetUser = 'ShopFloor', # account that RUNS PC-DMIS; HKCU settings (probe search paths) land in ITS hive
|
|
[string]$ReplaceFrom,
|
|
[string]$ReplaceTo,
|
|
[switch]$NoDefaultRewrite
|
|
)
|
|
|
|
# Built-in FQDN migration (same as the goCMM restore). Add pairs as domains retire.
|
|
$DefaultRewrites = @(
|
|
@{ From = 'rd.ds.ge.com'; To = 'wjs.geaerospace.net' }
|
|
)
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
$ts = Get-Date -Format 'yyyyMMdd-HHmmss'
|
|
$logDir = 'C:\Logs\CMM'
|
|
New-Item -ItemType Directory -Path $logDir -Force -ErrorAction SilentlyContinue | Out-Null
|
|
$log = Join-Path $logDir "pcdmis-restore-$ts.log"
|
|
function Log($m){ Write-Host $m; $m | Out-File -FilePath $log -Append }
|
|
|
|
Log "==== PC-DMIS restore on $env:COMPUTERNAME from $BackupPath ===="
|
|
|
|
# resolve backup to a dir
|
|
$src = $BackupPath; $extracted = $false
|
|
if ($BackupPath -match '\.zip$') {
|
|
$src = Join-Path $env:TEMP "pcd-rs-$ts"
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
[System.IO.Compression.ZipFile]::ExtractToDirectory($BackupPath, $src); $extracted = $true
|
|
}
|
|
if (-not (Test-Path $src)) { Log "ERROR: backup path not found: $src"; exit 1 }
|
|
|
|
# If pointed at a FOLDER that merely contains the backup zip (common mistake), use the zip
|
|
if ((Test-Path $src -PathType Container) -and -not (Test-Path (Join-Path $src 'manifest.json'))) {
|
|
$z = Get-ChildItem $src -Filter 'pcdmis_backup_*.zip' -File -ErrorAction SilentlyContinue | Sort-Object LastWriteTime | Select-Object -Last 1
|
|
if ($z) {
|
|
Log "BackupPath was a folder; using the zip inside it: $($z.Name)"
|
|
$src = Join-Path $env:TEMP "pcd-rs-$ts"
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
[System.IO.Compression.ZipFile]::ExtractToDirectory($z.FullName, $src); $extracted = $true
|
|
}
|
|
}
|
|
|
|
# manifest
|
|
$vendor='Hexagon'; $ver=''; $installDir=''
|
|
$mani = Join-Path $src 'manifest.json'
|
|
if (Test-Path $mani) {
|
|
try { $m = Get-Content $mani -Raw | ConvertFrom-Json
|
|
$vendor=$m.Vendor; $ver=$m.Version; $installDir=$m.InstallDir
|
|
Log "manifest: $vendor PC-DMIS $ver install=$installDir" } catch { Log "WARN: manifest parse: $($_.Exception.Message)" }
|
|
}
|
|
|
|
# Fail LOUD if this isn't actually a PC-DMIS backup (don't silently "succeed")
|
|
$regDir = Join-Path $src 'registry'
|
|
$haveReg = (Test-Path $regDir) -and @(Get-ChildItem $regDir -Filter '*.reg' -ErrorAction SilentlyContinue).Count -gt 0
|
|
if (-not (Test-Path $mani) -and -not $haveReg) {
|
|
Log "ERROR: '$BackupPath' is not a PC-DMIS backup - no manifest.json and no registry\*.reg found."
|
|
Log " Point -BackupPath at the pcdmis_backup_<PC>_<ver>_<ts>.zip file (or its extracted folder), NOT a parent dir like the Desktop."
|
|
exit 1
|
|
}
|
|
if (-not $ver) { Log "ERROR: manifest has no Version - cannot place per-version data. Aborting (bad/empty backup)."; exit 1 }
|
|
|
|
# --- resolve the target user's hive so HKCU settings (probe search paths) land in ITS profile ---
|
|
$targetHiveReg = $null; $unloadHive = $false
|
|
if ($TargetUser) {
|
|
$tsid = $null
|
|
try { $tsid = (New-Object System.Security.Principal.NTAccount($TargetUser)).Translate([System.Security.Principal.SecurityIdentifier]).Value } catch {}
|
|
if ($tsid -and (Test-Path "Registry::HKEY_USERS\$tsid")) {
|
|
$targetHiveReg = "HKEY_USERS\$tsid"; Log "Target user $TargetUser is logged in - HKCU settings -> $targetHiveReg"
|
|
} else {
|
|
$ntuser = "C:\Users\$TargetUser\NTUSER.DAT"
|
|
if (Test-Path $ntuser) {
|
|
reg load "HKU\PCDTGT" "$ntuser" 2>&1 | Out-Null
|
|
if (Test-Path 'Registry::HKEY_USERS\PCDTGT') { $targetHiveReg = 'HKEY_USERS\PCDTGT'; $unloadHive = $true; Log "Loaded $TargetUser NTUSER.DAT - HKCU settings -> $targetHiveReg" }
|
|
else { Log "WARN: failed to load $ntuser - HKCU settings will go to the CURRENT user, not $TargetUser" }
|
|
} else { Log "WARN: $ntuser not found ($TargetUser may not have logged in yet) - HKCU settings go to CURRENT user" }
|
|
}
|
|
}
|
|
|
|
# registry import - HKCU files routed into the target user's hive when resolved
|
|
Get-ChildItem $regDir -Filter '*.reg' -ErrorAction SilentlyContinue | ForEach-Object {
|
|
$f = $_.FullName; $n = $_.Name
|
|
if (($n -like 'HKCU-*') -and $targetHiveReg) {
|
|
$txt = [System.IO.File]::ReadAllText($f) -replace 'HKEY_CURRENT_USER', $targetHiveReg
|
|
$tmp = Join-Path $env:TEMP "tgt-$n"
|
|
[System.IO.File]::WriteAllText($tmp, $txt, [System.Text.Encoding]::Unicode)
|
|
reg import $tmp 2>&1 | Out-Null; Remove-Item $tmp -Force -ErrorAction SilentlyContinue
|
|
Log " reg import $n -> $TargetUser ($targetHiveReg)"
|
|
} else {
|
|
reg import $f 2>&1 | Out-Null; Log " reg import $n"
|
|
}
|
|
}
|
|
|
|
# install-dir files back
|
|
$instSrc = Join-Path $src 'install'
|
|
if ((Test-Path $instSrc) -and $installDir) {
|
|
if (-not (Test-Path $installDir)) { Log "WARN: install dir $installDir absent - skipping install-file restore" }
|
|
else { robocopy $instSrc $installDir /E /R:1 /W:1 /NFL /NDL /NJH /NJS | Out-Null; Log " restored install-dir probe/cal files -> $installDir" }
|
|
}
|
|
|
|
# data folders back
|
|
$dataMap = @(
|
|
@{ Src=(Join-Path $src 'ProgramData'); Dst="$env:ProgramData\$vendor\PC-DMIS\$ver" },
|
|
@{ Src=(Join-Path $src 'PublicDocs'); Dst="$env:PUBLIC\Documents\$vendor\PC-DMIS\$ver" },
|
|
@{ Src=(Join-Path $src 'AppData\Roaming'); Dst="$env:APPDATA\$vendor\PC-DMIS\$ver" },
|
|
@{ Src=(Join-Path $src 'AppData\Local'); Dst="$env:LOCALAPPDATA\$vendor\PC-DMIS\$ver" }
|
|
)
|
|
foreach ($d in $dataMap) {
|
|
if (Test-Path $d.Src) {
|
|
New-Item -ItemType Directory -Path $d.Dst -Force -EA SilentlyContinue | Out-Null
|
|
# /XD Homepage: never restore the Homepage start-screen state (Recent,
|
|
# Favorites, DetailsView - absolute S:\ / C:\geaofi paths) - it null-refs
|
|
# PC-DMIS on launch. Also protects against OLD backups that still contain it.
|
|
robocopy $d.Src $d.Dst /E /XD Homepage /R:1 /W:1 /NFL /NDL /NJH /NJS | Out-Null
|
|
Log " restored -> $($d.Dst)"
|
|
}
|
|
}
|
|
|
|
# Defensive: strip any pre-existing Homepage recent/favorites in BOTH the running
|
|
# user's and the target user's profile, so a stale list can't crash PC-DMIS.
|
|
$profiles = @($env:LOCALAPPDATA)
|
|
if ($TargetUser -and (Test-Path "C:\Users\$TargetUser\AppData\Local")) { $profiles += "C:\Users\$TargetUser\AppData\Local" }
|
|
foreach ($la in ($profiles | Sort-Object -Unique)) {
|
|
foreach ($f in "$la\$vendor\PC-DMIS\$ver\Homepage\Recent\RecentExecutedFiles.xml",
|
|
"$la\$vendor\PC-DMIS\$ver\Homepage\Favorites\Favorites.xml") {
|
|
if (Test-Path $f) { Remove-Item $f -Force -ErrorAction SilentlyContinue; Log " cleared stale Homepage state: $f" }
|
|
}
|
|
}
|
|
|
|
# grant operator Modify on the ProgramData data dir (save custom probes under lockdown)
|
|
$pdDir = "$env:ProgramData\$vendor\PC-DMIS\$ver"
|
|
if (Test-Path $pdDir) { & icacls $pdDir /grant 'BUILTIN\Users:(OI)(CI)M' /T /C 2>&1 | Out-Null; Log " granted BUILTIN\Users Modify on $pdDir" }
|
|
|
|
# --- find/replace (default legacy->new FQDN + optional extra) across reg values + data files ---
|
|
$rewrites = @()
|
|
if (-not $NoDefaultRewrite) { $rewrites += $DefaultRewrites }
|
|
if ($ReplaceFrom -and $ReplaceTo) { $rewrites += @{ From=$ReplaceFrom; To=$ReplaceTo } }
|
|
if ($rewrites.Count -gt 0) {
|
|
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
|
$regKeys = @("HKLM:\SOFTWARE\WOW6432Node\$vendor\PC-DMIS\$ver","HKCU:\SOFTWARE\$vendor\PC-DMIS\$ver")
|
|
if ($targetHiveReg) { $regKeys += "Registry::$targetHiveReg\SOFTWARE\$vendor\PC-DMIS\$ver" } # also the ShopFloor hive we imported into
|
|
$fileDirs = @($pdDir, "$env:PUBLIC\Documents\$vendor\PC-DMIS\$ver", "$env:APPDATA\$vendor\PC-DMIS\$ver", $installDir) | Where-Object { $_ -and (Test-Path $_) }
|
|
foreach ($rw in $rewrites) {
|
|
$from=$rw.From; $to=$rw.To; if (-not $from) { continue }
|
|
Log "Find/replace: '$from' -> '$to' (case-insensitive)"
|
|
$rx = [regex]::Escape($from)
|
|
foreach ($rk in $regKeys) {
|
|
if (-not (Test-Path $rk)) { continue }
|
|
Get-ChildItem $rk -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
|
|
$props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
|
|
if (-not $props) { return }
|
|
foreach ($p in $props.PSObject.Properties) {
|
|
if ($p.Name -like 'PS*') { continue }
|
|
if (($p.Value -is [string]) -and [regex]::IsMatch($p.Value,$rx,'IgnoreCase')) {
|
|
Set-ItemProperty $_.PSPath -Name $p.Name -Value ([regex]::Replace($p.Value,$rx,$to,'IgnoreCase'))
|
|
Log " reg [$($p.Name)]"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
foreach ($fd in $fileDirs) {
|
|
Get-ChildItem $fd -Recurse -File -Include *.dat,*.prb,*.xml,*.txt,*.cfg,*.ini,*.bas -ErrorAction SilentlyContinue | ForEach-Object {
|
|
try { $c=[System.IO.File]::ReadAllText($_.FullName)
|
|
if ([regex]::IsMatch($c,$rx,'IgnoreCase')) { [System.IO.File]::WriteAllText($_.FullName,[regex]::Replace($c,$rx,$to,'IgnoreCase'),$utf8); Log " file [$($_.Name)]" }
|
|
} catch {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($unloadHive) { [gc]::Collect(); [gc]::WaitForPendingFinalizers(); Start-Sleep 1; reg unload 'HKU\PCDTGT' 2>&1 | Out-Null; Log "unloaded $TargetUser hive" }
|
|
if ($extracted) { Remove-Item $src -Recurse -Force -ErrorAction SilentlyContinue }
|
|
Log "==== DONE ===="
|
|
Write-Host ""
|
|
Write-Host "PC-DMIS restore complete ($vendor $ver). Log: $log" -ForegroundColor Green
|