CMM: add PC-DMIS + combined CMM backup/restore + diagnostic scripts

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>
This commit is contained in:
cproudlock
2026-06-12 08:42:32 -04:00
parent bfe17fe123
commit 1d65103cc0
15 changed files with 998 additions and 1 deletions

View File

@@ -0,0 +1,19 @@
@echo off
REM Backup-CMM.bat - ONE backup for a whole CMM bay (goCMM + PC-DMIS, all versions).
REM
REM Run AS ADMINISTRATOR on the live CMM. Do NOT run on DODA bays.
REM
REM Backup-CMM.bat -CmmId CMM3 (names the folder by CMM #)
REM Backup-CMM.bat (uses the computer name)
REM
REM Output: C:\Logs\CMM\cmm-backup\<CmmId>\ (gocmm + pcdmis zips + index)
REM Copy that whole folder to the PXE staging area for restore-by-machine-#.
setlocal
set "HERE=%~dp0"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%HERE%Backup-CMM.ps1" %*
echo.
echo ---------------------------------------------------------------
echo Backup folder is under C:\Logs\CMM\cmm-backup\
echo ---------------------------------------------------------------
pause

View File

@@ -0,0 +1,62 @@
<#
Backup-CMM.ps1
ONE backup for a whole CMM bay - runs both:
- Backup-goCMMSettings.ps1 (HKLM goCMM key + C:\geaofi, minus LocalProgramCopies/logs)
- Backup-PCDMISSettings.ps1 (PC-DMIS registry + data/probe/cal + interfac.dll,
every installed version; Homepage state excluded)
All zips land together in one per-CMM folder so they can be staged on PXE and
restored by CMM machine-# at imaging.
Run as ADMINISTRATOR on the live CMM. Do NOT run on DODA bays (handled separately).
Output: C:\Logs\CMM\cmm-backup\<CmmId>\
gocmm_backup_<PC>_<ts>.zip
pcdmis_backup_<PC>_<ver>_<ts>.zip (one per PC-DMIS version)
cmm-backup-index.json
Params:
-CmmId <id> e.g. CMM3 - names the folder + index (default: computer name)
-OutDir <p> base output folder (default C:\Logs\CMM\cmm-backup)
#>
param(
[string]$CmmId,
[string]$OutDir = 'C:\Logs\CMM\cmm-backup'
)
$ErrorActionPreference = 'Continue'
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$ts = Get-Date -Format 'yyyyMMdd-HHmmss'
if (-not $CmmId) { $CmmId = $env:COMPUTERNAME }
$dest = Join-Path $OutDir $CmmId
New-Item -ItemType Directory -Path $dest -Force -ErrorAction SilentlyContinue | Out-Null
$log = Join-Path $dest "cmm-backup-$ts.log"
function Log($m){ Write-Host $m; $m | Out-File -FilePath $log -Append }
Log "================ CMM backup: $CmmId on $env:COMPUTERNAME at $(Get-Date) ================"
foreach ($s in 'Backup-goCMMSettings.ps1','Backup-PCDMISSettings.ps1') {
$p = Join-Path $here $s
if (-not (Test-Path $p)) { Log "MISSING sibling script: $p - skipping"; continue }
Log "---- running $s ----"
try { & $p -OutDir $dest *>&1 | ForEach-Object { Log " $_" } }
catch { Log " ERROR in $s : $($_.Exception.Message)" }
}
# index of what we captured
$zips = Get-ChildItem $dest -Filter '*.zip' -File -ErrorAction SilentlyContinue
[pscustomobject]@{
CmmId = $CmmId
Computer = $env:COMPUTERNAME
Timestamp = (Get-Date -Format o)
goCMM = @($zips | Where-Object { $_.Name -like 'gocmm_backup_*' } | Select-Object -Expand Name)
PCDMIS = @($zips | Where-Object { $_.Name -like 'pcdmis_backup_*' } | Select-Object -Expand Name)
} | ConvertTo-Json | Out-File (Join-Path $dest 'cmm-backup-index.json') -Encoding ascii
Log "================ DONE ================"
Log "Folder: $dest"
$zips | ForEach-Object { Log (" {0} ({1} KB)" -f $_.Name, [math]::Round($_.Length/1KB)) }
Write-Host ""
Write-Host "CMM backup for $CmmId complete:" -ForegroundColor Green
Write-Host " $dest"
$zips | ForEach-Object { Write-Host " $($_.Name)" }

View File

@@ -0,0 +1,19 @@
@echo off
REM Backup-PCDMISSettings.bat - capture PC-DMIS settings / probes / calibration
REM for every installed version (2016 / 2019 / 2026), headless.
REM
REM Run on a LIVE bay AS ADMINISTRATOR (reg export of HKLM + read of install dir).
REM
REM Backup-PCDMISSettings.bat (all detected versions)
REM Backup-PCDMISSettings.bat -Version 2026.1 (one version)
REM
REM Output: C:\Logs\CMM\pcdmis-backup\pcdmis_backup_<PC>_<ver>_<ts>.zip
setlocal
set "HERE=%~dp0"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%HERE%Backup-PCDMISSettings.ps1" %*
echo.
echo ---------------------------------------------------------------
echo Backups under C:\Logs\CMM\pcdmis-backup\ (one zip per version)
echo ---------------------------------------------------------------
pause

View File

@@ -0,0 +1,180 @@
<#
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 " $_" }

View File

@@ -0,0 +1,23 @@
@echo off
REM Clear-PCDMISRecent.bat - fix the PC-DMIS startup crash (NullReferenceException
REM in RecentExecutedItem.LoadRealNode) caused by a stale Homepage "Recent
REM Executed Files" list pointing at routine paths that don't resolve on this bay.
REM
REM Deletes the Recent (and Favorites) Homepage state for every user + PC-DMIS
REM version; PC-DMIS rebuilds an empty list on next launch.
REM
REM Run AS ADMINISTRATOR.
REM
REM Clear-PCDMISRecent.bat (all users, Recent + Favorites)
REM Clear-PCDMISRecent.bat -RecentOnly (keep Favorites)
REM Clear-PCDMISRecent.bat -User ShopFloor
setlocal
set "HERE=%~dp0"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%HERE%Clear-PCDMISRecent.ps1" %*
echo.
echo ---------------------------------------------------------------
echo Done. Relaunch PC-DMIS - the recent-list crash should be gone.
echo Log: C:\Logs\CMM\pcdmis-clearrecent-*.log
echo ---------------------------------------------------------------
pause

View File

@@ -0,0 +1,75 @@
<#
Clear-PCDMISRecent.ps1
Fixes the PC-DMIS startup crash:
System.NullReferenceException
at Adapter.Service.Recent.Models.RecentExecutedItem.LoadRealNode()
at Adapter.Service.Recent.Models.RecentExecutedFiles.LoadAllWhenReady()
Cause: the Homepage "Recent Executed Files" list contains absolute routine
paths (S:\..., C:\geaofi\LocalProgramCopies\...). When a path does not resolve
in the running user's context (drive not visible, cache not yet rebuilt, file
gone), PC-DMIS dereferences a null while async-loading the list and crashes on
launch. A list carried over from another bay (e.g. a settings restore) is the
common trigger.
Fix: delete the Recent (and Favorites) Homepage state for every user profile
and every PC-DMIS version. PC-DMIS rebuilds an empty list on next start.
Run as ADMINISTRATOR (to reach all user profiles).
Output: C:\Logs\CMM\pcdmis-clearrecent-<PC>-<ts>.log
Params:
-RecentOnly only clear the Recent list, keep Favorites
-User <name> only this user's profile (default: all profiles)
#>
param(
[switch]$RecentOnly,
[string]$User
)
$ErrorActionPreference = 'Continue'
$ts = Get-Date -Format 'yyyyMMdd-HHmmss'
$dir = 'C:\Logs\CMM'
New-Item -ItemType Directory -Path $dir -Force -ErrorAction SilentlyContinue | Out-Null
$log = Join-Path $dir "pcdmis-clearrecent-$env:COMPUTERNAME-$ts.log"
function Log($m){ Write-Host $m; $m | Out-File -FilePath $log -Append }
Log "==== Clear PC-DMIS Recent/Favorites on $env:COMPUTERNAME at $(Get-Date) ===="
# which user profiles
$userDirs = @()
if ($User) {
if (Test-Path "C:\Users\$User") { $userDirs += "C:\Users\$User" } else { Log "User profile C:\Users\$User not found" }
} else {
$userDirs = (Get-ChildItem 'C:\Users' -Directory -ErrorAction SilentlyContinue).FullName
}
$targets = @('Homepage\Recent\RecentExecutedFiles.xml')
if (-not $RecentOnly) { $targets += 'Homepage\Favorites\Favorites.xml' }
$deleted = 0; $scanned = 0
foreach ($u in $userDirs) {
foreach ($vendor in 'Hexagon','WAI') {
$base = Join-Path $u "AppData\Local\$vendor\PC-DMIS"
if (-not (Test-Path $base)) { continue }
foreach ($vdir in (Get-ChildItem $base -Directory -ErrorAction SilentlyContinue)) {
$scanned++
foreach ($rel in $targets) {
$f = Join-Path $vdir.FullName $rel
if (Test-Path $f) {
try { Remove-Item $f -Force -ErrorAction Stop; $deleted++
Log " DELETED $f"
} catch { Log " FAILED $f : $($_.Exception.Message)" }
}
}
}
}
}
Log ""
Log "Scanned $scanned PC-DMIS version folder(s) across $($userDirs.Count) profile(s); deleted $deleted file(s)."
Log "PC-DMIS will rebuild an empty recent list on next launch."
Write-Host ""
if ($deleted -gt 0) { Write-Host "Cleared $deleted stale Homepage file(s). Relaunch PC-DMIS - the crash should be gone." -ForegroundColor Green }
else { Write-Host "Nothing to clear (no RecentExecutedFiles.xml found). If it still crashes, the cause is elsewhere - grab the event log." -ForegroundColor Yellow }
Log "Log: $log"

View File

@@ -0,0 +1,21 @@
@echo off
REM Export-PCDMISCrashEvents.bat - pull the Windows event-log entries that
REM explain the PC-DMIS crash (the full .NET exception + stack, which the
REM crash minidump does NOT contain).
REM
REM Run AS ADMINISTRATOR on the bay that crashed, soon after the crash.
REM
REM Export-PCDMISCrashEvents.bat (last 72 hours)
REM Export-PCDMISCrashEvents.bat -Hours 12 (narrower window)
REM
REM Output: C:\Logs\CMM\pcdmis-crash-events-<PC>-<ts>.txt
REM Send that file back - the first .NET Runtime (ID 1026) event is the answer.
setlocal
set "HERE=%~dp0"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%HERE%Export-PCDMISCrashEvents.ps1" %*
echo.
echo ---------------------------------------------------------------
echo Wrote C:\Logs\CMM\pcdmis-crash-events-*.txt - send it back.
echo ---------------------------------------------------------------
pause

View File

@@ -0,0 +1,77 @@
<#
Export-PCDMISCrashEvents.ps1
Pull the Windows event-log entries that explain a PC-DMIS crash. The crash
minidump has no symbols, but Windows logs the full .NET exception (type +
message + stack) in the Application log. This grabs those.
Run as administrator on the bay that crashed, soon after the crash.
Collects from the Application log (default last 72 hours):
- Event ID 1026 (.NET Runtime) <- the FULL managed exception + stack
- Event ID 1000 (Application Error) <- faulting module/offset for PCDLRN.exe
- Event ID 1001 (Windows Error Reporting) <- crash bucket / report path
Output: C:\Logs\CMM\pcdmis-crash-events-<PC>-<ts>.txt
Params:
-Hours <n> look-back window in hours (default 72)
#>
param(
[int]$Hours = 72,
[string]$OutDir = 'C:\Logs\CMM'
)
$ErrorActionPreference = 'Continue'
$ts = Get-Date -Format 'yyyyMMdd-HHmmss'
New-Item -ItemType Directory -Path $OutDir -Force -ErrorAction SilentlyContinue | Out-Null
$out = Join-Path $OutDir "pcdmis-crash-events-$env:COMPUTERNAME-$ts.txt"
function W($m){ $m | Out-File -FilePath $out -Append -Encoding utf8 }
$since = (Get-Date).AddHours(-$Hours)
W "==== PC-DMIS crash events on $env:COMPUTERNAME ===="
W "Generated : $(Get-Date)"
W "Window : since $since (last $Hours h)"
W ""
# Pull the candidate events
$events = @()
try {
$events = Get-WinEvent -FilterHashtable @{ LogName='Application'; Id=1000,1001,1026; StartTime=$since } -ErrorAction SilentlyContinue
} catch { W "Get-WinEvent failed: $($_.Exception.Message)" }
if (-not $events) { W "No Application 1000/1001/1026 events in the window."; W "Widen with -Hours, or the crash logged elsewhere."; Write-Host "Wrote $out"; return }
# Most relevant first: anything mentioning PCDLRN / PC-DMIS / a .NET exception
$relevant = $events | Where-Object { $_.Message -match 'PCDLRN|PC-DMIS|Hexagon|\.NET|System\.[A-Za-z.]+Exception|Qdas|QDAS|Report|Blade' }
W "================ RELEVANT (PC-DMIS / .NET) - $($relevant.Count) event(s) ================"
foreach ($e in ($relevant | Sort-Object TimeCreated -Descending)) {
W "-------------------------------------------------------------"
W ("Time : {0}" -f $e.TimeCreated)
W ("EventID : {0} Provider: {1} Level: {2}" -f $e.Id, $e.ProviderName, $e.LevelDisplayName)
W "Message :"
W ($e.Message)
W ""
}
W ""
W "================ ALL 1000/1001/1026 in window ($($events.Count)) - summary ================"
$events | Sort-Object TimeCreated -Descending | ForEach-Object {
$firstLine = ($_.Message -split "`r?`n" | Where-Object { $_.Trim() } | Select-Object -First 1)
W ("{0} ID={1,-4} {2,-26} {3}" -f $_.TimeCreated, $_.Id, $_.ProviderName, $firstLine)
}
# Also note where PC-DMIS drops its own crash zips (CrashRpt)
W ""
W "================ PC-DMIS CrashRpt zips on disk ================"
foreach ($d in "$env:LOCALAPPDATA\CrashRpt","$env:TEMP","$env:ProgramData\Hexagon") {
if (Test-Path $d) {
Get-ChildItem $d -Recurse -Filter '*.zip' -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -gt $since } |
ForEach-Object { W (" {0} {1}" -f $_.LastWriteTime, $_.FullName) }
}
}
Write-Host ""
Write-Host "Wrote: $out" -ForegroundColor Green
Write-Host "Open it and look at the first RELEVANT 1026 (.NET Runtime) event - that has the exact exception + stack."

View File

@@ -0,0 +1,10 @@
$Folders = @(
"C:\Apps\"
"C:\BladeRunner"
"C:\Users\Public"
"C:\Program Files\Hexagon"
"C:\ProgramData\Hexagon"
)
foreach ($Folder in $Folders) {
icacls $Folder /grant "*S-1-5-32-545:(OI)(CI)F" /T
}

View File

@@ -0,0 +1,32 @@
@echo off
REM Install-PCDMISSettings.bat - restore a PC-DMIS backup (SAME version).
REM
REM Run AS ADMINISTRATOR / at imaging. Imports the registry hive + data/probe
REM files, grants the operator write on the data dir, and auto-applies the
REM legacy->new FQDN rewrite (rd.ds.ge.com -> wjs.geaerospace.net).
REM
REM Install-PCDMISSettings.bat "<zip>"
REM Install-PCDMISSettings.bat "<zip>" /replace <from> <to>
REM
REM CROSS-VERSION upgrade (e.g. 2016 backup -> 2026 bay): do NOT use this -
REM use the 2026 Settings Editor IMPORT feature, which remaps the older layout.
REM This script does a verbatim same-version restore.
setlocal
set "HERE=%~dp0"
if "%~1"=="" (
echo Usage: %~nx0 "^<zip^>" [/replace ^<from^> ^<to^>]
pause
exit /b 1
)
set "ZIP=%~1"
set "PS=powershell.exe -NoProfile -ExecutionPolicy Bypass -File ""%HERE%Install-PCDMISSettings.ps1"" -BackupPath ""%ZIP%"""
if /i "%~2"=="/replace" (
if "%~4"=="" ( echo /replace needs ^<from^> ^<to^> & pause & exit /b 1 )
%PS% -ReplaceFrom "%~3" -ReplaceTo "%~4"
) else (
%PS%
)
echo.
pause

View File

@@ -0,0 +1,194 @@
<#
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

View File

@@ -134,7 +134,7 @@ if ($rewrites.Count -gt 0) {
# files: every text file under the Shared Data Directory # files: every text file under the Shared Data Directory
if (Test-Path $sharedDir) { if (Test-Path $sharedDir) {
Get-ChildItem -Path $sharedDir -Recurse -File -Include *.xml,*.txt,*.csv,*.config,*.ini -ErrorAction SilentlyContinue | ForEach-Object { Get-ChildItem -Path $sharedDir -Recurse -File -Include *.xml,*.txt,*.csv,*.config,*.ini,*.bas -ErrorAction SilentlyContinue | ForEach-Object {
try { try {
$c = [System.IO.File]::ReadAllText($_.FullName) $c = [System.IO.File]::ReadAllText($_.FullName)
if ([regex]::IsMatch($c, $rxFrom, 'IgnoreCase')) { if ([regex]::IsMatch($c, $rxFrom, 'IgnoreCase')) {

View File

@@ -0,0 +1,110 @@
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force
Start-Transcript -Path "C:\Logs\PC-DMISrights.txt" -Append
# 1. Define the authorized username
$authorizedUser = "SupportUser"
# 2. Check if the current environment user matches
if ($env:USERNAME -ne $authorizedUser) {
Write-Warning "Unauthorized user detected. Run as SupportUser instead."
return
}
# 3. Access granted for SupportUser
Write-Host "Welcome, $authorizedUser. Access granted."
# 4. Define the list of registry keys to modify
$registryKeys = @(
"HKLM:\SOFTWARE\Classes\PCDLRN.Application",
"HKCU:\SOFTWARE\Hexagon"
"HKCU:\SOFTWARE\WAI"
"HKLM:\SOFTWARE\Hexagon"
"HKLM:\SOFTWARE\WAI"
"HKLM:\SOFTWARE\Wow6432Node\Hexagon"
"HKLM:\SOFTWARE\Wow6432Node\WAI"
"Registry::HKU\.DEFAULT\SOFTWARE\Hexagon"
"Registry::HKU\.DEFAULT\SOFTWARE\WAI"
)
# 5. Define the permission rule details
$identity = "BUILTIN\Users" # The target group
$rights = "FullControl" # Permission level
$inheritance = "ContainerInherit, ObjectInherit" # Applies to subkeys and values
$propagation = "None"
$type = "Allow"
# 6. Create the Access Rule object
$accessRule = New-Object System.Security.AccessControl.RegistryAccessRule($identity, $rights, $inheritance, $propagation, $type)
# 7. Loop through each key and apply the new rule
foreach ($keyPath in $registryKeys) {
try {
if (Test-Path $keyPath) {
Write-Host "Applying permissions to: $keyPath" -ForegroundColor Cyan
# Get existing ACL (Access Control List)
$acl = Get-Acl -Path $keyPath
# Add the new rule to the existing ACL
$acl.SetAccessRule($accessRule)
# Apply the updated ACL back to the registry key
Set-Acl -Path $keyPath -AclObject $acl
Write-Host "Success!" -ForegroundColor Green
} else {
Write-Warning "Registry key not found: $keyPath"
}
} catch {
Write-Error "Failed to update $keyPath. Error: $($_.Exception.Message)"
}
}
# 8. Define the list of root folders to modify
$folderPaths = @(
"C:\Program Files\Hexagon",
"C:\Program Files\WAI",
"C:\Program Files (x86)\Hexagon"
"C:\Program Files (x86)\WAI"
"C:\ProgramData\Hexagon"
"C:\ProgramData\WAI"
)
# 9. Define the permission rule details
$identity = "BUILTIN\Users" # The target group
$rights = "FullControl" # Permission level
$inheritance = "ContainerInherit, ObjectInherit" # Applies to subfolders and files
$propagation = "None"
$type = "Allow"
# 10. Create the Access Rule object
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($identity, $rights, $inheritance, $propagation, $type)
# 11. Loop through each folder path and apply the new rule
foreach ($path in $folderPaths) {
try {
if (Test-Path $path) {
Write-Host "Applying permissions to: $path" -ForegroundColor Cyan
# Get existing ACL (Access Control List)
$acl = Get-Acl -Path $path
# Add the new rule to the existing ACL
$acl.SetAccessRule($accessRule)
# Apply the updated ACL back to the folder
Set-Acl -Path $path -AclObject $acl
Write-Host "Success!" -ForegroundColor Green
} else {
Write-Warning "Folder not found: $path"
}
} catch {
Write-Error "Failed to update $keyPath. Error: $($_.Exception.Message)"
}
}
pause
exit 1
Stop-Transcript

View File

@@ -0,0 +1,21 @@
@echo off
REM pcdmis-probe-debug.bat - find why custom probes don't show in PC-DMIS.
REM
REM *** RUN AS THE OPERATOR (the account that launches PC-DMIS), NOT elevated. ***
REM The probe search path + VirtualStore are per-user, so it must run in that
REM account to see the right view. Machine-wide ProgramData/ACLs are read anyway.
REM
REM Output: C:\Logs\CMM\pcdmis-probe-debug-<PC>-<timestamp>.txt
setlocal
set "HERE=%~dp0"
echo.
echo Running PC-DMIS probe debug as %USERDOMAIN%\%USERNAME% ...
echo (If the title bar says Administrator, STOP - run as the operator instead.)
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%HERE%pcdmis-probe-debug.ps1"
echo.
echo ---------------------------------------------------------------
echo Done. Collect C:\Logs\CMM\pcdmis-probe-debug-*.txt
echo ---------------------------------------------------------------
pause

View File

@@ -0,0 +1,154 @@
<#
pcdmis-probe-debug.ps1
Diagnose "custom probes don't show in PC-DMIS" on a locked-down Win11 bay.
RUN AS THE OPERATOR (the account that actually launches PC-DMIS), NOT elevated -
the probe search path is a PER-USER registry setting and the UAC VirtualStore is
per-user, so we must see THAT account's view. (Machine-wide ProgramData + ACLs
are read regardless.)
What it reports:
1. Installed PC-DMIS version(s) + install dir (HKLM)
2. The configured Probe Directory / search path (this user's HKCU + every
loaded user hive) - where PC-DMIS is actually looking
3. Probe files (*.prb, probe.dat, usrprobe.dat, probebuilder.dat) found under
ProgramData, Public Documents, and the install dir
4. ACL on C:\ProgramData\Hexagon\PC-DMIS\<ver> - can the operator WRITE
(needed to save/qualify custom probes)?
5. UAC VirtualStore shadow of those paths (probes silently redirected)
6. Version-folder match (probes in a subfolder the running build ignores)
Output: C:\Logs\CMM\pcdmis-probe-debug-<PC>-<timestamp>.txt
#>
$ErrorActionPreference = 'Continue'
$ts = Get-Date -Format 'yyyyMMdd-HHmmss'
$dir = 'C:\Logs\CMM'
New-Item -ItemType Directory -Path $dir -Force -ErrorAction SilentlyContinue | Out-Null
$log = Join-Path $dir "pcdmis-probe-debug-$env:COMPUTERNAME-$ts.txt"
function W($m){ $m | Tee-Object -FilePath $log -Append }
W "================ PC-DMIS probe debug ================"
W "When : $(Get-Date)"
W "PC : $env:COMPUTERNAME"
W "User : $env:USERDOMAIN\$env:USERNAME (process $([IntPtr]::Size*8)-bit)"
$elev = (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
W "Elevated: $elev (run as the OPERATOR, not elevated, for the per-user view)"
W ""
# ---- 1. Installed PC-DMIS version(s) + install dir ----
W "================ 1. Installed PC-DMIS (HKLM) ================"
$instVers = @()
foreach ($root in 'HKLM:\SOFTWARE\WOW6432Node\Hexagon\PC-DMIS','HKLM:\SOFTWARE\WOW6432Node\Wai\PC-DMIS','HKLM:\SOFTWARE\Hexagon\PC-DMIS','HKLM:\SOFTWARE\Wai\PC-DMIS') {
if (Test-Path $root) {
Get-ChildItem $root -ErrorAction SilentlyContinue | ForEach-Object {
$v = $_.PSChildName; $instVers += $v
$p = (Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue)
$exe = $p.Directory; if (-not $exe) { $exe = $p.InstallDir }
W (" $root\$v InstallDir=$exe")
}
}
}
$instVers = $instVers | Sort-Object -Unique
foreach ($d in 'C:\Program Files\Hexagon','C:\Program Files (x86)\Hexagon','C:\Program Files\WAI','C:\Program Files (x86)\WAI') {
if (Test-Path $d) { Get-ChildItem $d -Filter 'PC-DMIS*' -Directory -EA SilentlyContinue | ForEach-Object {
$exe = Get-ChildItem $_.FullName -Filter 'Pcdlrn.exe' -Recurse -EA SilentlyContinue | Select-Object -First 1
if (-not $exe) { $exe = Get-ChildItem $_.FullName -Filter 'PCDMIS.exe' -Recurse -EA SilentlyContinue | Select-Object -First 1 }
if ($exe) { W (" exe: $($exe.FullName) v$((Get-Item $exe.FullName).VersionInfo.FileVersion)") }
}}
}
if (-not $instVers) { W " (no PC-DMIS install keys found under Hexagon/Wai)" }
W ""
# ---- 2. Probe search path in the registry (this user + all loaded hives) ----
W "================ 2. Probe / search-path registry (per user) ================"
W "(values whose name or data mentions probe/search/path/dir - that's where PC-DMIS looks)"
New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS -ErrorAction SilentlyContinue | Out-Null
$userRoots = @('HKCU:\Software\Hexagon\PC-DMIS','HKCU:\Software\Wai\PC-DMIS')
Get-ChildItem 'HKU:\' -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^S-1-5-21' -and $_.PSChildName -notmatch '_Classes$' } | ForEach-Object {
$sid = $_.PSChildName
$userRoots += "HKU:\$sid\Software\Hexagon\PC-DMIS"
$userRoots += "HKU:\$sid\Software\Wai\PC-DMIS"
}
foreach ($ur in ($userRoots | Sort-Object -Unique)) {
if (-not (Test-Path $ur)) { continue }
Get-ChildItem $ur -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
$k = $_
$props = Get-ItemProperty $k.PSPath -ErrorAction SilentlyContinue
if (-not $props) { return }
foreach ($p in $props.PSObject.Properties) {
if ($p.Name -like 'PS*') { continue }
if ($p.Name -match 'probe|search|path|dir' -or "$($p.Value)" -match 'probe|Hexagon\\PC-DMIS|\\\\') {
W (" $($k.PSPath -replace '.*::','')")
W (" [$($p.Name)] = $($p.Value)")
}
}
}
}
W ""
# ---- 3. Probe files on disk ----
W "================ 3. Probe files on disk ================"
$bases = @(
"$env:ProgramData\Hexagon\PC-DMIS",
"$env:ProgramData\WAI\PC-DMIS",
"$env:PUBLIC\Documents\Hexagon\PC-DMIS",
"$env:PUBLIC\Documents\WAI\PC-DMIS",
"$env:APPDATA\Hexagon\PC-DMIS", # Roaming per-user
"$env:LOCALAPPDATA\Hexagon\PC-DMIS", # Local per-user
"$env:APPDATA\WAI\PC-DMIS",
"$env:LOCALAPPDATA\WAI\PC-DMIS",
"$env:USERPROFILE\Documents\Hexagon\PC-DMIS"
)
foreach ($d in 'C:\Program Files\Hexagon','C:\Program Files (x86)\Hexagon') { if (Test-Path $d) { $bases += (Get-ChildItem $d -Filter 'PC-DMIS*' -Directory -EA SilentlyContinue).FullName } }
foreach ($b in ($bases | Sort-Object -Unique)) {
if (-not (Test-Path $b)) { W " (absent) $b"; continue }
W " --- $b ---"
Get-ChildItem $b -Recurse -File -Include *.prb,probe.dat,usrprobe.dat,probebuilder.dat -ErrorAction SilentlyContinue |
ForEach-Object { W (" {0,10} {1:yyyy-MM-dd} {2}" -f $_.Length, $_.LastWriteTime, $_.FullName) }
}
W ""
# ---- 4. ACL on the ProgramData probe dir (can operator write?) ----
W "================ 4. ProgramData probe-dir ACL (write = save custom probes) ================"
$pdRoots = @("$env:ProgramData\Hexagon\PC-DMIS","$env:ProgramData\WAI\PC-DMIS")
foreach ($pr in $pdRoots) {
if (-not (Test-Path $pr)) { continue }
Get-ChildItem $pr -Directory -ErrorAction SilentlyContinue | ForEach-Object {
W " --- $($_.FullName) ---"
try {
(Get-Acl $_.FullName).Access | Where-Object { "$($_.IdentityReference)" -match 'Users|Everyone|Authenticated' } |
ForEach-Object { W (" {0,-30} {1,-24} {2}" -f $_.IdentityReference, $_.FileSystemRights, $_.AccessControlType) }
} catch { W " Get-Acl failed: $($_.Exception.Message)" }
}
}
W ""
# ---- 5. UAC VirtualStore shadow ----
W "================ 5. UAC VirtualStore shadow (probes silently redirected) ================"
$vs = "$env:LOCALAPPDATA\VirtualStore"
$found = $false
foreach ($sub in 'ProgramData\Hexagon\PC-DMIS','Program Files\Hexagon','Program Files (x86)\Hexagon','ProgramData\WAI\PC-DMIS') {
$p = Join-Path $vs $sub
if (Test-Path $p) {
$found = $true
Get-ChildItem $p -Recurse -File -Include *.prb,probe.dat,usrprobe.dat,probebuilder.dat -EA SilentlyContinue |
ForEach-Object { W (" SHADOW: $($_.FullName)") }
}
}
if (-not $found) { W " (no VirtualStore shadow for $env:USERNAME - good, or PC-DMIS runs under a different account)" }
W ""
# ---- 6. Version-folder match ----
W "================ 6. Version-folder match ================"
W (" installed versions: " + (($instVers -join ', ') | ForEach-Object { if($_){$_}else{'(unknown)'} }))
foreach ($b in "$env:ProgramData\Hexagon\PC-DMIS","$env:PUBLIC\Documents\Hexagon\PC-DMIS") {
if (Test-Path $b) { W (" data subfolders in $b : " + ((Get-ChildItem $b -Directory -EA SilentlyContinue).Name -join ', ')) }
}
W " >> if a data subfolder holding your .prb files does NOT match the running build's version, PC-DMIS ignores it."
W ""
W "================ DONE ================"
W "Log: $log"
Write-Host ""
Write-Host "Collected: $log" -ForegroundColor Green