Let a metrology bay name the instrument it drives

The server has accepted `measuringtoolid` since the adoption work landed, and it
is the FIRST entry in the resolution order precisely because it is the identity
that survives a PC swap. Nothing ever sent it. The reporter read the eDNC
registry, cmmid.txt, machine-number.txt and pc-type.txt, and its own comment
said metrology bays have no per-bay id and therefore send nothing - so a Keyence
or Genspect bay fell through all four steps to minting, which the server's own
docstring calls the last resort.

Minting derives the asset number from the HOSTNAME, so a permanent instrument
inherits the identity of whichever PC drove it that week: replace the PC and
either the number lies or a second tool appears for the same physical unit. That
is how 43 legacy MT-#### tools ended up shadowed by minted twins. The half that
prevents it was built, tested and undeliverable.

The reporter now reads C:\Enrollment\measuringtool-id.txt and sends it when
present. Its own file, NOT machine-number.txt: machinenumber answers "which bay
is this" and is what GE-Enforce TargetMachineNumbers gates on, so naming a tool
there would silently stop every bay-gated manifest entry from matching.

The paste-ready reporter in COLLECTOR-INTEGRATION.md is a second implementation
of the same payload, so it gets the same resolver rather than being left to
drift. The field was also missing from the payload table and from the classic
api.asp mapping, and there was no prose anywhere describing how a tool is
resolved - added, including why minting is last and what the two guards refuse.

VERIFIED ON WINDOWS 11 (build 26200), four cases: a named instrument is read and
sent; no file sends no field and exits 0; a whitespace-only file behaves as
absent rather than sending an empty string; and a padded value with a second
line yields the first line trimmed.
This commit is contained in:
cproudlock
2026-08-20 10:28:24 -04:00
parent adb30c8875
commit 21afa0b56e
2 changed files with 79 additions and 4 deletions

View File

@@ -201,6 +201,7 @@ a column.
|---|---|---|
| `hostname` (required) | string | Identity. Matches `Computer.hostname` (case-insensitive), then falls back to `Asset.assetnumber`. New asset created if no match. |
| `machinenumber` | string | Identifies the MACHINE this PC drives, never the PC. A new PC always takes `assetnumber = hostname`, and an existing PC's `assetnumber` is left alone. The number resolves a machine asset and builds a PC -> machine `controls` relationship; an unknown number warns rather than creating a machine. The placeholder `9999` and empty string link nothing. See "Machine links" below. |
| `measuringtoolid` | string | Identifies the INSTRUMENT this PC drives, read from `C:\Enrollment\measuringtool-id.txt`. SEPARATE from `machinenumber` on purpose: "which bay is this" and "which instrument is this" are different facts, and `machinenumber` is what GE-Enforce `TargetMachineNumbers` gates on, so repointing it at a tool would silently stop every bay-gated manifest entry from matching. The value is an existing measuring tool's `assetnumber`. An unknown value warns rather than minting a tool, and a value that resolves to something which is NOT a measuring tool is refused with the asset named. See "Measuring tool links" below. |
| `pctype` | string | `gea-shopfloor-*` imaging type -> `Computer.computertypeid` via the configurable `pctypemap` settings. Unmapped value -> warning, not error. |
| `pcsubtype` | string | Accepted but not stored yet -> warning. |
| `serialnumber` | string | `Asset.serialnumber`. |
@@ -263,6 +264,43 @@ so control still follows from controlling the marker, without two markers
contesting a link only one can hold. Backups from a marker PC file against the
marker rather than the operation.
### PC -> measuring tool links
A metrology pc-type (CMM, Keyence, Genspect, wax-and-trace) means the PC drives
an attached instrument. The collector links it PC -> tool as `controls`, tagged
`assetrelationships.label = 'collector:measuringtool'`.
Resolution runs most-stable-identity first, and ADOPTS before it mints:
1. **`measuringtoolid`** - the instrument named in
`C:\Enrollment\measuringtool-id.txt`. Explicit, survives a PC swap, and it is
what a Keyence or Genspect bay has instead of a bay id.
2. A prior collector link on this PC, reactivated.
3. An existing tool this PC already controls that the collector did NOT create -
a legacy `MT-####` row, or one somebody made by hand. Adopting stamps the
label so it is ours from then on.
4. The reported machine number, when it resolves to a measuring tool. This is
the CMM case: `cmmid.txt` already reports `CMM4`.
5. Mint, only when none of the above matched.
**Why minting is last.** It derives the asset number from the HOSTNAME, so a
permanent instrument inherited the identity of whichever PC drove it that week:
replace the PC and either the number lies or a second tool appears for the same
physical unit. It also could not see a tool it had not created itself, so at the
reference site 43 legacy `MT-####` tools ended up shadowed by minted
`<HOST>-CMM` twins, three records deep in places. Treat anything minted as a
placeholder to be reconciled.
Two guards on the file's contents:
- An **unresolvable** value warns and links nothing, rather than inventing a
phantom instrument nobody can account for.
- A value that resolves to an asset which is **not a measuring tool** is refused,
naming the asset it hit. A machine number pasted into `measuringtool-id.txt`
would otherwise link the PC to that MACHINE under a measuring-tool label - a
link that reads as an instrument everywhere downstream, on a row the machine
sync also owns.
### PC -> printer relationship sync
When a payload carries `defaultprinter` and/or `printers`, the collector syncs
@@ -308,6 +346,7 @@ The fleet's classic-ASP reporter posts form fields to
|---|---|
| `hostname` | `hostname` |
| `machineNo` | `machinenumber` |
| `measuringToolId` | `measuringtoolid` |
| `pcType` | `pctype` |
| `serialNumber` | `serialnumber` |
| `loggedInUser` | `loggedinuser` |
@@ -599,6 +638,19 @@ function Get-ShopdbMachineNumber {
return $machineNumber
}
function Get-ShopdbMeasuringToolId {
# The INSTRUMENT this PC drives, for a metrology bay. Deliberately its own
# file: machinenumber is what GE-Enforce TargetMachineNumbers gates on, so
# naming a tool there would silently stop bay-gated entries from matching.
if (Test-Path 'C:\Enrollment\measuringtool-id.txt') {
try {
$v = (Get-Content 'C:\Enrollment\measuringtool-id.txt' -First 1 -ErrorAction Stop).Trim()
if ($v) { return $v }
} catch {}
}
return ''
}
function Get-ShopdbCorpIPv4 {
# Pick the corp/AESFMA NIC IP. Same allowed-range gate as
# Report-AssetToShopDB.ps1 - update the ranges if the site re-VLANs.
@@ -658,6 +710,7 @@ function Send-ShopdbCollectorReport {
if (-not $hostname) { $hostname = $env:COMPUTERNAME }
$machineNumber = Get-ShopdbMachineNumber
$measuringToolId = Get-ShopdbMeasuringToolId
$ipAddress = Get-ShopdbCorpIPv4
$serialNumber = ''
@@ -723,6 +776,7 @@ function Send-ShopdbCollectorReport {
if ($defaultPrinter) { $payload['defaultprinter'] = $defaultPrinter }
if ($printerIds.Count) { $payload['printers'] = $printerIds }
if ($machineNumber) { $payload['machinenumber'] = $machineNumber }
if ($measuringToolId) { $payload['measuringtoolid'] = $measuringToolId }
if ($pcType) { $payload['pctype'] = $pcType }
if ($pcSubType) { $payload['pcsubtype'] = $pcSubType }
if ($serialNumber) { $payload['serialnumber'] = $serialNumber }

View File

@@ -172,6 +172,7 @@ try {
# 2. C:\Enrollment\cmm\cmmid.txt - CMM bay id (e.g. CMM3).
# 3. C:\Enrollment\machine-number.txt - imaging value.
# keyence / genspect / part-marker have no per-bay id -> no machineNo sent.
# A metrology bay names its INSTRUMENT instead, in measuringtool-id.txt below.
$machineNo = ''
foreach ($regPath in @(
'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\General',
@@ -236,6 +237,25 @@ try {
Log "WARN could not read logged-in user: $($_.Exception.Message)"
}
# The INSTRUMENT this PC drives, named by its own enrollment file. Deliberately
# NOT machine-number.txt: machinenumber answers "which bay is this" and is what
# GE-Enforce TargetMachineNumbers gates on, so pointing it at a tool would
# silently stop every bay-gated manifest entry from matching. A Keyence or
# Genspect bay has an instrument and no bay id, which is the case this exists
# for; a CMM already reports its bay id through cmmid.txt and the server falls
# back to that.
#
# The server ADOPTS the named instrument and refuses to mint one from a value it
# cannot resolve, so a typo here warns instead of inventing a phantom tool.
$measuringToolId = ''
$mtFile = 'C:\Enrollment\measuringtool-id.txt'
if (Test-Path -LiteralPath $mtFile) {
try {
$measuringToolId = ([string](Get-Content -LiteralPath $mtFile -First 1 -ErrorAction Stop)).Trim()
if ($measuringToolId) { Log "measuringToolId from ${mtFile}: $measuringToolId" }
} catch { Log "WARN could not read ${mtFile}: $($_.Exception.Message)" }
}
# imaging pc-type (gea-shopfloor-*), read from the enrollment file. Sent only
# when present; absent -> server leaves existing pctype untouched (a bare report
# never re-types a PC). Unmapped values return a warning, not an error.
@@ -302,6 +322,7 @@ $body = @{ hostname = $hostname }
if ($serialNumber) { $body['serialnumber'] = $serialNumber }
if ($machineNo) { $body['machinenumber'] = $machineNo }
if ($pcType) { $body['pctype'] = $pcType }
if ($measuringToolId) { $body['measuringtoolid'] = $measuringToolId }
if ($manufacturer) { $body['vendorname'] = $manufacturer }
if ($model) { $body['modelnumber'] = $model }
if ($osVersion) { $body['osname'] = $osVersion }
@@ -312,8 +333,8 @@ $body['lastcheckin'] = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:s
$json = $body | ConvertTo-Json -Compress -Depth 4
Log ("POST {0} host={1} serial={2} pcType={3} make={4} model={5} os={6} boot={7} machineNo={8} user={9} ip={10}" -f `
$ApiUrl, $hostname, $serialNumber, $pcType, $manufacturer, $model, $osVersion, $lastBootTime, $machineNo, $loggedInUser, $corpIp)
Log ("POST {0} host={1} serial={2} pcType={3} make={4} model={5} os={6} boot={7} machineNo={8} user={9} ip={10} toolId={11}" -f `
$ApiUrl, $hostname, $serialNumber, $pcType, $manufacturer, $model, $osVersion, $lastBootTime, $machineNo, $loggedInUser, $corpIp, $measuringToolId)
try {
$resp = Invoke-RestMethod -Uri $ApiUrl -Method Post -Body $json `