From 48bc609eb59ab6c8ddc1042652a33c17c853b4e9 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Fri, 19 Jun 2026 08:59:08 -0400 Subject: [PATCH] CMM/DODA: fix DODA-bay profile resolution + goCMM 2.12 DataFolder + settings converter - Get-PCProfile: subtype-strip fallback. DODA bays set pc-subtype.txt=doda, so the profile key became "gea-shopfloor-cmm-doda" which matched NO profile/alias -> Get-PCProfile returned null -> callers fell to hardcoded defaults (no PC-DMIS desktop icons; Defect Tracker / WJ Shopfloor / Plant Apps force-started). Now an unmatched compound key falls back to the bare pc-type (-> CMM). VM-tested: gea-shopfloor-cmm/doda + CMM/doda resolve to CMM (7 apps, PC-DMIS present); non-CMM unaffected. - 09-Setup-CMM Step 2.5c: Active Setup seed for goCMM 2.12 DataFolder. goCMM 2.12 stores its shared-data-dir in HKCU\Software\General Electric\goCMM\DataFolder (decompiled: RegistrySettings uses Registry.CurrentUser - per-user). Imaging as SupportUser wouldn't reach the ShopFloor operator's HKCU. Active Setup runs the StubPath once per user at first logon -> every user gets DataFolder=C:\geaofi\. VM-tested: StubPath writes the value with the trailing backslash intact. - Convert-goCMMSettings.ps1: converts legacy goCMM 1.1 ApplicationSettings.xml -> goCMM 2.12 goCMMSettings.xml schema. VM-tested: output byte-identical to a real goCMM-2.12-produced CMM10 goCMMSettings.xml. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Shopfloor/lib/Get-PCProfile.ps1 | 22 ++ .../gea-shopfloor-cmm/09-Setup-CMM.ps1 | 21 ++ .../scripts/Convert-goCMMSettings.ps1 | 195 ++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Convert-goCMMSettings.ps1 diff --git a/playbook/shopfloor-setup/Shopfloor/lib/Get-PCProfile.ps1 b/playbook/shopfloor-setup/Shopfloor/lib/Get-PCProfile.ps1 index 09d54a6..db2c38b 100644 --- a/playbook/shopfloor-setup/Shopfloor/lib/Get-PCProfile.ps1 +++ b/playbook/shopfloor-setup/Shopfloor/lib/Get-PCProfile.ps1 @@ -113,6 +113,28 @@ if ($siteConfig -and $siteConfig.pcProfiles -and $profileKey) { } } } + # Subtype-strip fallback: a compound key like "gea-shopfloor-cmm-doda" (DODA + # CMM bays set pc-subtype.txt=doda) has no profile of its own. Fall back to + # the BARE pc-type ("gea-shopfloor-cmm" -> "CMM"). Without this the profile + # resolves to $null and callers silently use hardcoded defaults (wrong + # desktop icons + unwanted startup items). + if (-not $pcProfile -and $pcSubtype) { + $bare = $pcType + $pcProfile = $siteConfig.pcProfiles.$bare + if (-not $pcProfile) { + foreach ($g in $pcProfileAliasGroups) { + if ($g -icontains $bare) { + foreach ($alias in $g) { + if ($alias -ieq $bare) { continue } + $candidate = $siteConfig.pcProfiles.$alias + if ($candidate) { $pcProfile = $candidate; break } + } + break + } + } + } + if ($pcProfile) { $profileKey = $bare } + } } if ($pcProfile) { diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/09-Setup-CMM.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/09-Setup-CMM.ps1 index 4bf9d7b..bb7c442 100644 --- a/playbook/shopfloor-setup/gea-shopfloor-cmm/09-Setup-CMM.ps1 +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/09-Setup-CMM.ps1 @@ -193,6 +193,27 @@ try { Write-CMMLog "Granted Users + Authenticated Users Modify on $frontEnd" } catch { Write-CMMLog "Failed to ensure ${frontEnd}: $_" "WARN" } +# ============================================================================ +# Step 2.5c: Active Setup - seed goCMM 2.12 DataFolder per-user for ALL users +# ============================================================================ +# goCMM 2.12 stores its shared-data-dir in HKCU\Software\General Electric\goCMM +# \DataFolder (decompiled: RegistrySettings uses Registry.CurrentUser - PER USER, +# not HKLM). Setting it at imaging only touches the imaging user (SupportUser); +# the ShopFloor operator's HKCU would be empty -> goCMM prompts them to pick the +# folder on first launch. Active Setup runs StubPath once in EACH user's context +# at first logon, so every user (SupportUser, ShopFloor, future) gets +# DataFolder=C:\geaofi\. Bump Version to re-push. (Uses Set-ItemProperty in the +# stub, not reg.exe, to avoid the trailing-backslash quoting trap.) +$asKey = 'HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{8F3B2A10-7C4D-4E9A-B1F6-0A9C5D2E7B43}' +try { + New-Item -Path $asKey -Force | Out-Null + Set-ItemProperty -Path $asKey -Name '(default)' -Value 'goCMM DataFolder (per-user)' + Set-ItemProperty -Path $asKey -Name 'Version' -Value '1' + $stub = 'powershell.exe -NoProfile -WindowStyle Hidden -Command "New-Item -Path ''HKCU:\SOFTWARE\General Electric\goCMM'' -Force | Out-Null; Set-ItemProperty -Path ''HKCU:\SOFTWARE\General Electric\goCMM'' -Name DataFolder -Value ''C:\geaofi\''"' + Set-ItemProperty -Path $asKey -Name 'StubPath' -Value $stub + Write-CMMLog "Seeded Active Setup: goCMM DataFolder=C:\geaofi\ (per-user, all users)" +} catch { Write-CMMLog "Failed to seed Active Setup goCMM DataFolder: $_" 'WARN' } + # ============================================================================ # Step 2.6: First-run-as-admin for each installed PC-DMIS version # ============================================================================ diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Convert-goCMMSettings.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Convert-goCMMSettings.ps1 new file mode 100644 index 0000000..07b8e13 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/scripts/Convert-goCMMSettings.ps1 @@ -0,0 +1,195 @@ +<# +Convert-goCMMSettings.ps1 + +Convert a legacy goCMM 1.1 ApplicationSettings.xml (C:\geaofi\) to the goCMM +2.12.3 goCMMSettings.xml schema. + +The two are different SettingsModel schemas (different filename too). This is a +TEMPLATE-based converter: new-only fields get defaults, the per-bay values are +carried over from the old XML (+ optional overrides). Decisions baked in +(2026-06-19): access-control left blank, operator key normalized to "Operator", +cal intervals/restart use template defaults (old values NOT carried), Slack/Andon +/Quindos/Emx dropped. + +Program paths default to the goCMM 2.12 convention (C:\geaofi\goCMM PC-DMIS +Programs\) - override with -ResetProgram / -ProbeCalibrationProgram if the .PRG +files live elsewhere. Those .PRG files MUST exist at the path written here. + +Usage: + .\Convert-goCMMSettings.ps1 -OldXml C:\geaofi\ApplicationSettings.xml ` + -OutFile C:\geaofi\goCMMSettings.xml -PcdmisVersion 2016 -CmmId CMM10 +#> +param( + [Parameter(Mandatory=$true)][string]$OldXml, + [Parameter(Mandatory=$true)][string]$OutFile, + [string]$PcdmisVersion, # 2016 | 2019 | 2026 (or full "PC-DMIS ... 64-bit") + [string]$CmmId, # e.g. CMM10 - used for the cal-program name default + [string]$PartGroup, # optional override (friendly S:\ or UNC); else from old XML + [string]$ResetProgram, # optional override + [string]$ProbeCalibrationProgram # optional override +) +$ErrorActionPreference = 'Stop' + +if (-not (Test-Path -LiteralPath $OldXml)) { throw "Old XML not found: $OldXml" } +[xml]$old = Get-Content -LiteralPath $OldXml -Raw + +# --- part-group share canonicalization (FQDN + \SHARED, matches goCMM 2.12) --- +function Canon([string]$pg) { + if (-not $pg) { return $pg } + $pg = $pg -replace '(?i)^S:\\', '\\tsgwp00525.wjs.geaerospace.net\SHARED\' + $pg = [regex]::Replace($pg, '(?i)\\\\tsgwp00525(?:\.[A-Za-z0-9.\-]+)?\\shared(?=\\|$)', '\\tsgwp00525.wjs.geaerospace.net\SHARED') + return $pg +} + +# --- resolve per-bay values (override -> old XML -> default) --- +# NOTE: read attributes with GetAttribute() - .Name collides with XmlElement.Name. +$pg = $PartGroup +if (-not $pg) { + $pgNode = $old.SettingsModel.PartGroups.PartGroup + if ($pgNode) { $pg = $pgNode.GetAttribute('FullName'); if (-not $pg) { $pg = $pgNode.FullName } } +} +$pg = Canon $pg + +# NB: PowerShell vars are case-insensitive - do NOT name this $cmmid (collides with the $CmmId param). +$machineCmmId = $old.SettingsModel.MachineDefinition.CMMID +if (-not $machineCmmId -and $CmmId) { $machineCmmId = "WJRP/$CmmId" } + +# SelectedVersionString from -PcdmisVersion +$verMap = @{ '2016' = 'PC-DMIS 2016.0 64-bit'; '2019' = 'PC-DMIS 2019 R2 64-bit'; '2026' = 'PC-DMIS 2026.1 64-bit' } +$selVer = '' +if ($PcdmisVersion) { $selVer = if ($verMap.ContainsKey($PcdmisVersion)) { $verMap[$PcdmisVersion] } else { $PcdmisVersion } } + +# bool carryovers (old flat flags -> new IsEnabled) +function OldBool($node, $default='false') { $v = $node; if ($null -eq $v -or $v -eq '') { $default } else { ([string]$v).ToLower() } } +$pcdmisEnabled = OldBool $old.SettingsModel.UsingPcDmis 'true' +$modusEnabled = OldBool $old.SettingsModel.UsingModus 'false' +$winState = if ($old.SettingsModel.DefaultWindowState) { $old.SettingsModel.DefaultWindowState } else { 'Maximized' } +$allowSel = OldBool $old.SettingsModel.AllowOperationProgramSelection 'true' +$visExec = OldBool $old.SettingsModel.PcDmisSettings.VisibleDuringExecution 'true' +$cmmMode = if ($old.SettingsModel.PcDmisSettings.CMMMode) { $old.SettingsModel.PcDmisSettings.CMMMode } else { 'Online' } +$sizeTol = if ($old.SettingsModel.MachineDefinition.ProbeCalSizeTol) { $old.SettingsModel.MachineDefinition.ProbeCalSizeTol } else { '0.0005' } +$formTol = if ($old.SettingsModel.MachineDefinition.ProbeCalFormTol) { $old.SettingsModel.MachineDefinition.ProbeCalFormTol } else { '0.0005' } +$hasRotary = OldBool $old.SettingsModel.MachineDefinition.MachineHasRotaryTable 'false' + +# program paths (override -> goCMM 2.12 convention default) +$progDir = 'C:\geaofi\goCMM PC-DMIS Programs' +if (-not $ResetProgram) { $ResetProgram = "$progDir\MachineResetProgram.prg" } +if (-not $ProbeCalibrationProgram) { + $calName = if ($CmmId) { "${CmmId}_Cal_All_Probes_rev2.PRG" } else { 'Cal_All_Probes_rev2.PRG' } + $ProbeCalibrationProgram = "$progDir\$calName" +} + +# --- operator inputs (old KeyLabel + NonTBIInputs -> new OperatorInput; OPER -> Operator) --- +$inputs = @() +$kl = $old.SettingsModel.OperatorInputs.KeyLabel +if ($kl) { $inputs += [pscustomobject]@{ Key = $kl.GetAttribute('Name'); Display = $kl.GetAttribute('Display') } } +foreach ($oi in $old.SettingsModel.OperatorInputs.NonTBIInputs.OperatorInput) { + if ($oi -is [System.Xml.XmlElement]) { $inputs += [pscustomobject]@{ Key = $oi.GetAttribute('Name'); Display = $oi.GetAttribute('Display') } } +} +if (-not $inputs) { + $inputs = @([pscustomobject]@{Key='SERNO';Display='Serial Number'}, [pscustomobject]@{Key='Operator';Display='Operator'}) +} +$opXml = ($inputs | ForEach-Object { + $k = if ($_.Key -ieq 'OPER') { 'Operator' } else { $_.Key } # decision #4 + $d = if ($_.Key -ieq 'OPER') { 'Operator' } else { $_.Display } + " `n $k`n $d`n `n " +}) -join "`n" + +function X([string]$s){ [System.Security.SecurityElement]::Escape($s) } + +$xml = @" + + + true + PCDMIS + + + $(X $pg) + + + + +$opXml + + false + $(X $winState) + $allowSel + + + $pcdmisEnabled + $visExec + $(X $ResetProgram) + + false + false + $(X $ProbeCalibrationProgram) + + + + false + false + 1000 + true + ProgramCount + false + $(X $selVer) + 5 + 5 + 0 + false + false + + + + + $modusEnabled + true + + + false + false + + + + + -Organiser + + + $(X $machineCmmId) + + + false + $sizeTol + $formTol + + 72 + 0 + + + 24 + 0 + + $hasRotary + false + $(X $cmmMode) + + + + + + + +"@ + +$dir = Split-Path -Parent $OutFile +if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } +# validate well-formed before writing +[void][xml]$xml +Set-Content -LiteralPath $OutFile -Value $xml -Encoding UTF8 +Write-Host "Wrote $OutFile" +Write-Host " PartGroup = $pg" +Write-Host " CMMID = $machineCmmId" +Write-Host " SelectedVersion = $selVer" +Write-Host " ResetProgram = $ResetProgram" +Write-Host " ProbeCalProgram = $ProbeCalibrationProgram" +Write-Host "NOTE: confirm those .PRG files exist at the paths above (-ResetProgram / -ProbeCalibrationProgram to override)."