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>
181 lines
9.6 KiB
PowerShell
181 lines
9.6 KiB
PowerShell
<#
|
|
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 <vendor>\PC-DMIS\<ver> (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_<PC>_<ver>_<ts>.zip
|
|
|
|
Params:
|
|
-Version <x> back up only this version (e.g. 2026.1). Default: every detected.
|
|
-OutDir <p> 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 " $_" }
|