| File | Purpose |
| --- | --- |
| `setup-windows.bat` | Entry point. Shows the plan and lets you choose, then runs the winget installs, launches the elevated half and prints its log, then runs the non-elevated script. |
-| `setup-windows-plan.ps1` | The plan. Run by the batch file before anything else: lists every step for this machine, grouped and numbered, and takes your choice. Run it on its own with `-WhatIf` to preview; add `-HostArch ARM64 -Elevation NOLUA` to preview another kind of box. |
+| `setup-windows-plan.ps1` | The plan. Run by the batch file before anything else: lists every step for this machine, grouped and numbered, and takes your choice. Run it on its own with `-WhatIf` to preview; add `-HostArch ARM64 -Elevation NOLUA`, or `-VirtualMachine Hyper-V` / `-VirtualMachine none`, to preview another kind of box. |
| `setup-windows-steps.ps1` | The catalog the other four read: every step's name, what it does, and where it cannot run and why. Defines functions only; it is dot-sourced, never run. |
| `setup-windows-current.ps1` | Asked by the batch file before the Claude Code, NASM and Android Studio installs: is what is on the box already current? Those are the installs that would otherwise run in full every time — see [Re-running](#re-running). |
-| `setup-windows-no-uac.ps1` | The non-elevated, per-user half: WinMerge, BinSkim and upstream LLVM on the user `PATH` (x64 only), the global git config (identity, plus `core.sshCommand`), the native `ninja` pinned ahead of the one Visual Studio bundles, and an architecture audit of everything provisioned. Can also be run directly from an ordinary prompt; it lists its steps and asks first. |
+| `setup-windows-no-uac.ps1` | The non-elevated, per-user half: `%USERPROFILE%\.local\bin` (where `claude.exe` lives), WinMerge, BinSkim and upstream LLVM on the user `PATH` (x64 only), the global git config (identity, plus `core.sshCommand`), the native `ninja` pinned ahead of the one Visual Studio bundles, and an architecture audit of everything provisioned. Can also be run directly from an ordinary prompt; it lists its steps and asks first. |
| `setup-windows-with-uac.ps1` | The elevated half, started via UAC by the batch file. Enables `ssh-agent`, installs the OpenSSH Client and Server capabilities and starts `sshd`, unpacks the `rsync-windows` release zip for this architecture (`rsync.exe` plus the `ssh.exe` it runs) into `C:\Tools\rsync` on the machine `PATH`, then installs Visual Studio Community with the required components, the WDK, and the Windows Performance Toolkit, and reports whether Intel VTune Profiler is present. Can also be run directly from an Administrator prompt; it lists its steps and asks first. |
| `setup-windows-7-test-env.bat` | Prepares a **Windows 7 VM** as a test target driven from the host by `VBoxManage guestcontrol`. Copy it into the guest and run it there; it is idempotent, so re-run it after any snapshot restore. The per-user half needs no UAC (crash-dialog suppression, no screen blanking, a staging directory, the shared folder on `Z:`); the machine-wide half is skipped with a notice unless run elevated inside the guest. It then reports what the box can actually test: DWM composition, printers, audio capture devices. |
were asked already. With nothing selected for a half it is not started — so
no UAC prompt is raised for an elevated half with no steps.
- Every step is declared once, in `setup-windows-steps.ps1`: its name, what it
- does, and — with the reason — whether it is x64-only (`-X64Only`) or needs an
+ does, and — with the reason — whether it is x64-only (`-X64Only`), kept off a
+ Windows that is itself a virtual machine (`-NotInVM`), or needs an
administrator (`-Admin`). The planner, the batch file and both halves read
that one list and check each other: a step the list does not know is refused
rather than run, and a selected step that nothing implements fails the run
| winget packages | `winget install` on a package winget can see is installed upgrades it when a newer version exists and otherwise does nothing — winget's own default. `--force`, which would install regardless, is never used. |
| `ClaudeCode` | Anthropic's installer always downloads and reinstalls, so `setup-windows-current.ps1` asks first: is `%USERPROFILE%\.local\bin\claude.exe` the latest release — the version the installer's own endpoint names — and the build for this host? Only both skip it. |
| `NASM` | Its per-user fallback install registers nothing, so winget cannot see it and would rerun the installer every time. `setup-windows-current.ps1` compares that `nasm.exe` with the version winget offers. A machine-wide NASM is left to winget. |
+| `Msys2` | The base archive is downloaded only when `%LOCALAPPDATA%\msys64\usr\bin\pacman.exe` is missing. Otherwise `pacman -Syuu` updates what is there, and `pacman -S --needed` leaves the GCC packages alone when they are already installed. |
| `Rustup` | Needs no help: rustup registers itself under the manifest's product code and rewrites its version when it updates itself, so winget sees what is really there. |
| `AndroidStudio` | Installed only when it is missing. Studio updates itself in the app, and winget's package is marked never to upgrade it; the check keeps winget from installing over a Studio it cannot match to its package, as on a box where Studio was installed some other way. |
| `WiX` | Skipped when 5.0.2 is already in the .NET global tool store. |
`Anthropic.ClaudeCode` are broken through that route, so the batch file runs
Anthropic's own installer, `irm https://claude.ai/install.ps1 | iex`: it
checks the download against the release manifest's SHA-256 and puts `claude`
- in `%USERPROFILE%\.local\bin`, per-user. Two things are fixed up around it.
+ in `%USERPROFILE%\.local\bin`, per-user. The `LocalBinPath` step in
+ `setup-windows-no-uac.ps1` makes sure that directory is on the user `PATH`
+ (creating it if need be); the installer itself only prints a reminder. Two
+ things are fixed up around it.
The script picks its ARM64 or x64 build from `$env:PROCESSOR_ARCHITECTURE`
alone, which reads `AMD64` in an emulated x64 shell on ARM64 (see
[Detecting the architecture](#detecting-the-architecture)), so the batch file
`PATH`; the step warns if it finds one (`winget uninstall
Anthropic.ClaudeCode` removes it), and the architecture audit shows which
`claude.exe` wins.
+- **Windows Terminal gets its framework package registered first.** Terminal is
+ an MSIX that depends on `Microsoft.UI.Xaml.2.8`, which winget installs for it
+ from a pinned build (8.2310.30001.0). When a *newer* build is already on the
+ machine but not registered for the account running the setup, Windows
+ declines to stage the older one, winget then tries to register a package
+ that is not there, and what it prints is `Installer failed with exit code:
+ 0x80070002 : The system cannot find the file specified` — with Terminal
+ itself never attempted, on every run. So the batch file first registers the
+ build that is already there (`Add-AppxPackage -RegisterByFamilyName`, no
+ elevation needed), and winget then finds the dependency met. With none on
+ the machine it does nothing, and winget installs the framework as usual.
- **Rust.** winget installs rustup (`Rustlang.Rustup`, per-user, under
`%USERPROFILE%\.cargo` and `.rustup`). Its silent install is
`rustup-init -y -q`, which already sets up a stable toolchain for whatever
emulation on ARM64, which every Windows on Arm has; the audit lists it as
expected. It stays on ARM64 for the same reason `iperf3` does: it is bound by
the link to the device, not by the CPU.
+- **MSYS2 and MinGW-w64 GCC** (`Msys2`) go into `%LOCALAPPDATA%\msys64`, per-user,
+ with no elevation: the MSYS2 base self-extracting archive unpacks wherever it
+ is pointed, whereas the winget package runs the installer machine-wide into
+ `C:\msys64`. Two environments get GCC: `ucrt64` (x86_64, links the UCRT) and
+ `mingw32` (i686, links `msvcrt.dll`, the one that can still target Windows XP).
+ These are the same toolchains GitHub's `windows-latest` runner carries, so a
+ build that works here works there. Nothing is added to the `PATH`: the
+ environments' `bin` directories hold same-named `gcc`, `ld` and `windres`, so a
+ project names the one it wants by full path, e.g.
+ `%LOCALAPPDATA%\msys64\mingw32\bin`.
- **Android Studio and the Android SDK** are separate steps. Studio
(`Google.AndroidStudio`) is machine-wide — its installer elevates itself — and
is installed only when missing, since it updates itself. The SDK is per-user,
| `WinMerge.WinMerge` | `setup-windows.bat` | ARM64 ships as a **machine-scope** installer only, so an unelevated `winget` can install nothing but the emulated x64 build |
| `LLVM.LLVM` | `setup-windows.bat` | second Clang, dropped along with VS's `clang-cl` |
| `NASM.NASM` | `setup-windows.bat` | x86/x86-64 assembler with no ARM64 target; MSVC's `armasm64.exe` is the native one |
+| MSYS2 (`Msys2`) | `setup-windows.bat` | its base system is x86_64 and would run emulated, and the Windows XP target it is installed for is x64-box work |
| `Google.AndroidGPUInspector` | `setup-windows.bat` | x64-only, and it profiles Android devices rather than anything compiled here |
| `OpenCppCoverage` | `setup-windows.bat` | collects coverage by stepping x86/x64 instructions, so it cannot instrument an ARM64 binary at all |
| VS Clang/LLVM component | `setup-windows-with-uac.ps1` | see below |
Windows 7 *x86* guest, which neither VirtualBox nor Hyper-V on ARM64 can run.
(That script itself is unchanged; it runs inside the guest, so it never sees
the host architecture.)
+- **VirtualBox is also skipped inside a virtual machine**, on any architecture.
+ The planner reads the firmware's SMBIOS manufacturer and model
+ (`Win32_ComputerSystem`) and recognises VirtualBox, VMware, Hyper-V (and
+ Azure), QEMU/KVM, Parallels, Xen, EC2 and GCE; the plan's `machine:` line
+ names what it found. It does not use `HypervisorPresent`, which is also true
+ on physical hardware running Hyper-V, WSL2 or VBS. The test VMs belong on the
+ physical host.
- **The `v141` / Windows XP toolset is skipped**, and the verification step says
`n/a` instead of warning about it. The components are still *listed* in the
catalog on Arm, so this is a deliberate skip rather than a hard unavailability