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
This commit is contained in:
264
plugins/computers/client/Set-ShopDBDeviceId.ps1
Normal file
264
plugins/computers/client/Set-ShopDBDeviceId.ps1
Normal file
@@ -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_<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
|
||||||
Reference in New Issue
Block a user