From bfe17fe123d67b35dffe6e75e55a95a821b8668f Mon Sep 17 00:00:00 2001 From: cproudlock Date: Thu, 11 Jun 2026 08:27:30 -0400 Subject: [PATCH] CMM: add goCMM settings backup/restore + debug scripts goCMM settings live in two places: 3 pointer values at HKLM\SOFTWARE\WOW6432Node\General Electric\goCMM, and the real content of all 7 Settings tabs (PC-DMIS, Quindos, Modus, Machine Definition, User Input, Notifications, Part Groups) in C:\geaofi\ApplicationSettings.xml. Capture-replay pair, mirroring the Wax/Trace Backup/Install scripts: - Backup-goCMMSettings.ps1/.bat: on a live legacy bay (admin), zips the registry key + the C:\geaofi tree (minus transient LocalProgramCopies/logs) to gocmm_backup__.zip. - Install-goCMMSettings.ps1/.bat: restore at imaging (admin). Imports the key + lays down C:\geaofi, then grants BUILTIN\Users WriteKey on the reg key and Modify on C:\geaofi - goCMM's RegistrySettings.GetRegistryString opens the key with writable:true even to READ, so a locked-down operator throws a SecurityException without the grant (the post-lockdown 'registry access not allowed' error). Applies a built-in legacy->new FQDN rewrite (rd.ds.ge.com -> wjs.geaerospace.net) automatically across the registry values and ApplicationSettings.xml (incl PartGroup FullName); -NoDefaultRewrite skips it, /replace adds an extra pair, -SelectedPartGroup overrides per bay. - gocmm-debug.ps1/.bat: run as the operator to reproduce the SecurityException and dump the goCMM key ACL (confirms whether lockdown stripped the grant). All round-trip + FQDN-rewrite verified on the win11 VM. NOTE: covers goCMM only; PC-DMIS probe calibrations / custom tip angles / machine comp are owned by PC-DMIS (Hexagon) and not captured here. Not yet wired into 09-Setup-CMM auto-discovery. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../scripts/Backup-goCMMSettings.bat | 19 ++ .../scripts/Backup-goCMMSettings.ps1 | 91 +++++++++ .../scripts/Install-goCMMSettings.bat | 40 ++++ .../scripts/Install-goCMMSettings.ps1 | 172 ++++++++++++++++++ .../gea-shopfloor-cmm/scripts/gocmm-debug.bat | 25 +++ .../gea-shopfloor-cmm/scripts/gocmm-debug.ps1 | 115 ++++++++++++ 6 files changed, 462 insertions(+) create mode 100644 playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-goCMMSettings.bat create mode 100644 playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-goCMMSettings.ps1 create mode 100644 playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-goCMMSettings.bat create mode 100644 playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-goCMMSettings.ps1 create mode 100644 playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/gocmm-debug.bat create mode 100644 playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/gocmm-debug.ps1 diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-goCMMSettings.bat b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-goCMMSettings.bat new file mode 100644 index 0000000..93b9c2b --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-goCMMSettings.bat @@ -0,0 +1,19 @@ +@echo off +REM Backup-goCMMSettings.bat - capture this bay's goCMM settings. +REM +REM Run on a LIVE legacy goCMM device, AS ADMINISTRATOR (reg export of HKLM + +REM read of C:\geaofi needs admin). Captures the registry pointers + the whole +REM Shared Data Directory (ApplicationSettings.xml = all 7 Settings tabs) into +REM one zip under C:\Logs\CMM\gocmm-backup\. +REM +REM Backup-goCMMSettings.bat (default output dir) +REM Backup-goCMMSettings.bat -OutDir D:\path + +setlocal +set "HERE=%~dp0" +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%HERE%Backup-goCMMSettings.ps1" %* +echo. +echo --------------------------------------------------------------- +echo Backup zip is under C:\Logs\CMM\gocmm-backup\ (gocmm_backup_*.zip) +echo --------------------------------------------------------------- +pause diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-goCMMSettings.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-goCMMSettings.ps1 new file mode 100644 index 0000000..d9d0d3d --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Backup-goCMMSettings.ps1 @@ -0,0 +1,91 @@ +<# +Backup-goCMMSettings.ps1 + +Capture a goCMM bay's COMPLETE settings into one zip. Run on a LIVE legacy +goCMM device. Mirrors Backup-FormtracepakSettings.ps1. + +goCMM stores settings in two places: + 1. Registry pointers: HKLM\SOFTWARE\WOW6432Node\General Electric\goCMM + Shared Data Directory, Selected Part Group, Installation Directory + 2. The Shared Data Directory (default C:\geaofi\) holds ApplicationSettings.xml + = the real content of ALL 7 Settings tabs: PC-DMIS, Quindos, Modus, + Machine Definition, User Input, Notifications, Part Groups. + +This zip captures both so a re-imaged bay can be restored to identical settings. + +Output: C:\Logs\CMM\gocmm-backup\gocmm_backup__.zip +Run as administrator (reg export of HKLM + read of the data dir). +#> +param( + [string]$OutDir = 'C:\Logs\CMM\gocmm-backup' +) +$ErrorActionPreference = 'Continue' +$ts = Get-Date -Format 'yyyyMMdd-HHmmss' +New-Item -ItemType Directory -Path $OutDir -Force -ErrorAction SilentlyContinue | Out-Null +$log = Join-Path $OutDir "gocmm-backup-$ts.log" +function Log($m){ Write-Host $m; $m | Out-File -FilePath $log -Append } + +$stage = Join-Path $env:TEMP "gocmm-bk-$ts" +New-Item -ItemType Directory -Path $stage, "$stage\registry", "$stage\geaofi" -Force | Out-Null + +Log "==== goCMM backup on $env:COMPUTERNAME at $(Get-Date) ====" + +# --- Read the 3 registry pointers (32-bit / WOW6432Node view, as the 32-bit app sees them) --- +$sharedDir = 'C:\geaofi'; $partGroup = ''; $installDir = '' +try { + $base32 = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine','Registry32') + $k = $base32.OpenSubKey('SOFTWARE\General Electric\goCMM', $false) + if ($k) { + $sharedDir = ([string]$k.GetValue('Shared Data Directory','C:\geaofi')).TrimEnd('\') + $partGroup = [string]$k.GetValue('Selected Part Group','') + $installDir = [string]$k.GetValue('Installation Directory','') + $k.Close() + Log "Shared Data Directory = $sharedDir" + Log "Selected Part Group = $partGroup" + Log "Installation Directory= $installDir" + } else { Log "WARN: goCMM registry key not found - defaulting Shared Data Directory to $sharedDir" } +} catch { Log "WARN: reading goCMM registry failed: $($_.Exception.Message)" } + +# --- Export the registry key --- +reg export 'HKLM\SOFTWARE\WOW6432Node\General Electric\goCMM' "$stage\registry\goCMM.reg" /y 2>&1 | Out-Null +if (Test-Path "$stage\registry\goCMM.reg") { Log "Exported registry key" } else { Log "WARN: registry export produced no file" } + +# --- Copy the Shared Data Directory (skip transient LocalProgramCopies + logs) --- +if (Test-Path $sharedDir) { + robocopy $sharedDir "$stage\geaofi" /E /XD LocalProgramCopies logs /R:1 /W:1 /NFL /NDL /NJH /NJS | Out-Null + Log "Copied $sharedDir (excluded LocalProgramCopies, logs)" + if (Test-Path "$stage\geaofi\ApplicationSettings.xml") { + Log "ApplicationSettings.xml captured (PC-DMIS + all Settings tabs)" + } else { + Log "WARN: ApplicationSettings.xml NOT found under $sharedDir - settings tabs may be unconfigured" + } +} else { + Log "WARN: Shared Data Directory $sharedDir does not exist - only the registry key will be in this backup" +} + +# --- Manifest --- +$ver = '' +if ($installDir) { + $exe = Join-Path ($installDir.TrimEnd('\')) 'goCMM.exe' + if (Test-Path $exe) { $ver = (Get-Item $exe).VersionInfo.FileVersion } +} +[pscustomobject]@{ + Computer = $env:COMPUTERNAME + Timestamp = (Get-Date -Format o) + SharedDataDirectory = $sharedDir + SelectedPartGroup = $partGroup + InstallationDirectory = $installDir + goCMMVersion = $ver +} | ConvertTo-Json | Out-File "$stage\manifest.json" -Encoding ascii + +# --- Zip --- +$zip = Join-Path $OutDir "gocmm_backup_${env:COMPUTERNAME}_$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 ====" +Write-Host "" +Write-Host "goCMM backup written:" -ForegroundColor Green +Write-Host " $zip" diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-goCMMSettings.bat b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-goCMMSettings.bat new file mode 100644 index 0000000..06a663e --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-goCMMSettings.bat @@ -0,0 +1,40 @@ +@echo off +REM Install-goCMMSettings.bat - restore goCMM settings from a backup zip. +REM +REM Run AS ADMINISTRATOR / at imaging. Imports the registry pointers + the +REM Shared Data Directory and grants the access goCMM needs under lockdown. +REM +REM Usage: +REM Install-goCMMSettings.bat "" +REM plain restore +REM Install-goCMMSettings.bat "" "\\server\SHARED\CMM\CMM7\Spool" +REM restore + override the Selected Part Group (per-bay) +REM Install-goCMMSettings.bat "" /replace +REM restore + find/replace -> across ALL registry values and +REM data files (e.g. legacy FQDN -> new FQDN). Swap the DOMAIN SUFFIX to +REM fix every host at once: +REM Install-goCMMSettings.bat "bk.zip" /replace rd.ds.ge.com wjs.geaerospace.net + +setlocal +set "HERE=%~dp0" +if "%~1"=="" ( + echo Usage: + echo %~nx0 "^" + echo %~nx0 "^" "^" + echo %~nx0 "^" /replace ^ ^ + pause + exit /b 1 +) +set "ZIP=%~1" +set "PS=powershell.exe -NoProfile -ExecutionPolicy Bypass -File ""%HERE%Install-goCMMSettings.ps1"" -BackupPath ""%ZIP%""" + +if /i "%~2"=="/replace" ( + if "%~4"=="" ( echo /replace needs ^ ^ & pause & exit /b 1 ) + %PS% -ReplaceFrom "%~3" -ReplaceTo "%~4" +) else if not "%~2"=="" ( + %PS% -SelectedPartGroup "%~2" +) else ( + %PS% +) +echo. +pause diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-goCMMSettings.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-goCMMSettings.ps1 new file mode 100644 index 0000000..7b1b991 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Install-goCMMSettings.ps1 @@ -0,0 +1,172 @@ +<# +Install-goCMMSettings.ps1 + +Restore a goCMM bay's settings from a zip produced by Backup-goCMMSettings.ps1. +Mirrors Install-FormtracepakSettings.ps1. + +Lays back both halves: + 1. Registry pointers -> HKLM\SOFTWARE\WOW6432Node\General Electric\goCMM + 2. Shared Data Directory (C:\geaofi\, incl ApplicationSettings.xml = all 7 + Settings tabs: PC-DMIS, Quindos, Modus, Machine Definition, User Input, + Notifications, Part Groups). + +Then grants the access goCMM needs so it works under lockdown: + - BUILTIN\Users ReadKey+WriteKey on the goCMM reg key. goCMM's + RegistrySettings.GetRegistryString opens that key with writable:true even + to READ, so a read-only operator hits a SecurityException without this. + - BUILTIN\Users Modify on the Shared Data Directory (goCMM writes + ApplicationSettings.xml back there when settings are saved). + +Run as administrator / SYSTEM (imaging context). If the lockdown pass strips +the registry ACE, re-run this (or just its ACL section) AFTER lockdown. + +NOTE: this restores goCMM only. PC-DMIS probe calibrations / custom tip angles / +machine comp are owned by PC-DMIS (Hexagon) and are NOT in this backup. +#> +param( + [Parameter(Mandatory=$true)][string]$BackupPath, # zip or already-extracted dir + [string]$SelectedPartGroup, # optional per-bay override of the part-group UNC + [string]$ReplaceFrom, # optional EXTRA find/replace across reg + xml + [string]$ReplaceTo, # ... replacement. Case-insensitive. + [switch]$NoDefaultRewrite # skip the built-in legacy->new FQDN swaps below +) + +# ============================================================================ +# Built-in FQDN migration - applied AUTOMATICALLY on every restore (no flag). +# Add pairs here as more legacy domains retire. -NoDefaultRewrite disables them. +# ============================================================================ +$DefaultRewrites = @( + @{ From = 'rd.ds.ge.com'; To = 'wjs.geaerospace.net' } # WJ legacy domain -> new domain +) +$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 "gocmm-restore-$ts.log" +function Log($m){ Write-Host $m; $m | Out-File -FilePath $log -Append } + +$goCmmKey = 'HKLM:\SOFTWARE\WOW6432Node\General Electric\goCMM' +Log "==== goCMM restore on $env:COMPUTERNAME from $BackupPath ====" + +# --- Resolve the backup to a directory --- +$src = $BackupPath +$extracted = $false +if ($BackupPath -match '\.zip$') { + $src = Join-Path $env:TEMP "gocmm-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 } + +# --- Read manifest for the shared-dir target --- +$sharedDir = 'C:\geaofi' +$mani = Join-Path $src 'manifest.json' +if (Test-Path $mani) { + try { + $m = Get-Content $mani -Raw | ConvertFrom-Json + if ($m.SharedDataDirectory) { $sharedDir = $m.SharedDataDirectory } + Log "manifest: shared=$sharedDir partGroup=$($m.SelectedPartGroup) ver=$($m.goCMMVersion)" + } catch { Log "WARN: could not parse manifest.json: $($_.Exception.Message)" } +} + +# --- Import the registry pointers --- +$reg = Join-Path $src 'registry\goCMM.reg' +if (Test-Path $reg) { + reg import "$reg" 2>&1 | Out-Null + Log "Imported registry key from goCMM.reg" +} else { + Log "WARN: registry\goCMM.reg missing in backup - creating an empty key so the ACL still lands" + if (-not (Test-Path $goCmmKey)) { New-Item -Path $goCmmKey -Force | Out-Null } +} + +# --- Optional per-bay Selected Part Group override (use when restoring to a different bay) --- +if ($SelectedPartGroup) { + if (-not (Test-Path $goCmmKey)) { New-Item -Path $goCmmKey -Force | Out-Null } + New-ItemProperty -Path $goCmmKey -Name 'Selected Part Group' -Value $SelectedPartGroup -PropertyType String -Force | Out-Null + Log "Override Selected Part Group = $SelectedPartGroup" +} + +# --- Lay down the Shared Data Directory (ApplicationSettings.xml = all tabs) --- +$geaofiSrc = Join-Path $src 'geaofi' +if (Test-Path $geaofiSrc) { + New-Item -ItemType Directory -Path $sharedDir -Force -ErrorAction SilentlyContinue | Out-Null + robocopy $geaofiSrc $sharedDir /E /R:1 /W:1 /NFL /NDL /NJH /NJS | Out-Null + Log "Restored Shared Data Directory -> $sharedDir" + if (Test-Path (Join-Path $sharedDir 'ApplicationSettings.xml')) { + Log "ApplicationSettings.xml in place (PC-DMIS + all Settings tabs)" + } else { + Log "WARN: ApplicationSettings.xml not present after restore" + } +} else { + Log "WARN: geaofi payload missing in backup - settings tabs NOT restored" +} + +# --- Find/replace across ALL restored data (registry values + every text file +# under the Shared Data Directory). The built-in legacy->new FQDN swaps run +# AUTOMATICALLY; -ReplaceFrom/-ReplaceTo adds one more; case-insensitive. --- +$rewrites = @() +if (-not $NoDefaultRewrite) { $rewrites += $DefaultRewrites } +if ($ReplaceFrom -and $ReplaceTo) { $rewrites += @{ From = $ReplaceFrom; To = $ReplaceTo } } + +if ($rewrites.Count -gt 0) { + $utf8NoBom = New-Object System.Text.UTF8Encoding($false) + foreach ($rw in $rewrites) { + $from = $rw.From; $to = $rw.To + if (-not $from) { continue } + Log "Find/replace: '$from' -> '$to' (case-insensitive)" + $rxFrom = [regex]::Escape($from) + + # registry: every string value under the goCMM key + if (Test-Path $goCmmKey) { + try { + $props = Get-ItemProperty -Path $goCmmKey + foreach ($p in $props.PSObject.Properties) { + if ($p.Name -like 'PS*') { continue } + if (($p.Value -is [string]) -and ([regex]::IsMatch($p.Value, $rxFrom, 'IgnoreCase'))) { + $new = [regex]::Replace($p.Value, $rxFrom, $to, 'IgnoreCase') + Set-ItemProperty -Path $goCmmKey -Name $p.Name -Value $new + Log " reg [$($p.Name)] -> $new" + } + } + } catch { Log " WARN: registry rewrite failed: $($_.Exception.Message)" } + } + + # 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 { + try { + $c = [System.IO.File]::ReadAllText($_.FullName) + if ([regex]::IsMatch($c, $rxFrom, 'IgnoreCase')) { + $nc = [regex]::Replace($c, $rxFrom, $to, 'IgnoreCase') + [System.IO.File]::WriteAllText($_.FullName, $nc, $utf8NoBom) + Log " file [$($_.Name)] rewritten" + } + } catch { Log " WARN: file rewrite $($_.FullName): $($_.Exception.Message)" } + } + } + } +} + +# --- Grant BUILTIN\Users ReadKey+WriteKey on the reg key (goCMM opens it writable:true to read) --- +if (Test-Path $goCmmKey) { + try { + $acl = Get-Acl -Path $goCmmKey + $rule = New-Object System.Security.AccessControl.RegistryAccessRule( + 'BUILTIN\Users','ReadKey,WriteKey','ContainerInherit','None','Allow') + $acl.AddAccessRule($rule) + Set-Acl -Path $goCmmKey -AclObject $acl -ErrorAction Stop + Log "Granted BUILTIN\Users ReadKey,WriteKey on $goCmmKey" + } catch { Log "WARN: registry ACL grant failed: $($_.Exception.Message)" } +} + +# --- Grant BUILTIN\Users Modify on the Shared Data Directory (goCMM saves settings there) --- +if (Test-Path $sharedDir) { + & icacls $sharedDir /grant 'BUILTIN\Users:(OI)(CI)M' /T /C 2>&1 | Out-Null + Log "Granted BUILTIN\Users Modify on $sharedDir" +} + +if ($extracted) { Remove-Item $src -Recurse -Force -ErrorAction SilentlyContinue } +Log "==== DONE ====" +Write-Host "" +Write-Host "goCMM restore complete. Log: $log" -ForegroundColor Green diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/gocmm-debug.bat b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/gocmm-debug.bat new file mode 100644 index 0000000..a00ac01 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/gocmm-debug.bat @@ -0,0 +1,25 @@ +@echo off +REM gocmm-debug.bat - launcher for gocmm-debug.ps1 +REM +REM *** RUN THIS AS THE OPERATOR (the locked-down shop-floor user). *** +REM *** DO NOT right-click "Run as administrator" - that hides the bug. *** +REM +REM Reproduces the goCMM "Requested registry access is not allowed" error and +REM dumps the goCMM registry key's ACL so we can confirm the lockdown stripped +REM the BUILTIN\Users write grant. Output lands in C:\Logs\CMM\. + +setlocal +set "HERE=%~dp0" + +echo. +echo Running goCMM debug as %USERDOMAIN%\%USERNAME% ... +echo (If you see "Administrator: ..." in this window title, STOP - run as the operator instead.) +echo. + +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%HERE%gocmm-debug.ps1" + +echo. +echo --------------------------------------------------------------- +echo Done. Collect everything under C:\Logs\CMM\ (the .txt and .reg). +echo --------------------------------------------------------------- +pause diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/gocmm-debug.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/gocmm-debug.ps1 new file mode 100644 index 0000000..ed0dd3f --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/gocmm-debug.ps1 @@ -0,0 +1,115 @@ +# gocmm-debug.ps1 - diagnose goCMM "Requested registry access is not allowed" post-lockdown. +# +# RUN AS THE OPERATOR (the locked-down shop-floor user), NOT elevated / not "Run as administrator". +# Elevation would falsely succeed (admins have write) and hide the bug. +# +# Root cause being tested: goCMM's GEA_OFI_Common.RegistrySettings.GetRegistryString opens +# HKLM\Software\General Electric\goCMM with writable:TRUE even to READ a value. goCMM is +# 32-bit, so that redirects to HKLM\SOFTWARE\WOW6432Node\General Electric\goCMM. If the +# operator lacks WRITE on that key (lockdown stripped the BUILTIN\Users grant), the open throws. +# +# Output: C:\Logs\CMM\gocmm-debug--.txt (pull this back for review) + +$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 "gocmm-debug-$env:COMPUTERNAME-$ts.txt" +function W($m){ $m | Tee-Object -FilePath $log -Append } + +$key32native = 'SOFTWARE\General Electric\goCMM' # path under the 32-bit (Registry32) base +$keyWow = 'HKLM:\SOFTWARE\WOW6432Node\General Electric\goCMM' + +W "================ goCMM debug ================" +W "When : $(Get-Date)" +W "PC : $env:COMPUTERNAME" +W "User : $env:USERDOMAIN\$env:USERNAME" +W "PS : $($PSVersionTable.PSVersion) (process is $([IntPtr]::Size*8)-bit)" +W "Elevated: $((New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))" +W "" + +W "================ whoami /all (identity + groups + privileges) ================" +(whoami /all 2>&1 | Out-String) | W +W "" + +W "================ goCMM key values (32-bit view, read-only open) ================" +try { + $base32 = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine','Registry32') + $kr = $base32.OpenSubKey($key32native, $false) + if ($kr) { + foreach ($n in $kr.GetValueNames()) { W (" {0} = {1}" -f $n, $kr.GetValue($n)) } + $kr.Close() + } else { W " (key NOT found in 32-bit view)" } +} catch { W " ERROR reading values: $($_.Exception.Message)" } +W "" + +W "================ PROBE 1: mimic goCMM GetRegistryString -> OpenSubKey(writable:TRUE), 32-bit view ================" +W "(this is the exact call that throws in the app)" +try { + $base32 = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine','Registry32') + $kw = $base32.OpenSubKey($key32native, $true) + if ($kw) { W " RESULT: SUCCESS - opened goCMM key WRITABLE. (operator HAS write; settings should work)"; $kw.Close() } + else { W " RESULT: NULL - key missing; app would attempt CreateSubKey (also needs write)" } +} catch [System.Security.SecurityException] { + W " RESULT: *** REPRODUCED *** SecurityException: $($_.Exception.Message)" + W " -> operator lacks WRITE on the goCMM key. Lockdown stripped the BUILTIN\Users grant, or a Deny applies." +} catch { + W " RESULT: OTHER $($_.Exception.GetType().FullName): $($_.Exception.Message)" +} +W "" + +W "================ PROBE 2: read-only open (writable:FALSE) - does the operator at least READ? ================" +try { + $base32 = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine','Registry32') + $kr2 = $base32.OpenSubKey($key32native, $false) + if ($kr2) { W " read-only open OK (read works; only WRITE-open is denied -> confirms the writable:true bug)"; $kr2.Close() } + else { W " read-only open returned NULL (key missing)" } +} catch { W " read-only open FAILED: $($_.Exception.Message)" } +W "" + +W "================ ACL on goCMM key (the smoking gun) ================" +try { + $acl = Get-Acl -Path $keyWow + W (" Owner : " + $acl.Owner) + W (" SDDL : " + $acl.Sddl) + W " Access rules:" + $acl.Access | ForEach-Object { + W (" {0,-30} {1,-22} {2,-6} Inherited={3}" -f $_.IdentityReference, $_.RegistryRights, $_.AccessControlType, $_.IsInherited) + } + $usersWrite = $acl.Access | Where-Object { + $_.AccessControlType -eq 'Allow' -and + "$($_.IdentityReference)" -match 'Users' -and + ("$($_.RegistryRights)" -match 'WriteKey|SetValue|FullControl') + } + if ($usersWrite) { W " >> BUILTIN\Users WRITE ACE present (grant survived). Failure is elsewhere - check for a Deny ACE or wrong view." } + else { W " >> NO BUILTIN\Users WRITE ACE. Confirms lockdown removed the grant. <<" } +} catch { W " Get-Acl failed: $($_.Exception.Message)" } +W "" + +W "================ raw reg export of the key (for record) ================" +$regOut = Join-Path $dir "gocmm-key-$env:COMPUTERNAME-$ts.reg" +reg export "HKLM\SOFTWARE\WOW6432Node\General Electric\goCMM" "$regOut" /y 2>&1 | Out-String | W +W " exported -> $regOut" +W "" + +W "================ goCMM version + install ================" +foreach ($p in @( + 'C:\Program Files (x86)\General Electric\goCMM\GEAOperatorFriendlyInterface.exe', + 'C:\Program Files (x86)\General Electric\goCMM\GEA_OFI_Common.dll')) { + if (Test-Path $p) { $vi = (Get-Item $p).VersionInfo; W (" {0} FileVer={1} ProductVer={2}" -f (Split-Path $p -Leaf), $vi.FileVersion, $vi.ProductVersion) } + else { W " MISSING: $p" } +} +W "" + +W "================ UAC / registry virtualization ================" +(reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v EnableLUA 2>&1 | Out-String) | W +(reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v EnableVirtualization 2>&1 | Out-String) | W +W "" + +W "================ DONE ================" +W "Log : $log" +W "Reg : $regOut" +Write-Host "" +Write-Host "Done. Collected:" -ForegroundColor Green +Write-Host " $log" +Write-Host " $regOut"