diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..ed7bc32 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,20 @@ +#!/bin/bash +# Refuse a commit that stages an unattend answer file the linter rejects. +# A bad answer file stops every bay imaged with it at "Windows could not +# parse or process unattend answer file". Enable with: +# git config core.hooksPath .githooks +set -euo pipefail +ROOT="$(git rev-parse --show-toplevel)" +mapfile -t FILES < <(git diff --cached --name-only --diff-filter=ACM | grep -iE '(^|/)(Flat)?Unattend[^/]*\.xml$' || true) +[ ${#FILES[@]} -eq 0 ] && exit 0 +TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT +STAGED=() +for f in "${FILES[@]}"; do + out="$TMP/$(echo "$f" | tr '/' '_')" + git show ":$f" > "$out" # lint what is staged, not the working copy + STAGED+=("$out") +done +if ! python3 "$ROOT/scripts/lint-unattend.py" --local "${STAGED[@]}"; then + echo "pre-commit: unattend lint failed for: ${FILES[*]}" >&2 + exit 1 +fi diff --git a/playbook/FlatUnattendW10-engineer.xml b/playbook/FlatUnattendW10-engineer.xml index 8641c72..ae26e91 100644 --- a/playbook/FlatUnattendW10-engineer.xml +++ b/playbook/FlatUnattendW10-engineer.xml @@ -61,11 +61,12 @@ 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 + powershell.exe -NoP -EP Bypass -File C:\Deploy\Applications\extra\pxe-status\Send-ImagingStage.ps1 -Persist -PCType gea-engineer -StageIndex 2 -Stage "Windows setup: installing apps" PXE status: stage 2 of 5 @@ -310,7 +311,7 @@ 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 + powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-engineer -StageIndex 3 -Stage "Apps installed - waiting for first logon" PXE status: stage 3 of 5 @@ -337,7 +338,7 @@ - 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 + powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-engineer -StageIndex 4 -Stage "First logon: installing engineering layer" PXE status: stage 4 of 5 1 @@ -391,7 +392,7 @@ 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 + powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-engineer -StageIndex 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 ab198f4..cf36f14 100644 --- a/playbook/FlatUnattendW10.xml +++ b/playbook/FlatUnattendW10.xml @@ -61,11 +61,12 @@ 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 + powershell.exe -NoP -EP Bypass -File C:\Deploy\Applications\extra\pxe-status\Send-ImagingStage.ps1 -Persist -PCType gea-standard -StageIndex 2 -Stage "Windows setup: installing apps" PXE status: stage 2 of 5 @@ -310,7 +311,7 @@ 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 + powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-standard -StageIndex 3 -Stage "Apps installed - waiting for first logon" PXE status: stage 3 of 5 @@ -337,7 +338,7 @@ - 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 + powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-standard -StageIndex 4 -Stage "First logon" PXE status: stage 4 of 5 1 @@ -380,7 +381,7 @@ 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 + powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-standard -StageIndex 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 index ec65fdd..d5d135f 100644 --- a/playbook/imaging-status/Send-ImagingStage.ps1 +++ b/playbook/imaging-status/Send-ImagingStage.ps1 @@ -4,7 +4,9 @@ # own reporting in Shopfloor\lib\Send-PxeStatus.ps1). # # Called from the image's FlatUnattendW10.xml (specialize RunSynchronous and -# oobeSystem FirstLogonCommands). Deployed to +# oobeSystem FirstLogonCommands). Keep those command lines short: Windows +# rejects the whole unattend if a RunSynchronous Path exceeds 259 characters +# (check with scripts/lint-unattend.py --local). Deployed to # _shared\Applications\extra\pxe-status\ on the PXE server, which PESetup # copies to C:\Deploy\Applications\extra\pxe-status\. # @@ -20,7 +22,10 @@ param( [ValidateSet('in_progress', 'succeeded', 'failed')][string]$Status = 'in_progress', [string]$PxeServer = '172.16.9.1', [int]$Port = 9009, - [int]$TimeoutSec = 5 + [int]$TimeoutSec = 5, + # Copy this script to C:\ProgramData\PxeStatus first: the bulk-enrollment + # task deletes C:\Deploy before first logon, when the later steps run. + [switch]$Persist ) $logFile = 'C:\Logs\send-imaging-stage.log' @@ -31,6 +36,13 @@ function Log([string]$msg) { } catch { } } +if ($Persist) { + try { + New-Item -ItemType Directory -Path 'C:\ProgramData\PxeStatus' -Force | Out-Null + Copy-Item -LiteralPath $PSCommandPath -Destination 'C:\ProgramData\PxeStatus\Send-ImagingStage.ps1' -Force + } catch { Log "persist failed: $($_.Exception.Message)" } +} + try { $serial = $null try { $serial = (Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop).SerialNumber } catch { diff --git a/playbook/ipxe/GetPxeScript.aspx b/playbook/ipxe/GetPxeScript.aspx index 908a8ed..34f2299 100644 --- a/playbook/ipxe/GetPxeScript.aspx +++ b/playbook/ipxe/GetPxeScript.aspx @@ -21,8 +21,10 @@ choose --default winpe --timeout 30000 target && goto ${target} # iPXE 2.0 reads the firmware variable; older iPXE cannot (empty), so it # warns as before. Secure Boot confirmed on: no warning, no wait. iseq ${efi/SecureBoot:uint8} 1 && goto winpe_boot || +iseq ${efi/SecureBoot:hex} 01 && goto winpe_boot || echo echo Secure Boot is OFF on this PC (or this iPXE cannot tell). +echo (iPXE read SecureBoot as: [${efi/SecureBoot:hex}] [${efi/SecureBoot:uint8}]) echo Windows deployment requires Secure Boot to be ENABLED. echo If you disabled it for Clonezilla/Memtest, re-enable it now. echo diff --git a/scripts/deploy-unattend.sh b/scripts/deploy-unattend.sh new file mode 100755 index 0000000..add7b4c --- /dev/null +++ b/scripts/deploy-unattend.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# +# deploy-unattend.sh - the ONLY way to put an unattend answer file on a PXE +# server. A bad answer file does not fail at copy time: every bay imaged +# with it stops at "Windows could not parse or process unattend answer +# file" and has to be re-imaged. So this refuses to copy unless the +# linter passes, backs the live file up, swaps it in atomically, and lints +# the copy on the server afterwards. +# +# Usage: scripts/deploy-unattend.sh +# e.g. scripts/deploy-unattend.sh gea-engineer playbook/FlatUnattendW10-engineer.xml +# +# Env: PXE_SERVER (default 172.16.9.1), PXE_USER (default pxe); the +# remote password is read from PXE_PASS or prompted for. + +set -euo pipefail + +IMAGE="${1:?usage: $0 }" +SRC="${2:?usage: $0 }" +SERVER="${PXE_SERVER:-172.16.9.1}" +USER_="${PXE_USER:-pxe}" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)" +LINT="$REPO_ROOT/scripts/lint-unattend.py" +DEST="/srv/samba/winpeapps/$IMAGE/Deploy/FlatUnattendW10.xml" +STAMP="$(date +%Y%m%d-%H%M%S)" + +case "$IMAGE" in + *[!A-Za-z0-9._-]*|"") echo "bad image type: $IMAGE" >&2; exit 2 ;; +esac +[ -f "$SRC" ] || { echo "no such file: $SRC" >&2; exit 2; } + +echo "== 1/3 lint $SRC" +if ! python3 "$LINT" --local "$SRC"; then + echo "REFUSED: the linter failed; nothing was copied." >&2 + exit 1 +fi + +if [ -z "${PXE_PASS:-}" ]; then + read -rs -p "password for $USER_@$SERVER: " PXE_PASS; echo +fi +export SSHPASS="$PXE_PASS" +SSH=(sshpass -e ssh -o StrictHostKeyChecking=no -o ConnectTimeout=8 "$USER_@$SERVER") + +echo "== 2/3 back up and swap $DEST on $SERVER" +sshpass -e scp -q -o StrictHostKeyChecking=no "$SRC" "$USER_@$SERVER:/tmp/unattend-$STAMP.xml" +"${SSH[@]}" "set -e; P() { echo \"\$SSHPASS_REMOTE\" | sudo -S -p '' \"\$@\"; }; SSHPASS_REMOTE='$PXE_PASS' + [ -f '$DEST' ] || { echo 'no live file at $DEST' >&2; exit 1; } + P cp -a '$DEST' '$DEST.bak-$STAMP' + P install -m 644 -o root -g root /tmp/unattend-$STAMP.xml '$DEST.new' + P mv -f '$DEST.new' '$DEST' + rm -f /tmp/unattend-$STAMP.xml + echo \"backup: $DEST.bak-$STAMP\"; sha256sum '$DEST'" + +echo "== 3/3 lint the live copy" +if ! python3 "$LINT" --server "$SERVER" --image "$IMAGE"; then + echo "LIVE FILE FAILS LINT: restoring the backup." >&2 + "${SSH[@]}" "echo '$PXE_PASS' | sudo -S -p '' cp -a '$DEST.bak-$STAMP' '$DEST'" + exit 1 +fi +echo "Deployed $SRC to $IMAGE."