collections: report host + IP + machine number to ShopDB each enforce cycle
Adds Report-AssetToShopDB.ps1 (Type=PS1, DetectionMethod=Always manifest entry) for collections PCs. Reads hostname, BIOS serial, eDNC MachineNo and the corp NIC IPv4 (filtered to WJ corp ranges, controller NIC dropped) and POSTs action=updateCompleteAsset to ShopDB api.asp, which upserts the machine, stores the IP, and links the PC to its machine-number equipment. manifest-entry-report-asset.json is the snippet to merge into the SFLD share collections manifest (+ stage the script under apps/). Note: relies on the ShopDB api.asp LogToFile Err-leak fix (separate shopdb repo commit) to create the relationship reliably. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,171 @@
|
|||||||
|
# Report-AssetToShopDB.ps1
|
||||||
|
#
|
||||||
|
# Reports a collections bay's identity to ShopDB so the machines record stays
|
||||||
|
# current with whatever the bay actually is right now: hostname, BIOS serial,
|
||||||
|
# DNC machine number (2001, 2002, ...) and its corp/AESFMA IPv4 address.
|
||||||
|
#
|
||||||
|
# Runs every GE-Enforce cycle as a Type=PS1 manifest entry (DetectionMethod
|
||||||
|
# Always) under the SYSTEM scheduled task. Idempotent on the server side:
|
||||||
|
# ShopDB api.asp action=updateCompleteAsset upserts the machines row keyed by
|
||||||
|
# hostname, clears+reinserts the interface rows, and (re)creates the
|
||||||
|
# PC-to-machine relationship from machineNo. Safe to fire repeatedly.
|
||||||
|
#
|
||||||
|
# WHY collections-only and corp-NIC-only:
|
||||||
|
# Collections (controller-NIC) bays carry two NICs - a private controller
|
||||||
|
# NIC (e.g. 192.168.x / 10.x stray) and the routable corp/AESFMA NIC. Only
|
||||||
|
# the corp NIC belongs in ShopDB, so we filter to the WJ corp ranges and
|
||||||
|
# drop everything else. Mirrors the allowed-range gate in
|
||||||
|
# Invoke-FilteredReportIP.ps1.
|
||||||
|
#
|
||||||
|
# Always exits 0 so the GE-Enforce "last run result" stays clean; failures are
|
||||||
|
# logged, never thrown.
|
||||||
|
|
||||||
|
param(
|
||||||
|
# ShopDB asset endpoint. Override via the manifest entry's "Args" field if
|
||||||
|
# the host or path ever moves.
|
||||||
|
[string]$ApiUrl = 'https://tsgwp00525.wjs.geaerospace.net/shopdb/api.asp',
|
||||||
|
|
||||||
|
[int]$TimeoutSec = 30
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
|
||||||
|
$logDir = 'C:\Logs\Shopfloor'
|
||||||
|
if (-not (Test-Path $logDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $logDir -Force -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
}
|
||||||
|
$logFile = Join-Path $logDir ('report-asset-{0}.log' -f (Get-Date -Format 'yyyyMMdd'))
|
||||||
|
|
||||||
|
function Log([string]$msg) {
|
||||||
|
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||||
|
"$ts $msg" | Tee-Object -FilePath $logFile -Append | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# corp ranges - same gate as Invoke-FilteredReportIP. update if site re-VLANs.
|
||||||
|
$allowedRanges = @(
|
||||||
|
@{ Network = '10.134.48.0'; PrefixLen = 23 },
|
||||||
|
@{ Network = '10.48.249.0'; PrefixLen = 26 }
|
||||||
|
)
|
||||||
|
|
||||||
|
function ConvertTo-Uint32([string]$ip) {
|
||||||
|
$bytes = ([System.Net.IPAddress]::Parse($ip)).GetAddressBytes()
|
||||||
|
[Array]::Reverse($bytes)
|
||||||
|
return [BitConverter]::ToUInt32($bytes, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-InAllowedRange([string]$ip) {
|
||||||
|
try {
|
||||||
|
$ipInt = ConvertTo-Uint32 $ip
|
||||||
|
foreach ($r in $allowedRanges) {
|
||||||
|
$netInt = ConvertTo-Uint32 $r.Network
|
||||||
|
$mask = [uint32]([math]::Pow(2, 32) - [math]::Pow(2, 32 - $r.PrefixLen))
|
||||||
|
if (($ipInt -band $mask) -eq ($netInt -band $mask)) { return $true }
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
Log '=== Report asset to ShopDB ==='
|
||||||
|
|
||||||
|
# hostname
|
||||||
|
$hostname = $env:COMPUTERNAME
|
||||||
|
|
||||||
|
# BIOS serial - required by api.asp. bail if missing.
|
||||||
|
$serialNumber = ''
|
||||||
|
try {
|
||||||
|
$serialNumber = (Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop).SerialNumber
|
||||||
|
if ($serialNumber) { $serialNumber = $serialNumber.Trim() }
|
||||||
|
} catch {
|
||||||
|
Log "WARN could not read BIOS serial: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
if (-not $serialNumber) {
|
||||||
|
Log 'ERROR no BIOS serial number; api.asp requires hostname + serialNumber. Skipping.'
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# DNC machine number from eDNC reg (2001, 2002, ...). optional - sent only if present.
|
||||||
|
$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()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Log "WARN could not read eDNC MachineNo: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS caption for the operatingsystems lookup.
|
||||||
|
$osVersion = ''
|
||||||
|
try {
|
||||||
|
$osVersion = (Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop).Caption
|
||||||
|
if ($osVersion) { $osVersion = $osVersion.Trim() }
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
# gather corp NICs only. one networkInterfaces entry per allowed IPv4.
|
||||||
|
$interfaces = @()
|
||||||
|
try {
|
||||||
|
$ipObjs = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction Stop |
|
||||||
|
Where-Object { $_.IPAddress -notmatch '^169\.254' -and $_.IPAddress -ne '127.0.0.1' }
|
||||||
|
foreach ($ipo in $ipObjs) {
|
||||||
|
if (-not (Test-InAllowedRange $ipo.IPAddress)) { continue }
|
||||||
|
|
||||||
|
$mac = ''
|
||||||
|
$gw = ''
|
||||||
|
try {
|
||||||
|
$adapter = Get-NetAdapter -InterfaceIndex $ipo.InterfaceIndex -ErrorAction Stop
|
||||||
|
$mac = $adapter.MacAddress
|
||||||
|
} catch {}
|
||||||
|
try {
|
||||||
|
$gw = (Get-NetRoute -InterfaceIndex $ipo.InterfaceIndex -DestinationPrefix '0.0.0.0/0' -ErrorAction Stop |
|
||||||
|
Select-Object -First 1).NextHop
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
# CIDR prefix -> dotted subnet mask
|
||||||
|
$maskInt = [uint32]([math]::Pow(2, 32) - [math]::Pow(2, 32 - $ipo.PrefixLength))
|
||||||
|
$maskBytes = [BitConverter]::GetBytes($maskInt)
|
||||||
|
[Array]::Reverse($maskBytes)
|
||||||
|
$subnetMask = ($maskBytes | ForEach-Object { $_ }) -join '.'
|
||||||
|
|
||||||
|
$interfaces += [pscustomobject]@{
|
||||||
|
IPAddress = $ipo.IPAddress
|
||||||
|
MACAddress = $mac
|
||||||
|
SubnetMask = $subnetMask
|
||||||
|
DefaultGateway = $gw
|
||||||
|
InterfaceName = $ipo.InterfaceAlias
|
||||||
|
IsMachineNetwork = $false # corp NIC, not the controller LAN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Log "WARN interface enumeration failed: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($interfaces.Count -eq 0) {
|
||||||
|
Log 'WARN no corp-range IPv4 found; posting identity without interfaces.'
|
||||||
|
}
|
||||||
|
|
||||||
|
$networkInterfacesJson = if ($interfaces.Count -gt 0) { $interfaces | ConvertTo-Json -Compress -Depth 4 } else { '' }
|
||||||
|
# ConvertTo-Json emits a bare object (not an array) for a single element; force array shape for api.asp ParseJSONArray.
|
||||||
|
if ($interfaces.Count -eq 1) { $networkInterfacesJson = '[' + $networkInterfacesJson + ']' }
|
||||||
|
|
||||||
|
$body = @{
|
||||||
|
action = 'updateCompleteAsset'
|
||||||
|
hostname = $hostname
|
||||||
|
serialNumber = $serialNumber
|
||||||
|
pcType = 'Shopfloor'
|
||||||
|
osVersion = $osVersion
|
||||||
|
networkInterfaces = $networkInterfacesJson
|
||||||
|
}
|
||||||
|
if ($machineNo) { $body['machineNo'] = $machineNo }
|
||||||
|
|
||||||
|
Log ("POST {0} host={1} serial={2} machineNo={3} ips={4}" -f `
|
||||||
|
$ApiUrl, $hostname, $serialNumber, $machineNo, (($interfaces | ForEach-Object { $_.IPAddress }) -join ','))
|
||||||
|
|
||||||
|
try {
|
||||||
|
$resp = Invoke-RestMethod -Uri $ApiUrl -Method Post -Body $body -TimeoutSec $TimeoutSec -ErrorAction Stop
|
||||||
|
Log ("RESPONSE {0}" -f ($resp | ConvertTo-Json -Compress -Depth 4))
|
||||||
|
} catch {
|
||||||
|
Log "ERROR POST failed: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Drop this entry into the SFLD share at \\tsgwp00525\\sfld$\\v2\\shared\\dt\\shopfloor\\gea-shopfloor-collections\\manifest.json (Applications array). Place Report-AssetToShopDB.ps1 in the apps/ dir on the share. Type=PS1 + DetectionMethod=Always means GE-Enforce runs it straight off the share every cycle (at-logon, periodic 5-min, shift-change) under SYSTEM, no local copy. The script reads hostname, BIOS serial, eDNC MachineNo and the corp/AESFMA IPv4 (filtered to WJ corp ranges 10.134.48.0/23 + 10.48.249.0/26) and POSTs action=updateCompleteAsset to ShopDB. api.asp upserts the machines row by hostname, clears+reinserts interface rows, and recreates the PC-to-machine relationship from machineNo, so repeat fires are safe. Override the ShopDB URL with the Args field if the host/path moves, e.g. \"Args\": \"-ApiUrl https://newhost/shopdb/api.asp\".",
|
||||||
|
"Name": "Report asset (host + IP + machine number) to ShopDB",
|
||||||
|
"PCTypes": ["gea-shopfloor-collections"],
|
||||||
|
"Script": "apps/Report-AssetToShopDB.ps1",
|
||||||
|
"Type": "PS1",
|
||||||
|
"DetectionMethod": "Always"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user