debug.log from the bay shows the part group enumerates fine (14 .geop files
opened), then goCMM connects to PC-DMIS over COM and throws:
System.ArgumentNullException: Value cannot be null. Parameter name: type
at System.Activator.CreateInstance(Type type)
at GEAE.Common.CMM.CMMInterfaces.PCDMIS.PCDMIS.<ConnectToSoftware>b__43_0()
-> System.NullReferenceException at ConnectToSoftware(...)
Root cause: PC-DMIS automation server is not COM-registered, so
Type.GetTypeFromProgID returns null -> CreateInstance(null) throws -> the NRE
is the downstream symptom. Not part-group, not permissions, not calibration.
- PROBE 4: pull the .NET Runtime / Application Error crash stack for goCMM from
the Application log so the next run captures the null in one shot.
- PROBE 5: (a) part-group UNC reachability; (b) PC-DMIS COM registration check
(PCDLRN.* ProgID -> CLSID -> LocalServer32) that names the missing registration
and the Pcdlrn.exe /regserver fix; plus install presence for context.
- .bat header documents the COM root cause.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
257 lines
14 KiB
PowerShell
257 lines
14 KiB
PowerShell
# 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 ================"
|
|
# The goCMM MSI installs goCMM.exe (1.1.6718.x) to this dir and points the Start
|
|
# Menu shortcut at it - goCMM.exe IS the launcher. (An earlier version of this
|
|
# script checked for GEAOperatorFriendlyInterface.exe, which this product does
|
|
# NOT install - that produced a false MISSING. Check the real exe.)
|
|
foreach ($p in @(
|
|
'C:\Program Files (x86)\General Electric\goCMM\goCMM.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 "================ PROBE 3: Selected Part Group vs ApplicationSettings.xml (the startup NRE) ================"
|
|
# goCMM at start matches the registry 'Selected Part Group' against the
|
|
# <PartGroup FullName> entries in ApplicationSettings.xml with a CASE-SENSITIVE
|
|
# (Ordinal) compare. No match -> SelectedPartGroup is null -> a startup deref
|
|
# throws "Object reference not set to an instance of an object". This is a
|
|
# DIFFERENT failure from the registry SecurityException probed above.
|
|
# Mismatch forms seen: host (bare vs FQDN) and share segment (\shared vs \SHARED).
|
|
try {
|
|
$base32 = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine','Registry32')
|
|
$kpg = $base32.OpenSubKey($key32native, $false)
|
|
$regPg = $null; $sharedDir = 'C:\geaofi'
|
|
if ($kpg) {
|
|
$regPg = [string]$kpg.GetValue('Selected Part Group','')
|
|
$sd = [string]$kpg.GetValue('Shared Data Directory','')
|
|
if ($sd) { $sharedDir = $sd.TrimEnd('\') }
|
|
$kpg.Close()
|
|
}
|
|
W (" reg 'Selected Part Group' = [{0}]" -f $regPg)
|
|
W (" Shared Data Directory = {0}" -f $sharedDir)
|
|
|
|
$xml = Join-Path $sharedDir 'ApplicationSettings.xml'
|
|
if (-not (Test-Path $xml)) {
|
|
W " *** ApplicationSettings.xml NOT FOUND at $xml - no part groups to match -> null SelectedPartGroup -> NRE likely. <<"
|
|
} else {
|
|
$txt = [System.IO.File]::ReadAllText($xml)
|
|
# Capture FullName in both attribute (FullName="...") and element (<FullName>...</FullName>) forms.
|
|
$names = New-Object System.Collections.Generic.List[string]
|
|
foreach ($m in [regex]::Matches($txt, 'FullName\s*=\s*"([^"]*)"')) { $names.Add($m.Groups[1].Value) }
|
|
foreach ($m in [regex]::Matches($txt, '<FullName>([^<]*)</FullName>')) { $names.Add($m.Groups[1].Value) }
|
|
$names = $names | Select-Object -Unique
|
|
W (" ApplicationSettings.xml part-group FullName entries ({0}):" -f @($names).Count)
|
|
foreach ($n in $names) { W (" [{0}]" -f $n) }
|
|
|
|
if (-not $regPg) {
|
|
W " >> reg 'Selected Part Group' is EMPTY - goCMM has no part group pinned -> null -> NRE likely. <<"
|
|
} else {
|
|
$exact = $false; $caseOnly = $false
|
|
foreach ($n in $names) {
|
|
if ([string]::Equals($n, $regPg, [StringComparison]::Ordinal)) { $exact = $true; break }
|
|
if ([string]::Equals($n, $regPg, [StringComparison]::OrdinalIgnoreCase)) { $caseOnly = $true }
|
|
}
|
|
if ($exact) {
|
|
W " >> MATCH: reg value matches an XML FullName exactly (case-sensitive). Part group is NOT the NRE cause. <<"
|
|
} elseif ($caseOnly) {
|
|
W " >> *** CASE-ONLY MATCH *** reg value matches an XML entry only when case is ignored."
|
|
W " >> goCMM's compare is case-sensitive -> Find returns null -> SelectedPartGroup null -> NRE. <<"
|
|
W " >> FIX: re-run Install-goCMMSettings.ps1 (>= commit d441abd canonicalizes host + \SHARED case). <<"
|
|
} else {
|
|
W " >> *** NO MATCH *** reg 'Selected Part Group' is absent from ApplicationSettings.xml (any case)."
|
|
W " >> Find returns null -> SelectedPartGroup null -> NRE. Wrong per-bay override, or XML from a different bay. <<"
|
|
}
|
|
}
|
|
}
|
|
} catch { W " ERROR in part-group probe: $($_.Exception.Message)" }
|
|
W ""
|
|
|
|
W "================ PROBE 4: goCMM crash events (Application log) - the actual stack ================"
|
|
# The NRE reproduces even for admin, so it is NOT a rights problem. The faulting
|
|
# stack names the null object. .NET pushes it to the Application log under
|
|
# '.NET Runtime' / 'Application Error' / 'Windows Error Reporting'.
|
|
try {
|
|
$since = (Get-Date).AddDays(-14)
|
|
$ev = Get-WinEvent -FilterHashtable @{ LogName='Application'; StartTime=$since } -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.ProviderName -match '\.NET Runtime|Application Error|Windows Error Reporting|Application Hang' -and "$($_.Message)" -match 'goCMM' }
|
|
if (-not $ev) {
|
|
W " (no goCMM-related crash events in the last 14 days - launch goCMM once, then re-run this)"
|
|
} else {
|
|
foreach ($e in ($ev | Select-Object -First 5)) {
|
|
W (" ---- {0} [{1}] EventId={2} ----" -f $e.TimeCreated, $e.ProviderName, $e.Id)
|
|
foreach ($line in ("$($e.Message)" -split "`r?`n")) { W (" " + $line) }
|
|
W ""
|
|
}
|
|
W " >> The topmost goCMM / GEA_OFI frame in the stack above is where the null is dereferenced. <<"
|
|
}
|
|
} catch { W " ERROR reading event log: $($_.Exception.Message)" }
|
|
W ""
|
|
|
|
W "================ PROBE 5: part-group UNC reachability + PC-DMIS install ================"
|
|
# (a) Can THIS bay open the Selected Part Group share? An unreachable / unauthenticated
|
|
# UNC -> goCMM enumerates null -> startup NRE, regardless of local rights (admin too).
|
|
try {
|
|
$base32 = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine','Registry32')
|
|
$kpg = $base32.OpenSubKey($key32native, $false)
|
|
$pg = if ($kpg) { [string]$kpg.GetValue('Selected Part Group','') } else { '' }
|
|
if ($kpg) { $kpg.Close() }
|
|
if ($pg) {
|
|
W (" part group UNC: $pg")
|
|
if (Test-Path -LiteralPath $pg -ErrorAction SilentlyContinue) {
|
|
$n = @(Get-ChildItem -LiteralPath $pg -ErrorAction SilentlyContinue).Count
|
|
W (" >> REACHABLE - $n item(s) under it. (not the NRE cause) <<")
|
|
} else {
|
|
W " >> *** UNREACHABLE *** bay cannot open the part-group UNC (share offline, not on the"
|
|
W " >> production net yet, or no credential to \\tsgwp00525\SHARED). goCMM enumerating this"
|
|
W " >> path returns null -> startup NRE even for admin. Check net connectivity + share auth. <<"
|
|
}
|
|
} else { W " (no Selected Part Group in registry)" }
|
|
} catch { W " ERROR testing part-group UNC: $($_.Exception.Message)" }
|
|
W ""
|
|
# (b) PC-DMIS COM automation - THE confirmed root cause (see debug.log). goCMM
|
|
# connects via Activator.CreateInstance on the PC-DMIS COM type. If the
|
|
# automation server is not registered, Type.GetTypeFromProgID returns null
|
|
# -> CreateInstance(null) -> "ArgumentNullException: ... Parameter name: type"
|
|
# -> ConnectToSoftware derefs null -> NRE. PC-DMIS can be RUNNING and still
|
|
# not be COM-registered. Stack: GEAE.Common.CMM.CMMInterfaces.PCDMIS.ConnectToSoftware.
|
|
$comOk = $false
|
|
foreach ($pgid in 'PCDLRN.Application','PCDLRN.Automation','Pcdlrn.Application','PCDLRN.Object') {
|
|
foreach ($cls in "HKLM:\SOFTWARE\Classes\$pgid","HKLM:\SOFTWARE\Classes\WOW6432Node\$pgid") {
|
|
if (Test-Path $cls) {
|
|
$clsid = (Get-ItemProperty -Path "$cls\CLSID" -ErrorAction SilentlyContinue).'(default)'
|
|
$comOk = $true
|
|
$srv = $null
|
|
if ($clsid) {
|
|
foreach ($cb in "HKLM:\SOFTWARE\Classes\CLSID\$clsid\LocalServer32","HKLM:\SOFTWARE\Classes\WOW6432Node\CLSID\$clsid\LocalServer32") {
|
|
if (Test-Path $cb) { $srv = (Get-ItemProperty -Path $cb -EA SilentlyContinue).'(default)' }
|
|
}
|
|
}
|
|
W (" COM ProgID registered: $pgid CLSID=$clsid server=$srv")
|
|
}
|
|
}
|
|
}
|
|
if (-not $comOk) {
|
|
W " >> *** PC-DMIS COM SERVER NOT REGISTERED *** no PCDLRN.* ProgID in HKCR. This is exactly the"
|
|
W " >> null behind 'ArgumentNullException: ... Parameter name: type' in debug.log -> goCMM startup NRE."
|
|
W " >> FIX: run the installed Pcdlrn.exe once ELEVATED (self-registers), or 'Pcdlrn.exe /regserver'."
|
|
W " >> Then confirm the registered build matches the goCMM-expected PC-DMIS version (the version gate). <<"
|
|
}
|
|
# install presence (context for the COM verdict)
|
|
$pcdlrn = $null
|
|
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) {
|
|
$hit = Get-ChildItem $d -Filter 'Pcdlrn.exe' -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if ($hit) { $pcdlrn = $hit.FullName; W (" Pcdlrn.exe: $($hit.FullName) v$((Get-Item $hit.FullName).VersionInfo.FileVersion)"); break }
|
|
}
|
|
}
|
|
if (-not $pcdlrn) { W " Pcdlrn.exe NOT found in standard dirs - PC-DMIS not installed where expected." }
|
|
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"
|