<# Backup-PCDMISSettings.ps1 Manual PC-DMIS settings/probe/calibration backup - replicates what the Settings Editor captures (registry + data/probe files), but headless and scriptable, because SettingsEditor.exe /b only works through the GUI and fails non-interactively. Works across PC-DMIS 2016 / 2019 / 2026 (auto-detects version + vendor hive: 'Hexagon' on 2019/2026, 'Wai' on older 2016 builds). Captures, per installed version, into one zip: - registry: HKLM + HKCU \PC-DMIS\ (settings, probe search paths) - install-dir probe/cal master files: PROBE.DAT, usrprobe.dat, comp.dat, tool.dat, *.prb (top level + Configuration\) - the per-version data folders under ProgramData, Public\Documents, and per-user AppData (Roaming + Local) Run as administrator on a LIVE bay. One zip per installed version. Output: C:\Logs\CMM\pcdmis-backup\pcdmis_backup___.zip Params: -Version back up only this version (e.g. 2026.1). Default: every detected. -OutDir

output folder (default C:\Logs\CMM\pcdmis-backup) #> param( [string]$Version, [string]$OutDir = 'C:\Logs\CMM\pcdmis-backup' ) $ErrorActionPreference = 'Continue' $ts = Get-Date -Format 'yyyyMMdd-HHmmss' New-Item -ItemType Directory -Path $OutDir -Force -ErrorAction SilentlyContinue | Out-Null $log = Join-Path $OutDir "pcdmis-backup-$ts.log" function Log($m){ Write-Host $m; $m | Out-File -FilePath $log -Append } # --- discover installed PC-DMIS versions: vendor (Hexagon/Wai), version, install dir --- function Get-PCDMISInstalls { $found = @() $roots = @( @{ Path='HKLM:\SOFTWARE\WOW6432Node\Hexagon\PC-DMIS'; Vendor='Hexagon' }, @{ Path='HKLM:\SOFTWARE\Hexagon\PC-DMIS'; Vendor='Hexagon' }, @{ Path='HKLM:\SOFTWARE\WOW6432Node\Wai\PC-DMIS'; Vendor='Wai' }, @{ Path='HKLM:\SOFTWARE\Wai\PC-DMIS'; Vendor='Wai' } ) foreach ($r in $roots) { if (-not (Test-Path $r.Path)) { continue } Get-ChildItem $r.Path -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^\d' } | ForEach-Object { $p = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue $ver = $_.PSChildName $instDir = $p.Directory; if (-not $instDir) { $instDir = $p.InstallDir } # Fallback: the registry Directory value is often blank - find the install dir on disk if (-not $instDir) { foreach ($pf in "$env:ProgramFiles\$($r.Vendor)","${env:ProgramFiles(x86)}\$($r.Vendor)") { if (-not (Test-Path $pf)) { continue } $cand = Get-ChildItem $pf -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "PC-DMIS*$ver*" -and (Test-Path (Join-Path $_.FullName 'PCDLRN.exe')) } | Select-Object -First 1 if ($cand) { $instDir = $cand.FullName; break } } } $found += [pscustomobject]@{ Vendor=$r.Vendor; Version=$ver; HiveRoot=$r.Path; InstallDir=$instDir } } } $found } function Backup-OneVersion($inst) { $ver = $inst.Version; $vendor = $inst.Vendor Log "==== Backing up PC-DMIS $vendor $ver ====" $stage = Join-Path $env:TEMP "pcd-bk-$ver-$ts" New-Item -ItemType Directory -Path $stage,"$stage\registry","$stage\install","$stage\ProgramData","$stage\PublicDocs","$stage\AppData" -Force | Out-Null # registry: HKLM + HKCU under both Hexagon and Wai (export whichever exists) foreach ($hk in @('HKLM','HKCU')) { foreach ($v in @('Hexagon','Wai')) { $regPath = "$hk\SOFTWARE\$(if($hk -eq 'HKLM'){'WOW6432Node\'} )$v\PC-DMIS\$ver" $regPathNative = "$hk\SOFTWARE\$v\PC-DMIS\$ver" foreach ($rp in @($regPath,$regPathNative)) { $test = $rp -replace '^HKLM','HKLM:' -replace '^HKCU','HKCU:' if (Test-Path $test) { $f = "$stage\registry\$hk-$v-$ver.reg" reg export $rp "$f" /y 2>&1 | Out-Null if (Test-Path $f) { Log " reg export $rp" } } } } } # install-dir master probe/cal files if ($inst.InstallDir -and (Test-Path $inst.InstallDir)) { # interfac.dll = the active controller's interface DLL, renamed to interfac.dll # by PC-DMIS per the machine's controller. Machine-specific - capture it. foreach ($pat in 'PROBE.DAT','usrprobe.dat','comp.dat','compens.dat','tool.dat','machine.dat','interfac.dll') { Get-ChildItem $inst.InstallDir -Filter $pat -ErrorAction SilentlyContinue | ForEach-Object { Copy-Item $_.FullName "$stage\install\" -Force } } Get-ChildItem $inst.InstallDir -Filter '*.prb' -Recurse -ErrorAction SilentlyContinue | ForEach-Object { $rel = $_.FullName.Substring($inst.InstallDir.TrimEnd('\').Length).TrimStart('\') $dst = Join-Path "$stage\install" $rel; New-Item -ItemType Directory -Path (Split-Path $dst) -Force -EA SilentlyContinue | Out-Null Copy-Item $_.FullName $dst -Force } Log " copied install-dir probe/cal files" } # --- Identify the controller: which DLL became interfac.dll --- # PC-DMIS makes the active controller's interface DLL into interfac.dll. If it # was COPIED, an identical sibling .dll still exists -> hash match names it. If # it was RENAMED (no copy), no sibling matches - so we also read the PE version # resource's OriginalFilename, which survives a rename and names the source. $controllerInfo = $null $ifPath = if ($inst.InstallDir) { Join-Path $inst.InstallDir 'interfac.dll' } else { $null } if ($ifPath -and (Test-Path $ifPath)) { try { $ifItem = Get-Item $ifPath $ifHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $ifPath).Hash $vi = $ifItem.VersionInfo # size pre-filter so we don't hash every DLL in the install dir $match = Get-ChildItem $inst.InstallDir -Filter '*.dll' -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne 'interfac.dll' -and $_.Length -eq $ifItem.Length } | Where-Object { try { (Get-FileHash -Algorithm SHA256 -LiteralPath $_.FullName).Hash -eq $ifHash } catch { $false } } | Select-Object -First 1 $controllerInfo = [pscustomobject]@{ InterfacSha256 = $ifHash MatchedSourceDll = if ($match) { $match.Name } else { $null } # null = renamed, no copy OriginalFilename = $vi.OriginalFilename FileDescription = $vi.FileDescription ProductName = $vi.ProductName FileVersion = $vi.FileVersion } Log (" controller: interfac.dll source=" + $(if ($match) { $match.Name } else { '(renamed, no copy)' }) + " origName=$($vi.OriginalFilename) desc=$($vi.FileDescription)") } catch { Log " WARN: controller identification failed: $($_.Exception.Message)" } } else { Log " (no interfac.dll in install dir - controller not identified)" } # per-version data folders $dataMap = @( @{ Src="$env:ProgramData\$vendor\PC-DMIS\$ver"; Dst="$stage\ProgramData" }, @{ Src="$env:PUBLIC\Documents\$vendor\PC-DMIS\$ver"; Dst="$stage\PublicDocs" }, @{ Src="$env:APPDATA\$vendor\PC-DMIS\$ver"; Dst="$stage\AppData\Roaming" }, @{ Src="$env:LOCALAPPDATA\$vendor\PC-DMIS\$ver"; Dst="$stage\AppData\Local" } ) foreach ($d in $dataMap) { if (Test-Path $d.Src) { New-Item -ItemType Directory -Path $d.Dst -Force | Out-Null # Exclude bay/path-specific Homepage state. Recent + Favorites store # absolute routine paths (S:\..., C:\geaofi\LocalProgramCopies\...). # Restoring them onto another bay makes PC-DMIS null-ref on launch # (RecentExecutedItem.LoadRealNode) trying to resolve missing paths. # Exclude the whole Homepage start-screen state (Recent, Favorites, # DetailsView) - it stores absolute routine paths (S:\..., C:\geaofi\...) # and PC-DMIS null-refs on launch resolving them (LoadRealNode). Rebuilt # on use. Also skip regenerable caches/logs. robocopy $d.Src $d.Dst /E /XD Cache Caches Temp logs Logs Homepage /R:1 /W:1 /NFL /NDL /NJH /NJS | Out-Null Log " copied $($d.Src)" } } # manifest [pscustomobject]@{ Computer=$env:COMPUTERNAME; Timestamp=(Get-Date -Format o) Vendor=$vendor; Version=$ver; InstallDir=$inst.InstallDir ControllerInterface=$controllerInfo } | ConvertTo-Json -Depth 4 | Out-File "$stage\manifest.json" -Encoding ascii # zip $zip = Join-Path $OutDir "pcdmis_backup_${env:COMPUTERNAME}_${ver}_$ts.zip" if (Test-Path $zip) { Remove-Item $zip -Force } Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::CreateFromDirectory($stage,$zip) Remove-Item $stage -Recurse -Force -ErrorAction SilentlyContinue Log "==== DONE: $zip ====" return $zip } Log "PC-DMIS backup on $env:COMPUTERNAME at $(Get-Date)" $installs = Get-PCDMISInstalls | Where-Object { $_.Version -match '^\d' } | Sort-Object Version -Unique if ($Version) { $installs = $installs | Where-Object { $_.Version -eq $Version } } if (-not $installs) { Log "No PC-DMIS installs detected (Hexagon/Wai). Nothing to back up."; return } $made = @() foreach ($inst in $installs) { $made += (Backup-OneVersion $inst) } Write-Host "" Write-Host "PC-DMIS backups written:" -ForegroundColor Green $made | ForEach-Object { Write-Host " $_" }