Adds end-to-end progress tracking for PXE imaging sessions and surfaces
each Blancco report's BIOS serial in the report list.
webapp:
* services/imaging_status.py - JSON-per-serial state store under
IMAGING_DIR (default /var/log/pxe-imaging). Atomic write via
tempfile + rename. log_tail capped at 50 lines. Merges partial
updates so clients can post just the current_stage tick.
* config.py - new IMAGING_DIR env-overridable path.
* services/csrf.py - explicit exempt list for machine-to-machine
endpoints; /imaging/status is the first entry. Air-gapped LAN;
trust-by-network for client posts.
* app.py - four new routes:
GET /imaging dashboard (renders all sessions)
POST /imaging/status client status push (JSON body)
GET /imaging/<serial>.json raw session JSON for ad-hoc polling
POST /imaging/delete/<s> clear a session from the dashboard
Also parses each Blancco XML in the /reports list to surface
system.serial + system.model columns.
* templates/imaging.html - Bootstrap dashboard with per-session
cards (state badge, progress bar, stage idx/total, mac, elapsed,
log tail). meta http-equiv refresh=5 for auto-tick.
* templates/base.html - new "Imaging Progress" nav entry.
* templates/reports.html - Serial + Model columns added.
playbook:
* shopfloor-setup/Shopfloor/lib/Send-PxeStatus.ps1 - new helper.
Dot-source this then call Send-PxeStatus -Stage X -StageIndex N
-StageTotal M from any stage script. BIOS serial via CIM, MAC via
Get-NetAdapter, pctype + machinenumber from C:\Enrollment.
Failures are swallowed to a local log so a network blip doesn't
block imaging.
* shopfloor-setup/Run-ShopfloorSetup.ps1 - dot-sources helper +
posts at three coarse milestones (start, PPKG enrollment,
handoff to Monitor-IntuneProgress).
* shopfloor-setup/gea-shopfloor-keyence/09-Setup-Keyence.ps1 -
posts at session start + after Install-FromManifest with
succeeded/failed status derived from $rc. Other 09-Setup-*.ps1
scripts can follow the same pattern.
ID is BIOS serial (stable across WinPE -> Windows transition and
across reboots, unlike hostname which is random pre-PPKG). Operator
already knows the serial of the bay they imaged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
78 lines
3.1 KiB
PowerShell
78 lines
3.1 KiB
PowerShell
# 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 = @(),
|
|
[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 }
|
|
|
|
$body = $payload | ConvertTo-Json -Compress
|
|
$uri = "http://${PxeServer}:${Port}/imaging/status"
|
|
|
|
try {
|
|
Invoke-WebRequest -Uri $uri -Method POST `
|
|
-Body $body -ContentType 'application/json' `
|
|
-UseBasicParsing -TimeoutSec $TimeoutSec `
|
|
-ErrorAction Stop | Out-Null
|
|
} catch {
|
|
# Never block imaging on a failed status push. Write to local log only.
|
|
try {
|
|
$logDir = 'C:\Logs'
|
|
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }
|
|
"$(Get-Date -Format s) Send-PxeStatus failed: $($_.Exception.Message)" |
|
|
Out-File -FilePath (Join-Path $logDir 'send-pxe-status.log') -Append -Encoding utf8
|
|
} catch { }
|
|
}
|
|
}
|