Files
shopdb-flask/plugins/computers/client/Set-ShopDBDeviceId.ps1
cproudlock 9947a477b8
Some checks failed
CI / naming (push) Successful in 3s
CI / frontend (push) Successful in 12s
CI / migrations-mysql (push) Successful in 43s
CI / backend (push) Failing after 5m6s
Seed DeviceId in the registry from the identity a bay already carries
Phase 3 of the device-identity proposal moves device identity to
HKLM:\SOFTWARE\GE\ShopDB\DeviceId, which survives the profile cleanup and disk
hygiene that eats C:\Enrollment and needs no new key: the collector already
opens that path for BaseUrl and ApiToken. This is the backfill that gets a
value in there without re-imaging anything.

The source depends on the pc-type, and that is the whole point. DeviceId names
the DEVICE, while a machine number answers "which bay is this" - a different
fact that coincides for exactly one family:

  CMM           the machine number IS the instrument id (CMM4), which is why
                cmmid.txt has always doubled as identity. Copied.
  part marker   the machine number is the OPERATION, and several markers serve
                one operation. Copying it would name the operation as the
                device, which the collector refuses as the wrong kind of asset
                because a machine filed under a device label reads as a marker
                everywhere downstream. Never copied.
  Keyence,      no per-bay id has ever existed; asset-id.txt is the only
  Genspect,     identity these have.
  wax-trace
  anything else not a device bay, so nothing is written. DeviceId is not
                consumed for those pc-types and a value left there is a
                landmine for the day one gains a device spec.

Resolution per family mirrors Report-AssetToShopDB.ps1, including the 9999
placeholder skip and reading WOW6432Node before the native eDNC key.

Refuses to overwrite a DeviceId that disagrees, unless -Force. asset-id.txt is
written once at imaging and nothing updates it on a device swap, so a stale
file clobbering a corrected registry value would reintroduce the exact defect
this move exists to fix. GE-Enforce already reached the same conclusion for
machine-number.txt.

Verified in the win11 VM across ten cases: not-a-device bay, part marker,
CMM precedence, idempotent re-run, machine-number exclusion, eDNC fallback,
9999 skip, stale-file refusal (exit 2), -Force, and -WhatIf. The part-marker
guard was then planted - flipping it to treat the machine number as identity
writes operation 0613 into DeviceId, so the passing test is not vacuous.

Scope: product
2026-09-04 09:24:15 -04:00

265 lines
9.5 KiB
PowerShell

#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_<pctype> 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