diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-CMM.bat b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-CMM.bat new file mode 100644 index 0000000..f4d5e64 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-CMM.bat @@ -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\\ (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 diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-CMM.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-CMM.ps1 new file mode 100644 index 0000000..d2553db --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-CMM.ps1 @@ -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\\ + gocmm_backup__.zip + pcdmis_backup___.zip (one per PC-DMIS version) + cmm-backup-index.json + +Params: + -CmmId e.g. CMM3 - names the folder + index (default: computer name) + -OutDir

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)" } diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-PCDMISSettings.bat b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-PCDMISSettings.bat new file mode 100644 index 0000000..4f06a79 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-PCDMISSettings.bat @@ -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___.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 diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-PCDMISSettings.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-PCDMISSettings.ps1 new file mode 100644 index 0000000..b06497b --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-PCDMISSettings.ps1 @@ -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 \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 " $_" } diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Clear-PCDMISRecent.bat b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Clear-PCDMISRecent.bat new file mode 100644 index 0000000..5a3c919 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Clear-PCDMISRecent.bat @@ -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 diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Clear-PCDMISRecent.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Clear-PCDMISRecent.ps1 new file mode 100644 index 0000000..04c76b8 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Clear-PCDMISRecent.ps1 @@ -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--.log + +Params: + -RecentOnly only clear the Recent list, keep Favorites + -User 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" diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Export-PCDMISCrashEvents.bat b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Export-PCDMISCrashEvents.bat new file mode 100644 index 0000000..751b612 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Export-PCDMISCrashEvents.bat @@ -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--.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 diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Export-PCDMISCrashEvents.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Export-PCDMISCrashEvents.ps1 new file mode 100644 index 0000000..3cf4ac3 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Export-PCDMISCrashEvents.ps1 @@ -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--.txt + +Params: + -Hours 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." diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Grant-FullControl.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Grant-FullControl.ps1 new file mode 100755 index 0000000..251ddbe --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Grant-FullControl.ps1 @@ -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 +} \ No newline at end of file diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-PCDMISSettings.bat b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-PCDMISSettings.bat new file mode 100644 index 0000000..97400e9 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-PCDMISSettings.bat @@ -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 "" +REM Install-PCDMISSettings.bat "" /replace +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 "^" [/replace ^ ^] + 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 ^ ^ & pause & exit /b 1 ) + %PS% -ReplaceFrom "%~3" -ReplaceTo "%~4" +) else ( + %PS% +) +echo. +pause diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-PCDMISSettings.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-PCDMISSettings.ps1 new file mode 100644 index 0000000..f6f87a3 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-PCDMISSettings.ps1 @@ -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 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 diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-goCMMSettings.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-goCMMSettings.ps1 index 7b1b991..a38cba0 100644 --- a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-goCMMSettings.ps1 +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-goCMMSettings.ps1 @@ -134,7 +134,7 @@ if ($rewrites.Count -gt 0) { # files: every text file under the Shared Data Directory 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 { $c = [System.IO.File]::ReadAllText($_.FullName) if ([regex]::IsMatch($c, $rxFrom, 'IgnoreCase')) { diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Modify-PCDMISRights.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Modify-PCDMISRights.ps1 new file mode 100755 index 0000000..e4077c9 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Modify-PCDMISRights.ps1 @@ -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 diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/pcdmis-probe-debug.bat b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/pcdmis-probe-debug.bat new file mode 100644 index 0000000..18b359a --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/pcdmis-probe-debug.bat @@ -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--.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 diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/pcdmis-probe-debug.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/pcdmis-probe-debug.ps1 new file mode 100644 index 0000000..6c0a744 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/pcdmis-probe-debug.ps1 @@ -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\ - 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--.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