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_<PC>_<ts>.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) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-06-11 08:27:30 -04:00
parent da380fbcd7
commit bfe17fe123
6 changed files with 462 additions and 0 deletions

View File

@@ -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

View File

@@ -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_<PC>_<timestamp>.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"

View File

@@ -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 "<zip>"
REM plain restore
REM Install-goCMMSettings.bat "<zip>" "\\server\SHARED\CMM\CMM7\Spool"
REM restore + override the Selected Part Group (per-bay)
REM Install-goCMMSettings.bat "<zip>" /replace <from> <to>
REM restore + find/replace <from> -> <to> 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 "^<zip^>"
echo %~nx0 "^<zip^>" "^<SelectedPartGroup^>"
echo %~nx0 "^<zip^>" /replace ^<from^> ^<to^>
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 ^<from^> ^<to^> & pause & exit /b 1 )
%PS% -ReplaceFrom "%~3" -ReplaceTo "%~4"
) else if not "%~2"=="" (
%PS% -SelectedPartGroup "%~2"
) else (
%PS%
)
echo.
pause

View File

@@ -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

View File

@@ -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

View File

@@ -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-<PC>-<timestamp>.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"