diff --git a/plugins/computers/client/Set-ShopDBDeviceId.ps1 b/plugins/computers/client/Set-ShopDBDeviceId.ps1 new file mode 100644 index 00000000..49aa9702 --- /dev/null +++ b/plugins/computers/client/Set-ShopDBDeviceId.ps1 @@ -0,0 +1,264 @@ +#requires -Version 5.1 +<# +.SYNOPSIS + Seed HKLM:\SOFTWARE\GE\ShopDB\DeviceId from whatever identity a bay already + carries, so device identity can move off C:\Enrollment and into the + registry. + +.DESCRIPTION + Phase 3 of docs/proposals/device-identity.md. The registry is the + destination because it survives the profile cleanup and disk hygiene that + eats C:\Enrollment, is readable as SYSTEM with no file ACL to get wrong, and + is already where the collector looks for BaseUrl and ApiToken. + + This script does the one-time backfill: it reads the identity a bay holds + today and writes it to DeviceId. It is idempotent and safe to run every + GE-Enforce cycle. + + THE SOURCE DEPENDS ON THE PC TYPE, AND THAT IS THE WHOLE POINT. + + DeviceId names the DEVICE hanging off this PC. A machine number is a + different fact - "which bay is this" rather than "which device is this" - + and the two coincide for exactly one family: + + CMM bays the machine number IS the instrument id (CMM4), which is + why cmmid.txt has always doubled as identity. Copying it + is correct. + + part markers the machine number is the OPERATION (0613), and several + markers serve one operation. Copying it would name the + operation as the device. The collector refuses that + ("asset X is not a Part Marker; not linked") because a + machine filed under a device label reads as a marker + everywhere downstream. So machine number is NEVER a + source here. + + Keyence, no per-bay id exists at all. asset-id.txt is the only + Genspect, identity these have ever had. + wax-trace + + everything not a device bay. DeviceId is not consumed for these + else pc-types, so writing one is a landmine for the day that + pc-type gains a device spec. Writes nothing. + + 9999 is the unassigned placeholder and is skipped everywhere, matching + Report-AssetToShopDB.ps1 and the server. + +.PARAMETER Force + Overwrite a DeviceId that is already set to a DIFFERENT value. + + Off by default on purpose. asset-id.txt is written ONCE at imaging and + nothing updates it when a device is swapped, so a stale file overwriting a + corrected registry value would re-introduce the exact defect this move is + meant to fix. A disagreement is reported and left alone for a human. + +.PARAMETER PCType + Override the pc-type instead of reading C:\Enrollment\pc-type.txt. For + testing and for a bay whose file is missing. + +.PARAMETER DeviceId + Write this value directly, skipping resolution. Still honours -Force and + the 9999 rule. + +.PARAMETER LogPath + Append a transcript here as well as writing to the pipeline. + +.EXAMPLE + .\Set-ShopDBDeviceId.ps1 -WhatIf + Show what would be written on this bay without touching the registry. + +.EXAMPLE + .\Set-ShopDBDeviceId.ps1 + Seed DeviceId. Safe to run repeatedly and from a GE-Enforce PS1 entry. + +.NOTES + Exit codes: + 0 wrote a value, it was already correct, or this is not a device bay + 1 an error stopped the write + 2 an existing DeviceId disagrees with the resolved value; needs a human + or -Force +#> +[CmdletBinding(SupportsShouldProcess = $true)] +param( + [switch]$Force, + [string]$PCType, + [string]$DeviceId, + [string]$LogPath +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$RegPath = 'HKLM:\SOFTWARE\GE\ShopDB' +$RegValueName = 'DeviceId' +$Placeholder = '9999' + +function Log { + param([string]$Message) + $line = "{0} [Set-ShopDBDeviceId] {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message + Write-Output $line + if ($LogPath) { + try { Add-Content -LiteralPath $LogPath -Value $line -ErrorAction Stop } catch { } + } +} + +function Read-FirstLine { + # One line, trimmed, empty when absent or unreadable. A bay missing a file + # is the normal case, not an error. + param([string]$Path) + if (-not (Test-Path -LiteralPath $Path)) { return '' } + try { + $v = [string](Get-Content -LiteralPath $Path -First 1 -ErrorAction Stop) + return $v.Trim() + } catch { + Log "WARN could not read ${Path}: $($_.Exception.Message)" + return '' + } +} + +function Get-MachineNoFromRegistry { + # eDNC/NTLARS, WOW6432Node first, matching Report-AssetToShopDB.ps1. + foreach ($rp in @( + 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\General', + 'HKLM:\SOFTWARE\GE Aircraft Engines\DNC\General' + )) { + try { + if (Test-Path $rp) { + $v = [string](Get-ItemProperty -Path $rp -Name MachineNo -ErrorAction Stop).MachineNo + if ($v) { + $v = $v.Trim() + if ($v -and $v -ne $Placeholder) { return $v } + } + } + } catch { + Log "WARN could not read MachineNo from ${rp}: $($_.Exception.Message)" + } + } + return '' +} + +# Which pc-types drive a device, and whether the machine number is a legitimate +# identity for that device. Mirrors SUBORDINATE_DEVICE_MAP in +# plugins/computers/pctypemap.py. A site that added its own device pc-type +# through a subordinatedevice_ setting adds it here too. +$DeviceFamilies = @{ + 'gea-shopfloor-cmm' = @{ Label = 'CMM'; MachineNoIsDevice = $true } + 'gea-shopfloor-keyence' = @{ Label = 'Vision System'; MachineNoIsDevice = $false } + 'gea-shopfloor-genspect' = @{ Label = 'Genspect'; MachineNoIsDevice = $false } + 'gea-shopfloor-waxtrace' = @{ Label = 'Form Tracer'; MachineNoIsDevice = $false } + 'gea-shopfloor-partmarker'= @{ Label = 'Part Marker'; MachineNoIsDevice = $false } +} + +# --- 1. what kind of bay is this ------------------------------------------- + +if (-not $PCType) { $PCType = Read-FirstLine 'C:\Enrollment\pc-type.txt' } + +if (-not $PCType) { + Log 'no pc-type on this PC (C:\Enrollment\pc-type.txt missing or empty); nothing to do' + exit 0 +} + +if (-not $DeviceFamilies.ContainsKey($PCType)) { + Log "pc-type '$PCType' does not drive a device; DeviceId not written" + exit 0 +} + +$family = $DeviceFamilies[$PCType] +Log "pc-type '$PCType' drives a $($family.Label)" + +# --- 2. resolve the identity ------------------------------------------------ + +$resolved = '' +$source = '' + +if ($DeviceId) { + $resolved = $DeviceId.Trim() + $source = '-DeviceId parameter' +} else { + # Most stable identity first, and the machine number only where it really + # names the device. + if ($family.MachineNoIsDevice) { + $v = Read-FirstLine 'C:\Enrollment\cmm\cmmid.txt' + if ($v -and $v -ne $Placeholder) { $resolved = $v; $source = 'C:\Enrollment\cmm\cmmid.txt' } + + if (-not $resolved) { + $v = Get-MachineNoFromRegistry + if ($v) { $resolved = $v; $source = 'eDNC MachineNo registry' } + } + } + + if (-not $resolved) { + $v = Read-FirstLine 'C:\Enrollment\asset-id.txt' + if ($v -and $v -ne $Placeholder) { $resolved = $v; $source = 'C:\Enrollment\asset-id.txt' } + } + + if (-not $resolved) { + # The 0.12.0 name, before the file was generalised. + $v = Read-FirstLine 'C:\Enrollment\measuringtool-id.txt' + if ($v -and $v -ne $Placeholder) { $resolved = $v; $source = 'C:\Enrollment\measuringtool-id.txt' } + } +} + +if (-not $resolved) { + # Nothing to copy. Silence is the right answer: the collector still adopts + # or mints as it does today, and inventing a value here would name a device + # that nobody can account for. + Log "no identity found for this $($family.Label) bay; DeviceId not written" + exit 0 +} + +if ($resolved -eq $Placeholder) { + Log "resolved value is the $Placeholder placeholder; DeviceId not written" + exit 0 +} + +Log "resolved DeviceId '$resolved' from $source" + +# --- 3. write it ------------------------------------------------------------ + +$existing = '' +try { + if (Test-Path $RegPath) { + $prop = Get-ItemProperty -Path $RegPath -Name $RegValueName -ErrorAction SilentlyContinue + if ($prop) { $existing = [string]$prop.$RegValueName; $existing = $existing.Trim() } + } +} catch { + Log "WARN could not read existing ${RegValueName}: $($_.Exception.Message)" +} + +if ($existing -eq $resolved) { + Log "DeviceId already '$resolved'; nothing to do" + exit 0 +} + +if ($existing -and -not $Force) { + # The registry value wins by default. C:\Enrollment is written once at + # imaging and never updated on a device swap, so the file is the more + # likely one to be wrong. + Log "REFUSING to overwrite: DeviceId is '$existing', $source says '$resolved'" + Log 'Re-run with -Force if the file is right, or fix the file if the registry is.' + exit 2 +} + +try { + if (-not (Test-Path $RegPath)) { + if ($PSCmdlet.ShouldProcess($RegPath, 'Create registry key')) { + New-Item -Path $RegPath -Force | Out-Null + Log "created $RegPath" + } + } + $action = if ($existing) { "Set $RegValueName '$existing' -> '$resolved'" } + else { "Set $RegValueName '$resolved'" } + if ($PSCmdlet.ShouldProcess($RegPath, $action)) { + New-ItemProperty -Path $RegPath -Name $RegValueName -Value $resolved ` + -PropertyType String -Force | Out-Null + Log "wrote $RegPath\$RegValueName = '$resolved'" + } else { + Log "WHATIF would write $RegPath\$RegValueName = '$resolved'" + } +} catch { + Log "ERROR could not write ${RegValueName}: $($_.Exception.Message)" + exit 1 +} + +exit 0