# 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