shopfloor: CMM PC-DMIS version gate, ShopDB reporter fixes, staging self-heal

- lib Install-FromManifest 2.5->2.6: add _CmmVersion per-entry filter (reads
  C:\Enrollment\cmm\version.txt). Lifted the version gate out of 09-Setup-CMM
  into the shared lib so imaging and GE-Enforce apply it identically and cannot
  drift (root cause of PC-DMIS 2016 installing on every CMM).
- Install-goCMMSettings: canonicalize the part-group share host to the FQDN in
  both the registry and ApplicationSettings.xml. Handles bare \\tsgwp00525\ and
  the legacy rd.ds.ge.com domain; idempotent. VM-tested.
- Report-AssetToShopDB: resolve the machine number eDNC registry first, then fall
  back to C:\Enrollment\machine-number.txt (matches the lib resolution order) so
  a freshly imaged PC still reports its number for the PC-machine relationship.
- Add Update-CMMEnforcer.ps1/.bat: update one CMM's local lib to the gated
  version and self-heal its PC-DMIS version.
- Add Debug-ShopDBReporting.ps1/.bat: one-shot reporter triage (preconditions,
  client log, live test POST, verdict).
- Add Verify-And-Heal-Staging.ps1/.bat: post-boot check that every imaging
  payload arrived and re-pull anything missing from the share, including the CMM
  bundle and the selected bay's backup (the payload that times out in WinPE).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-06-14 09:14:54 -04:00
parent c2538a05c5
commit e97e5bd049
10 changed files with 735 additions and 45 deletions

View File

@@ -0,0 +1,37 @@
@echo off
REM ==========================================================================
REM Debug-ShopDBReporting.bat - diagnose a PC not updating its ShopDB entry.
REM
REM Usage (run on the problem PC):
REM Debug-ShopDBReporting.bat full triage incl a live test POST
REM Debug-ShopDBReporting.bat /nopost inspect log + registry only (no POST)
REM Debug-ShopDBReporting.bat /mn 2001 force the machine number sent
REM
REM The live POST writes this PC's row to ShopDB (idempotent upsert, same as the
REM scheduled reporter). Use /nopost to avoid writing.
REM ==========================================================================
setlocal EnableDelayedExpansion
REM --- self-elevate ---
net session >nul 2>&1
if %errorlevel% neq 0 (
echo Requesting administrator elevation...
powershell -NoProfile -Command "Start-Process -Verb RunAs -FilePath '%~f0' -ArgumentList '%*'"
exit /b
)
set "PS=%~dp0Debug-ShopDBReporting.ps1"
set "ARGS="
:parse
if "%~1"=="" goto run
if /I "%~1"=="/nopost" set "ARGS=!ARGS! -NoPost" & shift & goto parse
if /I "%~1"=="/mn" set "ARGS=!ARGS! -MachineNo '%~2'" & shift & shift & goto parse
shift
goto parse
:run
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%PS%" !ARGS!
echo.
pause
endlocal

View File

@@ -0,0 +1,199 @@
<#
Debug-ShopDBReporting.ps1
One-shot diagnosis of why a shopfloor PC is not updating its ShopDB entry through
api.asp (action=updateCompleteAsset). Runs the whole triage and prints a verdict:
1. Preconditions - hostname, BIOS serial (api.asp requires it), eDNC MachineNo
(the PC->machine relationship needs it).
2. Client log - the latest C:\Logs\Shopfloor\report-asset-*.log POST/RESPONSE.
3. Live test POST - calls api.asp updateCompleteAsset and parses the JSON.
4. Verdict - likely cause + fix.
NOTE: the live POST WRITES to ShopDB (it is the real reporter action - an idempotent
upsert of this PC's row, same as the scheduled reporter does). Use -NoPost to skip
it and only inspect the local log + registry.
Run as administrator on the problem PC.
Params:
-ApiUrl override the api.asp endpoint
-MachineNo override the machine number sent (default: read from eDNC registry)
-NoPost do not send the live test POST
#>
param(
[string]$ApiUrl = 'https://tsgwp00525.wjs.geaerospace.net/shopdb/api.asp',
[string]$MachineNo,
[int]$TimeoutSec = 30,
[switch]$NoPost
)
$ErrorActionPreference = 'Continue'
function Section($t){ Write-Host ''; Write-Host ("==== {0} ====" -f $t) -ForegroundColor Cyan }
function KV($k,$v){ Write-Host (" {0,-20}: {1}" -f $k, $v) }
Write-Host '########################################################'
Write-Host '# ShopDB Reporting Debug'
Write-Host ("# {0}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'))
Write-Host '########################################################'
# ---------------------------------------------------------------------------
# 1. Preconditions
# ---------------------------------------------------------------------------
Section '1. Preconditions'
$hostname = $env:COMPUTERNAME
$serial = ''
try { $serial = (Get-CimInstance Win32_BIOS -ErrorAction Stop).SerialNumber } catch {
try { $serial = (Get-WmiObject Win32_BIOS -ErrorAction Stop).SerialNumber } catch {}
}
$serial = ("" + $serial).Trim()
# Resolve machine number the same way the reporter / GE-Enforce lib do:
# eDNC registry (WOW6432Node then native) first, then C:\Enrollment\machine-number.txt.
$regMachineNo = ''
foreach ($rp in @('HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\General',
'HKLM:\SOFTWARE\GE Aircraft Engines\DNC\General')) {
if ($regMachineNo) { break }
if (Test-Path $rp) {
try { $regMachineNo = ("" + (Get-ItemProperty -Path $rp -Name MachineNo -ErrorAction Stop).MachineNo).Trim() } catch {}
}
}
$txtMachineNo = ''
$mnFile = 'C:\Enrollment\machine-number.txt'
if (Test-Path -LiteralPath $mnFile) {
try { $txtMachineNo = ("" + (Get-Content -LiteralPath $mnFile -First 1 -ErrorAction Stop)).Trim() } catch {}
}
$machineNoSource = ''
if (-not $MachineNo) {
if ($regMachineNo) { $MachineNo = $regMachineNo; $machineNoSource = 'eDNC registry' }
elseif ($txtMachineNo) { $MachineNo = $txtMachineNo; $machineNoSource = 'machine-number.txt (fallback)' }
} else { $machineNoSource = 'override param' }
$corpIp = ''
try {
$corpIp = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction Stop |
Where-Object { $_.IPAddress -notmatch '^(127\.|169\.254\.)' } |
Select-Object -First 1).IPAddress
} catch {}
KV 'Hostname' $hostname
KV 'BIOS serial' ($(if ($serial) { $serial } else { '(MISSING - api.asp will reject)' }))
KV 'eDNC MachineNo (reg)' ($(if ($regMachineNo) { $regMachineNo } else { '(none)' }))
KV 'machine-number.txt' ($(if ($txtMachineNo) { $txtMachineNo } else { '(none)' }))
KV 'MachineNo to send' ($(if ($MachineNo) { "$MachineNo [from $machineNoSource]" } else { '(none - relationship will be skipped)' }))
KV 'Corp IPv4' ($(if ($corpIp) { $corpIp } else { '(none found)' }))
KV 'API endpoint' $ApiUrl
# ---------------------------------------------------------------------------
# 2. Latest client reporter log
# ---------------------------------------------------------------------------
Section '2. Latest client reporter log'
$logDir = 'C:\Logs\Shopfloor'
$log = $null
if (Test-Path $logDir) {
$log = Get-ChildItem -Path $logDir -Filter 'report-asset-*.log' -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime | Select-Object -Last 1
}
if ($log) {
KV 'Log file' $log.FullName
KV 'Last written' $log.LastWriteTime
Write-Host ' --- last POST / RESPONSE / ERROR lines ---'
Get-Content $log.FullName -ErrorAction SilentlyContinue |
Select-String -Pattern 'POST |RESPONSE |ERROR ' |
Select-Object -Last 6 | ForEach-Object { Write-Host (" " + $_.Line) }
} else {
Write-Host ' No report-asset-*.log found. The reporter may never have run on this PC.'
Write-Host ' -> check the scheduled task / GE-Enforce entry that invokes Report-AssetToShopDB.ps1'
}
# ---------------------------------------------------------------------------
# 3. Live test POST
# ---------------------------------------------------------------------------
Section '3. Live test POST to api.asp'
$resp = $null; $postErr = $null; $rawText = $null
if ($NoPost) {
Write-Host ' -NoPost set: skipping the live POST.'
} elseif (-not $serial) {
Write-Host ' SKIPPED: no BIOS serial, api.asp requires serialNumber. Fix the hardware/WMI read first.'
} else {
# Accept the internal IIS certificate for this run (PS 5.1-safe).
try {
if (-not ([System.Management.Automation.PSTypeName]'TrustAllCerts').Type) {
Add-Type @"
using System.Net; using System.Security.Cryptography.X509Certificates;
public class TrustAllCerts : ICertificatePolicy {
public bool CheckValidationResult(ServicePoint sp, X509Certificate c, WebRequest r, int p) { return true; }
}
"@
}
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCerts
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
} catch {}
$body = @{
action = 'updateCompleteAsset'
hostname = $hostname
serialNumber = $serial
pcType = 'Shopfloor'
}
if ($MachineNo) { $body['machineNo'] = $MachineNo }
Write-Host (" POST host={0} serial={1} machineNo={2}" -f $hostname, $serial, $(if ($MachineNo) { $MachineNo } else { '(none)' }))
try {
$resp = Invoke-RestMethod -Uri $ApiUrl -Method Post -Body $body -TimeoutSec $TimeoutSec -ErrorAction Stop
Write-Host ' --- response JSON ---'
Write-Host (" " + ($resp | ConvertTo-Json -Compress -Depth 5))
} catch {
$postErr = $_.Exception.Message
Write-Host (" POST FAILED: {0}" -f $postErr) -ForegroundColor Red
try { $rawText = $_.Exception.Response } catch {}
}
}
# ---------------------------------------------------------------------------
# 4. Verdict
# ---------------------------------------------------------------------------
Section '4. VERDICT'
function Truthy($v){ return ($v -eq $true -or "$v" -eq 'True' -or "$v" -eq '1') }
if ($NoPost) {
Write-Host ' (live POST skipped) Inspect section 1-2 above.'
if (-not $MachineNo) { Write-Host ' NOTE: no machine number (eDNC registry and machine-number.txt both empty) -> no relationship until set.' -ForegroundColor Yellow }
}
elseif ($postErr) {
Write-Host ' CANNOT REACH api.asp - network, TLS, DNS, or IIS error.' -ForegroundColor Red
Write-Host (" Detail: {0}" -f $postErr)
Write-Host ' -> ping/curl the host, confirm IIS is up, check the cert and the /shopdb/ path.'
}
elseif (-not $serial) {
Write-Host ' BIOS SERIAL MISSING - api.asp rejects the POST (serialNumber required).' -ForegroundColor Red
Write-Host ' -> fix WMI/Win32_BIOS on this PC; it is a hardware/OS read issue, not ShopDB.'
}
elseif (-not (Truthy $resp.success)) {
Write-Host ' SERVER RETURNED success=false - it aborted at a DB step.' -ForegroundColor Red
Write-Host (" Breadcrumb/message: {0}" -f $resp.message)
Write-Host ' -> the LAST "N-OK," token in that message is where it stopped. Match N to api.asp.'
}
elseif (-not (Truthy $resp.relationshipCreated)) {
if (-not $MachineNo) {
Write-Host ' ASSET OK, but NO RELATIONSHIP because this PC has no machine number.' -ForegroundColor Yellow
Write-Host (" machineid={0}. Neither the eDNC registry nor C:\Enrollment\machine-number.txt" -f $resp.machineid)
Write-Host ' has a value, so api.asp skips the relationship by design.'
Write-Host ' -> set the bay machine number (Set-MachineNumber writes the eDNC registry), then'
Write-Host ' re-run. CMM/keyence/wax-trace bays have no DNC number and register asset-only.'
} else {
Write-Host ' ASSET OK, but RELATIONSHIP NOT CREATED even though MachineNo was sent.' -ForegroundColor Red
Write-Host (" machineid={0}, machineNo={1}." -f $resp.machineid, $MachineNo)
Write-Host ' -> this is the server-side silent abort (LogToFile Err-leak). Deploy the api.asp fix'
Write-Host ' (commit a4051e3) to prod IIS, then re-run. Also confirm the IIS logs dir is'
Write-Host ' writable by the app-pool identity (that is the trigger).'
}
}
else {
Write-Host ' HEALTHY.' -ForegroundColor Green
Write-Host (" Asset updated (machineid={0}) and PC->machine relationship created." -f $resp.machineid)
Write-Host ' If ShopDB still looks wrong, the issue is data/display, not the reporter path.'
}
Write-Host ''
Write-Host 'Done.'

View File

@@ -83,16 +83,38 @@ if (-not $serialNumber) {
exit 0
}
# DNC machine number from eDNC reg (2001, 2002, ...). optional - sent only if present.
# DNC machine number (2001, 2002, ...). optional - sent only if found.
# Resolution order mirrors the GE-Enforce lib Get-CurrentMachineNumber so the
# reporter and manifest gating agree:
# 1. eDNC registry (WOW6432Node, then native) - follows bay reassignment, which
# Set-MachineNumber rewrites here.
# 2. C:\Enrollment\machine-number.txt - the imaging-time value written once by
# startnet.cmd. Used when eDNC has not populated the registry yet (fresh
# image, or a bay where eDNC has not run), so the PC still reports its number
# and api.asp can build the relationship.
$machineNo = ''
$edncRegPath = 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\General'
try {
if (Test-Path $edncRegPath) {
$machineNo = [string](Get-ItemProperty -Path $edncRegPath -Name MachineNo -ErrorAction Stop).MachineNo
$machineNo = $machineNo.Trim()
foreach ($regPath in @(
'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\General',
'HKLM:\SOFTWARE\GE Aircraft Engines\DNC\General'
)) {
if ($machineNo) { break }
try {
if (Test-Path $regPath) {
$v = [string](Get-ItemProperty -Path $regPath -Name MachineNo -ErrorAction Stop).MachineNo
if ($v) { $machineNo = $v.Trim() }
}
} catch {
Log "WARN could not read MachineNo from ${regPath}: $($_.Exception.Message)"
}
}
if (-not $machineNo) {
$mnFile = 'C:\Enrollment\machine-number.txt'
if (Test-Path -LiteralPath $mnFile) {
try {
$v = Get-Content -LiteralPath $mnFile -First 1 -ErrorAction Stop
if ($v) { $machineNo = ([string]$v).Trim(); Log "machineNo from $mnFile (eDNC registry empty): $machineNo" }
} catch { Log "WARN could not read ${mnFile}: $($_.Exception.Message)" }
}
} catch {
Log "WARN could not read eDNC MachineNo: $($_.Exception.Message)"
}
# OS caption for the operatingsystems lookup.