diff --git a/playbook/FlatUnattendW10-engineer.xml b/playbook/FlatUnattendW10-engineer.xml index 26a79c5..8641c72 100644 --- a/playbook/FlatUnattendW10-engineer.xml +++ b/playbook/FlatUnattendW10-engineer.xml @@ -61,6 +61,13 @@ cmd /c sc config wlansvc start= auto & net start wlansvc & exit /b 0 Start WLAN AutoConfig so the WiFi profile can be added + + + 2 + cmd /c (if not exist C:\ProgramData\PxeStatus mkdir C:\ProgramData\PxeStatus) & copy /Y "C:\Deploy\Applications\extra\pxe-status\Send-ImagingStage.ps1" C:\ProgramData\PxeStatus\ & powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\PxeStatus\Send-ImagingStage.ps1" -PCType gea-engineer -StageIndex 2 -StageTotal 5 -Stage "Windows setup: installing apps" -Status in_progress & exit /b 0 + PXE status: stage 2 of 5 + 3 cmd /c netsh wlan add profile filename="C:\Deploy\Applications\extra\wireless\WiFi-Profile.xml" user=all @@ -301,6 +308,11 @@ reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices" /v Deny_All /t REG_DWORD /d 1 /f Block all removable media reads to prevent PPKG auto-detection at OOBE + + 49 + powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\PxeStatus\Send-ImagingStage.ps1" -PCType gea-engineer -StageIndex 3 -StageTotal 5 -Stage "Apps installed - waiting for first logon" -Status in_progress + PXE status: stage 3 of 5 + @@ -324,6 +336,11 @@ false + + powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\PxeStatus\Send-ImagingStage.ps1" -PCType gea-engineer -StageIndex 4 -StageTotal 5 -Stage "First logon: installing engineering layer" -Status in_progress + PXE status: stage 4 of 5 + 1 + @@ -373,6 +390,11 @@ detail lives here. --> Engineering Layer: stage payload out of C:\Deploy then install + + powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\PxeStatus\Send-ImagingStage.ps1" -PCType gea-engineer -StageIndex 5 -StageTotal 5 -Stage "Setup complete - enrollment continues on the PC" -Status succeeded + PXE status: stage 5 of 5 + 12 + diff --git a/playbook/FlatUnattendW10.xml b/playbook/FlatUnattendW10.xml index db8ae17..ab198f4 100644 --- a/playbook/FlatUnattendW10.xml +++ b/playbook/FlatUnattendW10.xml @@ -61,6 +61,13 @@ cmd /c sc config wlansvc start= auto & net start wlansvc & exit /b 0 Start WLAN AutoConfig so the WiFi profile can be added + + + 2 + cmd /c (if not exist C:\ProgramData\PxeStatus mkdir C:\ProgramData\PxeStatus) & copy /Y "C:\Deploy\Applications\extra\pxe-status\Send-ImagingStage.ps1" C:\ProgramData\PxeStatus\ & powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\PxeStatus\Send-ImagingStage.ps1" -PCType gea-standard -StageIndex 2 -StageTotal 5 -Stage "Windows setup: installing apps" -Status in_progress & exit /b 0 + PXE status: stage 2 of 5 + 3 cmd /c netsh wlan add profile filename="C:\Deploy\Applications\extra\wireless\WiFi-Profile.xml" user=all @@ -301,6 +308,11 @@ reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices" /v Deny_All /t REG_DWORD /d 1 /f Block all removable media reads to prevent PPKG auto-detection at OOBE + + 49 + powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\PxeStatus\Send-ImagingStage.ps1" -PCType gea-standard -StageIndex 3 -StageTotal 5 -Stage "Apps installed - waiting for first logon" -Status in_progress + PXE status: stage 3 of 5 + @@ -324,6 +336,11 @@ false + + powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\PxeStatus\Send-ImagingStage.ps1" -PCType gea-standard -StageIndex 4 -StageTotal 5 -Stage "First logon" -Status in_progress + PXE status: stage 4 of 5 + 1 + @@ -362,6 +379,11 @@ Restore removable media access after OOBE PPKG block 10 + + powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\PxeStatus\Send-ImagingStage.ps1" -PCType gea-standard -StageIndex 5 -StageTotal 5 -Stage "Setup complete - enrollment continues on the PC" -Status succeeded + PXE status: stage 5 of 5 + 11 + diff --git a/playbook/imaging-status/Send-ImagingStage.ps1 b/playbook/imaging-status/Send-ImagingStage.ps1 new file mode 100644 index 0000000..ec65fdd --- /dev/null +++ b/playbook/imaging-status/Send-ImagingStage.ps1 @@ -0,0 +1,73 @@ +# Send-ImagingStage.ps1 - posts one progress step of a gea-standard / +# gea-engineer build to the PXE webapp's /imaging/status endpoint, so the +# imaging floor follows these builds past WinPE (the shopfloor build has its +# own reporting in Shopfloor\lib\Send-PxeStatus.ps1). +# +# Called from the image's FlatUnattendW10.xml (specialize RunSynchronous and +# oobeSystem FirstLogonCommands). Deployed to +# _shared\Applications\extra\pxe-status\ on the PXE server, which PESetup +# copies to C:\Deploy\Applications\extra\pxe-status\. +# +# Best-effort: never blocks or fails Windows setup. Every error is swallowed, +# the script always exits 0, and each attempt is logged to +# C:\Logs\send-imaging-stage.log. + +param( + [Parameter(Mandatory = $true)][string]$Stage, + [int]$StageIndex = 0, + [int]$StageTotal = 5, + [string]$PCType = '', + [ValidateSet('in_progress', 'succeeded', 'failed')][string]$Status = 'in_progress', + [string]$PxeServer = '172.16.9.1', + [int]$Port = 9009, + [int]$TimeoutSec = 5 +) + +$logFile = 'C:\Logs\send-imaging-stage.log' +function Log([string]$msg) { + try { + if (-not (Test-Path 'C:\Logs')) { New-Item -ItemType Directory -Path 'C:\Logs' -Force | Out-Null } + "$(Get-Date -Format s) $msg" | Out-File -FilePath $logFile -Append -Encoding utf8 + } catch { } +} + +try { + $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 = $null + try { + $nic = Get-NetAdapter -Physical -ErrorAction Stop | + Where-Object { $_.Status -eq 'Up' -and $_.MediaType -eq '802.3' } | Select-Object -First 1 + if ($nic) { $mac = $nic.MacAddress -replace '-', ':' } + } catch { } + + $payload = @{ + serial = $serial + hostname_target = $env:COMPUTERNAME + current_stage = $Stage + stage_index = $StageIndex + stage_total = $StageTotal + status = $Status + } + if ($PCType) { $payload.pctype = $PCType } + if ($mac) { $payload.mac = $mac } + $body = $payload | ConvertTo-Json -Compress + $uri = "http://${PxeServer}:${Port}/imaging/status" + + try { + $resp = Invoke-WebRequest -Uri $uri -Method POST -Body $body -ContentType 'application/json' ` + -UseBasicParsing -TimeoutSec $TimeoutSec -ErrorAction Stop + Log "OK $($resp.StatusCode) $body" + } catch { + # Off the imaging network (or the server is down): nothing to report to. + Log "FAIL $($_.Exception.Message) $body" + } +} catch { + Log "ERROR $($_.Exception.Message)" +} +exit 0 diff --git a/playbook/winpe-status-push.ps1 b/playbook/winpe-status-push.ps1 index 9fba978..521950e 100644 --- a/playbook/winpe-status-push.ps1 +++ b/playbook/winpe-status-push.ps1 @@ -52,13 +52,26 @@ if (-not $serial) { exit 0 } +# gea-standard / gea-engineer set no PCTYPE: read the image from the share +# startnet mapped as Z: (\\server\winpeapps\). Those builds report +# 5 steps (WinPE, then Send-ImagingStage.ps1 from their unattend); the +# shopfloor build reports 8. +$total = 8 +if (-not $PCType) { + try { + $z = (& net use Z: 2>$null | Out-String) + if ($z -match 'winpeapps\\(gea-standard|gea-engineer)\b') { $PCType = $Matches[1]; $total = 5 } + } catch { Log "net use Z: exception: $_" } + Log "image from Z: = '$PCType'" +} + # Compose JSON. ConvertTo-Json works in WinPE. $payload = @{ serial = $serial pctype = $PCType current_stage = $CurrentStage stage_index = $StageIndex - stage_total = 8 + stage_total = $total status = 'in_progress' } if ($mac) { $payload.mac = $mac } diff --git a/webapp/templates/_imaging_tiles.html b/webapp/templates/_imaging_tiles.html index 8ca7db9..33dd5db 100644 --- a/webapp/templates/_imaging_tiles.html +++ b/webapp/templates/_imaging_tiles.html @@ -17,6 +17,17 @@ 'Lockdown applied. Bay rebooted into ShopFloor session. Ready for production.') } %} +{# gea-standard / gea-engineer builds: WinPE push, then + Send-ImagingStage.ps1 from the image's unattend; stage_total 5. #} +{% set desk_stage_labels = { + 1: ('Applying Windows image', 'WinPE loaded - PESetup is applying the Windows image to disk.'), + 2: ('Windows setup: installing apps','First boot (specialize): certificates, WiFi, fonts, Adobe, Java and the enrollment task.'), + 3: ('Waiting for first logon', 'Apps installed. Windows setup finishes when someone logs on to the PC.'), + 4: ('First logon', 'First logon commands running (the engineering layer on Engineer builds).'), + 5: ('Setup complete', 'Imaging is done; bulk enrollment continues on the PC.') +} %} +{% set desk_kind = {'gea-standard': 'Standard', 'gea-engineer': 'Engineer'} %} + {# Blancco Drive Eraser sessions (source/pctype "blancco"), posted by blancco-init.sh and blancco-progress.py; stage_total 7. #} {% set blancco_stage_labels = { @@ -69,7 +80,7 @@ {% endif %} {% if stage_idx == 4 and s.status != 'failed' %}{% set warn = True %}{% endif %} {% else %} - {% set friendly = stage_labels.get(stage_idx, ('Stage ' ~ stage_idx, '')) %} + {% set friendly = (desk_stage_labels if s.pctype in desk_kind else stage_labels).get(stage_idx, ('Stage ' ~ stage_idx, '')) %} {% if stage_idx == 1 and s.current_stage and 'bios' in s.current_stage|lower %} {% set friendly = ('Updating BIOS: do not power off', 'WinPE detected a firmware update for this model. Do NOT power off until the next reboot. Imaging continues afterward.') %} @@ -97,7 +108,7 @@ {% if s.hostname_target and s.serial %}{% set _ = ident.append(s.hostname_target) %}{% endif %} {% if s.ip %}{% set _ = ident.append(s.ip) %}{% endif %} {% set title = s.serial or s.hostname_target or s.mac or s.ip or '(unknown bay)' %} - {% set kind = 'Blancco wipe' if is_blancco else (s.pctype or '') %} + {% set kind = 'Blancco wipe' if is_blancco else desk_kind.get(s.pctype, s.pctype or '') %}