Seven Layers of Permission Denied: Netbooting Windows 11 Without a TPM
I have a PXE server that installs Windows 11 onto whatever machine I point at it. It stopped working. The symptom was "permission denied."
It was not a permission problem. Or rather, it was — eventually, at layer five of seven. Here is the whole stack, because every single layer produced a plausible-looking error that sent me somewhere useless.
1. The server had moved
The netboot box was on .48. Everything referencing it — the DHCP options, my own notes, the iPXE menu — said .60. Worse, .60 had since been reassigned to a different container, so it answered pings and even had a service on :3000. Nothing failed loudly. It just quietly pointed at the wrong machine.
Lesson I keep re-learning: a stale IP that still responds is far more expensive than one that doesn't.
2. UEFI clients were being hijacked to the public internet
BIOS clients got my local boot menu. UEFI clients got the upstream project's public menu over HTTPS. Same DHCP, same server, completely different outcome.
The stock netbootable binary I was serving ignores the boot filename the DHCP proxy hands it and chains to its own hardcoded default. Vanilla iPXE does not — it boots what DHCP tells it to:
apt-get install ipxe
# then serve /boot/ipxe.efi instead of the vendor's .efi
dhcp-match=set:ipxe,175
dhcp-boot=tag:ipxe,http://<server>/menu.ipxe
One binary swap and UEFI started landing on the local menu.
3. Bad MZ magic d5e9
Emulating drive 0x80
Bad MZ magic d5e9
FATAL: Could not load bootmgr.exe
0xE9 is a near jump. MZ is a DOS/PE header. The file being loaded wasn't a PE executable at all.
The bootmgr sitting at the root of Windows install media is a real-mode stub, not the boot manager. The real one is inside boot.wim:
\Windows\Boot\PXE\bootmgr.exe # BIOS
\Windows\Boot\EFI\bootmgfw.efi # UEFI
Pull them out with wimlib-imagex extract and serve those instead.
4. My script wasn't running at all
I'd replaced startnet.cmd inside the boot image to map a share and launch setup. Setup launched — but not from my script, and with no install source. Recent Windows setup images auto-start their own installer and never touch startnet.cmd.
The override is winpeshl.ini, which replaces the WinPE shell outright:
[LaunchApps]
"cmd.exe", "/c X:\Windows\System32\startnet.cmd"
5. The actual permission denied (two of them)
Now my script ran, and finally produced a real error:
The Workstation service has not been started.
NET HELPMSG 2138
wpeinit returns before the SMB redirector is usable. The fix is wpeutil InitializeNetwork. What you must not do is net start LanmanWorkstation — in WinPE that call blocks forever, which cost me two debugging cycles convincing myself the machine had hung.
Share mounted. Then:
Access is denied.
That one was a permission — but not the one anyone would guess. My file server re-exports a mount whose files are mode 0644. No execute bit, so the SMB layer refused to run setup.exe off it. Samba has a switch for exactly this:
[share]
acl allow execute always = yes
6. The answer file was silently wrong
Windows could not parse or process unattended answer file for pass
[windowsPE]. A component or setting specified in the answer file does
not exist.
Not a syntax error — a placement error. Locale settings belong to Microsoft-Windows-International-Core-WinPE, not Microsoft-Windows-Setup. One element in the wrong component invalidates the entire file, and the message names neither the element nor the component.
Then, immediately after:
Windows cannot access one or more of the installation images using
the provided credentials.
My script had already mapped the share. The answer file's <Credentials> block tried to open a second authenticated session to the same server, which Windows refuses. Dropping the credentials and pointing at the existing mapped drive fixed it.
Also worth knowing: newer install media ships two setups. The one at the media root is a launcher that discards /unattend: for the early pass. sources\setup.exe is the classic engine that honours it.
7. Firmware and partition style must agree
Windows cannot be installed to this disk. The selected disk is of the
GPT partition style.
My answer file created an EFI/MSR/GPT layout. The machine was legacy BIOS, which needs MBR. So the script now picks the answer file at runtime:
set AU=autounattend.xml
reg query "HKLM\System\CurrentControlSet\Control" /v PEFirmwareType ^
| find "0x1" >nul 2>&1 && set AU=autounattend-bios.xml
PEFirmwareType is 1 for BIOS, 2 for UEFI. Two answer files, one script.
The bonus round
Along the way I also discovered the install ISO was 8.4 GB of which the first 64 KB were zeros — no filesystem descriptor, completely unbootable, and it had been sitting there looking like a normal file for three months. Rebuilt it with xorriso and moved on.
And a virtualisation trap: a default kvm64 CPU model lacks SSE4.2 and POPCNT, which recent Windows requires. The failure mode is the logo appearing and the machine instantly resetting — indistinguishable from a corrupt boot image. Set the CPU to host.
What I'd do differently
Every layer here produced an error message that was technically accurate and directionally useless. "Permission denied" meant a service wasn't started. "Bad MZ magic" meant I'd picked the wrong file out of the media. "A component does not exist" meant a valid element was in the wrong parent.
The thing that actually broke the logjam was making the failure observable: a writable diagnostic share, screenshots of the console at 4-second intervals, and removing every >nul 2>&1 from the script. I'd spent an hour theorising about layers I couldn't see. Ten minutes of making them visible was worth more than all of it.
The other lesson: I fixed layer one and assumed I was done. Then layer two, same assumption. When a system has been quietly rotting, the first fix you find is almost never the last one you need.