]> vilimpoc.org git repositories - dotfiles/blame - README.md
setup-windows: Msys2 step - MSYS2 with MinGW-w64 GCC, per-user, no elevation
[dotfiles] / README.md
CommitLineData
3c096107
MV
1# dotfiles
2
3Windows development-box provisioning scripts.
4
5`setup-windows.bat` takes a fresh Windows install to a working C++ / native
649b04df 6development environment: editors and shells, Python, the Visual Studio
3c096107 7toolchain (including the Clang and Windows XP targeting toolsets), the Windows
0b412520 8Driver Kit, Rust (rustup, with the stable MSVC toolchain for the host as the
4776737b
MV
9default), Android Studio and a per-user Android SDK, Android's `adb`, and a
10handful of analysis tools (Sysinternals, OpenCppCoverage, BinSkim, the Windows
11Performance Toolkit). It also sets the box up to be driven remotely: OpenSSH
12Server plus an rsync build for Windows, which is what makes a throwaway VM
13reachable from a Linux host.
0b412520
MV
14
15**Nothing happens until you have seen the plan.** It first lists every step it
16can take on this machine and lets you choose which ones to run — see
17[Choosing what runs](#choosing-what-runs).
3c096107 18
7840e3c2 19Runs on **x64 and on ARM64** (Windows 11 on Arm), and installs *different things*
0b412520
MV
20on each: x64 is the full workstation, ARM64 a lean build box with a single
21C/C++ compiler. See
7840e3c2 22[Running on ARM64](#running-on-arm64) for what differs and why.
649b04df 23
3c096107
MV
24## Files
25
26| File | Purpose |
27| --- | --- |
0b412520 28| `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. |
325b494c 29| `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. |
0b412520 30| `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. |
4776737b 31| `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). |
325b494c 32| `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. |
0b412520 33| `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. |
541058e3 34| `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. |
3c096107
MV
35
36## Usage
37
2e281421
MV
381. **Edit `setup-windows-no-uac.ps1` first.** The global git identity near the
39 top of the file is empty:
3c096107 40
2e281421
MV
41 ```powershell
42 $GitUserName = '' # e.g. 'Ada Lovelace'
43 $GitUserEmail = '' # e.g. 'ada@example.com'
3c096107
MV
44 ```
45
2e281421
MV
46 Fill in your own name and email, or leave them empty to keep your identity
47 per-repository - the script skips `user.name` / `user.email` rather than
48 writing a placeholder, and says so. Everything else in that script is set
49 either way.
3c096107
MV
50
512. Run it from a normal (non-elevated) prompt:
52
53 ```bat
54 setup-windows.bat
55 ```
56
0b412520
MV
57 Before doing anything it prints the plan — every step it can take on this
58 machine, grouped and numbered — and waits:
59
60 ```text
61 Setup plan - nothing has been done yet
62 host: x64 - the full workstation
63 elevation: not elevated; expect a UAC prompt partway through, for the elevated half
64
65 U Per-user installs (winget, except Claude Code)
66 [x] 1 ClaudeCode Claude Code, native installer: irm https://claude.ai/install.ps1 | iex
67 [x] 2 Brave Brave.Brave
68 ...
69 E Elevated setup (setup-windows-with-uac.ps1) - one UAC prompt, runs in its own window
4776737b 70 [x] 25 OpenSshClient OpenSSH Client capability (ssh.exe, and the libcrypto.dll rsync's ssh.exe needs)
0b412520 71 ...
4776737b 72 [x] 29 VisualStudio Visual Studio 2022 Community, C++: MSVC for x86/x64 + ARM64, SDK 26100 (several GB)
0b412520
MV
73 ...
74
4776737b 75 44 of 44 steps selected.
0b412520
MV
76
77 Enter runs the selected steps. To change them, type step numbers or ranges
78 (3 5-7), step names, or a group letter (U M E C) to switch them on or off;
79 'all' or 'none' for everything; 'q' to quit without doing anything.
80 ```
81
82 Press Enter to run everything marked `[x]`, or switch steps off and on first;
83 the list is redrawn after each change. Steps that cannot run on this machine
84 are shown as `[-]` with the reason and have no number.
85
86 After Enter it runs unattended apart from a single UAC prompt for the
87 elevated half — none at all if you switched every elevated step off. Accept
88 it; declining leaves Visual Studio and the WDK uninstalled, and the script
89 says so.
3c096107
MV
90
913. Restart your shell afterwards so the updated user `PATH` is picked up, and
92 reboot if a step reported that a restart was required.
93
0b412520
MV
94## Choosing what runs
95
96Arguments to `setup-windows.bat` set the starting point of the plan; you can
97still adjust it before pressing Enter unless you also pass `-Yes`.
98
99| | |
100| --- | --- |
101| `setup-windows.bat -WhatIf` | Show the plan for this machine and stop. Nothing is changed. |
102| `setup-windows.bat -Skip VirtualBox,Brave` | Start with those switched off. |
103| `setup-windows.bat -Only E` | Start with only the elevated half. |
104| `setup-windows.bat -Yes` | Do not ask: run the plan as it stands. For unattended runs. |
105| `setup-windows.bat /?` | Usage. |
106
107- Steps are named as the plan shows them, comma-separated. A group letter works
108 anywhere a name does: `U` per-user installs, `M` machine-wide installs and the
109 WiX pin, `E` the elevated half, `C` per-user configuration and Pillow. A name
110 it does not recognise stops the run before anything happens — a mistyped
111 `-Skip` that was quietly ignored would install exactly what you meant to leave
112 out.
113- **With no console to ask on** — piped, scheduled, a non-interactive `ssh`
114 command — it prints the plan and stops with exit code 2 unless given `-Yes`.
115 It never assumes yes.
116- The two halves take `-Only`, `-Skip` and `-Yes` too, and when run on their own
117 print their part of the plan and ask before doing anything, e.g.
118 `.\setup-windows-with-uac.ps1 -Only VisualStudio,Wdk`. From the batch file
119 they are handed exactly what the plan left selected, with `-Yes`, since you
120 were asked already. With nothing selected for a half it is not started — so
121 no UAC prompt is raised for an elevated half with no steps.
122- Every step is declared once, in `setup-windows-steps.ps1`: its name, what it
325b494c
MV
123 does, and — with the reason — whether it is x64-only (`-X64Only`), kept off a
124 Windows that is itself a virtual machine (`-NotInVM`), or needs an
0b412520
MV
125 administrator (`-Admin`). The planner, the batch file and both halves read
126 that one list and check each other: a step the list does not know is refused
127 rather than run, and a selected step that nothing implements fails the run
128 rather than quietly not happening. Adding a step means a row there plus the
129 code that does it, gated on the same name.
130- Some steps build on others — the WiX pin needs the .NET SDK, the Clang and XP
131 toolsets need Visual Studio. The plan notes it when you switch the
132 prerequisite off, but does not stop you: it may well be installed already.
133
134## Re-running
135
136Every step checks before it acts, so running `setup-windows.bat` again on a
137provisioned box installs only what is missing or has a newer version out.
138Nothing is reinstalled at the version it already has. Where a check cannot
139decide — no network, a version string it does not recognise — it installs as
140before rather than skipping: a failed check must never be why something is left
141missing or out of date.
142
143| step | how it decides |
144| --- | --- |
145| 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. |
146| `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. |
147| `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. |
8ea42089 148| `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. |
0b412520 149| `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. |
4776737b 150| `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. |
0b412520
MV
151| `WiX` | Skipped when 5.0.2 is already in the .NET global tool store. |
152| `OpenSshClient`, `OpenSshServer` | Installed only when the capability is not. |
153| `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. |
154| `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. |
155| `Wdk`, `Wpt` | Skipped when the kit, or the toolkit, is already there. |
156| `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. |
157| `RustToolchain` | `rustup toolchain install` updates stable when there is a newer one and otherwise reports it unchanged. |
4776737b 158| `AndroidSdk` | The command-line tools are re-downloaded only when Google's package index has a newer revision than the installed one; `android sdk install` updates the platform, build-tools and platform-tools only when they are out of date. A re-run with nothing new takes seconds. |
0b412520
MV
159| `Pillow` | `pip install` leaves an installed Pillow alone. It does not upgrade it either. |
160| the rest | Configuration: `PATH` entries are added only when missing, and settings are written with the values they already have. |
161
3c096107
MV
162## Notes
163
164- The elevated half writes a transcript to `setup-windows-uac.log` next to the
165 script; the batch file prints it when the elevated window closes. The log is
166 gitignored, as it contains local paths.
2bbc9b41
MV
167- **git uses a Win32-OpenSSH client.** `setup-windows-no-uac.ps1` sets
168 `core.sshCommand`. Git for Windows otherwise prefers its own bundled MSYS2
169 `ssh.exe`, which cannot reach the Windows `ssh-agent` service that the elevated
170 half enables - Win32-OpenSSH publishes the agent on a named pipe the MSYS2
171 build does not speak. Without this, keys loaded with `ssh-add` from PowerShell
172 are invisible to `git`, and a push falls back to hunting for a key file and
173 prompting for its passphrase. The value uses forward slashes on purpose: git
174 parses `core.sshCommand` with shell quoting rules, in which a backslash is an
175 escape character.
176- **Which `ssh.exe` git gets.** `C:\Tools\rsync\ssh.exe` — the fast build the
177 elevated half unpacks — if it is there, `%WINDIR%/System32/OpenSSH/ssh.exe`
178 otherwise. It is the same client with the same `~/.ssh`, agent and
179 `known_hosts`; the difference is the stdin pump, and without it anything git
180 *pushes* is capped at ~17 MB/s. Candidates are tried by **running** them
181 (`ssh -V`), not by `Test-Path`: the fast build needs a `libcrypto.dll` that an
182 image without the OpenSSH Client capability does not have, and a client that
183 will not start should be found here rather than on the next `git push`.
184 This is also why `setup-windows.bat` now runs the elevated half **first** — the
185 fast `ssh.exe` has to exist before the git config step can prefer it. The
186 non-elevated half still runs even when the elevated one failed; it just falls
187 back. Re-run `setup-windows-no-uac.ps1` on its own at any time to re-pick.
0b412520
MV
188- All the scripts are safe to re-run: nothing is reinstalled at the version it
189 already has. See [Re-running](#re-running) for how each step decides.
2e281421
MV
190- `setup-windows-no-uac.ps1` runs its steps independently: one failing warns and
191 the rest still run, and it exits 1 if any did. The `.bat` reports that and
0b412520
MV
192 carries on. Use `-Only` / `-Skip` to re-run a subset, e.g.
193 `.\setup-windows-no-uac.ps1 -Skip BinSkim`.
2e281421
MV
194 Run it **non-elevated**: it writes per-user state (the `HKCU` `PATH`, the
195 `.gitconfig` under `%USERPROFILE%`), so an elevated run would configure the
196 administrator's profile instead. It warns if you do.
452f525c
MV
197- **Remote access.** OpenSSH Server is installed from the Windows on-demand
198 capability (10/1809+), set to start automatically, and given an inbound TCP 22
199 firewall rule on *all* profiles — a VM's host-only or bridged adapter is
200 routinely classified Public, which is the usual reason a running `sshd` is
201 unreachable. Windows ships no `rsync`, so a build of it
202 ([nuket/rsync-windows](https://github.com/nuket/rsync-windows)) is installed to
203 `C:\Tools\rsync` and added to the **machine** `PATH`. That last detail matters:
204 the remote end of an `rsync` runs non-interactively, with no login shell, and
205 Win32-OpenSSH builds that environment from the registry `PATH` rather than from
206 a profile. Key auth needs `~/.ssh/authorized_keys` ACL'd to just you and
207 `SYSTEM`; accounts in the Administrators group use
208 `C:\ProgramData\ssh\administrators_authorized_keys` instead.
b8144088 209- **rsync brings its own `ssh.exe`.** That release ships as one zip per
f51da37a
MV
210 architecture — `rsync-windows-x86.zip`, `-x64.zip` and `-arm64.zip`, each
211 holding `rsync.exe`, an `ssh.exe`, `COPYING.txt` and `NOTICE-ssh.txt` — and the
212 elevated half picks the zip matching the host, verifies it against the
213 published `.sha256`, unpacks the pair together, then **runs** the unpacked
214 `ssh.exe` and removes it if it will not start. Together is the point:
b8144088
MV
215 `rsync.exe` prefers an `ssh.exe` sitting in its own directory, and the release
216 builds one because the client Windows ships reads its stdin 3 KB at a time,
217 which holds a transfer *from* the box at ~17 MB/s however fast the link is.
218 Nothing else about it differs — same `~/.ssh`, same `ssh-agent`, same
219 `known_hosts` — and a bare `ssh` still resolves to the in-box client, which
220 sits ahead of `C:\Tools\rsync` on the `PATH`. It links against the
221 `libcrypto.dll` the **OpenSSH Client** capability puts in `System32` (Windows'
222 own LibreSSL, which uses AES-NI) and ships no copy of its own, so the elevated
223 half installs that capability first and falls back to `rsync.exe` alone,
224 warning, on an image that will not offer it.
225- The `rsync` download follows the `releases/latest/download/` redirect rather
226 than the GitHub API: unauthenticated API calls are rate-limited to 60/hour per
227 IP, which a provisioning run behind a shared NAT can genuinely exhaust. To hold
228 a box on a known build, pin the tag in `$RsyncUrl`
229 (`.../releases/download/<tag>/<asset>`) instead.
3c096107 230- Visual Studio is installed in three labelled passes (base workload, Clang/LLVM,
7840e3c2
MV
231 XP toolset) so a failure identifies which component group is responsible. On
232 ARM64 only the first runs — the other two are skipped there, each printing why.
233 The last two are optional passes: a failure there warns and provisioning continues,
649b04df
MV
234 because neither is needed to build with MSVC and losing the whole toolchain
235 over a component you can add later from the installer UI is the worse outcome.
236- **Which Visual Studio generation.** `$VsChannel` near the VS step picks it —
237 `17` for VS 2022, `18` for VS 2026 — and `$VsEdition` picks Community /
238 Professional / Enterprise. It is **architecture-split by default**: 17 on x64,
239 where the `v141`/XP toolset in the component list is the reason to pin a
240 generation, and 18 on ARM64, where that toolset cannot exist anyway (no
241 ARM64-hosted 14.16 compiler), so nothing holds Arm back to the older one.
242- **The aka.ms path differs per generation** — VS 2022 is published under
243 `/release/`, VS 2026 under `/stable/`, which `$VsChannelPath` derives. This is
244 not cosmetic: `https://aka.ms/vs/18/release/vs_community.exe` is *not* a 404,
245 it silently redirects to Bing and returns **200 with an HTML body**. A wrong
246 guess there downloads a web page, names it `vs_community.exe`, and fails later
247 at `Start-Process` with something that looks nothing like a bad URL. The step
248 therefore checks the downloaded file starts with an `MZ` header and throws with
249 the URL if it does not.
250- `Get-VsInstallPath` scopes its `vswhere` query to that generation
251 (`-version "[17.0,18.0)"`). That scoping is load-bearing: a bare
252 `vswhere -products *` returns **every** Visual Studio on the box, newest
253 first, and on a machine that already has VS 2026 the old unscoped lookup handed
254 an 18.x install path to a 17.x bootstrapper as `modify --installPath`, so every
255 pass targeted the wrong product. The step now also lists any other generations
256 it found and states that it left them alone — when none of them matches
257 `$VsChannel` the script is about to fetch a second, largely redundant
258 toolchain, and that should be a visible decision. On ARM64, where `$VsChannel`
259 is 18, an existing VS 2026 **is** the match and gets modified in place rather
260 than duplicated.
99725a30
MV
261- **Windows Performance Analyzer is not part of Visual Studio.** VS has its own
262 Performance Profiler, which is a different, `.diagsession`-based tool and
263 cannot open an `.etl`. WPA ships with `xperf` and `wpr` in the Windows
264 Performance Toolkit, which exists in exactly two places: as an optional
265 *feature* of the Windows SDK (`OptionId.WindowsPerformanceToolkit`) and inside
266 the Windows ADK, which bundles the same toolkit. Whether the SDK install that
267 Visual Studio performs selects that feature varies by version, so the elevated
268 half **detects first** — `%ProgramFiles(x86)%\Windows Kits\10\Windows
269 Performance Toolkit`, its 64-bit twin, and the ADK location — and only falls
270 back to `winget install Microsoft.WindowsADK` when nothing is there. It then
271 re-asserts that directory on the machine `PATH` (the toolkit's own installer
272 usually does this, and the Start Menu gets *Windows Kits > Windows Performance
273 Toolkit* shortcuts for WPA and WPR). To install just the toolkit instead of the
274 whole ADK, run the standalone SDK setup with
275 `winsdksetup.exe /features OptionId.WindowsPerformanceToolkit /q`. A newer WPA
276 also exists in the Microsoft Store (`winget install --id 9N0W1B2BXGNZ --source
277 msstore`); it is not installed here because the Store source needs an
278 interactive, signed-in session, which the unattended elevated half does not
279 have.
1e91b38f
MV
280- **Intel VTune Profiler is reported, not installed.** The elevated half prints
281 whether it is on the box, its version, and the path to `vtune.exe`; if it is
282 missing it prints the download page instead (and says so if the CPU is not
283 Intel). Automating the install is not worth it here: the offline installer is a
284 ~750 MB download from a URL carrying a per-release GUID with no "latest"
285 redirect behind it, so every new build would mean editing a hard-coded link,
286 and it is only worth having on Intel silicon since hardware event-based
287 sampling reads Intel PMU counters. It does install unattended if you want it
288 scripted elsewhere:
289
290 ```powershell
291 intel-vtune-<version>_offline.exe -a --silent --cli --eula accept
292 ```
293
95307dbb
MV
294 **Run it from an administrator account, elevated.** Hardware event-based
295 sampling (`uarch-exploration`, `memory-access`, `hotspots -knob
296 sampling-mode=hw`) requires it, and VTune warns about that at the top of every
297 unelevated run. Worth knowing that the failure it gives there is *"cannot
5c01c42c
MV
298 recognize the processor"*, which reads like a hardware problem and is not one:
299 the drivers (`sepdrv5`, `sepdal`, `vtss`) are installed and running, and VTune
95307dbb
MV
300 identifies the PMU through them. There is no group to join to get around it —
301 the Linux driver can be handed to a `vtune` group, but on Windows the
5c01c42c
MV
302 documented answer is to run as administrator.
303
95307dbb
MV
304- **Collect traces from an elevated Administrator session. Non-elevated
305 collection was tried here and abandoned.** The attempt was to put one ordinary
306 account into `BUILTIN\Performance Log Users`, which appears in the default
307 security descriptors ETW keeps per provider GUID under
308 `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security`, and collect without a UAC
309 prompt. It does not survive contact with the real workflow: `xperf -on base`
310 and `wpr -start` drive the *NT Kernel Logger*, reserved for Administrators and
311 LocalSystem, and granting the group `SeSystemProfilePrivilege` ("Profile system
312 performance") plus an explicit ACE for `TRACELOG_ACCESS_KERNEL_LOGGER` on
313 `SystemTraceControlGuid` — all three in place, across a reboot — still answered
99725a30
MV
314
315 ```text
95307dbb 316 xperf: error: NT Kernel Logger: Access is denied. (0x5).
99725a30
MV
317 ```
318
95307dbb
MV
319 It is not a check an ACE overrides, and the same wall turned up often enough
320 elsewhere that the whole approach was dropped rather than carried as a
321 half-working path. **Sign in to an administrator account and run `xperf`, `wpr`
322 and VTune from an elevated prompt.** Analysis is the exception and never needed
323 any of this: `wpa.exe` opens an existing `.etl` as a plain user.
324
325 If an earlier revision of these scripts ran on a box, it left that account in
326 the group. Take it back out with:
0aec6243
MV
327
328 ```powershell
95307dbb 329 net localgroup "Performance Log Users" DOMAIN\user /delete
0aec6243 330 ```
593189b7 331
95307dbb
MV
332 Two revisions also granted the privilege and the ACE. Undo the privilege in
333 `secpol.msc` > Local Policies > User Rights Assignment > "Profile system
334 performance" by removing Performance Log Users. The ACEs sit in the
335 `{9e814aad-3204-11d2-9a82-006008a86939}` value under
593189b7
MV
336 `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Security`: strip the `LU` entries
337 from that descriptor rather than deleting the value, which also carries entries
338 for SYSTEM, Administrators and two service accounts.
95307dbb 339
0b412520
MV
340- **Claude Code is not installed through winget.** Recent releases of
341 `Anthropic.ClaudeCode` are broken through that route, so the batch file runs
342 Anthropic's own installer, `irm https://claude.ai/install.ps1 | iex`: it
343 checks the download against the release manifest's SHA-256 and puts `claude`
325b494c
MV
344 in `%USERPROFILE%\.local\bin`, per-user. The `LocalBinPath` step in
345 `setup-windows-no-uac.ps1` makes sure that directory is on the user `PATH`
346 (creating it if need be); the installer itself only prints a reminder. Two
347 things are fixed up around it.
0b412520
MV
348 The script picks its ARM64 or x64 build from `$env:PROCESSOR_ARCHITECTURE`
349 alone, which reads `AMD64` in an emulated x64 shell on ARM64 (see
350 [Detecting the architecture](#detecting-the-architecture)), so the batch file
351 sets that variable from the registry's answer for the installer's process.
352 And the script refuses a 32-bit PowerShell, which is what `powershell` is from a
353 32-bit `cmd.exe`, so it is run through `%SystemRoot%\Sysnative` when that
354 exists. A copy left over from the winget route can shadow the new one on the
355 `PATH`; the step warns if it finds one (`winget uninstall
356 Anthropic.ClaudeCode` removes it), and the architecture audit shows which
357 `claude.exe` wins.
dd9ba4de
MV
358- **Windows Terminal gets its framework package registered first.** Terminal is
359 an MSIX that depends on `Microsoft.UI.Xaml.2.8`, which winget installs for it
360 from a pinned build (8.2310.30001.0). When a *newer* build is already on the
361 machine but not registered for the account running the setup, Windows
362 declines to stage the older one, winget then tries to register a package
363 that is not there, and what it prints is `Installer failed with exit code:
364 0x80070002 : The system cannot find the file specified` — with Terminal
365 itself never attempted, on every run. So the batch file first registers the
366 build that is already there (`Add-AppxPackage -RegisterByFamilyName`, no
367 elevation needed), and winget then finds the dependency met. With none on
368 the machine it does nothing, and winget installs the framework as usual.
0b412520
MV
369- **Rust.** winget installs rustup (`Rustlang.Rustup`, per-user, under
370 `%USERPROFILE%\.cargo` and `.rustup`). Its silent install is
371 `rustup-init -y -q`, which already sets up a stable toolchain for whatever
372 host rustup detects. The `RustToolchain` step in `setup-windows-no-uac.ps1`
373 then pins the answer: `stable-x86_64-pc-windows-msvc` on x64,
374 `stable-aarch64-pc-windows-msvc` on ARM64, installed, made the default, and set
375 as rustup's default host. That matters on ARM64, where an x64 rustup —
376 an older install, or one started from an emulated shell — would otherwise
377 quietly set up an emulated compiler. The `-msvc` toolchains link with the
378 Visual Studio the elevated half installs, but only need it to *build*:
379 installing rustup first is fine, and with `-y` rustup-init just warns that
380 the prerequisites are missing rather than installing a Visual Studio of its
381 own. Re-running the step updates stable. It also resets the default to
382 stable, so leave `RustToolchain` out of the plan if you have chosen another
383 default on purpose. The architecture audit reports the compiler *inside* the
384 toolchain: `.cargo\bin\rustc.exe` is a rustup proxy, so its architecture is
385 rustup's, not the compiler's.
386- **`adb` and `fastboot`** come from `Google.PlatformTools`, a portable zip that
387 winget unpacks under `%LOCALAPPDATA%\Microsoft\WinGet\Packages` and puts on the
388 user `PATH` (`adb.exe` needs the `AdbWinApi` DLLs beside it). The manifest
389 calls it x64, but every binary in it is 32-bit **x86** — checked on r37.0.1 —
390 and there is no ARM64 build. It runs under WOW64 on x64 and under x86
391 emulation on ARM64, which every Windows on Arm has; the audit lists it as
392 expected. It stays on ARM64 for the same reason `iperf3` does: it is bound by
393 the link to the device, not by the CPU.
8ea42089
MV
394- **MSYS2 and MinGW-w64 GCC** (`Msys2`) go into `%LOCALAPPDATA%\msys64`, per-user,
395 with no elevation: the MSYS2 base self-extracting archive unpacks wherever it
396 is pointed, whereas the winget package runs the installer machine-wide into
397 `C:\msys64`. Two environments get GCC: `ucrt64` (x86_64, links the UCRT) and
398 `mingw32` (i686, links `msvcrt.dll`, the one that can still target Windows XP).
399 These are the same toolchains GitHub's `windows-latest` runner carries, so a
400 build that works here works there. Nothing is added to the `PATH`: the
401 environments' `bin` directories hold same-named `gcc`, `ld` and `windres`, so a
402 project names the one it wants by full path, e.g.
403 `%LOCALAPPDATA%\msys64\mingw32\bin`.
4776737b
MV
404- **Android Studio and the Android SDK** are separate steps. Studio
405 (`Google.AndroidStudio`) is machine-wide — its installer elevates itself — and
406 is installed only when missing, since it updates itself. The SDK is per-user,
407 in Studio's default `%LOCALAPPDATA%\Android\Sdk` (or wherever `ANDROID_HOME`
408 already points): the command-line tools, platform-tools, and the newest
409 stable platform and build-tools from Google's package index, read at run
410 time. That is what Studio's first-run wizard would set up, and it is enough
411 to build: Gradle fetches whatever else a project asks for. The step sets
412 `ANDROID_HOME`, puts the command-line tools on the user `PATH`, and points
413 `JAVA_HOME` at Studio's bundled JDK for a command-line `gradlew` when there is
414 no `JAVA_HOME` already. The SDK licences are accepted on your behalf — the
415 plan says so before anything runs. Both are x64-only: Studio has no ARM64
416 build, and the SDK's Windows build tools are x86/x64. Three things about
417 the current command-line tools (23.0), found when this step was written:
418 - `sdkmanager` is deprecated. It is now a shim over the new **Android CLI**
419 (`android.exe`), which on first use installs itself into
420 `%USERPROFILE%\.android` — about 90 MB in `bin\`, and a `cli\` folder with
421 its own Java runtime. It also keeps a spool of **usage metrics** there and
422 uploads them; no opt-out was found. The step calls the CLI directly.
423 - **CLI 1.0.16406183 crashes on the way out of every `sdk` command**
424 (`0xC0000409`) on this Windows 10, after the work is done. So the step does
425 not trust its exit code: each package must be present afterwards, and a
426 crash with everything in place is printed as a note, not a failure.
427 - Windows PowerShell 5.1's progress bar made the 148 MB command-line tools
428 download take about ten minutes; with it off, the whole first install took
429 under a minute. The BinSkim download now skips the progress bar for the
430 same reason.
3c096107
MV
431- The scripts were extracted from a native Windows project, so the component
432 selection is tuned for that: Spectre-mitigated runtimes, the v141/XP toolset,
433 and driver-kit headers. Trim the component lists in the `.ps1` if you don't
434 need them — each group is a plain array near the top.
649b04df
MV
435
436## Running on ARM64
437
438All three scripts detect the host architecture and adapt. Nothing needs a flag —
7840e3c2 439run `setup-windows.bat` exactly as on x64.
649b04df 440
7840e3c2 441**The two architectures deliberately install different things.** x64 is the full
0b412520
MV
442workstation; ARM64 is provisioned as a lean build box with a single C/C++
443compiler. See
7840e3c2
MV
444[ARM64 installs less, on purpose](#arm64-installs-less-on-purpose) below for the
445list and the reasoning.
446
447### Detecting the architecture
448
449All three scripts read the **machine-level registry value**, not the environment
450and not `RuntimeInformation.OSArchitecture`. Both of those report the *emulated*
451architecture inside an emulated x64 process — which is what you get by launching
452`powershell` or `cmd` from an x64 shell (Git Bash, for one) on an ARM64 box.
453Measured on this machine, from that emulated shell:
454
455| source | reports |
456| --- | --- |
457| `[RuntimeInformation]::OSArchitecture` | `X64` ✗ |
458| `%PROCESSOR_ARCHITECTURE%` | `AMD64` ✗ |
459| `HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment` | `ARM64` ✓ |
460
461`OSArchitecture` is documented as the *OS's* architecture, and on .NET Core it
462is; under .NET Framework on Prism it is not. It is kept only as a fallback. This
463used to be a cosmetic mislabel in a log line — now that the architecture decides
464which packages are installed at all, reading it wrong provisions the wrong
465machine.
466
467> The batch file calls `reg.exe` by full path and parses it **without** piping to
468> `find`/`findstr`. Both are shadowable by anything earlier on the `PATH`, and a
469> POSIX `find` turns the whole detection into a silent no-op that leaves the
470> emulated answer standing. That is not hypothetical; it is how this was found.
471
472**The build toolchain is fully native.** Visual Studio's ARM64 installer and MSVC
473are ARM64 binaries, and MSVC cross-compiles every target from an ARM64 host with
474no emulation in the compiler:
649b04df
MV
475
476```bat
477cmake -B build -A ARM64 && cmake --build build --config Release :: native
478cmake -B build -A x64 && cmake --build build --config Release :: cross
479cmake -B build -A Win32 && cmake --build build --config Release :: cross
480```
481
482**ARM64 uses Visual Studio 2026** (`$VsChannel = 18`) while x64 stays on VS 2022.
483Nothing forces that split — the Arm box simply has no reason to stay on the older
484generation, since the `v141`/XP toolset that pins x64 there cannot run on Arm at
485all — so Arm takes the newer MSVC. Note this is a *default*, not a limitation:
486VS 2026 does still offer `v141` and `WinXP` as components on x64.
487
488The base component group now names `VC.Tools.x86.x64` **and** `VC.Tools.ARM64`
489explicitly instead of relying on `--includeRecommended`, which resolves
490differently per host: on an ARM64 machine the workload's recommended set is the
491ARM64-targeting toolchain, and the x64 cross-compiler is *not* implied. Spectre
492runtimes and ATL are likewise requested for both target families
493(`VC.ATL.ARM64.Spectre` is a separate component from `VC.ATL.Spectre`).
494
7840e3c2
MV
495### ARM64 installs less, on purpose
496
497The ARM64 machine here is a constrained VM, and nearly everything it was
498installing without a native build was going to run under Prism emulation anyway.
499The exceptions were individually defensible and collectively a VM full of x64
500binaries. So the split is now explicit: **ARM64 gets MSVC and nothing that
501duplicates it or that exists only as an x64 binary.**
502
503| dropped on ARM64 | where | why |
504| --- | --- | --- |
505| `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 |
506| `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 |
507| `LLVM.LLVM` | `setup-windows.bat` | second Clang, dropped along with VS's `clang-cl` |
508| `NASM.NASM` | `setup-windows.bat` | x86/x86-64 assembler with no ARM64 target; MSVC's `armasm64.exe` is the native one |
8ea42089 509| 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 |
7840e3c2
MV
510| `Google.AndroidGPUInspector` | `setup-windows.bat` | x64-only, and it profiles Android devices rather than anything compiled here |
511| `OpenCppCoverage` | `setup-windows.bat` | collects coverage by stepping x86/x64 instructions, so it cannot instrument an ARM64 binary at all |
512| VS Clang/LLVM component | `setup-windows-with-uac.ps1` | see below |
513| Windows Driver Kit | `setup-windows-with-uac.ps1` | see below |
514| BinSkim | `setup-windows-no-uac.ps1` | the NuGet package publishes `win-x64` only |
4776737b
MV
515| Android Studio (`Google.AndroidStudio`) | `setup-windows.bat` | Google publishes no Windows ARM64 build |
516| Android SDK | `setup-windows-no-uac.ps1` | its Windows build tools are x86/x64 only, as is Android Studio |
7840e3c2 517
0b412520
MV
518x64 keeps every one of them. The decision is made in one place,
519`setup-windows-steps.ps1` (`-X64Only`, with the reason): the plan shows each as
520`[-]` with that reason, and the run log repeats it where the step would have
521run, rather than leaving a silent gap.
7840e3c2
MV
522
523**`clang-cl` is dropped by choice, not by limitation.** A native ARM64 `clang-cl`
524*is* offered on Arm and did work here before the split. Two things establish
525that: the `VC.Llvm.Clang` VSIX is `productArch=neutral` with no `chip` or
649b04df
MV
526`machineArch` restriction, so the Arm installer offers it; and MSVC's
527`VC\Tools\Llvm` tree is partitioned by **host** architecture, with real ARM64
7840e3c2 528binaries in `ARM64\bin`, which is where `clang-cl.exe` lands:
649b04df
MV
529
530| directory | host |
531| --- | --- |
532| `VC\Tools\Llvm\bin` | x86 |
533| `VC\Tools\Llvm\x64\bin` | x64 |
534| `VC\Tools\Llvm\ARM64\bin` | ARM64 |
535
649b04df
MV
536> Watch for a false positive when checking by hand: `VC\Tools\Llvm\*\bin` holds
537> `clang-format.exe` and `clang-tidy.exe` on **any** host, installed or not —
538> those ship with the NativeDesktop workload. Their presence does not mean the
7840e3c2
MV
539> compiler is there; look for `clang-cl.exe`.
540
541Note that the VS installer never uninstalls a component you simply stopped
542asking for, so a box provisioned before this split keeps its `clang-cl` until it
543is explicitly removed. The verification step says so and prints the
544`--remove` line.
545
546**The WDK is dropped for two reasons, and the second one bites on x64 too.** The
547ARM64 box builds user-mode software, so it does not need a driver kit — but this
548particular WDK *cannot* install here regardless. The kit requires the **matching
549Windows SDK revision**, and revisions are not side-by-side: `Include\10.0.26100.0`
550is one directory, whichever revision wrote it. VS 2026 installs SDK
551**26100.7705**; `linkid=2335869` is WDK **26100.6584**, the kit paired with VS
5522022. `wdksetup.exe /quiet` therefore aborts:
553
554```
555WDK installer exit code: 15605
556WER: WDK / 10.1.26100.6584 / Install / 0x80070642
557```
558
559`0x642` is `1602`, `ERROR_INSTALL_USEREXIT` — a silent install cancelling itself
560on a failed prerequisite check. Nothing to do with Arm or emulation; the same
561pairing fails on x64. If a driver ever does need building against VS 2026, use
562the kit Microsoft's [supported WDK downloads](https://learn.microsoft.com/en-us/windows-hardware/drivers/other-wdk-downloads)
563table pairs with it — the 28000.x one — not this link. x64 with VS 2022 has the
564matched pair, so it installs there.
565
566**Compiler diversity now lives on the x64 box**, which has room for three
567toolchains: MSVC, VS's `clang-cl`, and **upstream LLVM** (`winget install
568LLVM.LLVM`). Upstream runs on its own schedule, is usually several major versions
569ahead of the Clang VS bundles, and installs to `C:\Program Files\LLVM` rather
570than inside the VS tree, so the two are genuinely independent. Two Clang majors
571plus MSVC over the same sources is what catches the bugs a single toolchain
572agrees with itself about. Watch the `PATH` there: a bare `clang-cl` may resolve
573to upstream while CMake's `-T ClangCL` keeps using VS's, so pass a full
574`-DCMAKE_CXX_COMPILER` when it matters.
649b04df 575
0b412520
MV
576**Rust is on both boxes, and native on ARM64.** It is a different language
577rather than a second C/C++ compiler, so it does not bend the one-compiler rule,
578and its `-msvc` toolchain links through MSVC's own `link.exe`. The ARM64 box
579gets `stable-aarch64-pc-windows-msvc`; to build x64 Rust code from there, add
580the x64 *target* (`rustup target add x86_64-pc-windows-msvc`) rather than an x64
581host toolchain, which would run emulated.
582
7840e3c2 583What you end up with on ARM64:
649b04df
MV
584
585| | On ARM64 |
586| --- | --- |
0b412520
MV
587| **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. |
588| **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). |
649b04df 589| **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). |
4776737b
MV
590| **Not installed** | Brave, WinMerge, upstream LLVM, NASM, Android GPU Inspector, OpenCppCoverage, BinSkim, VS `clang-cl`, the WDK, and the Android SDK — see the table above. |
591| **Unavailable** | VirtualBox, the Windows 7 x86 test VM, Intel VTune, Android Studio, and the `v141` / Windows XP targeting toolset. |
649b04df
MV
592
593### The architecture audit
594
595`setup-windows-no-uac.ps1` ends with an **`ArchAudit`** step that reads the PE
596COFF header of every tool the box provisions and reports what you would actually
597run, resolved PATH-first. It reads the file itself rather than trusting package
598metadata — a multi-architecture package (Sysinternals) ships every build in one
599zip under different names, and an installer's metadata says nothing about which
600binary landed. Purely informational; it never fails the run. Skip it with
601`-Skip ArchAudit`.
602
603Anything emulated with a listed reason prints as expected. Anything emulated
604*without* one is called out, with a remedy where a native build exists — which is
605how the `ninja` problem below was found.
606
cd774a19
MV
607### Elevation, and the case where there is no prompt
608
609`setup-windows.bat` works out **once, at the top**, whether this account can
610elevate, and reports it as `Elevation: ALREADY | PROMPT | NOLUA`:
611
612| | |
613| --- | --- |
614| `ALREADY` | Already elevated. The elevated half runs **directly** — no point asking to elevate what already is. |
615| `PROMPT` | Not elevated, UAC on. Elevation is requested: an administrator gets a consent prompt, a standard user gets an over-the-shoulder **credential** prompt. |
616| `NOLUA` | UAC is off machine-wide (`EnableLUA = 0`) **and** this is not an administrator. Elevation is *impossible*, not merely declined. |
617
618`IsInRole(Administrator)` is false for an admin running unelevated under UAC,
619which is what makes `ALREADY` mean *actually elevated* rather than *could be*.
620
621**Why `NOLUA` needs detecting rather than attempting.** `Start-Process -Verb
622RunAs` is not honest about it. With UAC disabled the `RunAs` verb is **silently
623ignored**: no prompt appears, the child starts with the caller's own unelevated
624token, and `Start-Process` reports **exit code 0** as though it had worked. The
625elevated script then refuses itself on its `#Requires -RunAsAdministrator` line,
626exits 1, and never reaches its `Start-Transcript` — so there is no log either.
627Read naively that is indistinguishable from a cancelled UAC prompt, which is
628exactly what this script used to report, sending you to look for a prompt that
629could never have appeared.
630
631Measured on such a box: `Start-Process -Verb RunAs` returned exit 0 while the
632child reported itself as the *unelevated* calling user.
633
634**What is skipped when elevation is impossible.** The elevated half, plus four
635packages that sit in the "per-user" section but are machine-wide installers.
636They used to fail on every run with opaque codes — `exit 5`
637(`ERROR_ACCESS_DENIED`) for the .NET SDK, MSI `1603` for CMake and Android GPU
638Inspector, `exit 1` for OpenCppCoverage, whose installer self-elevates. They are
0b412520
MV
639now shown as unavailable in the plan, with the reason, and skipped with one
640clear line each. None blocks a build: the .NET SDK is
cd774a19
MV
641only there for the WiX MSI tooling, CMake also ships inside Visual Studio, AGI
642profiles Android devices, and OpenCppCoverage cannot instrument ARM64 binaries
643anyway.
644
645A `NOLUA` run is a **success** (exit 0), not a failure — the per-user half did
646its job. To finish the box, run it from an administrator account, then re-run
647`setup-windows-no-uac.ps1` as yourself so the per-user `PATH` and `.gitconfig`
648land in *your* profile rather than the administrator's.
fbe899b6
MV
649
650Two consequences worth knowing rather than rediscovering:
651
652- **LLVM does not fail — it relocates.** Its NSIS installer targets
653 `%ProgramFiles%\LLVM`, and when it cannot write there it silently falls back to
654 a per-user directory (observed: `%USERPROFILE%\Documents\LLVM`) while winget
655 still reports *Successfully installed*. The compiler is genuinely there and
656 native; nothing can find it. The `LlvmPath` step looks in that fallback, puts
657 the directory on the user `PATH`, and says so.
658- **WinMerge silently downgrades to x64.** Upstream publishes ARM64 as a
659 **machine-scope** installer and x64 as a **per-user** one, so an unelevated
660 winget lets its scope preference beat its architecture preference. The batch
661 now asks for `--architecture arm64` first and falls back, so an admin run gets
662 the native build and a standard-user run still gets a working one.
663
649b04df
MV
664### Two gotchas the audit surfaces
665
3e0aa9fc
MV
666- **Visual Studio bundles an x64 `ninja.exe` even on ARM64.** It matters more
667 than a one-off tool would, because ninja is re-invoked for every edge in the
668 build graph — it is the one place emulation is paid over and over rather than
669 once. `winget install Ninja-build.Ninja` gets the native `ninja-winarm64`
670 build, and the `NinjaPath` step pins its directory to the **front** of the user
671 `PATH`. VS's bundled `cmake.exe` *is* ARM64, so only ninja has this problem.
672
673 On which copy wins inside a Developer Command Prompt: the native one. VS adds
674 its ninja from `Common7\Tools\vsdevcmd\ext\cmake.bat`, which does
675 `set "PATH=%PATH%;...\CMake\bin;...\CMake\Ninja"` — an **append**, so the VS
676 directories land at the very end of the composed `PATH`, behind every machine
677 and user entry. A native ninja anywhere on the user `PATH` already beats it,
678 measured on an ARM64 box. The `NinjaPath` step is therefore insurance rather
679 than the load-bearing fix: winget *appends* its package directory to the user
680 `PATH`, so without it the margin rests on two append orders staying as they
681 are, one of them in a file Microsoft owns and revises.
649b04df
MV
682- **Sysinternals ships every architecture in one zip**, and the ARM64 build is
683 not the default-named exe. `ProcessExplorer.zip` contains `procexp.exe` (x86),
684 `procexp64.exe` (x64) and `procexp64a.exe` (**ARM64**) — so run the `*64a.exe`
685 variants; plain `procexp.exe` runs emulated. (`Microsoft.Sysinternals.Suite`
686 has a dedicated `SysinternalsSuite-ARM64.zip` if you prefer the whole set.)
687
688- **x64 emulation is checked, not assumed.** Everything in the "emulated" row
689 needs Prism, which is present on Windows 11 on Arm but absent on Windows 10 on
690 Arm (x86-only there) and on some Server images. The elevated half tests for
691 `System32\xtajit64.dll` up front and warns, rather than letting the failure
692 surface much later as a binary that will not start.
693- **VirtualBox is skipped.** There is no ARM64 Windows host build, and the x64
694 one cannot be rescued by emulation: a hypervisor is kernel-mode, and its x64
695 driver will not load on an ARM64 kernel. This is also what makes
696 `setup-windows-7-test-env.bat` unreachable from an ARM64 host — it prepares a
697 Windows 7 *x86* guest, which neither VirtualBox nor Hyper-V on ARM64 can run.
698 (That script itself is unchanged; it runs inside the guest, so it never sees
699 the host architecture.)
325b494c
MV
700- **VirtualBox is also skipped inside a virtual machine**, on any architecture.
701 The planner reads the firmware's SMBIOS manufacturer and model
702 (`Win32_ComputerSystem`) and recognises VirtualBox, VMware, Hyper-V (and
703 Azure), QEMU/KVM, Parallels, Xen, EC2 and GCE; the plan's `machine:` line
704 names what it found. It does not use `HypervisorPresent`, which is also true
705 on physical hardware running Hyper-V, WSL2 or VBS. The test VMs belong on the
706 physical host.
649b04df
MV
707- **The `v141` / Windows XP toolset is skipped**, and the verification step says
708 `n/a` instead of warning about it. The components are still *listed* in the
709 catalog on Arm, so this is a deliberate skip rather than a hard unavailability
710 — but the 14.16 toolset predates Windows on Arm as a host and ships
711 `HostX86`/`HostX64` compilers only, so the best you could get is an
712 x86-emulated compiler, and XP targeting is not supported from an Arm host.
713 Nothing is lost that this box could have used:
714 Windows XP never ran on ARM64, so the only point of an XP-targeting build here
715 would be producing x86 binaries, which the current toolset does natively via
716 `-A Win32`.
7840e3c2
MV
717- **OpenCppCoverage is no longer installed on ARM64.** Unlike the rest of the
718 emulated row this was a real ceiling rather than a slowdown: it collects
719 coverage by debugging the process under test and stepping x86/x64
720 instructions, so it can cover cross-compiled x86/x64 binaries but **not** an
721 ARM64 one. A coverage tool that cannot cover this machine's own output belongs
722 on the x64 box, which is where it still installs. Run coverage there.
723- **BinSkim is no longer installed on ARM64 either**, though the reason is
724 weaker: the NuGet package publishes `win-x64` only (its other RIDs are
725 `linux-x64`, `linux-arm64`, `osx-x64`), but BinSkim *reads* PE headers and load
726 configs, so its own architecture is independent of the binaries it analyses —
727 an emulated x64 BinSkim checks ARM64 binaries perfectly well. It went with the
728 rest of the x64-only set to keep the rule simple, not because it was broken.
729 The RID list is still ordered preference on x64, so a future `win-arm64` build
730 would be picked up with no further change.
f51da37a
MV
731- **`rsync` is native on ARM64 now**, and its `ssh.exe` is still verified by
732 running it. The release publishes an `arm64` asset as of `v3.5.0-gdeeda96f`;
733 both binaries in it are `0xAA64`, and the `ssh.exe` reports
734 `OpenSSH_for_Windows_10.0p2 ... LibreSSL 3.8.2` on this box.
735
736 Before that asset existed, ARM64 took the x64 zip. The `rsync.exe` was fine —
737 a transfer is bounded by the socket, not by emulated CPU — but its `ssh.exe`
738 links against `System32\libcrypto.dll`, which on ARM64 is an **ARM64** binary
739 an x64 process cannot load. It died with `0xC0000135`
740 (`STATUS_DLL_NOT_FOUND`) and had to be deleted on every run. That matters more
741 than it sounds: `rsync.exe` *prefers* an `ssh.exe` in its own directory, so a
742 present-but-broken one does not degrade to the in-box client — it breaks rsync
743 outright, with an error naming a dead remote shell rather than `ssh.exe`.
744
745 The elevated half still runs `ssh -V` after unpacking and **deletes** the
746 binary if it will not start. The check is kept because it is what found that
747 problem, and it still covers a missing or too-old `libcrypto.dll` on any
748 architecture. An x64 `rsync.exe` surviving on an ARM64 box now shows up in the
749 audit as emulation *without* a listed reason, with a remedy pointing at a
750 re-run. The `core.sshCommand` step in the non-elevated half already picked its
751 candidate by running it, and is unchanged.
649b04df
MV
752- **Intel VTune reports `n/a`** rather than printing a download link: there is no
753 Windows-on-Arm build, and its value is reading Intel PMU counters. Use the
754 Windows Performance Toolkit (`wpr`/`xperf` to collect, `wpa` to analyse), or
755 Arm Performance Studio / Streamline for Arm PMU sampling.
f51da37a
MV
756- **NASM is not installed on ARM64.** It assembles x86/x86-64 only — there is no
757 ARM64 target in it and no ARM64 build of it — so it went into the x64-only set
758 with the rest. The ARM64 assembler is `armasm64.exe`, which ships with MSVC.
759 Its installer is also one of the two here that fall back to a per-user
760 directory it never registers (`%LOCALAPPDATA%\bin\NASM`), so `winget uninstall`
761 cannot find it afterwards; the `Uninstall.exe` in that directory is the way out.
649b04df
MV
762- **`vswhere` stays under the 32-bit Program Files** on ARM64 too
763 (`%ProgramFiles(x86)%\Microsoft Visual Studio\Installer`), so that path is
764 unchanged. The VS Installer is x86-registered there by contract even though the
765 installer binaries themselves are native.
766- **The WDK registry probe reads both hives.** `wdksetup.exe` is 32-bit and
767 writes under `WOW6432Node` on x64, but which hive a kit lands in has varied
768 across versions and architectures, and reading only one made an installed WDK
769 look absent — costing a needless multi-GB reinstall on every run.