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."