| name | windows-bootstrap |
| description | Wipe and reinstall Windows 11 on a target PC via a fully-automated USB build, then take it over remotely on the tailnet. Sister skill to optiplex-bootstrap (Ubuntu); same shape, different OS. Use when the user says "bootstrap <hostname>", "wipe and reinstall Windows on <machine>", "build a Win 11 install USB", "set up a fresh Windows box", or hands a `<hostname>/<user>/<license-key>` triple. |
| triggers | windows-bootstrap, build windows usb, wipe windows, reinstall windows, fresh windows install, win 11 install media, autounattend, devtower, new windows box, install windows agentically |
| allowed-tools | ["Read","Write","Edit","Bash","PowerShell","Glob","Grep"] |
windows-bootstrap — From a Plug-In USB Stick to Live Tailnet Workstation
The user wants to wipe an existing Windows install (or set up a fresh one) on a target PC and join it to the Tailscale fleet. The full chain — build USB → physical handoff → unattended install → tailnet auto-join → remote takeover — is encoded here. Follow it; don't reinvent.
Status: alpha. This is a standing procedure for repeatable Windows bring-ups. The first attempted bring-up (DevTower, Dell XPS 15 9550/9560, P82G, 2026-05-07) revealed multiple silent autounattend failure modes (PITFALLS #12-#16) that v4 templates address with fallbacks. End-to-end re-validation pending. Treat as a USB-builder with documented recovery paths, not as a turnkey plug-and-walk-away until a clean second run confirms.
Cost-safety is not the concern (owned hardware). The concern is convergence: every Windows node ends up identically-configured, identically-named, identically-monitored, with one row in your fleet credentials registry (e.g. ~/.config/<fleet-name>/credentials.yaml) and one alias in ~/.ssh/config.
Supporting files (read as needed):
CHECKLIST.md — eight steps from "user hands hostname + license key" to "tailscale ssh works"
PITFALLS.md — every gotcha logged across the DevTower bring-up (twelve documented)
templates/autounattend.xml.tmpl — Setup answer file, eight {{PLACEHOLDERS}}
templates/post-install.ps1.tmpl — first-boot bootstrap (Tailscale + OpenSSH + beacon)
templates/SetupComplete.cmd.tmpl — SYSTEM-context shim (no placeholders)
templates/build-usb.ps1.tmpl — agentic USB writer (validate → wipe → write → verify)
templates/build-usb-runner.ps1.tmpl — UAC + zombie-cleanup wrapper, writes done-flag
templates/generate-tailscale-key.py.tmpl — CDP-driven pre-auth key generator
When to Use
All of these:
- The user has a target Windows-capable PC (UEFI firmware, x64, ≥ 8 GB RAM, ≥ 64 GB storage).
- The user has a license key — Win 10 Pro keys activate Win 11 Pro; Win 11 Pro keys also work.
- There's a USB stick (≥ 8 GB) plugged into this machine.
- There's internet for the Win 11 ISO download (~7-8 GB) and the Tailscale MSI fetch on first boot.
- The user has hands on the target PC for a single ~60 sec window (plug USB, hit boot menu).
If any precondition is missing, surface the gap before starting.
When NOT to Use
- Target is non-x64 (ARM laptops, etc.) — Win 11 ARM ISO is different + autounattend has different processor architecture strings. Adapt manually.
- Target boots legacy BIOS only (no UEFI) — autounattend's GPT layout and
efi/boot/bootx64.efi boot path won't work. Need MBR layout + bootmgr-only.
- User wants to preserve data on the target drive — this skill wipes Disk 0 unconditionally. Use a different procedure (in-place upgrade, image-and-restore).
- Target has TPM/SecureBoot fully locked down by IT policy — registry bypass tweaks won't bypass enforced firmware. Need vendor-approved image.
- User has no Tailscale admin access — required to generate pre-auth key (or to fall back to interactive auth on first boot).
The Procedure (8 Steps)
1. PROBE — USB plugged in? ISO present or downloadable? Disk 1 is the USB?
2. SCOPE — collect hostname, username, password, license key, target HW model
3. ISO — pull Win 11 ISO via Fido.ps1 if not already in iso/ folder
4. AUTH-KEY — generate Tailscale pre-auth key via CDP (Chrome :9333) or accept paste-in
5. BUILD-MEDIA — render templates → wipe USB → copy ISO → split install.wim → drop autounattend + $OEM$
6. HANDOFF — user plugs USB into target, hits boot key, walks away (the only physical step)
7. JOIN — poll tailnet for the new hostname (~25-30 min after handoff)
8. RECORD — append stanza to credentials registry, add ssh alias, smoke-test
Detailed walkthrough: see CHECKLIST.md.
Naming Conventions (Lock These In)
| Slot | Pattern | DevTower example |
|---|
| Tailnet hostname | <role-or-name> | DevTower |
| Local Windows username | matches hostname | DevTower |
| Initial password | <hostname><year> (rotate post-install) | DevTower2026 |
| autounattend.xml | always autounattend.xml (Setup auto-discovers at root) | — |
| $OEM$ scripts dir on USB | $OEM$\$$\Setup\Scripts\ | — |
SetupComplete.cmd lands at | C:\Windows\Setup\Scripts\SetupComplete.cmd | — |
| Post-install log | C:\Windows\Setup\Scripts\setup-complete.log | — |
| Project folder | Documents\2026\windows-bootstrap\ (per build) | — |
| ssh alias | lowercase 1-letter-tag matches hostname-prefix | Host dt -> DevTower@DevTower |
The hostname is set in two places that must agree:
autounattend.xml <ComputerName>DevTower</ComputerName> in the specialize pass
post-install.ps1 tailscale up --hostname=DevTower
If they disagree the OS will boot with name A and join the tailnet as name B — confusing and breaks tailscale ssh DevTower.
Hardware-Check Bypass: What and Why
Microsoft's official Win 11 supported-CPU list starts at 8th gen Intel (2018+). Many older-but-capable boxes (XPS 15 9550/9560 from 2015-2017, anything with 6th/7th gen) are excluded. Two paths:
| Method | Where | Reliability |
|---|
LabConfig registry tweaks | autounattend.xml windowsPE pass RunSynchronousCommand | Primary, always works |
appraiserres.dll truncation | USB at sources\appraiserres.dll set to 0 bytes | Belt-and-suspenders, sometimes blocked by ISO read-only attr |
We do both. The registry method is the load-bearing one; if appraiserres truncation fails (read-only) the install still succeeds. See PITFALLS #6.
The bypass keys (BypassTPMCheck, BypassSecureBootCheck, BypassRAMCheck, BypassCPUCheck, BypassStorageCheck) are widely-used, supported by every Win 11 ISO, and Microsoft has never patched them out. Future feature updates may require re-bypassing per-version. Acceptable for bring-up.
License Key Strategy
- Win 10 Pro keys activate Win 11 Pro. Same product family, free-upgrade pipeline. Bake into autounattend.
- G2A / gray-market keys often work but can be retroactively revoked. Install always succeeds; activation is at risk. If revoked:
slmgr /ipk <new-key>; slmgr /ato from elevated PowerShell post-install. Don't block the build on activation.
- Activation post-install (skip key in autounattend, install unactivated, key it later): also works. Use when the key isn't certain.
Tailscale Pre-Auth: Two Paths
| Path | Setup | Result |
|---|
| Headless (preferred) | Run templates/generate-tailscale-key.py against a CDP-instrumented Chrome (default: port 9333) where the user is logged into Tailscale admin. Drives the admin UI to generate a single-use auth key. Substitute into post-install.ps1. | Target boots, joins tailnet automatically, no clicks. |
| Interactive (fallback) | Leave __TAILSCALE_AUTH_KEY__ placeholder unsubstituted. post-install.ps1 calls tailscale up --ssh which prints a login URL into setup-complete.log. | User reads the URL post-boot, clicks Approve once. |
The CDP key generator's "Pre-approved" toggle has a fragile selector. Sometimes the key requires manual approval in https://login.tailscale.com/admin/machines after the node first registers. One click; no big deal. PITFALLS #11.
What "Done" Looks Like
After a successful run:
- Target PC powers on cleanly to Windows 11 25H2 Pro desktop, logged in as the configured user (autologin once, then disabled).
tailscale status | Select-String <hostname> from your operator machine shows the new node with a 100.x.x.x IP and windows OS tag.
tailscale ssh <hostname>@<hostname> lands you in PowerShell as the user, no password.
setup-complete.log on the target's C:\Windows\Setup\Scripts\ shows OpenSSH installed, Tailscale up, beacon written.
- Stanza added to your fleet credentials registry.
Host <alias> block added to ~/.ssh/config.
If any of these is missing → the post-install script hit a snag. Pull the log via tailscale ssh and investigate.
When Post-Install Didn't Fire — Recovery Path
If the target boots cleanly to Windows but C:\Windows\Setup\Scripts\ is empty / Tailscale isn't installed (PITFALLS #13 / #14), don't rebuild the USB. Two paths, in order of UX-niceness:
Path A (preferred) — re-use the install USB as a recovery vector
The build script (build-usb.ps1.tmpl step 9b) drops a per-build RECOVER.cmd + recover.ps1 at the USB root. Auth key + hostname are baked into RECOVER.cmd. So after the failed install:
- User unplugs the USB from the target (or grabs whichever USB they used).
- Plugs it back into the now-running target.
- Double-clicks
RECOVER.cmd from File Explorer.
- UAC pops (one click, Yes), the cmd self-elevates, runs
recover.ps1, joins tailnet.
Total: one click after plug-in. ~30 sec end-to-end. No typing, no remembering URLs, no auth-key paste.
Path B (fallback) — one-line iwr|iex from the target
If the USB isn't around, or the build was older and didn't include the safety net:
$env:TS_AUTH_KEY = 'tskey-auth-...'
iwr https://raw.githubusercontent.com/<owner>/agentic-windows-bootstrap/master/recover.ps1 -UseBasicParsing | iex
Run from elevated PowerShell on the target. Same recovery script, same outcome.
What recover.ps1 does (both paths)
Idempotent. Picks up where install left off:
- Verifies elevation + network.
- Installs OpenSSH Server (skips if already present).
- Installs Tailscale via direct MSI download (skips if already installed).
- Brings the tailnet up with the supplied auth key + hostname.
- Writes a beacon JSON at
C:\Windows\Setup\Scripts\recover-beacon.json.
Then the operator takes over via tailscale ssh <user>@<hostname>. The Tailscale --ssh flag means tailnet ACLs gate access, not Windows passwords -- so even a broken-password install (PITFALLS #12) is reachable once recover.ps1 runs.
The safety net is the safety net, not the primary flow. Each failure mode that lands here gets a new pitfall logged and an upstream fix to build-usb.ps1.tmpl so the next bring-up doesn't need recovery.
Output
After successful run:
Documents/2026/windows-bootstrap/ (or per-build folder) holds the rendered scripts + ISO + logs
BUILD-COMPLETE.md in the build folder documents the run
- New row in the credentials registry
- New ssh-config alias
- Target node live on tailnet, SSH-reachable
- This skill's PITFALLS.md gets a new entry if anything new went wrong
See Also
~/.claude/skills/optiplex-bootstrap/ — sister skill for Ubuntu nodes (same procedural shape)
- A CDP-over-WebSocket helper module — required by
generate-tailscale-key.py for driving Chrome. The template references from browser.cdp_capture import CDP, open_tab, find_tab_by_host and sys.path.insert(0, r"{{LIFE_ROOT}}") — point {{LIFE_ROOT}} at any folder containing such a module, or replace the import with your own CDP client (websocket-client over ws://localhost:9333/devtools/...).
feedback_no_dashes.md — outbound-copy convention; doesn't apply to scripts here, but be careful pasting prose into log lines
feedback_check_existing_design_assets.md — for any branding work, check ~/Documents/2026/<HOSTNAME>/ first
BUILD-COMPLETE.md — auto-generated per-run artifact (DevTower's at Documents/2026/windows-bootstrap/)