# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Repository nature This is a **configuration template repo**, not a code project. It ships three artifacts: - `unattend.xml` — the Windows Setup answer file. - `Setup.ps1` — the post-install PowerShell that runs at first logon (firewall/WinRM/RDP/ICMP, KMS, Win11Debloat, RemoveWindowsAI, OneDrive removal, Cortana off, Default-profile UI/IME defaults, account rename, Telegram, Chrome via winget). Lives in the repo and is also copied to the Ventoy USB at `/ventoy/script/Setup.ps1`. At first logon `unattend.xml` scans removable drives for `\ventoy\script\Setup.ps1`, copies it to `C:\Scripts\Setup.ps1`, then runs it. - `ventoy.json` — the Ventoy Auto Install plugin config that tells Ventoy which ISO to pair with which template. Deployment is via **[Ventoy](https://www.ventoy.net/)**, not Rufus or direct ISO modification. The user keeps a Ventoy USB with multiple Windows ISOs; `ventoy.json` at `/ventoy/ventoy.json` on the USB, `unattend.xml` at `/ventoy/script/unattend.xml`, and `Setup.ps1` at `/ventoy/script/Setup.ps1` let Ventoy inject the answer file at boot without touching the ISO and have the post-install script ready next to it. Assume this Ventoy-based flow when reasoning about deploy-time behavior. There is no build, no test suite, no package manager, and no runtime. Changes are validated by: 1. **Schema validation** via Windows SIM (Windows System Image Manager, part of Windows ADK). 2. **End-to-end VM test**: boot a VM from the Ventoy USB (or a Ventoy VHD) and confirm the install reaches the desktop without prompts. `QUICKSTART.md` has the exact steps. There is no way to "run" this repo on the dev machine (macOS). All verification happens on a Windows target. ## The three-pass mental model `unattend.xml` is not a flat config — it is split into three `` blocks that correspond to **three distinct moments in Windows Setup**. Each setting has a required pass; putting it in the wrong pass means Windows silently ignores it. | Pass | When it runs | System state | What lives here | |---|---|---|---| | `windowsPE` | Booted from USB, Windows not yet installed | In-memory mini-OS | Disk partitioning, image selection, product key, EULA, WinPE UI language | | `specialize` | Image applied, before first boot | `C:\` exists, no users yet | Computer name, timezone, registered owner, domain join | | `oobeSystem` | First boot, OOBE running | Full Windows, waiting for account | Local account creation, AutoLogon, `FirstLogonCommands`, OOBE screen skips | **Language settings appear twice on purpose** — once in `windowsPE` (`International-Core-WinPE`) for the installer UI, once in `oobeSystem` (`International-Core`) for the OOBE UI. That is not duplication; removing either will surface an unwanted language prompt. ## Non-obvious constraints when editing - **Deployment is via Ventoy, not filename-based auto-detection.** The old "rename to `autounattend.xml` at USB root" trick does *not* apply here — Ventoy boots the ISO directly and injects the template per `ventoy.json`. Keep the repo filename as `unattend.xml` and keep the `ventoy.json` `template` path in sync. - **`DiskID` uses the Ventoy variable `$$VT_WINDOWS_DISK_1ST_NONVTOY$$`, not a numeric literal.** Ventoy's Auto Install plugin substitutes this at runtime with the first non-Ventoy disk, so Windows doesn't wipe the Ventoy USB itself. **Never replace it with `0` or any hardcoded number.** Other Ventoy-provided variables (`$$VT_WINDOWS_DISK_MAX_SIZE$$`, `$$VT_WINDOWS_DISK_CLOSEST_$$`) are valid substitutes for different selection policies but only work under Ventoy — they are meaningless if the XML is ever deployed via a non-Ventoy path. - **Passwords appear twice**: once in `UserAccounts/LocalAccount` and once in `AutoLogon`. Both must match — changing one silently breaks AutoLogon. - **`FirstLogonCommands` only fires if `AutoLogon.Enabled=true`**. Removing AutoLogon also disables the post-install script hand-off. - **`Order` values within `FirstLogonCommands` / `CreatePartitions` / `ModifyPartitions` must be unique and contiguous** — duplicates cause silent skips. - **The disk config assumes UEFI+GPT and wipes the selected disk unconditionally** (`WillWipeDisk=true`). Any BIOS/MBR target requires rewriting ``; there is no runtime branching. - **`processorArchitecture="amd64"`** is hardcoded on every ``. ARM64 deployments require a global find-and-replace to `arm64`. - **Password is plaintext** (`true</PlainText>`). This is a deliberate testing-only choice documented in `SUMMARY.md`. For production, generate Base64 via Windows SIM; do not hand-encode. - **`ComputerName` does not support `%RAND:5%` or any other macro.** Windows Setup only accepts a literal name, `*` (auto-generate a random name like `DESKTOP-ABC123D`), or an empty value. `%RAND:N%` is an MDT/SCCM task-sequence variable — under the Ventoy → raw `unattend.xml` path it is passed verbatim as a literal string, fails NetBIOS character validation (`%` is not allowed), and causes the specialize pass to abort with a `Microsoft-Windows-Shell-Setup` error. If a `PC-XXXXX` style name is required, set `<ComputerName>*</ComputerName>` here and do the rename in `FirstLogonCommands` via `Rename-Computer` (a reboot is needed for it to take effect — combine with the existing `shutdown /r` step). (Distinct from `$$VT_*$$`, which *is* a real Ventoy pre-processor substitution.) - **`Setup.ps1` is shipped via the Ventoy USB, not embedded in `unattend.xml`.** `FirstLogonCommands` Order 4 scans every mounted filesystem drive for `\ventoy\script\Setup.ps1` and copies the first match to `C:\Scripts\Setup.ps1`; Order 5 runs it. Two consequences: (1) the Ventoy USB **must remain plugged in until first logon completes** — yanking it after WinPE means Setup.ps1 never makes it to disk and the post-install steps silently skip (`C:\Windows\Temp\firstlogon-copysetup.log` will say `NOT FOUND`); (2) editing `Setup.ps1` in the repo is not enough — re-copy to `/Volumes/Ventoy/ventoy/script/Setup.ps1` before each test deploy. Both `firstlogon-copysetup.log` and `setup.log` live under `C:\Windows\Temp\`. - **`Setup.ps1` runs as the AutoLogon `Admin` session** in `FirstLogonCommands`. It modifies the Default profile's `NTUSER.DAT` (loaded via `reg load HKU\SetupDefault C:\Users\Default\NTUSER.DAT`) to push UI/IME defaults to *future* user logins — it does **not** touch the AutoLogon `Admin` session itself, and it cannot reach the just-created `user`/`User` profile because that profile dir doesn't exist until that user first logs in. Anything you want applied to `Admin`'s already-active session has to go through `HKCU` directly. - **The `User` account is created directly in `unattend.xml` Order 3** with capitalised name (`net user User 1234 /add`); there is no rename step. `Setup.ps1`'s `hide-admin-account` section only handles hiding `Admin` from the login screen via `SpecialAccounts\UserList\Admin=0`. If you change the account name convention, only `unattend.xml` Order 3 needs to change. ## When making changes - Preserve the `xmlns="urn:schemas-microsoft-com:unattend"` namespace and the `wcm:action="add"` attributes on list items — Windows SIM emits these and Setup requires them. - Every `<component>` needs all four identity attributes: `name`, `processorArchitecture`, `publicKeyToken="31bf3856ad364e35"`, `language="neutral"`, `versionScope="nonSxS"`. - The Chinese-language docs (`README.md`, `QUICKSTART.md`, `SUMMARY.md`, `DEPLOY.md`) explain the *why* behind design decisions — consult `SUMMARY.md` before proposing structural changes, as several current choices are explicit tradeoffs, not oversights. ## When making changes to `Setup.ps1` - Sections are wrapped with the `Section` helper (`Section 'name' { ... }`) which logs `START` / `DONE` / `ERR` to `C:\Windows\Temp\setup.log`. Each one is idempotent and tolerates partial earlier runs — preserve that property when adding new sections, because deploys do get re-tested by re-running `Setup.ps1` manually. - Phase ordering is enforced by file order, not by metadata: Phase 1 (no network — firewall, registry, Default profile, account rename) runs first; `wait-network` then blocks up to 60 s; Phase 2 (network — OpenSSH FoD, KMS, winget, RemoveAI, Win11Debloat, UWP removal, Telegram) follows. Don't move a network-dependent section above `wait-network`. - `$Win11DebloatConfigJson` and `$UwpRemoveList` are mirrored from `windows-remote-toolkit/config/win11debloat-config.json` and `remove-apps-config.json`. They were intentionally **inlined** rather than fetched at runtime so the script is self-contained on the Ventoy USB. If you change one, update the comment pointing at the toolkit source so the next sync isn't accidental. - `$InternalKmsServers` is intentionally empty — the public KMS list (`kms.digiboy.ir`, `kms8.msguides.com`, `kms.03k.org`, `kms.chinancce.com`) is the fallback. If a deployment is on a network with its own KMS host, that's the only line to change.