Skip to main content

provision-windows-dev-vm

Create a new UTM Windows 11 ARM64 VM for Windows kernel driver development and debugging, modelled on the "Windows Development" VM — unattended install, guest tools, autologon, OpenSSH both ways, SSHFS-Win Z: share of the host, Visual Studio 2022 + WDK, Python/CMake/Git, a static address on the kdnet network — ready for /utmapp-bot:build-viogpu-driver and to kernel-debug test VMs. Use when asked to provision, create or set up a Windows dev/build VM, or to make an existing dev VM a kdnet debugger.

Source facts

Repository
utmapp/utmapp-bot
Last source activity
October 31, 2026 at 21:58
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
provision-windows-dev-vm
description
Create a new UTM Windows 11 ARM64 VM for Windows kernel driver development and debugging, modelled on the "Windows Development" VM — unattended install, guest tools, autologon, OpenSSH both ways, SSHFS-Win Z: share of the host, Visual Studio 2022 + WDK, Python/CMake/Git, a static address on the kdnet network — ready for /utmapp-bot:build-viogpu-driver and to kernel-debug test VMs. Use when asked to provision, create or set up a Windows dev/build VM, or to make an existing dev VM a kdnet debugger.
# Provision a Windows development VM Arguments: `$ARGUMENTS` 1. `iso` — a Windows 11 **ARM64** installation ISO on this Mac. If it is missing, ask for it. Everything else has a default. ## Before running - UTM must allow automation: the first AppleScript call may make UTM or macOS ask for permission. If `osascript` times out (`AppleEvent timed out`), ask the user to approve the prompt and retry. - **Remote Login** must be on (System Settings > General > Sharing), with "Allow full disk access for remote users", or the guest's `Z:` share of `~/Documents` cannot be mounted or read. - About 30 GB free on the Mac (the VM disk grows to roughly that size). ## Run ```sh bash "${CLAUDE_PLUGIN_ROOT}/scripts/provision-windows-dev.sh" '<iso>' ``` Run it in the background and follow `~/.utmapp-bot/provision/<vm>/provision.log`; a full run takes one to three hours, most of it the Windows install and Visual Studio. Everything the scripts keep (VM settings, logs, the download cache) is in `~/.utmapp-bot/`; set `UTMAPP_BOT_DATA` to use another directory (without spaces: ssh cannot take a known_hosts path with spaces). Options: - `--name NAME` — VM name in UTM (default `Windows Development <n>`, the first free number from 2). The script refuses a name that already exists. - `--user USER` / `--password PASS` — Windows account (default `UTM` / `utm`; deliberately insecure, the VM is only reachable from this Mac). - `--ssh-key FILE` — public key the host logs in with (default: first of `~/.ssh/id_ed25519.pub`, `id_ecdsa.pub`, `id_rsa.pub`). - `--name NAME --from STAGE` — resume a failed run at `STAGE` (no ISO needed after the `media` stage). The script prints the exact command when a stage fails. - `--name NAME --only STAGE` — run one stage on an existing VM. Dev VMs made before the kdnet network existed become kdnet debuggers with `--only kdnet` (it shuts the VM down once to move its second NIC). Shared pieces live in `${CLAUDE_PLUGIN_ROOT}/scripts/provision/common.sh`, `${CLAUDE_PLUGIN_ROOT}/scripts/provision/guest/setup.ps1` and `${CLAUDE_PLUGIN_ROOT}/scripts/provision/media/`, which `/utmapp-bot:provision-windows-test-vm` uses too; change them with both skills in mind. ## What it does | Stage | Where | What | |------------|-------|------| | `media` | host | APFS clone of the ISO with `efisys_noprompt.bin` patched into its El Torito boot image (no "Press any key to boot from CD"), and a configuration ISO with `Autounattend.xml`, the setup scripts, the UTM guest tools installer and the host's public key | | `vm` | host | AppleScript creates the VM (aarch64, HVF, UEFI, 16 GiB, default cores, 128 GiB NVMe, shared NIC + host-only NIC for kdnet, virtio-ramfb-gl), exports it, enables TPM (which selects the Secure Boot capable firmware; the variable store has no keys, so Secure Boot is off), local-time RTC and sound in `config.plist`, imports it back with both ISOs inserted and pins the host-only NIC to the kdnet network | | `install` | guest | Unattended Windows 11 Pro install; specialize pass turns off UAC, SmartScreen, Smart App Control, automatic device encryption and the firewall and sets the Defender policies; first logon installs guest tools, makes autologon permanent and installs OpenSSH Server with the host key in `administrators_authorized_keys`. Host waits for SSH | | `system` | SSH | Re-applies the above, long paths, Explorer settings, the golden VM's Copilot/AI/Edge policies, no sleep, keeps the host-only NIC's default route from competing with the shared NIC; then turns Microsoft Defender off for good through one Safe Mode boot (its WdFilter driver blocks writes to its service keys whenever it runs, even in the specialize pass) | | `kdnet` | SSH | Renames the second NIC `kdnet` and gives it a static address, the first free one from `192.168.240.200` (kept in `state.env` as `KDNET_IP`), no gateway. An older VM's second NIC is first moved onto the kdnet network (shutdown, AppleScript, boot) | | `tools` | SSH | 7-Zip, Git, CMake, Python 3.15 (per user, on PATH), WinFsp, SSHFS-Win | | `sshfs` | both | Guest RSA key (SSHFS-Win's `\\sshfs.k\` uses `id_rsa`) appended to the host's `~/.ssh/authorized_keys`; persistent `Z:` = `\\sshfs.k\<you>@<host>\Documents` | | `vs` | SSH | Visual Studio 2022 Community with exactly the golden VM's components (`${CLAUDE_PLUGIN_ROOT}/scripts/provision/guest/vs2022.vsconfig`: C++ desktop, ARM64/ARM64EC/x64/x86 tools, Spectre libraries, Windows 11 SDK 26100, WDK extension), then `pip install meson mako packaging pyyaml` (pyyaml has no Python 3.15 win-arm64 wheel and is compiled) | | `wdk` | SSH | WDK 10.0.26100.6584 | | `finalize` | host | Shuts down, replaces the ISO drives with one empty removable drive, boots, adds the VM to `~/.ssh/known_hosts` | | `verify` | SSH | Checks every tool `build-mesa` and `build-drivers.cmd` look for, the MSVC/Spectre libraries, SDK/WDK, `Z:`, `kd.exe` and the kdnet address, UAC/firewall/Defender/BitLocker/autologon/sshd | ### The kdnet network Every dev and test VM's second NIC is on one host-only vmnet network pinned to `192.168.240.0/24` (UTM's "vlan guest address", set through AppleScript): the Mac is `.1`, DHCP hands out `.2`–`.199` (a test VM's kdnet needs DHCP: it cannot use a static address), and dev VMs, the debuggers, use `.200` and up. Without the pin vmnet picks a different subnet from boot to boot. Do not give the NIC a UTM "Host Network" (`HostNetUuid`): that network has no DHCP. The golden "Windows Development" VM is on such a network and is not a kdnet debugger for test VMs. Installers are pinned with SHA-256 in `${CLAUDE_PLUGIN_ROOT}/scripts/provision/downloads.txt` and cached in `~/.utmapp-bot/cache/downloads/`; nothing outside this plugin is needed except those downloads (and the Visual Studio payload the bootstrapper fetches). The UTM guest tools are the exception: they come from the URL UTM itself uses, `https://getutm.app/downloads/utm-guest-tools-3d-latest.iso` (`Platform/UTMDownloadSupportToolsTask.swift` in the UTM source), which always serves the latest release, so they cannot be pinned. Like UTM, the script downloads them again only when the server's `Last-Modified` is newer than the cached copy, and keeps using the cache when the server cannot be reached. A VM gets whatever version was latest when its `media` stage ran. ## Output On success the log ends with `Done: '<name>' is ready at UTM@<ip> (kdnet debugger address <kdnet-ip>)`. Report the VM name, its SSH destination, its kdnet address and the verify summary (`~/.utmapp-bot/provision/<vm>/step-verify.log`). Then offer to run `/utmapp-bot:build-viogpu-driver UTM@<ip> arm64x 'Z:\<mesa>' 'Z:\<kmd>'` as the end-to-end check (a first build compiles LLVM and takes hours). ## When it fails - **No SSH after the install timeout**: Windows Setup stopped at a prompt or failed. Ask the user to look at the VM's window in UTM. Setup logs are in `C:\Windows\Panther\` (`setupact.log`, `setuperr.log`), provisioning logs in `C:\utmapp-bot\specialize.log` and `C:\utmapp-bot\firstlogon.log`. - **A guest step failed**: read `~/.utmapp-bot/provision/<vm>/step-<step>.log`, fix the cause, resume with `--from <step>`. Steps skip what is already installed. - **`system` fails with "Microsoft Defender is still on"**: the Safe Mode pass did not run (`C:\utmapp-bot\defender-off.log` is missing) or a service key was not written (the log shows which). If the VM is stuck in Safe Mode, run `bcdedit /deletevalue {current} safeboot` in it and reboot. Then rerun `--from system`. - **`verify` fails on `Z:`**: check Remote Login and full disk access for remote users on the Mac, then reboot the VM (the mapping is restored at logon) and rerun `--from verify`. - **SHA-256 mismatch**: a download changed upstream. Do not bypass the check; tell the user, and update `downloads.txt` only if they confirm the new file. - **Guest tools download fails** with no cached copy: check `curl -sI https://getutm.app/downloads/utm-guest-tools-3d-latest.iso`. If UTM's URL in its source has changed, update `GUEST_TOOLS_URL` in `${CLAUDE_PLUGIN_ROOT}/scripts/provision/common.sh` to match. Do not pin a release URL instead.
View on GitHub