# Send-PxeStatus.ps1 # Posts a coarse-grained progress update to the PXE webapp's /imaging/status # endpoint. Never blocks imaging on a failed push (try/catch with no rethrow). # Air-gapped LAN; no auth - the webapp endpoint is CSRF-exempt for machine # clients (see services/csrf.py). function Send-PxeStatus { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$Stage, [int]$StageIndex = 0, [int]$StageTotal = 0, [ValidateSet('in_progress','succeeded','failed')] [string]$Status = 'in_progress', [string]$Error_ = '', [string[]]$LogLines = @(), # Intune device ID (AAD/Entra device GUID from `dsregcmd /status`). # Only available post-AAD-join; pass it from Monitor-IntuneProgress # once captured. The dashboard renders a QR of this value. [string]$IntuneDeviceId = '', [string]$PxeServer = '10.9.100.1', [int]$Port = 9009, [int]$TimeoutSec = 5 ) # Get serial early; if WMI fails we still want to push under a best-effort id. $serial = $null try { $serial = (Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop).SerialNumber } catch { try { $serial = (Get-WmiObject -Class Win32_BIOS -ErrorAction Stop).SerialNumber } catch { } } if ([string]::IsNullOrWhiteSpace($serial)) { $serial = $env:COMPUTERNAME } $serial = ($serial -as [string]).Trim() # MAC of the first up wired adapter (best-effort; PXE servers see this MAC in DHCP). $mac = $null try { $nic = Get-NetAdapter -Physical | Where-Object { $_.Status -eq 'Up' -and $_.MediaType -eq '802.3' } | Select-Object -First 1 if ($nic) { $mac = $nic.MacAddress -replace '-', ':' } } catch { } # Enrollment context files (present after startnet.cmd stages them). $pctype = '' $machno = '' if (Test-Path 'C:\Enrollment\pc-type.txt') { $pctype = (Get-Content 'C:\Enrollment\pc-type.txt' -ErrorAction SilentlyContinue | Select-Object -First 1).Trim() } if (Test-Path 'C:\Enrollment\machine-number.txt') { $machno = (Get-Content 'C:\Enrollment\machine-number.txt' -ErrorAction SilentlyContinue | Select-Object -First 1).Trim() } $payload = @{ serial = $serial mac = $mac hostname_target = $env:COMPUTERNAME pctype = $pctype machinenumber = $machno current_stage = $Stage stage_index = $StageIndex stage_total = $StageTotal status = $Status } if ($Error_) { $payload.error = $Error_ } if ($LogLines) { $payload.log_lines = $LogLines } if ($IntuneDeviceId) { $payload.intune_device_id = $IntuneDeviceId } $body = $payload | ConvertTo-Json -Compress $uri = "http://${PxeServer}:${Port}/imaging/status" # Always log the attempt to C:\Logs\send-pxe-status.log so the operator # can correlate dashboard state against actual outbound POSTs. Logs both # success (one line per fired stage) and failure (with exception). $logFile = 'C:\Logs\send-pxe-status.log' try { if (-not (Test-Path 'C:\Logs')) { New-Item -ItemType Directory -Path 'C:\Logs' -Force | Out-Null } } catch { } try { $resp = Invoke-WebRequest -Uri $uri -Method POST ` -Body $body -ContentType 'application/json' ` -UseBasicParsing -TimeoutSec $TimeoutSec ` -ErrorAction Stop try { "$(Get-Date -Format s) OK idx=$StageIndex/$StageTotal status=$Status http=$($resp.StatusCode) stage='$Stage'" | Out-File -FilePath $logFile -Append -Encoding utf8 } catch { } } catch { try { "$(Get-Date -Format s) ERR idx=$StageIndex/$StageTotal status=$Status uri=$uri stage='$Stage' err=$($_.Exception.Message)" | Out-File -FilePath $logFile -Append -Encoding utf8 } catch { } # Never block imaging on a failed status push. } }