CMM: subtype gating + relax exact-version + conditional cleanup + DODA placeholder

Carries over the lessons learned from wax/trace + Keyence imaging today
and threads the same pattern through the CMM path.

09-Setup-CMM.ps1:
- Pass PCType + PCSubType to Install-FromManifest so the manifest's
  per-entry PCTypes filter is honored. Without this every entry runs
  regardless of bay variant - the same bug Keyence had before per-model
  gating was added.
- Move bootstrap cleanup to a conditional that only deletes
  C:\CMM-Install once every (filter-applicable) manifest entry detects
  as installed. If a Hexagon installer forces an unplanned reboot
  mid-install, the new Run-ShopfloorSetup self-resume RunOnce fires on
  the next auto-login; the staging dir needs to still be on disk for
  the re-run to recover. Logs "retained ... not all entries installed
  yet - will retry on next self-resumed run" when partial.

cmm-manifest.json:
- Drop exact DetectionValue from PC-DMIS 2016, PC-DMIS 2019 R2, CLM
  1.8.73, and goCMM. Detection is now uninstall-key presence only, so
  a Hexagon security patch that bumps the DisplayVersion does not
  trigger a re-install loop with exit 1638 every GE-Enforce cycle.
  Bumping the installer in apps/ is the upgrade path - manifest engine
  detection should not also be a version drift catcher for vendor MSIs
  whose backward-compat is established by the vendor.
- Specific to goCMM: the installer filename version (1.1.6718.31289)
  does not match what the installer registers under its uninstall key.
  Dropping DetectionValue silences the false-mismatch loop the prior
  version would have triggered.
- Add DODA placeholder entry gated to PCTypes=["cmm-doda"]. Real
  Installer filename, args, and DetectionPath still TODO once the
  DODA binary is sourced + dropped at installers-post/cmm/.

startnet.cmd:
- Add :cmm_submenu after the user picks gea-shopfloor-cmm from the
  main menu. Two options: Standard (default PC-DMIS + CLM + goCMM
  + Protect Viewer) or With DODA. Mirrors :keyence_submenu pattern.
- Write CMMVARIANT to W:\Enrollment\pc-subtype.txt so Install-FromManifest
  on the bay can apply the PCTypes filter against gea-shopfloor-cmm-doda.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-05-22 09:48:16 -04:00
parent 45f39fd431
commit 548d85fed5
3 changed files with 89 additions and 23 deletions

View File

@@ -119,8 +119,16 @@ elseif (-not (Test-Path $libSource)) {
Write-CMMLog "Shared library not found at $libSource" "ERROR"
}
else {
Write-CMMLog "Running Install-FromManifest against $stagingRoot"
& $libSource -ManifestPath $stagingMani -InstallerRoot $stagingRoot -LogFile $logFile
# Pass PCType + PCSubType so cmm-manifest.json's PCTypes filter on
# subtype-specific entries (e.g. DODA gated to cmm-doda) is honored.
# Without this, ALL entries run regardless of subtype - same bug we
# had on Keyence before the per-model gating fix.
$pcType = ''
$pcSubType = ''
if (Test-Path 'C:\Enrollment\pc-type.txt') { $pcType = (Get-Content 'C:\Enrollment\pc-type.txt' -First 1 -EA 0).Trim() }
if (Test-Path 'C:\Enrollment\pc-subtype.txt') { $pcSubType = (Get-Content 'C:\Enrollment\pc-subtype.txt' -First 1 -EA 0).Trim() }
Write-CMMLog "Running Install-FromManifest against $stagingRoot (PCType=$pcType, PCSubType=$pcSubType)"
& $libSource -ManifestPath $stagingMani -InstallerRoot $stagingRoot -LogFile $logFile -PCType $pcType -PCSubType $pcSubType
$rc = $LASTEXITCODE
Write-CMMLog "Install-FromManifest returned $rc"
}
@@ -164,18 +172,52 @@ foreach ($dir in $pcdmisDirs) {
}
# ============================================================================
# Step 3: Clean up the bootstrap staging dir
# Step 3: Conditional cleanup of the bootstrap staging dir
# ============================================================================
# ~2 GB reclaimed. From here on, GE-Enforce takes over from the tsgwp00525
# share for ongoing updates.
if (Test-Path $stagingRoot) {
Write-CMMLog "Deleting bootstrap staging at $stagingRoot"
# Only delete C:\CMM-Install when EVERY manifest entry detected as installed.
# A vendor installer that forces an unplanned mid-install reboot would
# otherwise leave us with no recovery path on the self-resumed re-run
# (Run-ShopfloorSetup's new RunOnce would fire, but Step 2 would log
# "$stagingRoot does not exist" and bail). Leaving the staging dir in
# place until the manifest fully converges means a re-fire just re-runs
# the partial installs and completes.
$allDetected = $true
if (Test-Path $stagingMani) {
try {
$cfg = Get-Content $stagingMani -Raw | ConvertFrom-Json
foreach ($app in $cfg.Applications) {
if (-not $app.DetectionMethod -or -not $app.DetectionPath) { continue }
# Honor PCTypes filter when checking detection.
if ($app.PCTypes -and $app.PCTypes.Count -gt 0) {
$myNames = @($pcType)
if ($pcSubType) { $myNames += "$pcType-$pcSubType" }
$match = $false
foreach ($t in $app.PCTypes) { if ($myNames -contains $t) { $match = $true; break } }
if (-not $match) { continue } # not applicable to this PC, skip detection
}
if (-not (Test-Path $app.DetectionPath)) { $allDetected = $false; Write-CMMLog "Not installed: $($app.Name)"; break }
if ($app.DetectionName) {
$val = (Get-ItemProperty -Path $app.DetectionPath -Name $app.DetectionName -EA 0).$($app.DetectionName)
if (-not $val) { $allDetected = $false; Write-CMMLog "Not installed (no value): $($app.Name)"; break }
if ($app.DetectionValue -and $val -ne $app.DetectionValue) { $allDetected = $false; Write-CMMLog "Wrong version: $($app.Name) got $val expected $($app.DetectionValue)"; break }
}
}
} catch {
Write-CMMLog "Could not parse manifest for cleanup-gate check: $_" 'WARN'
$allDetected = $false
}
}
if ($allDetected -and (Test-Path $stagingRoot)) {
Write-CMMLog "All manifest entries installed. Deleting bootstrap staging at $stagingRoot"
try {
Remove-Item -LiteralPath $stagingRoot -Recurse -Force -ErrorAction Stop
Write-CMMLog "Bootstrap cleanup complete"
} catch {
Write-CMMLog "Failed to delete $stagingRoot : $_" "WARN"
}
} elseif (Test-Path $stagingRoot) {
Write-CMMLog "Bootstrap staging retained at $stagingRoot (not all entries installed yet - will retry on next self-resumed run)"
}
if (Get-Command Send-PxeStatus -ErrorAction SilentlyContinue) {

View File

@@ -3,26 +3,22 @@
"_comment": "CMM machine-app manifest, imaging-time only. Consumed by 09-Setup-CMM.ps1 reading from C:\\CMM-Install\\. Ongoing enforcement is handled separately by GE-Enforce reading cmm/manifest.json from the tsgwp00525 share. Option 3 (patched-MSI) install strategy: we bypass Hexagon's Burn bundle entirely for PC-DMIS 2016 and 2019 R2. The main PC-DMIS MSIs have been patched via COM SQL UPDATE (msibuild-style) to force the Condition column to '0' for two custom actions: ProcessLicensingFromBundle (which would otherwise spin for ~13 minutes trying to activate against licensing.wilcoxassoc.com with empty credentials) and IsLicenseDateValid (which would fail the install with 'no valid license'). With both CAs disabled, the MSI installs cleanly with no license present; PCDLRN.exe installs and loads at runtime and the tech activates a real license via clmadmin.exe after imaging. VS 2010/2012 x64 runtime prereqs are handled by the shared preinstall.json VC++ x64 entries (which run before this manifest). CLM Tools 1.5/1.7 chained MSIs from the original bundles are intentionally SKIPPED; CLM 1.8.73 standalone provides the admin + runtime interfaces. Protect Viewer is kept because it's useful alongside PC-DMIS 2019 R2.",
"Applications": [
{
"_comment": "PC-DMIS 2016 main MSI (PATCHED). ProcessLicensingFromBundle + IsLicenseDateValid custom actions have been pre-disabled by SQL UPDATE of InstallExecuteSequence.Condition to '0'. Install args: INSTALLFOLDER/APPLICATIONFOLDER paths have embedded double quotes to survive the runner's command-line concatenation when the path contains spaces. USINGWPFINSTALLER=1 mirrors the Burn bundle default and ensures HandleLicenseChoice CA (seq 783) stays skipped. HEIP=0 disables Hexagon telemetry. INSTALLPDFCONVERTER=0 skips the Nitro PDF converter. The patched MSI has a HashMismatch signature, which is expected and accepted by Windows Installer in /qn mode.",
"_comment": "PC-DMIS 2016 main MSI (PATCHED). ProcessLicensingFromBundle + IsLicenseDateValid custom actions have been pre-disabled by SQL UPDATE of InstallExecuteSequence.Condition to '0'. Install args: INSTALLFOLDER/APPLICATIONFOLDER paths have embedded double quotes to survive the runner's command-line concatenation when the path contains spaces. USINGWPFINSTALLER=1 mirrors the Burn bundle default and ensures HandleLicenseChoice CA (seq 783) stays skipped. HEIP=0 disables Hexagon telemetry. INSTALLPDFCONVERTER=0 skips the Nitro PDF converter. The patched MSI has a HashMismatch signature, which is expected and accepted by Windows Installer in /qn mode. Detection by uninstall-key presence (no DetectionValue) so a Hexagon security patch that bumps the DisplayVersion does not trigger a re-install loop with exit 1638; bumping the MSI in apps/ is the upgrade path, not version drift-catching.",
"Name": "PC-DMIS 2016",
"Installer": "pcdmis2016-main-patched.msi",
"Type": "MSI",
"InstallArgs": "/qn /norestart ALLUSERS=1 MSIFASTINSTALL=7 INSTALLFOLDER=\"C:\\Program Files\\Hexagon\\PC-DMIS 2016.0 64-bit\" APPLICATIONFOLDER=\"C:\\Program Files\\Hexagon\\PC-DMIS 2016.0 64-bit\" USINGWPFINSTALLER=1 HEIP=0 INSTALLPDFCONVERTER=0 REBOOT=ReallySuppress LICENSETYPE=LMSEntitlement",
"DetectionMethod": "Registry",
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{5389B196-81F0-44A9-A073-4C1D72041F09}",
"DetectionName": "DisplayVersion",
"DetectionValue": "11.0.1179.0"
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{5389B196-81F0-44A9-A073-4C1D72041F09}"
},
{
"_comment": "PC-DMIS 2019 R2 main MSI (PATCHED). Same patch strategy as 2016. Adds INSTALLOFFLINEHELP=0 (saves ~1.5 GB) and INSTALLUNIVERSALUPDATER=0 (disables Hexagon's auto-updater which we do not want on air-gapped shopfloor machines). Protect Viewer is a separate MSI installed next.",
"_comment": "PC-DMIS 2019 R2 main MSI (PATCHED). Same patch strategy as 2016. Adds INSTALLOFFLINEHELP=0 (saves ~1.5 GB) and INSTALLUNIVERSALUPDATER=0 (disables Hexagon's auto-updater which we do not want on air-gapped shopfloor machines). Protect Viewer is a separate MSI installed next. Detection by uninstall-key presence only - see PC-DMIS 2016 entry for reasoning.",
"Name": "PC-DMIS 2019 R2",
"Installer": "pcdmis2019-main-patched.msi",
"Type": "MSI",
"InstallArgs": "/qn /norestart ALLUSERS=1 MSIFASTINSTALL=7 INSTALLFOLDER=\"C:\\Program Files\\Hexagon\\PC-DMIS 2019 R2 64-bit\" APPLICATIONFOLDER=\"C:\\Program Files\\Hexagon\\PC-DMIS 2019 R2 64-bit\" USINGWPFINSTALLER=1 HEIP=0 INSTALLPDFCONVERTER=0 INSTALLOFFLINEHELP=0 INSTALLUNIVERSALUPDATER=0 REBOOT=ReallySuppress LICENSETYPE=LMSEntitlement",
"DetectionMethod": "Registry",
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{49DBE7F9-228A-4E66-8BB5-DB5A446DCAE7}",
"DetectionName": "DisplayVersion",
"DetectionValue": "14.2.728.0"
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{49DBE7F9-228A-4E66-8BB5-DB5A446DCAE7}"
},
{
"_comment": "Protect Viewer - companion tool bundled with PC-DMIS 2019 R2. Separate MSI with no license check of its own. Dark-extracted from the 2019 R2 Burn bundle and shipped as-is.",
@@ -34,28 +30,34 @@
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{7DE6B8AF-F580-4CDE-845F-FBE46C1FCF69}"
},
{
"_comment": "CLM 1.8.73 standalone bundle - provides clmadmin.exe and the runtime licensing libraries that both PC-DMIS 2016 and 2019 R2 use. Unlike the PC-DMIS bundles, CLM's WiX Burn bundle has no install-time license check (it IS the license tool), so we run it via its original EXE with no patches. The tech uses clmadmin.exe to activate a real license post-imaging, which unlocks both PC-DMIS versions.",
"_comment": "CLM 1.8.73 standalone bundle - provides clmadmin.exe and the runtime licensing libraries that both PC-DMIS 2016 and 2019 R2 use. Unlike the PC-DMIS bundles, CLM's WiX Burn bundle has no install-time license check (it IS the license tool), so we run it via its original EXE with no patches. The tech uses clmadmin.exe to activate a real license post-imaging, which unlocks both PC-DMIS versions. Detection by uninstall-key presence only - bumping the EXE is the upgrade path.",
"Name": "CLM 1.8.73",
"Installer": "CLM_1.8.73.0_x64.exe",
"Type": "EXE",
"InstallArgs": "/quiet /norestart /log \"C:\\Logs\\CMM\\CLM.log\"",
"LogFile": "C:\\Logs\\CMM\\CLM.log",
"DetectionMethod": "Registry",
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{a55fecde-0776-474e-a5b3-d57ea93d6a9f}",
"DetectionName": "DisplayVersion",
"DetectionValue": "1.8.73.0"
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{a55fecde-0776-474e-a5b3-d57ea93d6a9f}"
},
{
"_comment": "goCMM 1.1.6718 - Hexagon's CMM job launcher utility. No license check. Unpatched bundle EXE runs as-is.",
"_comment": "goCMM - Hexagon's CMM job launcher utility. No license check. Unpatched bundle EXE runs as-is. Note: the installer filename version (1.1.6718.31289) does not match the DisplayVersion the installer registers (it stamps an internal-build version under the uninstall key). Detection by uninstall-key presence avoids that mismatch + future version bumps.",
"Name": "goCMM",
"Installer": "goCMM_1.1.6718.31289.exe",
"Type": "EXE",
"InstallArgs": "/quiet /norestart /log \"C:\\Logs\\CMM\\goCMM.log\"",
"LogFile": "C:\\Logs\\CMM\\goCMM.log",
"DetectionMethod": "Registry",
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{94f02b85-bbca-422e-9b8b-0c16a769eced}",
"DetectionName": "DisplayVersion",
"DetectionValue": "1.1.6710.18601"
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{94f02b85-bbca-422e-9b8b-0c16a769eced}"
},
{
"_comment": "DODA - shopfloor variant add-on, gated to PCSubType=doda. PCTypes filter means a bay imaged as cmm-standard (or just cmm with no subtype) skips this entry; a bay imaged as cmm-doda runs it. TODO: fill in real Installer filename, InstallArgs, DetectionPath, and stage the binary at /home/camp/pxe-images/cmm/apps/ before sync-cmm.sh runs. Leaving as a placeholder with PCTypes filter so the wiring works as soon as the binary is ready.",
"Name": "DODA",
"PCTypes": ["cmm-doda"],
"Installer": "DODA-PLACEHOLDER.exe",
"Type": "EXE",
"InstallArgs": "/quiet /norestart",
"DetectionMethod": "Registry",
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{DODA-PRODUCT-CODE-TBD}"
}
]
}