From 5fe7e7767fea0c2208ae90fb69957e67cec0ebdc Mon Sep 17 00:00:00 2001 From: cproudlock Date: Sun, 3 May 2026 10:15:57 -0400 Subject: [PATCH] Install-FromManifest: PCTypes alias map for rename reorg Phase 1 of the gea-shopfloor-* rename per project-shopfloor-rename-reorg. Manifests can use either old names (Standard, Standard-Machine, CMM, Keyence, etc.) or new names (gea-shopfloor-collections, gea-shopfloor-cmm, gea-shopfloor-keyence, etc.) interchangeably. Equivalence sets defined inline. Each set is a list of names that all match the same identity. The match logic resolves the current PC's identity AND each PCTypes entry into their alias sets, then matches if the sets intersect. Standard maps to all three new shopfloor variants (collections, nocollections, common) so an existing PCTypes=['Standard'] manifest entry still applies when PC pc-type.txt becomes any of the three. Standard-Machine maps to (collections, nocollections) only since Timeclock subtype is now collapsed under common. Smoke-tested on win11 VM as SYSTEM via qga: dispatcher run with PCType='gea-shopfloor-collections' against the existing common manifest (Standard-only PCTypes filters) fires Oracle / FMS hosts pin correctly. Same run with PCType='Standard' PCSubType='Machine' fires identically. Phases 3+4 (repo folder renames + startnet.cmd menu reorg) deferred to the next session - high breakage risk, must ship atomically. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../common/lib/Install-FromManifest.ps1 | 112 +++++++++++++++++- 1 file changed, 109 insertions(+), 3 deletions(-) diff --git a/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1 b/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1 index 6ec23dc..4f37433 100644 --- a/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1 +++ b/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1 @@ -38,11 +38,16 @@ $ErrorActionPreference = 'Continue' # logged; manifests tagged with a newer MINOR are fine. # # Changelog: +# 2.3 - PCTypes filter accepts old (Standard, Standard-Machine, CMM, ...) +# and new (gea-shopfloor-collections, gea-shopfloor-cmm, ...) names +# interchangeably via alias sets. Transitional for the rename reorg. +# 2.2 - added TargetMachineNumbers filter (reads C:\Enrollment\machine-number.txt +# then falls back to DNC registry HKLM\...\GE Aircraft Engines\DNC\General\MachineNo) # 2.1 - added TargetHostnames filter (exact + -like wildcards) # 2.0 - initial Stage 2a: PS1/BAT/File/Registry/INF action types, # Always/MarkerFile/ValueMatches/pnputil detection, PCTypes filter $LIB_MANIFEST_MAJOR = 2 -$LIB_MANIFEST_MINOR = 1 +$LIB_MANIFEST_MINOR = 3 $logDir = Split-Path -Parent $LogFile if (-not (Test-Path $logDir)) { @@ -360,14 +365,67 @@ function Invoke-InstallerAction { # Both filters are ANDed so they compose: scope to a type AND a hostname # subset, or either alone. Case-insensitive throughout. # --------------------------------------------------------------------------- +# PCTypes alias map for the 2026-05-03 rename reorg. Manifests may use +# either old names (Standard, Standard-Machine, CMM, etc.) or new names +# (gea-shopfloor-collections, gea-shopfloor-cmm, etc.). Each entry below +# is a set of names that all match the same identity. The match logic +# resolves the current PC's identity AND each PCTypes entry into their +# alias sets, then matches if the sets intersect. See +# project-shopfloor-rename-reorg memory for the full rename plan. +$script:_pcTypeAliasGroups = @( + @('Standard', 'gea-shopfloor-collections', 'gea-shopfloor-nocollections', 'gea-shopfloor-common'), + @('Standard-Machine', 'gea-shopfloor-collections', 'gea-shopfloor-nocollections'), + @('Standard-Timeclock', 'gea-shopfloor-common'), + @('CMM', 'gea-shopfloor-cmm'), + @('Keyence', 'gea-shopfloor-keyence'), + @('Lab', 'gea-shopfloor-common'), + @('WaxAndTrace', 'gea-shopfloor-waxtrace'), + @('Genspect', 'gea-shopfloor-genspect'), + @('Display', 'gea-shopfloor-display'), + @('Heattreat', 'gea-shopfloor-heattreat') +) + +# Returns every alias set (each itself a string array) that contains $name. +# Multiple groups can return the same name (e.g. "Standard" appears in the +# super-group covering all three new shopfloor variants AND has its own +# subtype-specific groups - by design, matches widely). +function Get-PCTypeAliasSets { + param([string]$Name) + $hits = @() + foreach ($g in $script:_pcTypeAliasGroups) { + foreach ($n in $g) { + if ($n -ieq $Name) { $hits += ,$g; break } + } + } + return ,$hits +} + function Test-PCTypeMatches { param($App, [string]$Type, [string]$SubType) if (-not $App.PCTypes -or $App.PCTypes.Count -eq 0) { return $true } if (-not $Type) { return $true } + + # Build the set of strings the CURRENT PC matches: bare PCType, + # "Type-SubType", and every alias-set member of either. + $myNames = New-Object System.Collections.Generic.HashSet[string]([System.StringComparer]::OrdinalIgnoreCase) + [void]$myNames.Add($Type) + if ($SubType) { [void]$myNames.Add("$Type-$SubType") } + foreach ($n in @($Type, "$Type-$SubType") | Where-Object { $_ }) { + foreach ($g in (Get-PCTypeAliasSets -Name $n)) { + foreach ($alias in $g) { [void]$myNames.Add($alias) } + } + } + foreach ($t in $App.PCTypes) { if ($t -eq '*') { return $true } - if ($t -eq $Type) { return $true } - if ($SubType -and $t -eq "$Type-$SubType") { return $true } + if ($myNames.Contains($t)) { return $true } + # Manifest entry's PCTypes value may itself be an alias - expand it + # and check overlap with the PC's identity set. + foreach ($g in (Get-PCTypeAliasSets -Name $t)) { + foreach ($alias in $g) { + if ($myNames.Contains($alias)) { return $true } + } + } } return $false } @@ -387,6 +445,47 @@ function Test-HostnameMatches { return $false } +# Machine-number filter. Stable identifier tied to the bay; survives PC +# replacement at the same machine. Source of truth = the value the tech +# entered at the PXE menu, persisted to C:\Enrollment\machine-number.txt +# by startnet.cmd. Falls back to the DNC registry if that file is missing +# (covers PCs that pre-date this filter being introduced). +$script:_cachedMachineNumber = $null +function Get-CurrentMachineNumber { + if ($null -ne $script:_cachedMachineNumber) { return $script:_cachedMachineNumber } + $candidates = @( + 'C:\Enrollment\machine-number.txt' + ) + foreach ($p in $candidates) { + if (Test-Path -LiteralPath $p) { + $v = (Get-Content -LiteralPath $p -ErrorAction SilentlyContinue | Select-Object -First 1) + if ($v) { $script:_cachedMachineNumber = $v.Trim(); return $script:_cachedMachineNumber } + } + } + foreach ($r in @( + 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\General', + 'HKLM:\SOFTWARE\GE Aircraft Engines\DNC\General' + )) { + if (Test-Path $r) { + $p = Get-ItemProperty -Path $r -ErrorAction SilentlyContinue + if ($p.MachineNo) { $script:_cachedMachineNumber = [string]$p.MachineNo; return $script:_cachedMachineNumber } + } + } + $script:_cachedMachineNumber = '' + return '' +} + +function Test-MachineNumberMatches { + param($App) + if (-not $App.TargetMachineNumbers -or $App.TargetMachineNumbers.Count -eq 0) { return $true } + $myNumber = Get-CurrentMachineNumber + if (-not $myNumber) { return $false } # entry restricts by machine #, but PC has no machine # -> exclude + foreach ($n in $App.TargetMachineNumbers) { + if ([string]$n -ieq $myNumber) { return $true } + } + return $false +} + # --------------------------------------------------------------------------- # Main loop # --------------------------------------------------------------------------- @@ -414,6 +513,13 @@ foreach ($app in $config.Applications) { continue } + if (-not (Test-MachineNumberMatches -App $app)) { + $myNum = Get-CurrentMachineNumber + Write-InstallLog " TargetMachineNumbers filter: entry targets $($app.TargetMachineNumbers -join ',') but machine number is $(if ($myNum) { $myNum } else { '(none)' }) - skipping" + $pcFiltered++ + continue + } + if (Test-AppInstalled -App $app) { Write-InstallLog ' Already installed at expected version - skipping' $skipped++