3 @rem ---------------------------------------------------------------------------
\r
4 @rem setup-windows.bat - provision a fresh Windows box for native development
\r
6 @rem Runs on x64 and on ARM64 (Windows 11 on Arm). See the ARM64 block below for
\r
7 @rem what changes on Arm silicon.
\r
8 @rem ---------------------------------------------------------------------------
\r
10 @rem ---------------------------------------------------------------------------
\r
11 @rem Host architecture
\r
13 @rem The environment variables are not enough, and the failure is silent. A 32-bit
\r
14 @rem cmd.exe under emulation reports x86 and stashes the real one in
\r
15 @rem PROCESSOR_ARCHITEW6432 - that pair is handled below - but an EMULATED x64
\r
16 @rem cmd.exe on an ARM64 box reports AMD64 with PROCESSOR_ARCHITEW6432 unset, and
\r
17 @rem nothing in the environment contradicts it. Launching this script from an x64
\r
18 @rem shell (Git Bash, for one) is enough to hit that, and measured here it gives:
\r
19 @rem PROCESSOR_ARCHITECTURE AMD64
\r
20 @rem HKLM\...\Session Manager\Environment ARM64 <- the real one
\r
22 @rem So take the machine-level registry value as the answer and keep the
\r
23 @rem environment pair only as the fallback. The registry key is per-machine and
\r
24 @rem not subject to per-process emulation, and it is not one of the redirected
\r
25 @rem hives. This is load-bearing now: %IS_ARM64% decides which packages are
\r
26 @rem installed at all, so reading it wrong provisions the wrong machine.
\r
28 @rem The value drives three things below, and nothing else - winget resolves the
\r
29 @rem installer architecture on its own (native first, then whatever the box can
\r
30 @rem emulate), so the ordinary installs need no help:
\r
31 @rem - VirtualBox is skipped on ARM64 (see below)
\r
32 @rem - the x64-only package set is skipped on ARM64 (see :x64only)
\r
33 @rem - the summary tells you which tools you got as emulated x64
\r
34 @rem ---------------------------------------------------------------------------
\r
35 set "HOST_ARCH=%PROCESSOR_ARCHITECTURE%"
\r
36 if defined PROCESSOR_ARCHITEW6432 set "HOST_ARCH=%PROCESSOR_ARCHITEW6432%"
\r
37 @rem reg.exe by full path, and NOT through a pipe to find/findstr: both of those
\r
38 @rem are shadowable by anything earlier on the PATH, and a POSIX `find` on the
\r
39 @rem PATH turns this into a silent no-op that leaves the emulated answer standing.
\r
40 for /f "tokens=1,2,*" %%A in ('%SystemRoot%\System32\reg.exe query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v PROCESSOR_ARCHITECTURE 2^>nul') do if /i "%%A"=="PROCESSOR_ARCHITECTURE" set "HOST_ARCH=%%C"
\r
42 if /i "%HOST_ARCH%"=="ARM64" set "IS_ARM64=1"
\r
43 echo [setup-windows] Host architecture: %HOST_ARCH%
\r
45 @rem ---------------------------------------------------------------------------
\r
46 @rem Can this account elevate? Worked out ONCE, here, because it gates two very
\r
47 @rem different things: the elevated half far below, and a handful of the "per-user"
\r
48 @rem winget installs just after this, which are per-user in name only.
\r
50 @rem ALREADY - already elevated. IsInRole(Administrator) is false for an admin
\r
51 @rem running unelevated under UAC, so this means actually elevated,
\r
52 @rem not merely capable of it.
\r
53 @rem PROMPT - not elevated, UAC on, so elevation can be requested.
\r
54 @rem NOLUA - UAC is off machine-wide (EnableLUA = 0) AND this is not an
\r
55 @rem administrator. Elevation is impossible, not merely declined:
\r
56 @rem Windows has no prompt to offer. Note that with UAC off,
\r
57 @rem `Start-Process -Verb RunAs` does not fail - it is silently
\r
58 @rem ignored, runs the child with the caller's own token, and reports
\r
59 @rem success, which is why this needs detecting rather than trying.
\r
60 @rem ---------------------------------------------------------------------------
\r
62 for /f "usebackq tokens=*" %%A in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$id=[Security.Principal.WindowsIdentity]::GetCurrent(); if (([Security.Principal.WindowsPrincipal]$id).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { 'ALREADY' } elseif ((Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -ErrorAction SilentlyContinue).EnableLUA -eq 0) { 'NOLUA' } else { 'PROMPT' }"`) do set "ELEV=%%A"
\r
63 echo [setup-windows] Elevation: %ELEV%
\r
64 if "%ELEV%"=="NOLUA" (
\r
65 echo [setup-windows] This account cannot elevate ^(UAC off, not an administrator^). Packages that
\r
66 echo [setup-windows] need a machine-wide install will be SKIPPED rather than left to fail; details
\r
67 echo [setup-windows] at each one, and a summary before the elevated half.
\r
70 @rem ---------------------------------------------------------------------------
\r
71 @rem ARM64 is a lean build box, x64 is the full workstation
\r
73 @rem The ARM64 machine here is a small VM, and everything it was installing
\r
74 @rem without a native build was going to run under Prism emulation anyway. So the
\r
75 @rem two architectures now provision different things on purpose: x64 keeps the
\r
76 @rem full set, ARM64 installs ONE compiler - MSVC from Visual Studio - and
\r
77 @rem nothing that merely duplicates it or that exists only as an x64 binary.
\r
79 @rem Dropped on ARM64, and where each one is dropped:
\r
80 @rem Brave.Brave here no ARM64 build; Edge is in-box and native
\r
81 @rem WinMerge.WinMerge here ARM64 build is machine-scope only
\r
82 @rem LLVM.LLVM here second Clang, dropped with VS's clang-cl
\r
83 @rem NASM.NASM here x86/x64 assembler; ARM64 uses MSVC's armasm64
\r
84 @rem Google.AndroidGPUInspector here x64-only, profiles Android devices
\r
85 @rem OpenCppCoverage.OpenCppCoverage here x86/x64 only; cannot instrument ARM64 binaries
\r
86 @rem VS Clang/LLVM component with-uac see $ClangComponents
\r
87 @rem Windows Driver Kit with-uac see the WDK step
\r
88 @rem BinSkim no-uac NuGet publishes win-x64 only
\r
90 @rem :x64only at the bottom of this file is the one place that decision is
\r
91 @rem applied, so the skip and its reason land in the log together.
\r
92 @rem ---------------------------------------------------------------------------
\r
94 @rem --- Non-admin (per-user) winget installs ---
\r
96 @rem No --architecture anywhere on purpose. winget already picks the best
\r
97 @rem installer the manifest offers for this machine - arm64, else x64, else x86 -
\r
98 @rem and pinning it would turn "no native build, use the emulated one" into a
\r
101 @rem These all publish real ARM64 installers, so on an ARM64 box every one of
\r
102 @rem them lands as a native ARM64 binary with no emulation involved.
\r
104 @rem The Sysinternals packages are a multi-architecture ZIP rather than an
\r
105 @rem installer, so the ARM64 build is IN there but is not the default-named exe:
\r
106 @rem the zip holds procexp.exe (x86), procexp64.exe (x64) and procexp64a.exe
\r
107 @rem (ARM64). On ARM64, run the *64a.exe variants - plain procexp.exe is the x86
\r
108 @rem one and will run emulated.
\r
109 winget install Anthropic.ClaudeCode
\r
110 call :x64only Brave.Brave "no ARM64 build - the emulated x64 browser is the slowest thing on the box, and Edge is in-box and native"
\r
111 winget install Git.Git
\r
112 winget install Microsoft.PowerShell Microsoft.Sysinternals.ProcessExplorer Microsoft.Sysinternals.ProcessMonitor Microsoft.Sysinternals.SDelete Microsoft.VisualStudioCode Microsoft.WindowsTerminal
\r
113 winget install Python.Python.3.13
\r
115 @rem WinMerge is the one package here where winget's own preference order picks
\r
116 @rem the WRONG architecture, and it is worth knowing why: upstream publishes the
\r
117 @rem ARM64 build as a MACHINE-scope installer (WinMerge-<ver>-ARM64-Setup.exe) and
\r
118 @rem the x64 build as a PER-USER one (WinMerge-<ver>-x64-PerUser-Setup.exe). Run
\r
119 @rem unelevated, winget's scope preference beats its architecture preference and
\r
120 @rem you silently get the emulated x64 build.
\r
122 @rem That asymmetry is why ARM64 no longer installs it at all: the only build a
\r
123 @rem normal user can install there is the emulated one, and asking for --architecture
\r
124 @rem arm64 just fails unless the run is elevated. VS Code's diff view and
\r
125 @rem `git difftool` cover the same ground natively.
\r
126 call :x64only WinMerge.WinMerge "upstream ships ARM64 as a machine-scope installer only, so an unelevated winget can install nothing but the emulated x64 build"
\r
128 @rem --- Build drivers, standalone and native ---
\r
130 @rem CMake and Ninja BOTH ship with Visual Studio, so this is not about making
\r
131 @rem them exist - it is about which binary you get and where it is on the PATH:
\r
133 @rem - VS's cmake.exe is already ARM64, but it only lands on the PATH inside a
\r
134 @rem Developer Command Prompt. The build line further down assumes a plain
\r
135 @rem `cmake`, so install the standalone one to make that true in any shell.
\r
136 @rem - VS's bundled ninja.exe is x64 EVEN ON ARM64 (verified on this box), so it
\r
137 @rem runs under emulation. Ninja is re-invoked for every edge in the build
\r
138 @rem graph, which makes it the one tool here where emulation is actually paid
\r
139 @rem over and over, and ninja-winarm64.zip is a native build. Worth having.
\r
141 @rem Which ninja wins inside a VS Developer Command Prompt: the native one. VS
\r
142 @rem adds its copy from Common7\Tools\vsdevcmd\ext\cmake.bat with
\r
143 @rem set "PATH=%PATH%;...\CMake\bin;...\CMake\Ninja"
\r
144 @rem which APPENDS, so the VS directories sit at the very end of the composed
\r
145 @rem PATH, behind every machine and user entry. setup-windows-no-uac.ps1 then
\r
146 @rem pins the native ninja's directory to the FRONT of the user PATH so the
\r
147 @rem margin does not depend on two append orders staying as they are.
\r
149 @rem Ninja here, CMake in the machine-wide group below: Ninja is a portable zip
\r
150 @rem winget unpacks into the user profile, CMake is an MSI that installs for the
\r
151 @rem whole machine.
\r
152 winget install Ninja-build.Ninja
\r
154 @rem --- Second, independent Clang: compiler diversity, on x64 only ---
\r
156 @rem On x64 the elevated half installs Visual Studio's Clang component (clang-cl).
\r
157 @rem This is a DIFFERENT LLVM: the upstream release, on its own schedule and
\r
158 @rem usually several major versions ahead of the one VS bundles, installed to
\r
159 @rem C:\Program Files\LLVM rather than inside the VS tree. Two Clang majors plus
\r
160 @rem MSVC over the same sources is the point - it is what catches the bugs a
\r
161 @rem single toolchain agrees with itself about.
\r
163 @rem NOT ON ARM64, and not for lack of a native build - the manifest does resolve
\r
164 @rem to LLVM-<ver>-woa64.exe there. It is dropped because the ARM64 box carries
\r
165 @rem MSVC alone: with VS's clang-cl component gone too, a third compiler with
\r
166 @rem nothing to disagree with is just disk. Do the multi-compiler build on the
\r
167 @rem x64 machine, which keeps all three.
\r
169 @rem PATH ORDER MATTERS on x64 if you use it. Should this installer put its bin on
\r
170 @rem the PATH, a bare `clang-cl` resolves to the upstream one while CMake's
\r
171 @rem `-T ClangCL` toolset keeps using the VS copy - so the two can be selected
\r
172 @rem independently, but only if you know which one you are asking for. Prefer
\r
173 @rem -DCMAKE_CXX_COMPILER with a full path when you care.
\r
174 call :x64only LLVM.LLVM "ARM64 carries MSVC alone - VS's clang-cl is dropped there too, so a second Clang has nothing to cross-check against"
\r
176 @rem x64-only manifest: winget falls back to the x64 installer on ARM64 and it runs
\r
177 @rem under Prism emulation. Harmless for what it does here - iperf3 is a network
\r
178 @rem benchmark bounded by the link, not by CPU.
\r
179 winget install ar51an.iPerf3
\r
181 @rem NASM assembles x86/x86-64 only - there is no ARM64 target in it and no ARM64
\r
182 @rem build of it. x64 keeps it; ARM64 does not, because an emulated assembler is
\r
183 @rem only worth carrying if you cross-compile x86/x64 here, and that work belongs
\r
184 @rem on the x64 box. The ARM64 assembler is armasm64.exe, which ships with MSVC.
\r
186 @rem Its installer is also one of the two here that fall back to a per-user
\r
187 @rem directory when it cannot write to Program Files - C:\Users\<you>\AppData\
\r
188 @rem Local\bin\NASM, registered nowhere, so `winget uninstall` cannot find it
\r
189 @rem afterwards. Uninstall.exe in that directory is the only way back out.
\r
190 call :x64only NASM.NASM "x86/x86-64 assembler with no ARM64 target and no ARM64 build - MSVC's armasm64.exe is the native one"
\r
192 @rem VirtualBox has no ARM64 Windows host build, and the x64 one cannot be made to
\r
193 @rem work by emulation: a hypervisor is kernel-mode, and its x64 driver will not
\r
194 @rem load on an ARM64 kernel. Installing it on ARM64 gets you a broken product and
\r
195 @rem a failed driver install, so skip it and say so.
\r
197 @rem This is also what makes setup-windows-7-test-env.bat unreachable from an
\r
198 @rem ARM64 host: it prepares a Windows 7 *x86* guest, and neither VirtualBox nor
\r
199 @rem Hyper-V on ARM64 can run one.
\r
200 if "%IS_ARM64%"=="1" (
\r
201 echo [setup-windows] Skipping Oracle.VirtualBox: no ARM64 Windows host build ^(x64 hypervisor drivers cannot load on an ARM64 kernel^).
\r
202 echo [setup-windows] The Windows 7 x86 test-VM workflow ^(setup-windows-7-test-env.bat^) is therefore unavailable on this box.
\r
204 winget install Oracle.VirtualBox
\r
207 @rem ---------------------------------------------------------------------------
\r
208 @rem Machine-wide installers, despite sitting in the "per-user" section
\r
210 @rem These four are not per-user at all. On an account that cannot elevate they
\r
211 @rem fail on every run, with installer exit codes that say nothing useful:
\r
212 @rem Microsoft.DotNet.SDK.10 exit 5 (ERROR_ACCESS_DENIED)
\r
213 @rem Kitware.CMake exit 1603 (generic MSI failure)
\r
214 @rem Google.AndroidGPUInspector exit 1603
\r
215 @rem OpenCppCoverage exit 1 (its installer self-elevates)
\r
217 @rem Grouped and skipped outright where elevation is impossible. Attempting a
\r
218 @rem guaranteed failure four times per run - and paying the download for it -
\r
219 @rem teaches nobody anything, and the four opaque error codes bury the one line
\r
220 @rem that matters. On an account that can elevate they run exactly as before.
\r
222 @rem NOTE none of these is a build-blocker: the .NET SDK is only here for the WiX
\r
223 @rem MSI tooling, CMake also ships inside Visual Studio, AGI profiles Android
\r
224 @rem devices, and OpenCppCoverage cannot instrument ARM64 binaries anyway.
\r
226 @rem On ARM64 the group is just the first two - AGI and OpenCppCoverage are in the
\r
227 @rem x64-only set, so there they are skipped before elevation is even consulted.
\r
228 @rem ---------------------------------------------------------------------------
\r
229 if "%ELEV%"=="NOLUA" (
\r
231 echo [setup-windows] Skipping the machine-wide installers - this account cannot elevate:
\r
232 echo [setup-windows] Microsoft.DotNet.SDK.10 and Kitware.CMake, plus - on x64 only -
\r
233 echo [setup-windows] Google.AndroidGPUInspector and OpenCppCoverage.
\r
234 echo [setup-windows] None blocks a build. Re-run from an administrator account to get them.
\r
236 goto :after_admin_pkgs
\r
239 winget install Microsoft.DotNet.SDK.10
\r
240 winget install Kitware.CMake
\r
241 call :x64only Google.AndroidGPUInspector "x64-only build, and it profiles Android devices rather than anything compiled here"
\r
243 @rem OpenCppCoverage: native (PE) line coverage for the C++ binaries. run-coverage-occ.py drives the
\r
244 @rem pytest suite under it to produce an HTML report (the binaries under test build with PDBs,
\r
245 @rem which it reads). The installer elevates via UAC.
\r
247 @rem x86/x64 only, and unlike the other emulated tools that is a real limit rather
\r
248 @rem than a slowdown: it collects coverage by debugging the process under test and
\r
249 @rem stepping x86/x64 instructions, so it can cover x86/x64 binaries but NOT an
\r
250 @rem ARM64 one. That is what puts it in the x64-only set: on ARM64 it could only
\r
251 @rem ever cover the cross-compiled x64 build, which is a job for the x64 box.
\r
252 call :x64only OpenCppCoverage.OpenCppCoverage "it collects coverage by stepping x86/x64 instructions, so it cannot instrument an ARM64 binary at all"
\r
256 @rem --- WiX 5.0.2, pinned on purpose ---
\r
257 @rem WiX packages our proprietary software into MSIs, and the version is pinned to keep that
\r
258 @rem free of a fee. 5.0.2 is the last release distributed under the Microsoft Reciprocal
\r
259 @rem License alone. From 6.0 onward the package also carries OSMFEULA.txt, an Open Source
\r
260 @rem Maintenance Fee agreement: a monthly fee owed by anyone who uses the PREBUILT BINARIES
\r
261 @rem as part of revenue-generating activity and has annual gross revenue >= US$10,000.
\r
263 @rem It is a fee for the binaries, not a restriction on what we ship - MS-RL is file-scoped
\r
264 @rem and never reached the MSIs WiX builds, under any version - but 5.0.2 owes nothing.
\r
265 @rem The 6.x/7.x SOURCE is still MS-RL too, so self-compiling is another way out; a pin is
\r
266 @rem the cheaper one. This replaces `winget install WiXToolset.WiXCLI`, which has no version
\r
267 @rem selector and so installs the latest (7.0.0 today, EULA and all).
\r
269 @rem PIN THE MSBUILD SIDE TOO. A .wixproj referencing WixToolset.Sdk without a version
\r
270 @rem resolves to the latest - 7.x, same EULA - and nothing here constrains it. Pin it in the
\r
271 @rem project: <Project Sdk="WixToolset.Sdk/5.0.2">.
\r
273 @rem dotnet.exe is called by full path: winget put the SDK on the machine PATH a few lines
\r
274 @rem ago, but this cmd session inherited its environment before that and cannot see it.
\r
275 @rem install-then-update is for re-runs - install fails once the tool is there, and update
\r
276 @rem then holds it at exactly 5.0.2 - which keeps this script idempotent like the rest.
\r
278 @rem %ProgramFiles%\dotnet is the NATIVE SDK's root on every architecture, ARM64
\r
279 @rem included - what winget just installed. An x64 SDK installed alongside it on
\r
280 @rem an ARM64 box goes to %ProgramFiles%\dotnet\x64 instead, so fall back there
\r
281 @rem for a machine that only ever got the x64 one. Prefer the native root: the
\r
282 @rem ARM64 SDK runs the wix tool natively, and building an MSI is not something
\r
283 @rem worth doing through emulation.
\r
284 set "DOTNET_EXE=%ProgramFiles%\dotnet\dotnet.exe"
\r
285 if not exist "%DOTNET_EXE%" set "DOTNET_EXE=%ProgramFiles%\dotnet\x64\dotnet.exe"
\r
286 if not exist "%DOTNET_EXE%" echo [setup-windows] WARNING: dotnet.exe not found; skipping the WiX 5.0.2 pin.
\r
287 if not exist "%DOTNET_EXE%" goto :after_wix
\r
289 "%DOTNET_EXE%" tool install --global wix --version 5.0.2 || "%DOTNET_EXE%" tool update --global wix --version 5.0.2
\r
291 @rem Report what the pin actually produced. By full path again, and because the shim lands in
\r
292 @rem a directory this session's PATH predates: expect "5.0.2+<commit>", not 7.x.
\r
293 "%USERPROFILE%\.dotnet\tools\wix.exe" --version
\r
297 @rem ---------------------------------------------------------------------------
\r
298 @rem No package manager needed for the Windows build
\r
301 @rem cd windows && cmake -B build -G "Visual Studio 17 2022" -A x64 && cmake --build build --config Release
\r
303 @rem -A selects the TARGET, independently of the host. On an ARM64 box the same
\r
304 @rem generator cross-compiles all three, and MSVC's ARM64-hosted toolchain is used
\r
305 @rem for each - nothing is emulated:
\r
306 @rem -A ARM64 native ARM64 binaries
\r
307 @rem -A x64 x64 binaries (run here under emulation)
\r
308 @rem -A Win32 x86 binaries
\r
309 @rem Use the generator matching the Visual Studio the elevated half installed -
\r
310 @rem "Visual Studio 17 2022" on x64, "Visual Studio 18 2026" on ARM64 - or just
\r
311 @rem let CMake pick the newest it finds.
\r
312 @rem ---------------------------------------------------------------------------
\r
314 @rem --- Elevated installs (Visual Studio, WDK, system tools) ---
\r
315 @rem The elevated script runs in its own window and logs to setup-windows-uac.log.
\r
316 @rem -PassThru + $p.ExitCode propagates its real exit code back through to ERRORLEVEL.
\r
317 set "UAC_LOG=%~dp0setup-windows-uac.log"
\r
318 if exist "%UAC_LOG%" del "%UAC_LOG%"
\r
320 @rem %ELEV% was worked out at the top of this script - see the comment there for
\r
321 @rem why `Start-Process -Verb RunAs` cannot be trusted to report this itself.
\r
322 if "%ELEV%"=="NOLUA" goto :elev_impossible
\r
323 if "%ELEV%"=="ALREADY" goto :elev_direct
\r
325 @rem Not elevated, UAC is on: request it. Expect a prompt.
\r
326 echo [setup-windows] Requesting elevation ^(expect a UAC prompt^)...
\r
327 powershell -NoProfile -Command "$p = Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','""%~dp0setup-windows-with-uac.ps1""' -Wait -PassThru; exit $p.ExitCode"
\r
328 set "UAC_RC=%ERRORLEVEL%"
\r
332 echo [setup-windows] Already running elevated; running the elevated half directly.
\r
333 powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-with-uac.ps1"
\r
334 set "UAC_RC=%ERRORLEVEL%"
\r
339 echo [setup-windows] SKIPPING the elevated half: this account cannot elevate.
\r
340 echo [setup-windows] UAC is disabled machine-wide ^(EnableLUA = 0^) and you are not an administrator,
\r
341 echo [setup-windows] so Windows offers no way to elevate - there is no prompt to accept. With UAC off,
\r
342 echo [setup-windows] 'Start-Process -Verb RunAs' is silently ignored and reports success, which is why
\r
343 echo [setup-windows] this used to look like a cancelled prompt.
\r
344 echo [setup-windows]
\r
345 echo [setup-windows] Not installed: Visual Studio and its MSVC toolchain, rsync, and the OpenSSH
\r
346 echo [setup-windows] server. Everything per-user above is unaffected.
\r
347 echo [setup-windows]
\r
348 echo [setup-windows] To finish the box, either sign in to an administrator account and re-run this
\r
349 echo [setup-windows] script, or have an administrator run setup-windows-with-uac.ps1 there. Then
\r
350 echo [setup-windows] re-run setup-windows-no-uac.ps1 as yourself, so the per-user PATH and .gitconfig
\r
351 echo [setup-windows] land in YOUR profile rather than the administrator's.
\r
353 set "UAC_RC=SKIPPED"
\r
357 @rem --- Surface the elevated session's output (its window has already closed) ---
\r
359 @rem A missing log is NOT self-explanatory, so do not guess at one cause. The
\r
360 @rem elevated script writes its transcript as almost its first act, so no log
\r
361 @rem means it never got as far as running: either the prompt was declined, or it
\r
362 @rem started unelevated and stopped on its own #Requires line. Which of those it
\r
363 @rem was is already known from %ELEV%, so report that instead of speculating.
\r
364 if "%UAC_RC%"=="SKIPPED" goto :after_uac_log
\r
365 if exist "%UAC_LOG%" (
\r
367 echo ===== elevated setup log ^(%UAC_LOG%^) =====
\r
369 echo ===== end of elevated setup log =====
\r
371 echo [setup-windows] WARNING: no elevated log found at "%UAC_LOG%".
\r
372 if "%ELEV%"=="PROMPT" echo [setup-windows] The elevated window never started - the UAC prompt was most likely declined.
\r
373 if "%ELEV%"=="ALREADY" echo [setup-windows] The elevated half exited before writing its transcript; see its output above.
\r
377 @rem --- Non-elevated PowerShell half ---
\r
378 @rem WinMerge on the user PATH, BinSkim, and the global git config (identity +
\r
379 @rem core.sshCommand -> a Win32-OpenSSH client, so git shares the Windows
\r
380 @rem ssh-agent). EDIT THE GIT IDENTITY at the top of setup-windows-no-uac.ps1
\r
381 @rem before the first run.
\r
383 @rem Deliberately NOT elevated: every step writes per-user state (the HKCU PATH,
\r
384 @rem the .gitconfig under %USERPROFILE%), which the elevated half would write to
\r
385 @rem the administrator profile instead.
\r
387 @rem AFTER the elevated half on purpose: core.sshCommand prefers the ssh.exe that
\r
388 @rem half unpacks beside rsync.exe - a push through the in-box client is capped
\r
389 @rem at ~17MB/s - and it can only prefer it once it is on disk. Run either way,
\r
390 @rem including when the elevated half failed above: nothing here depends on it,
\r
391 @rem and the fallback is the in-box client that Windows already has.
\r
393 @rem Non-fatal: these are conveniences, and the elevated half is the part worth
\r
394 @rem the UAC prompt. A failure warns and provisioning continues.
\r
395 powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-no-uac.ps1"
\r
396 if not "%ERRORLEVEL%"=="0" echo [setup-windows] WARNING: setup-windows-no-uac.ps1 reported a failure ^(see above^); continuing.
\r
398 @rem Skipping the elevated half is a reported, understood outcome on a box where
\r
399 @rem elevation is impossible - not a failure to exit non-zero over. The per-user
\r
400 @rem provisioning above did run, and re-running from an administrator account is
\r
401 @rem the documented next step.
\r
402 if "%UAC_RC%"=="SKIPPED" goto :uac_reported
\r
403 if not "%UAC_RC%"=="0" (
\r
405 echo [setup-windows] ELEVATED SETUP FAILED ^(exit code %UAC_RC%^). See log above.
\r
409 echo [setup-windows] Elevated setup completed successfully.
\r
413 if "%UAC_RC%"=="SKIPPED" echo.
\r
414 if "%UAC_RC%"=="SKIPPED" echo [setup-windows] Per-user setup complete; the elevated half was skipped ^(see above^).
\r
416 @rem Removed: this doesn't work as well as I hoped, maybe try again later
\r
417 @rem -- Install Headroom ---
\r
418 @rem py -m pip install "headroom-ai[all]"
\r
419 @rem npm install headroom-ai
\r
421 @rem --- Find an interpreter by path, rather than trusting `py` ---
\r
423 @rem `py -m pip install Pillow` used to be this line, and it failed with
\r
424 @rem 'py' is not recognized as an internal or external command
\r
425 @rem for two independent reasons, which look identical at the prompt:
\r
427 @rem 1. PATH staleness. winget installed Python a few lines above, but this cmd
\r
428 @rem session inherited its environment before that, so nothing winget added
\r
429 @rem is visible here. Same problem the DOTNET_EXE lookup solves above, and it
\r
430 @rem would bite even where the launcher IS installed.
\r
432 @rem 2. The launcher may simply not be there. Measured on the ARM64 box: every
\r
433 @rem other Python component registered - Core Interpreter, Executables,
\r
434 @rem Standard Library, pip Bootstrap, Tcl/Tk, Add to Path - and there is no
\r
435 @rem launcher entry and no py.exe anywhere on disk, while the Launcher
\r
436 @rem directory it would occupy is on the user PATH but does not exist.
\r
438 @rem So resolve an interpreter by full path and prefer the launcher only when it
\r
439 @rem is real. python.exe is the thing that actually has to exist; `py` is a
\r
440 @rem convenience shim that picks between several of them, and there is exactly one
\r
441 @rem here. Reverse name order so the newest Python wins, and require python.exe
\r
442 @rem inside the directory - a half-removed version leaves the folder behind with
\r
443 @rem only Doc and Lib in it, which is exactly what this box has.
\r
445 if exist "%LOCALAPPDATA%\Programs\Python\Launcher\py.exe" set "PY_EXE=%LOCALAPPDATA%\Programs\Python\Launcher\py.exe"
\r
446 if not defined PY_EXE if exist "%WINDIR%\py.exe" set "PY_EXE=%WINDIR%\py.exe"
\r
447 if not defined PY_EXE for /f "delims=" %%P in ('dir /b /ad /o-n "%LOCALAPPDATA%\Programs\Python\Python3*" 2^>nul') do if not defined PY_EXE if exist "%LOCALAPPDATA%\Programs\Python\%%P\python.exe" set "PY_EXE=%LOCALAPPDATA%\Programs\Python\%%P\python.exe"
\r
449 @rem Pillow publishes win_arm64 wheels alongside win_amd64, so the ARM64 Python
\r
450 @rem winget just installed gets a native wheel and never falls back to building
\r
451 @rem from source. Any package WITHOUT an ARM64 wheel does fall back, and that
\r
452 @rem source build needs the MSVC toolchain the elevated half installs.
\r
453 if not defined PY_EXE (
\r
454 echo [setup-windows] WARNING: no Python interpreter found; skipping the Pillow install.
\r
455 echo [setup-windows] Looked for the py launcher, then %LOCALAPPDATA%\Programs\Python\Python3*\python.exe.
\r
457 echo [setup-windows] Python: %PY_EXE%
\r
458 "%PY_EXE%" -m pip install Pillow
\r
461 if "%IS_ARM64%"=="1" (
\r
463 echo [setup-windows] ARM64 notes:
\r
464 echo [setup-windows] native ARM64: Git, Python, .NET SDK, PowerShell, VS Code, Windows Terminal,
\r
465 echo [setup-windows] Sysinternals ^(the *64a.exe variants^), Claude Code, CMake,
\r
466 echo [setup-windows] Ninja, and the SDK/WPT tools.
\r
467 echo [setup-windows] compiler : MSVC from Visual Studio 2026, native ARM64. One compiler by
\r
468 echo [setup-windows] design - clang-cl and upstream LLVM are x64-box only.
\r
469 echo [setup-windows] emulated x64: iperf3 and rsync.exe, both network-bound.
\r
470 echo [setup-windows] not here : Brave, WinMerge, LLVM, NASM, AGI, OpenCppCoverage, BinSkim
\r
471 echo [setup-windows] and the WDK - dropped on ARM64, see the x64-only set above.
\r
472 echo [setup-windows] unavailable : VirtualBox, the Windows 7 x86 test VM, Intel VTune,
\r
473 echo [setup-windows] and the v141 / Windows XP targeting toolset.
\r
475 @rem The list above is what this script PROVIDES on ARM64, not necessarily what
\r
476 @rem landed on this run - so point at the audit, which reports the actual state.
\r
477 if "%ELEV%"=="NOLUA" (
\r
478 echo [setup-windows] NOT on this box: everything needing elevation was skipped, including
\r
479 echo [setup-windows] Visual Studio's MSVC, the .NET SDK and CMake. The
\r
480 echo [setup-windows] architecture audit above lists what is really installed.
\r
485 @rem ---------------------------------------------------------------------------
\r
486 @rem :x64only <winget-id> "<why not on ARM64>"
\r
488 @rem Install a package on x64 and skip it on ARM64, saying why. One subroutine
\r
489 @rem rather than a gate at each call site so that the reason is never separated
\r
490 @rem from the skip: the log line a future reader sees is the same text this file
\r
491 @rem carries, and there is exactly one place to change the policy.
\r
493 @rem The reason is a real argument, not a comment, because the interesting case is
\r
494 @rem reading the run log six months later and wondering where NASM went.
\r
495 @rem ---------------------------------------------------------------------------
\r
497 if not "%IS_ARM64%"=="1" (
\r
501 echo [setup-windows] Skipping %1 on ARM64: %~2
\r