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++