X-Git-Url: https://vilimpoc.org/repos/dotfiles/blobdiff_plain/649b04df2966260f38a18d5225a68bd4e0896b95..0b4125206461c1a63a971ac9cf503319bcb05798:/README.md diff --git a/README.md b/README.md index 349a693..55244d7 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,31 @@ Windows development-box provisioning scripts. `setup-windows.bat` takes a fresh Windows install to a working C++ / native development environment: editors and shells, Python, the Visual Studio toolchain (including the Clang and Windows XP targeting toolsets), the Windows -Driver Kit, and a handful of analysis tools (Sysinternals, OpenCppCoverage, -BinSkim, the Windows Performance Toolkit). It also sets the box up to be driven -remotely: OpenSSH Server plus an rsync build for Windows, which is what makes a -throwaway VM reachable from a Linux host. +Driver Kit, Rust (rustup, with the stable MSVC toolchain for the host as the +default), Android's `adb`, and a handful of analysis tools (Sysinternals, +OpenCppCoverage, BinSkim, the Windows Performance Toolkit). It also sets the +box up to be driven remotely: OpenSSH Server plus an rsync build for Windows, +which is what makes a throwaway VM reachable from a Linux host. -Runs on **x64 and on ARM64** (Windows 11 on Arm). See -[Running on ARM64](#running-on-arm64) for what differs. +**Nothing happens until you have seen the plan.** It first lists every step it +can take on this machine and lets you choose which ones to run — see +[Choosing what runs](#choosing-what-runs). + +Runs on **x64 and on ARM64** (Windows 11 on Arm), and installs *different things* +on each: x64 is the full workstation, ARM64 a lean build box with a single +C/C++ compiler. See +[Running on ARM64](#running-on-arm64) for what differs and why. ## Files | File | Purpose | | --- | --- | -| `setup-windows.bat` | Entry point. Runs the winget installs, then launches the elevated half and prints its log, then runs the non-elevated script. | -| `setup-windows-no-uac.ps1` | The non-elevated, per-user half: WinMerge and BinSkim on the user `PATH`, and the global git config (identity, plus `core.sshCommand`). Can also be run directly from an ordinary prompt. | -| `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. | +| `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-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 and NASM installs: is what is on the box already current? Those are the two 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-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. | ## Usage @@ -43,12 +53,107 @@ Runs on **x64 and on ARM64** (Windows 11 on Arm). See setup-windows.bat ``` - It will raise a single UAC prompt for the elevated half. Accept it — declining - leaves Visual Studio and the WDK uninstalled, and the script says so. + Before doing anything it prints the plan — every step it can take on this + machine, grouped and numbered — and waits: + + ```text + Setup plan - nothing has been done yet + host: x64 - the full workstation + elevation: not elevated; expect a UAC prompt partway through, for the elevated half + + U Per-user installs (winget, except Claude Code) + [x] 1 ClaudeCode Claude Code, native installer: irm https://claude.ai/install.ps1 | iex + [x] 2 Brave Brave.Brave + ... + E Elevated setup (setup-windows-with-uac.ps1) - one UAC prompt, runs in its own window + [x] 24 OpenSshClient OpenSSH Client capability (ssh.exe, and the libcrypto.dll rsync's ssh.exe needs) + ... + [x] 28 VisualStudio Visual Studio 2022 Community, C++: MSVC for x86/x64 + ARM64, SDK 26100 (several GB) + ... + + 42 of 42 steps selected. + + Enter runs the selected steps. To change them, type step numbers or ranges + (3 5-7), step names, or a group letter (U M E C) to switch them on or off; + 'all' or 'none' for everything; 'q' to quit without doing anything. + ``` + + Press Enter to run everything marked `[x]`, or switch steps off and on first; + the list is redrawn after each change. Steps that cannot run on this machine + are shown as `[-]` with the reason and have no number. + + After Enter it runs unattended apart from a single UAC prompt for the + elevated half — none at all if you switched every elevated step off. Accept + it; declining leaves Visual Studio and the WDK uninstalled, and the script + says so. 3. Restart your shell afterwards so the updated user `PATH` is picked up, and reboot if a step reported that a restart was required. +## Choosing what runs + +Arguments to `setup-windows.bat` set the starting point of the plan; you can +still adjust it before pressing Enter unless you also pass `-Yes`. + +| | | +| --- | --- | +| `setup-windows.bat -WhatIf` | Show the plan for this machine and stop. Nothing is changed. | +| `setup-windows.bat -Skip VirtualBox,Brave` | Start with those switched off. | +| `setup-windows.bat -Only E` | Start with only the elevated half. | +| `setup-windows.bat -Yes` | Do not ask: run the plan as it stands. For unattended runs. | +| `setup-windows.bat /?` | Usage. | + +- Steps are named as the plan shows them, comma-separated. A group letter works + anywhere a name does: `U` per-user installs, `M` machine-wide installs and the + WiX pin, `E` the elevated half, `C` per-user configuration and Pillow. A name + it does not recognise stops the run before anything happens — a mistyped + `-Skip` that was quietly ignored would install exactly what you meant to leave + out. +- **With no console to ask on** — piped, scheduled, a non-interactive `ssh` + command — it prints the plan and stops with exit code 2 unless given `-Yes`. + It never assumes yes. +- The two halves take `-Only`, `-Skip` and `-Yes` too, and when run on their own + print their part of the plan and ask before doing anything, e.g. + `.\setup-windows-with-uac.ps1 -Only VisualStudio,Wdk`. From the batch file + they are handed exactly what the plan left selected, with `-Yes`, since you + 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 + 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 + rather than quietly not happening. Adding a step means a row there plus the + code that does it, gated on the same name. +- Some steps build on others — the WiX pin needs the .NET SDK, the Clang and XP + toolsets need Visual Studio. The plan notes it when you switch the + prerequisite off, but does not stop you: it may well be installed already. + +## Re-running + +Every step checks before it acts, so running `setup-windows.bat` again on a +provisioned box installs only what is missing or has a newer version out. +Nothing is reinstalled at the version it already has. Where a check cannot +decide — no network, a version string it does not recognise — it installs as +before rather than skipping: a failed check must never be why something is left +missing or out of date. + +| step | how it decides | +| --- | --- | +| 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. | +| `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. | +| `WiX` | Skipped when 5.0.2 is already in the .NET global tool store. | +| `OpenSshClient`, `OpenSshServer` | Installed only when the capability is not. | +| `Rsync` | The release's `.sha256` is fetched first and compared with the hash recorded when the files were installed (`rsync-windows.sha256`). The zip is downloaded only when they differ or a file is missing. | +| `VisualStudio`, `VsClang`, `VsWinXP` | `vswhere -requires` says whether the install already has every component of a pass. A pass with nothing to add does not run the installer, and with nothing to add anywhere the bootstrapper is not even downloaded. The script never updates Visual Studio itself; the Visual Studio Installer does that. | +| `Wdk`, `Wpt` | Skipped when the kit, or the toolkit, is already there. | +| `BinSkim` | Asks NuGet for the newest stable version before downloading a package well over 100 MB; `nupkg-version.txt` beside the tool records what is installed. | +| `RustToolchain` | `rustup toolchain install` updates stable when there is a newer one and otherwise reports it unchanged. | +| `Pillow` | `pip install` leaves an installed Pillow alone. It does not upgrade it either. | +| the rest | Configuration: `PATH` entries are added only when missing, and settings are written with the values they already have. | + ## Notes - The elevated half writes a transcript to `setup-windows-uac.log` next to the @@ -75,15 +180,12 @@ Runs on **x64 and on ARM64** (Windows 11 on Arm). See fast `ssh.exe` has to exist before the git config step can prefer it. The non-elevated half still runs even when the elevated one failed; it just falls back. Re-run `setup-windows-no-uac.ps1` on its own at any time to re-pick. -- All three scripts are idempotent — re-running skips anything already installed. - BinSkim in particular checks NuGet for the newest stable version *before* - downloading: the package is a self-contained .NET build well over 100 MB, and - re-provisioning an up-to-date box should not pay for it. The installed version - is tracked in `nupkg-version.txt` beside the tool. +- All the scripts are safe to re-run: nothing is reinstalled at the version it + already has. See [Re-running](#re-running) for how each step decides. - `setup-windows-no-uac.ps1` runs its steps independently: one failing warns and the rest still run, and it exits 1 if any did. The `.bat` reports that and - carries on to the elevated half, which is the part worth the UAC prompt. Use - `-Skip` to re-run a subset, e.g. `.\setup-windows-no-uac.ps1 -Skip BinSkim`. + carries on. Use `-Only` / `-Skip` to re-run a subset, e.g. + `.\setup-windows-no-uac.ps1 -Skip BinSkim`. Run it **non-elevated**: it writes per-user state (the `HKCU` `PATH`, the `.gitconfig` under `%USERPROFILE%`), so an elevated run would configure the administrator's profile instead. It warns if you do. @@ -100,12 +202,11 @@ Runs on **x64 and on ARM64** (Windows 11 on Arm). See `SYSTEM`; accounts in the Administrators group use `C:\ProgramData\ssh\administrators_authorized_keys` instead. - **rsync brings its own `ssh.exe`.** That release ships as one zip per - architecture — `rsync-windows-x64.zip` / `rsync-windows-x86.zip`, each holding - `rsync.exe`, an `ssh.exe`, `COPYING.txt` and `NOTICE-ssh.txt` — and the - elevated half picks the zip for the host architecture (ARM64 takes the x64 one; - there is no ARM64 asset), verifies it against the published `.sha256`, unpacks - the pair together, then **runs** the unpacked `ssh.exe` and removes it if it - will not start. Together is the point: + architecture — `rsync-windows-x86.zip`, `-x64.zip` and `-arm64.zip`, each + holding `rsync.exe`, an `ssh.exe`, `COPYING.txt` and `NOTICE-ssh.txt` — and the + elevated half picks the zip matching the host, verifies it against the + published `.sha256`, unpacks the pair together, then **runs** the unpacked + `ssh.exe` and removes it if it will not start. Together is the point: `rsync.exe` prefers an `ssh.exe` sitting in its own directory, and the release builds one because the client Windows ships reads its stdin 3 KB at a time, which holds a transfer *from* the box at ~17 MB/s however fast the link is. @@ -122,8 +223,9 @@ Runs on **x64 and on ARM64** (Windows 11 on Arm). See a box on a known build, pin the tag in `$RsyncUrl` (`.../releases/download//`) instead. - Visual Studio is installed in three labelled passes (base workload, Clang/LLVM, - XP toolset) so a failure identifies which component group is responsible. The - last two are optional passes: a failure there warns and provisioning continues, + XP toolset) so a failure identifies which component group is responsible. On + ARM64 only the first runs — the other two are skipped there, each printing why. + The last two are optional passes: a failure there warns and provisioning continues, because neither is needed to build with MSVC and losing the whole toolchain over a component you can add later from the installer UI is the worse outcome. - **Which Visual Studio generation.** `$VsChannel` near the VS step picks it — @@ -230,6 +332,46 @@ Runs on **x64 and on ARM64** (Windows 11 on Arm). See from that descriptor rather than deleting the value, which also carries entries for SYSTEM, Administrators and two service accounts. +- **Claude Code is not installed through winget.** Recent releases of + `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. + 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 + sets that variable from the registry's answer for the installer's process. + And the script refuses a 32-bit PowerShell, which is what `powershell` is from a + 32-bit `cmd.exe`, so it is run through `%SystemRoot%\Sysnative` when that + exists. A copy left over from the winget route can shadow the new one on the + `PATH`; the step warns if it finds one (`winget uninstall + Anthropic.ClaudeCode` removes it), and the architecture audit shows which + `claude.exe` wins. +- **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 + host rustup detects. The `RustToolchain` step in `setup-windows-no-uac.ps1` + then pins the answer: `stable-x86_64-pc-windows-msvc` on x64, + `stable-aarch64-pc-windows-msvc` on ARM64, installed, made the default, and set + as rustup's default host. That matters on ARM64, where an x64 rustup — + an older install, or one started from an emulated shell — would otherwise + quietly set up an emulated compiler. The `-msvc` toolchains link with the + Visual Studio the elevated half installs, but only need it to *build*: + installing rustup first is fine, and with `-y` rustup-init just warns that + the prerequisites are missing rather than installing a Visual Studio of its + own. Re-running the step updates stable. It also resets the default to + stable, so leave `RustToolchain` out of the plan if you have chosen another + default on purpose. The architecture audit reports the compiler *inside* the + toolchain: `.cargo\bin\rustc.exe` is a rustup proxy, so its architecture is + rustup's, not the compiler's. +- **`adb` and `fastboot`** come from `Google.PlatformTools`, a portable zip that + winget unpacks under `%LOCALAPPDATA%\Microsoft\WinGet\Packages` and puts on the + user `PATH` (`adb.exe` needs the `AdbWinApi` DLLs beside it). The manifest + calls it x64, but every binary in it is 32-bit **x86** — checked on r37.0.1 — + and there is no ARM64 build. It runs under WOW64 on x64 and under x86 + 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. - The scripts were extracted from a native Windows project, so the component selection is tuned for that: Spectre-mitigated runtimes, the v141/XP toolset, and driver-kit headers. Trim the component lists in the `.ps1` if you don't @@ -238,14 +380,42 @@ Runs on **x64 and on ARM64** (Windows 11 on Arm). See ## Running on ARM64 All three scripts detect the host architecture and adapt. Nothing needs a flag — -run `setup-windows.bat` exactly as on x64. Both PowerShell halves use -`RuntimeInformation.OSArchitecture` rather than `%PROCESSOR_ARCHITECTURE%`, -because an emulated PowerShell reports the *emulated* architecture in the -environment variable and the real one through the API. +run `setup-windows.bat` exactly as on x64. + +**The two architectures deliberately install different things.** x64 is the full +workstation; ARM64 is provisioned as a lean build box with a single C/C++ +compiler. See +[ARM64 installs less, on purpose](#arm64-installs-less-on-purpose) below for the +list and the reasoning. + +### Detecting the architecture + +All three scripts read the **machine-level registry value**, not the environment +and not `RuntimeInformation.OSArchitecture`. Both of those report the *emulated* +architecture inside an emulated x64 process — which is what you get by launching +`powershell` or `cmd` from an x64 shell (Git Bash, for one) on an ARM64 box. +Measured on this machine, from that emulated shell: + +| source | reports | +| --- | --- | +| `[RuntimeInformation]::OSArchitecture` | `X64` ✗ | +| `%PROCESSOR_ARCHITECTURE%` | `AMD64` ✗ | +| `HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment` | `ARM64` ✓ | + +`OSArchitecture` is documented as the *OS's* architecture, and on .NET Core it +is; under .NET Framework on Prism it is not. It is kept only as a fallback. This +used to be a cosmetic mislabel in a log line — now that the architecture decides +which packages are installed at all, reading it wrong provisions the wrong +machine. + +> The batch file calls `reg.exe` by full path and parses it **without** piping to +> `find`/`findstr`. Both are shadowable by anything earlier on the `PATH`, and a +> POSIX `find` turns the whole detection into a silent no-op that leaves the +> emulated answer standing. That is not hypothetical; it is how this was found. -**The build toolchain is fully native.** Visual Studio's ARM64 installer, MSVC -and clang-cl are all ARM64 binaries, and MSVC cross-compiles every target from -an ARM64 host with no emulation in the compiler: +**The build toolchain is fully native.** Visual Studio's ARM64 installer and MSVC +are ARM64 binaries, and MSVC cross-compiles every target from an ARM64 host with +no emulation in the compiler: ```bat cmake -B build -A ARM64 && cmake --build build --config Release :: native @@ -266,24 +436,37 @@ ARM64-targeting toolchain, and the x64 cross-compiler is *not* implied. Spectre runtimes and ATL are likewise requested for both target families (`VC.ATL.ARM64.Spectre` is a separate component from `VC.ATL.Spectre`). -**Three native compilers, on purpose.** MSVC and `clang-cl` from Visual Studio, -plus **upstream LLVM** (`winget install LLVM.LLVM`, which resolves to -`LLVM--woa64.exe` on ARM64 — "Windows on Arm 64"). Upstream runs on its own -schedule and is usually several major versions ahead of the Clang that VS -bundles, and it installs to `C:\Program Files\LLVM` rather than inside the VS -tree, so the two are genuinely independent. Two Clang majors plus MSVC over the -same sources is what catches the bugs a single toolchain agrees with itself -about. Watch the `PATH` if you use it: a bare `clang-cl` may resolve to upstream -while CMake's `-T ClangCL` keeps using VS's, so pass a full -`-DCMAKE_CXX_COMPILER` when it matters. - -**Clang/LLVM is installed on ARM64 too, and it is native there** — MSVC and -`clang-cl` over the same sources is the compiler diversity this box is after. -Two things establish that it is genuinely native rather than an emulated x64 -compiler: the `VC.Llvm.Clang` VSIX is `productArch=neutral` with no `chip` or +### ARM64 installs less, on purpose + +The ARM64 machine here is a constrained VM, and nearly everything it was +installing without a native build was going to run under Prism emulation anyway. +The exceptions were individually defensible and collectively a VM full of x64 +binaries. So the split is now explicit: **ARM64 gets MSVC and nothing that +duplicates it or that exists only as an x64 binary.** + +| dropped on ARM64 | where | why | +| --- | --- | --- | +| `Brave.Brave` | `setup-windows.bat` | no ARM64 build; an emulated browser is the slowest thing on the box, and Edge is in-box and native | +| `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 | +| `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 Driver Kit | `setup-windows-with-uac.ps1` | see below | +| BinSkim | `setup-windows-no-uac.ps1` | the NuGet package publishes `win-x64` only | + +x64 keeps every one of them. The decision is made in one place, +`setup-windows-steps.ps1` (`-X64Only`, with the reason): the plan shows each as +`[-]` with that reason, and the run log repeats it where the step would have +run, rather than leaving a silent gap. + +**`clang-cl` is dropped by choice, not by limitation.** A native ARM64 `clang-cl` +*is* offered on Arm and did work here before the split. Two things establish +that: the `VC.Llvm.Clang` VSIX is `productArch=neutral` with no `chip` or `machineArch` restriction, so the Arm installer offers it; and MSVC's `VC\Tools\Llvm` tree is partitioned by **host** architecture, with real ARM64 -binaries already in `ARM64\bin`, which is where `clang-cl.exe` lands: +binaries in `ARM64\bin`, which is where `clang-cl.exe` lands: | directory | host | | --- | --- | @@ -291,23 +474,61 @@ binaries already in `ARM64\bin`, which is where `clang-cl.exe` lands: | `VC\Tools\Llvm\x64\bin` | x64 | | `VC\Tools\Llvm\ARM64\bin` | ARM64 | -Unlike the `v141`/XP group, nothing technical is in the way. Upstream LLVM ships -a Windows-on-Arm build independently too — `winget install LLVM.LLVM` resolves to -`LLVM--woa64.exe` on ARM64 — if you want a Clang outside Visual Studio. - > Watch for a false positive when checking by hand: `VC\Tools\Llvm\*\bin` holds > `clang-format.exe` and `clang-tidy.exe` on **any** host, installed or not — > those ship with the NativeDesktop workload. Their presence does not mean the -> compiler is there; look for `clang-cl.exe`. The verification step does exactly -> that, and warns separately if only a non-native `clang-cl` landed on Arm. +> compiler is there; look for `clang-cl.exe`. + +Note that the VS installer never uninstalls a component you simply stopped +asking for, so a box provisioned before this split keeps its `clang-cl` until it +is explicitly removed. The verification step says so and prints the +`--remove` line. -What changes, and why: +**The WDK is dropped for two reasons, and the second one bites on x64 too.** The +ARM64 box builds user-mode software, so it does not need a driver kit — but this +particular WDK *cannot* install here regardless. The kit requires the **matching +Windows SDK revision**, and revisions are not side-by-side: `Include\10.0.26100.0` +is one directory, whichever revision wrote it. VS 2026 installs SDK +**26100.7705**; `linkid=2335869` is WDK **26100.6584**, the kit paired with VS +2022. `wdksetup.exe /quiet` therefore aborts: + +``` +WDK installer exit code: 15605 +WER: WDK / 10.1.26100.6584 / Install / 0x80070642 +``` + +`0x642` is `1602`, `ERROR_INSTALL_USEREXIT` — a silent install cancelling itself +on a failed prerequisite check. Nothing to do with Arm or emulation; the same +pairing fails on x64. If a driver ever does need building against VS 2026, use +the kit Microsoft's [supported WDK downloads](https://learn.microsoft.com/en-us/windows-hardware/drivers/other-wdk-downloads) +table pairs with it — the 28000.x one — not this link. x64 with VS 2022 has the +matched pair, so it installs there. + +**Compiler diversity now lives on the x64 box**, which has room for three +toolchains: MSVC, VS's `clang-cl`, and **upstream LLVM** (`winget install +LLVM.LLVM`). Upstream runs on its own schedule, is usually several major versions +ahead of the Clang VS bundles, and installs to `C:\Program Files\LLVM` rather +than inside the VS tree, so the two are genuinely independent. Two Clang majors +plus MSVC over the same sources is what catches the bugs a single toolchain +agrees with itself about. Watch the `PATH` there: a bare `clang-cl` may resolve +to upstream while CMake's `-T ClangCL` keeps using VS's, so pass a full +`-DCMAKE_CXX_COMPILER` when it matters. + +**Rust is on both boxes, and native on ARM64.** It is a different language +rather than a second C/C++ compiler, so it does not bend the one-compiler rule, +and its `-msvc` toolchain links through MSVC's own `link.exe`. The ARM64 box +gets `stable-aarch64-pc-windows-msvc`; to build x64 Rust code from there, add +the x64 *target* (`rustup target add x86_64-pc-windows-msvc`) rather than an x64 +host toolchain, which would run emulated. + +What you end up with on ARM64: | | On ARM64 | | --- | --- | -| **Native ARM64** | Git, Python, .NET SDK, PowerShell, VS Code, Windows Terminal, WinMerge, Brave, Sysinternals, Claude Code, CMake, Ninja, the VS installer, **MSVC, VS clang-cl and upstream LLVM**, MSBuild, the SDK tools (`rc`, `signtool`), the in-box OpenSSH client, and the Windows Performance Toolkit. | -| **Emulated x64** | `iperf3`, NASM, OpenCppCoverage, BinSkim, `rsync.exe`, Android GPU Inspector, and the WDK/ADK *installers* (the kits they lay down are native). | +| **Native ARM64** | Git, Python, .NET SDK, PowerShell, VS Code, Windows Terminal, Sysinternals, Claude Code, CMake, Ninja, the VS installer, **MSVC**, MSBuild, the SDK tools (`rc`, `signtool`), rustup and **Rust** (`stable-aarch64-pc-windows-msvc`), the in-box OpenSSH client, and the Windows Performance Toolkit. | +| **Emulated** | `iperf3` (x64; network-bound), `adb` and `fastboot` (x86 — Google ships no other Windows build; bound by the USB/TCP link to the device), and the ADK *installer* (x64; the toolkit it lays down is native). | | **Emulated, minor** | `py.exe` (python.org ships the launcher shim as x86; it execs the native `python.exe`) and `vswhere.exe` (Microsoft ships x86 only; runs once). | +| **Not installed** | Brave, WinMerge, upstream LLVM, NASM, Android GPU Inspector, OpenCppCoverage, BinSkim, VS `clang-cl`, and the WDK — see the table above. | | **Unavailable** | VirtualBox, the Windows 7 x86 test VM, Intel VTune, and the `v141` / Windows XP targeting toolset. | ### The architecture audit @@ -324,15 +545,81 @@ Anything emulated with a listed reason prints as expected. Anything emulated *without* one is called out, with a remedy where a native build exists — which is how the `ninja` problem below was found. +### Elevation, and the case where there is no prompt + +`setup-windows.bat` works out **once, at the top**, whether this account can +elevate, and reports it as `Elevation: ALREADY | PROMPT | NOLUA`: + +| | | +| --- | --- | +| `ALREADY` | Already elevated. The elevated half runs **directly** — no point asking to elevate what already is. | +| `PROMPT` | Not elevated, UAC on. Elevation is requested: an administrator gets a consent prompt, a standard user gets an over-the-shoulder **credential** prompt. | +| `NOLUA` | UAC is off machine-wide (`EnableLUA = 0`) **and** this is not an administrator. Elevation is *impossible*, not merely declined. | + +`IsInRole(Administrator)` is false for an admin running unelevated under UAC, +which is what makes `ALREADY` mean *actually elevated* rather than *could be*. + +**Why `NOLUA` needs detecting rather than attempting.** `Start-Process -Verb +RunAs` is not honest about it. With UAC disabled the `RunAs` verb is **silently +ignored**: no prompt appears, the child starts with the caller's own unelevated +token, and `Start-Process` reports **exit code 0** as though it had worked. The +elevated script then refuses itself on its `#Requires -RunAsAdministrator` line, +exits 1, and never reaches its `Start-Transcript` — so there is no log either. +Read naively that is indistinguishable from a cancelled UAC prompt, which is +exactly what this script used to report, sending you to look for a prompt that +could never have appeared. + +Measured on such a box: `Start-Process -Verb RunAs` returned exit 0 while the +child reported itself as the *unelevated* calling user. + +**What is skipped when elevation is impossible.** The elevated half, plus four +packages that sit in the "per-user" section but are machine-wide installers. +They used to fail on every run with opaque codes — `exit 5` +(`ERROR_ACCESS_DENIED`) for the .NET SDK, MSI `1603` for CMake and Android GPU +Inspector, `exit 1` for OpenCppCoverage, whose installer self-elevates. They are +now shown as unavailable in the plan, with the reason, and skipped with one +clear line each. None blocks a build: the .NET SDK is +only there for the WiX MSI tooling, CMake also ships inside Visual Studio, AGI +profiles Android devices, and OpenCppCoverage cannot instrument ARM64 binaries +anyway. + +A `NOLUA` run is a **success** (exit 0), not a failure — the per-user half did +its job. To finish the box, run it from an administrator account, then re-run +`setup-windows-no-uac.ps1` as yourself so the per-user `PATH` and `.gitconfig` +land in *your* profile rather than the administrator's. + +Two consequences worth knowing rather than rediscovering: + +- **LLVM does not fail — it relocates.** Its NSIS installer targets + `%ProgramFiles%\LLVM`, and when it cannot write there it silently falls back to + a per-user directory (observed: `%USERPROFILE%\Documents\LLVM`) while winget + still reports *Successfully installed*. The compiler is genuinely there and + native; nothing can find it. The `LlvmPath` step looks in that fallback, puts + the directory on the user `PATH`, and says so. +- **WinMerge silently downgrades to x64.** Upstream publishes ARM64 as a + **machine-scope** installer and x64 as a **per-user** one, so an unelevated + winget lets its scope preference beat its architecture preference. The batch + now asks for `--architecture arm64` first and falls back, so an admin run gets + the native build and a standard-user run still gets a working one. + ### Two gotchas the audit surfaces -- **Visual Studio bundles an x64 `ninja.exe` even on ARM64**, and `VsDevCmd.bat` - **prepends** the VS directories to `PATH`. So inside a Developer Command Prompt - — exactly where C++ builds happen — the emulated ninja wins over the native one - `winget install Ninja-build.Ninja` provides. It matters more than a one-off - tool would, because ninja is re-invoked for every edge in the build graph. Pass - `-DCMAKE_MAKE_PROGRAM=` the native one, or put its directory ahead of the VS - one. VS's bundled `cmake.exe` *is* ARM64, so only ninja has this problem. +- **Visual Studio bundles an x64 `ninja.exe` even on ARM64.** It matters more + than a one-off tool would, because ninja is re-invoked for every edge in the + build graph — it is the one place emulation is paid over and over rather than + once. `winget install Ninja-build.Ninja` gets the native `ninja-winarm64` + build, and the `NinjaPath` step pins its directory to the **front** of the user + `PATH`. VS's bundled `cmake.exe` *is* ARM64, so only ninja has this problem. + + On which copy wins inside a Developer Command Prompt: the native one. VS adds + its ninja from `Common7\Tools\vsdevcmd\ext\cmake.bat`, which does + `set "PATH=%PATH%;...\CMake\bin;...\CMake\Ninja"` — an **append**, so the VS + directories land at the very end of the composed `PATH`, behind every machine + and user entry. A native ninja anywhere on the user `PATH` already beats it, + measured on an ARM64 box. The `NinjaPath` step is therefore insurance rather + than the load-bearing fix: winget *appends* its package directory to the user + `PATH`, so without it the margin rests on two append orders staying as they + are, one of them in a file Microsoft owns and revises. - **Sysinternals ships every architecture in one zip**, and the ARM64 build is not the default-named exe. `ProcessExplorer.zip` contains `procexp.exe` (x86), `procexp64.exe` (x64) and `procexp64a.exe` (**ARM64**) — so run the `*64a.exe` @@ -361,39 +648,51 @@ how the `ninja` problem below was found. Windows XP never ran on ARM64, so the only point of an XP-targeting build here would be producing x86 binaries, which the current toolset does natively via `-A Win32`. -- **OpenCppCoverage is installed but limited.** Unlike the rest of the emulated - row, this is a real ceiling rather than a slowdown: it collects coverage by - debugging the process under test and stepping x86/x64 instructions, so it - covers the x86/x64 binaries this box cross-compiles but **not** an ARM64 one. - Run coverage against the x64 build. The script prints this rather than leaving - you to discover it. -- **BinSkim is emulated and that is fine.** The NuGet package publishes `win-x64` - only (its other RIDs are `linux-x64`, `linux-arm64`, `osx-x64`). BinSkim *reads* - PE headers and load configs, so its own architecture is independent of the - binaries it analyses — an emulated x64 BinSkim checks ARM64 binaries perfectly - well. The RID list is ordered preference, so a future `win-arm64` build is - picked up with no further change. -- **`rsync` gets the x64 zip, and its `ssh.exe` is verified by running it.** The - release publishes x64 and x86 assets and no ARM64 one. x64 is still the right - pick — a transfer is bounded by the socket, not by emulated CPU, so lifting the - in-box client's 3 KB stdin cap wins by far more than emulation costs. But that - `ssh.exe` links against a `System32\libcrypto.dll` which on ARM64 is an **ARM64** - binary, so it may not load at all. That matters more than it sounds: - `rsync.exe` *prefers* an `ssh.exe` in its own directory, so a present-but-broken - one does not degrade to the in-box client — it breaks rsync outright, with an - error that names a dead remote shell rather than `ssh.exe`. The elevated half - therefore runs `ssh -V` after unpacking and **deletes** the binary if it will - not start, so `rsync` falls back to the native in-box client. The - `core.sshCommand` step in the non-elevated half already picked its candidate by - running it, and is unchanged. +- **OpenCppCoverage is no longer installed on ARM64.** Unlike the rest of the + emulated row this was a real ceiling rather than a slowdown: it collects + coverage by debugging the process under test and stepping x86/x64 + instructions, so it can cover cross-compiled x86/x64 binaries but **not** an + ARM64 one. A coverage tool that cannot cover this machine's own output belongs + on the x64 box, which is where it still installs. Run coverage there. +- **BinSkim is no longer installed on ARM64 either**, though the reason is + weaker: the NuGet package publishes `win-x64` only (its other RIDs are + `linux-x64`, `linux-arm64`, `osx-x64`), but BinSkim *reads* PE headers and load + configs, so its own architecture is independent of the binaries it analyses — + an emulated x64 BinSkim checks ARM64 binaries perfectly well. It went with the + rest of the x64-only set to keep the rule simple, not because it was broken. + The RID list is still ordered preference on x64, so a future `win-arm64` build + would be picked up with no further change. +- **`rsync` is native on ARM64 now**, and its `ssh.exe` is still verified by + running it. The release publishes an `arm64` asset as of `v3.5.0-gdeeda96f`; + both binaries in it are `0xAA64`, and the `ssh.exe` reports + `OpenSSH_for_Windows_10.0p2 ... LibreSSL 3.8.2` on this box. + + Before that asset existed, ARM64 took the x64 zip. The `rsync.exe` was fine — + a transfer is bounded by the socket, not by emulated CPU — but its `ssh.exe` + links against `System32\libcrypto.dll`, which on ARM64 is an **ARM64** binary + an x64 process cannot load. It died with `0xC0000135` + (`STATUS_DLL_NOT_FOUND`) and had to be deleted on every run. That matters more + than it sounds: `rsync.exe` *prefers* an `ssh.exe` in its own directory, so a + present-but-broken one does not degrade to the in-box client — it breaks rsync + outright, with an error naming a dead remote shell rather than `ssh.exe`. + + The elevated half still runs `ssh -V` after unpacking and **deletes** the + binary if it will not start. The check is kept because it is what found that + problem, and it still covers a missing or too-old `libcrypto.dll` on any + architecture. An x64 `rsync.exe` surviving on an ARM64 box now shows up in the + audit as emulation *without* a listed reason, with a remedy pointing at a + re-run. The `core.sshCommand` step in the non-elevated half already picked its + candidate by running it, and is unchanged. - **Intel VTune reports `n/a`** rather than printing a download link: there is no Windows-on-Arm build, and its value is reading Intel PMU counters. Use the Windows Performance Toolkit (`wpr`/`xperf` to collect, `wpa` to analyse), or Arm Performance Studio / Streamline for Arm PMU sampling. -- **NASM is still installed** (as emulated x64) because this box cross-compiles - x86/x64, where NASM is the assembler that builds them. If you only target - ARM64, nothing uses it — the ARM64 assembler is `armasm64.exe`, which ships - with MSVC. +- **NASM is not installed on ARM64.** It assembles x86/x86-64 only — there is no + ARM64 target in it and no ARM64 build of it — so it went into the x64-only set + with the rest. The ARM64 assembler is `armasm64.exe`, which ships with MSVC. + Its installer is also one of the two here that fall back to a per-user + directory it never registers (`%LOCALAPPDATA%\bin\NASM`), so `winget uninstall` + cannot find it afterwards; the `Uninstall.exe` in that directory is the way out. - **`vswhere` stays under the 32-bit Program Files** on ARM64 too (`%ProgramFiles(x86)%\Microsoft Visual Studio\Installer`), so that path is unchanged. The VS Installer is x86-registered there by contract even though the