Phase 3+4 rename reorg: repo dir renames + startnet.cmd menu
Pairs with Phase 1+2 from earlier (alias maps in Install-FromManifest,
GE-Enforce, Get-PCProfile, verify-state). See project-shopfloor-rename-reorg
memory for the plan.
Phase 3 (repo + paths):
- git mv per-PC-type dirs to gea-shopfloor-* names:
Standard -> gea-shopfloor-collections
CMM -> gea-shopfloor-cmm
Keyence -> gea-shopfloor-keyence
Genspect -> gea-shopfloor-genspect
WaxAndTrace -> gea-shopfloor-waxtrace
Display -> gea-shopfloor-display
Lab -> gea-shopfloor-common (folded; Timeclock+Lab merge)
- New gea-shopfloor-nocollections/ (clone of collections sans UDC scripts).
- New gea-shopfloor-heattreat/ (placeholder, README only).
- Move Standard/ntlars-backups/ -> _ntlars-backups/ (per-MN, not per-type).
- Run-ShopfloorSetup.ps1: Resolve-PCTypeDir helper walks alias group when
the on-disk dir for the current pcType is missing. Set-MachineNumber
helper-copy gated on collections|nocollections|legacy Standard-Machine.
- Update-MachineNumber.ps1: pcProfiles lookups try gea-shopfloor-collections
first, fall back to legacy Standard-Machine. PowerShell 5.1 compatible
(no null-coalesce).
Phase 4 (startnet.cmd menu):
- Choice 3 "GEA Shopfloor" now drills into a 9-item sub-menu instead of
going straight to enrollment. Sub-cats:
1. Machine with Collections -> gea-shopfloor-collections
2. Machine without Collections -> gea-shopfloor-nocollections
3. Common (Timeclock, Lab) -> gea-shopfloor-common
4. Keyence -> gea-shopfloor-keyence
5. CMM -> gea-shopfloor-cmm
6. Genspect -> gea-shopfloor-genspect
7. Heattreat -> gea-shopfloor-heattreat
8. Wax and Trace -> gea-shopfloor-waxtrace
9. Display -> gea-shopfloor-display
- Office menu (existing 6-option) follows for every sub-cat.
- Machine number prompt only for collections + nocollections.
- pc-subtype.txt + display-type.txt no longer written. PCTYPE is a
single full string (gea-shopfloor-*); subtype-aware code paths fall
back to empty and resolve via the alias map.
- CMM bootstrap stage gate switched from "%PCTYPE%"=="CMM" to
"%PCTYPE%"=="gea-shopfloor-cmm".
Test harness:
- B-enforce/run.sh PCSUBTYPE default changed from "Machine" to "" so
single-arg invocation matches the new single-string scheme. Two-arg
legacy form ("Standard Machine") still works via aliasing.
- B-enforce/tamper.ps1 alias-aware Test-MatrixEntryMatches mirroring
verify-state.ps1.
Smoke-tested on win11 VM as SYSTEM via qga: B-enforce harness 5-phase
cycle (stage / baseline / tamper / heal / idempotent) passes 10/10
with PCType=gea-shopfloor-collections AND with legacy "Standard Machine"
two-arg form.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
176
playbook/shopfloor-setup/gea-shopfloor-cmm/09-Setup-CMM.ps1
Normal file
176
playbook/shopfloor-setup/gea-shopfloor-cmm/09-Setup-CMM.ps1
Normal file
@@ -0,0 +1,176 @@
|
||||
# 09-Setup-CMM.ps1 - CMM type setup (runs during shopfloor-setup phase).
|
||||
#
|
||||
# At imaging time the tsgwp00525 SFLD share is NOT yet reachable - Azure DSC
|
||||
# has not provisioned the share credentials that early. So we install from a
|
||||
# WinPE-staged local copy at C:\CMM-Install (put there by startnet.cmd when
|
||||
# the tech picks pc-type=CMM). Ongoing enforcement is handled by GE-Enforce
|
||||
# (registered separately in Run-ShopfloorSetup.ps1) reading cmm/manifest.json
|
||||
# from the tsgwp00525 share.
|
||||
#
|
||||
# Sequence:
|
||||
# 1. Enable .NET Framework 3.5 (PC-DMIS 2016 prereq on Win10/11 where 3.5
|
||||
# is an off-by-default optional feature).
|
||||
# 2. Run Install-FromManifest against C:\CMM-Install\cmm-manifest.json.
|
||||
# 2.5. Grant BUILTIN\Users Modify on PC-DMIS install dirs (Hexagon-documented
|
||||
# approach for non-admin runtime).
|
||||
# 3. Delete C:\CMM-Install to reclaim the ~2 GB of bootstrap installers.
|
||||
#
|
||||
# Library lookup: the imaging-time install uses the common Install-FromManifest
|
||||
# library at ..\common\lib\Install-FromManifest.ps1 (relative to $PSScriptRoot).
|
||||
#
|
||||
# Log: C:\Logs\CMM\09-Setup-CMM.log (stdout from this script) plus the
|
||||
# install-time log at C:\Logs\CMM\install.log written by Install-FromManifest.
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
$stagingRoot = 'C:\CMM-Install'
|
||||
$stagingMani = Join-Path $stagingRoot 'cmm-manifest.json'
|
||||
$libSource = Join-Path $PSScriptRoot '..\common\lib\Install-FromManifest.ps1'
|
||||
|
||||
$logDir = 'C:\Logs\CMM'
|
||||
$logFile = Join-Path $logDir 'install.log'
|
||||
$transcriptLog = Join-Path $logDir '09-Setup-CMM.log'
|
||||
|
||||
if (-not (Test-Path $logDir)) {
|
||||
New-Item -Path $logDir -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
# Independent transcript in addition to whatever Run-ShopfloorSetup.ps1 is
|
||||
# capturing at the top level. Lets a tech open C:\Logs\CMM\09-Setup-CMM.log
|
||||
# and see the entire CMM-type setup run without scrolling through the
|
||||
# monolithic shopfloor-setup.log.
|
||||
try { Start-Transcript -Path $transcriptLog -Append -Force | Out-Null } catch {}
|
||||
|
||||
function Write-CMMLog {
|
||||
param([string]$Message, [string]$Level = 'INFO')
|
||||
$stamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
Write-Host "[$stamp] [$Level] $Message"
|
||||
}
|
||||
|
||||
Write-CMMLog "================================================================"
|
||||
Write-CMMLog "=== CMM Setup (imaging-time) session start (PID $PID) ==="
|
||||
Write-CMMLog "Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)"
|
||||
Write-CMMLog "================================================================"
|
||||
|
||||
# Diagnostic dump - knowing WHY the script took a branch is half the battle.
|
||||
Write-CMMLog "Script root: $PSScriptRoot"
|
||||
foreach ($file in @('pc-type.txt','pc-subtype.txt','machine-number.txt')) {
|
||||
$path = "C:\Enrollment\$file"
|
||||
if (Test-Path -LiteralPath $path) {
|
||||
$content = (Get-Content -LiteralPath $path -First 1 -ErrorAction SilentlyContinue).Trim()
|
||||
Write-CMMLog " $file = $content"
|
||||
} else {
|
||||
Write-CMMLog " $file = (not present)"
|
||||
}
|
||||
}
|
||||
if (Test-Path $stagingRoot) {
|
||||
$bootstrapFiles = @(Get-ChildItem -LiteralPath $stagingRoot -File -ErrorAction SilentlyContinue)
|
||||
Write-CMMLog "Bootstrap staging: $stagingRoot ($($bootstrapFiles.Count) files)"
|
||||
foreach ($f in $bootstrapFiles) {
|
||||
Write-CMMLog " - $($f.Name) ($([math]::Round($f.Length/1MB)) MB)"
|
||||
}
|
||||
} else {
|
||||
Write-CMMLog "Bootstrap staging: $stagingRoot (DOES NOT EXIST - startnet.cmd did not stage it)" "ERROR"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 1: Enable .NET Framework 3.5
|
||||
# ============================================================================
|
||||
# PC-DMIS 2016 lists .NET 3.5 as a prereq for some older components. On Win10/
|
||||
# Win11 it's an optional Windows feature that is OFF by default. Enable-
|
||||
# WindowsOptionalFeature pulls the payload from Windows Update when the PC
|
||||
# has internet; sources from the installed Windows image otherwise. Idempotent
|
||||
# (no-op if already enabled). We swallow failures because if internet and
|
||||
# media are both unavailable this becomes a known gap rather than an imaging
|
||||
# blocker - we'd still rather try to install PC-DMIS and surface the real
|
||||
# failure in its log.
|
||||
Write-CMMLog "Checking .NET Framework 3.5 state..."
|
||||
try {
|
||||
$netfx = Get-WindowsOptionalFeature -Online -FeatureName 'NetFx3' -ErrorAction Stop
|
||||
if ($netfx.State -eq 'Enabled') {
|
||||
Write-CMMLog " .NET 3.5 already enabled"
|
||||
} else {
|
||||
Write-CMMLog " .NET 3.5 state is $($netfx.State) - enabling now (may take a minute)..."
|
||||
$result = Enable-WindowsOptionalFeature -Online -FeatureName 'NetFx3' -All -NoRestart -ErrorAction Stop
|
||||
Write-CMMLog " Enable-WindowsOptionalFeature RestartNeeded=$($result.RestartNeeded)"
|
||||
}
|
||||
} catch {
|
||||
Write-CMMLog " Failed to enable .NET 3.5: $_" "WARN"
|
||||
Write-CMMLog " Continuing anyway - PC-DMIS installers will surface any hard dependency."
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 2: Install apps from the WinPE-staged bootstrap at C:\CMM-Install
|
||||
# ============================================================================
|
||||
if (-not (Test-Path $stagingRoot)) {
|
||||
Write-CMMLog "$stagingRoot does not exist - startnet.cmd did not stage CMM installers" "ERROR"
|
||||
Write-CMMLog "Skipping install. The logon enforcer will pick up from the share when SFLD creds are available."
|
||||
}
|
||||
elseif (-not (Test-Path $stagingMani)) {
|
||||
Write-CMMLog "$stagingMani missing - staging directory is incomplete" "ERROR"
|
||||
}
|
||||
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
|
||||
$rc = $LASTEXITCODE
|
||||
Write-CMMLog "Install-FromManifest returned $rc"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 2.5: Grant Users write access to PC-DMIS install directories
|
||||
# ============================================================================
|
||||
# PC-DMIS writes settings, probe configs, and measurement data to its own
|
||||
# install directory at runtime. Without Modify permission for BUILTIN\Users,
|
||||
# non-admin accounts get a UAC elevation prompt on every launch. Granting
|
||||
# the ACL here is the Hexagon-documented approach for non-admin deployment
|
||||
# and avoids the need for a first-run-as-admin (which hits a license dialog
|
||||
# and can't be automated silently).
|
||||
$pcdmisDirs = @(
|
||||
'C:\Program Files\Hexagon\PC-DMIS 2016.0 64-bit',
|
||||
'C:\Program Files\Hexagon\PC-DMIS 2019 R2 64-bit',
|
||||
'C:\ProgramData\Hexagon',
|
||||
'C:\Program Files (x86)\General Electric\goCMM',
|
||||
'C:\Program Files\DODA'
|
||||
)
|
||||
foreach ($dir in $pcdmisDirs) {
|
||||
if (-not (Test-Path -LiteralPath $dir)) {
|
||||
Write-CMMLog "PC-DMIS dir not found: $dir - skipping ACL"
|
||||
continue
|
||||
}
|
||||
try {
|
||||
$acl = Get-Acl -LiteralPath $dir
|
||||
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
||||
'BUILTIN\Users',
|
||||
'Modify',
|
||||
'ContainerInherit,ObjectInherit',
|
||||
'None',
|
||||
'Allow'
|
||||
)
|
||||
$acl.AddAccessRule($rule)
|
||||
Set-Acl -LiteralPath $dir -AclObject $acl -ErrorAction Stop
|
||||
Write-CMMLog "Granted BUILTIN\Users Modify on $dir"
|
||||
} catch {
|
||||
Write-CMMLog "Failed to set ACL on $dir : $_" "WARN"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 3: Clean up 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"
|
||||
try {
|
||||
Remove-Item -LiteralPath $stagingRoot -Recurse -Force -ErrorAction Stop
|
||||
Write-CMMLog "Bootstrap cleanup complete"
|
||||
} catch {
|
||||
Write-CMMLog "Failed to delete $stagingRoot : $_" "WARN"
|
||||
}
|
||||
}
|
||||
|
||||
Write-CMMLog "=== CMM Setup Complete ==="
|
||||
try { Stop-Transcript | Out-Null } catch {}
|
||||
61
playbook/shopfloor-setup/gea-shopfloor-cmm/cmm-manifest.json
Normal file
61
playbook/shopfloor-setup/gea-shopfloor-cmm/cmm-manifest.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"Version": "2.0",
|
||||
"_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.",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"_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.",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"_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.",
|
||||
"Name": "Protect Viewer",
|
||||
"Installer": "ProtectViewer.msi",
|
||||
"Type": "MSI",
|
||||
"InstallArgs": "/qn /norestart ALLUSERS=1 MSIFASTINSTALL=7 REBOOT=ReallySuppress",
|
||||
"DetectionMethod": "Registry",
|
||||
"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.",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"_comment": "goCMM 1.1.6718 - Hexagon's CMM job launcher utility. No license check. Unpatched bundle EXE runs as-is.",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user