<# 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 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___.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