#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