]> vilimpoc.org git repositories - dotfiles/blame - setup-windows.bat
dotfiles: split provisioning between x64 and ARM64
[dotfiles] / setup-windows.bat
CommitLineData
3c096107
MV
1@echo off\r
2\r
3@rem ---------------------------------------------------------------------------\r
452f525c 4@rem setup-windows.bat - provision a fresh Windows box for native development\r
649b04df
MV
5@rem\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
9\r
10@rem ---------------------------------------------------------------------------\r
11@rem Host architecture\r
12@rem\r
7840e3c2
MV
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
649b04df 21@rem\r
7840e3c2
MV
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
27@rem\r
28@rem The value drives three things below, and nothing else - winget resolves the\r
649b04df
MV
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
7840e3c2 32@rem - the x64-only package set is skipped on ARM64 (see :x64only)\r
649b04df 33@rem - the summary tells you which tools you got as emulated x64\r
3c096107 34@rem ---------------------------------------------------------------------------\r
649b04df
MV
35set "HOST_ARCH=%PROCESSOR_ARCHITECTURE%"\r
36if defined PROCESSOR_ARCHITEW6432 set "HOST_ARCH=%PROCESSOR_ARCHITEW6432%"\r
7840e3c2
MV
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
40for /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
649b04df
MV
41set "IS_ARM64=0"\r
42if /i "%HOST_ARCH%"=="ARM64" set "IS_ARM64=1"\r
43echo [setup-windows] Host architecture: %HOST_ARCH%\r
3c096107 44\r
cd774a19
MV
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
49@rem\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
61set "ELEV=PROMPT"\r
62for /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
63echo [setup-windows] Elevation: %ELEV%\r
64if "%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
68)\r
69\r
7840e3c2
MV
70@rem ---------------------------------------------------------------------------\r
71@rem ARM64 is a lean build box, x64 is the full workstation\r
72@rem\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
78@rem\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
89@rem\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
93\r
2e281421 94@rem --- Non-admin (per-user) winget installs ---\r
649b04df
MV
95@rem\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
99@rem hard failure.\r
100@rem\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
103@rem\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
3c096107 109winget install Anthropic.ClaudeCode\r
7840e3c2 110call :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
3c096107
MV
111winget install Git.Git\r
112winget install Microsoft.PowerShell Microsoft.Sysinternals.ProcessExplorer Microsoft.Sysinternals.ProcessMonitor Microsoft.Sysinternals.SDelete Microsoft.VisualStudioCode Microsoft.WindowsTerminal\r
113winget install Python.Python.3.13\r
fbe899b6
MV
114\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
121@rem\r
7840e3c2
MV
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
126call :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
3c096107 127\r
649b04df
MV
128@rem --- Build drivers, standalone and native ---\r
129@rem\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
132@rem\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
140@rem\r
3e0aa9fc
MV
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
cd774a19
MV
148@rem\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
649b04df
MV
152winget install Ninja-build.Ninja\r
153\r
7840e3c2 154@rem --- Second, independent Clang: compiler diversity, on x64 only ---\r
649b04df 155@rem\r
7840e3c2
MV
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
649b04df
MV
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
162@rem\r
7840e3c2
MV
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
649b04df 168@rem\r
7840e3c2
MV
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
649b04df
MV
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
7840e3c2 174call :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
649b04df 175\r
cd774a19
MV
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
649b04df 179winget install ar51an.iPerf3\r
649b04df
MV
180\r
181@rem NASM assembles x86/x86-64 only - there is no ARM64 target in it and no ARM64\r
7840e3c2
MV
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
185@rem\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
190call :x64only NASM.NASM "x86/x86-64 assembler with no ARM64 target and no ARM64 build - MSVC's armasm64.exe is the native one"\r
649b04df
MV
191\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
196@rem\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
200if "%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
203) else (\r
204 winget install Oracle.VirtualBox\r
205)\r
206\r
cd774a19
MV
207@rem ---------------------------------------------------------------------------\r
208@rem Machine-wide installers, despite sitting in the "per-user" section\r
209@rem\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
216@rem\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
221@rem\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
7840e3c2
MV
225@rem\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
cd774a19
MV
228@rem ---------------------------------------------------------------------------\r
229if "%ELEV%"=="NOLUA" (\r
230 echo.\r
231 echo [setup-windows] Skipping the machine-wide installers - this account cannot elevate:\r
7840e3c2
MV
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
cd774a19
MV
234 echo [setup-windows] None blocks a build. Re-run from an administrator account to get them.\r
235 echo.\r
236 goto :after_admin_pkgs\r
237)\r
238\r
239winget install Microsoft.DotNet.SDK.10\r
240winget install Kitware.CMake\r
7840e3c2 241call :x64only Google.AndroidGPUInspector "x64-only build, and it profiles Android devices rather than anything compiled here"\r
cd774a19 242\r
3c096107 243@rem OpenCppCoverage: native (PE) line coverage for the C++ binaries. run-coverage-occ.py drives the\r
452f525c 244@rem pytest suite under it to produce an HTML report (the binaries under test build with PDBs,\r
3c096107 245@rem which it reads). The installer elevates via UAC.\r
649b04df
MV
246@rem\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
7840e3c2
MV
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
252call :x64only OpenCppCoverage.OpenCppCoverage "it collects coverage by stepping x86/x64 instructions, so it cannot instrument an ARM64 binary at all"\r
cd774a19
MV
253\r
254:after_admin_pkgs\r
3c096107 255\r
aa6eb783
MV
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
262@rem\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
268@rem\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
272@rem\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
649b04df
MV
277@rem\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
aa6eb783 284set "DOTNET_EXE=%ProgramFiles%\dotnet\dotnet.exe"\r
649b04df
MV
285if not exist "%DOTNET_EXE%" set "DOTNET_EXE=%ProgramFiles%\dotnet\x64\dotnet.exe"\r
286if not exist "%DOTNET_EXE%" echo [setup-windows] WARNING: dotnet.exe not found; skipping the WiX 5.0.2 pin.\r
287if not exist "%DOTNET_EXE%" goto :after_wix\r
288\r
aa6eb783
MV
289"%DOTNET_EXE%" tool install --global wix --version 5.0.2 || "%DOTNET_EXE%" tool update --global wix --version 5.0.2\r
290\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
294\r
649b04df
MV
295:after_wix\r
296\r
3c096107
MV
297@rem ---------------------------------------------------------------------------\r
298@rem No package manager needed for the Windows build\r
299@rem\r
300@rem Just:\r
301@rem cd windows && cmake -B build -G "Visual Studio 17 2022" -A x64 && cmake --build build --config Release\r
649b04df
MV
302@rem\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
3c096107
MV
312@rem ---------------------------------------------------------------------------\r
313\r
649b04df 314@rem --- Elevated installs (Visual Studio, WDK, system tools) ---\r
3c096107
MV
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
317set "UAC_LOG=%~dp0setup-windows-uac.log"\r
318if exist "%UAC_LOG%" del "%UAC_LOG%"\r
319\r
cd774a19
MV
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
322if "%ELEV%"=="NOLUA" goto :elev_impossible\r
323if "%ELEV%"=="ALREADY" goto :elev_direct\r
324\r
325@rem Not elevated, UAC is on: request it. Expect a prompt.\r
326echo [setup-windows] Requesting elevation ^(expect a UAC prompt^)...\r
95307dbb 327powershell -NoProfile -Command "$p = Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','""%~dp0setup-windows-with-uac.ps1""' -Wait -PassThru; exit $p.ExitCode"\r
3c096107 328set "UAC_RC=%ERRORLEVEL%"\r
cd774a19
MV
329goto :elev_done\r
330\r
331:elev_direct\r
332echo [setup-windows] Already running elevated; running the elevated half directly.\r
333powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-with-uac.ps1"\r
334set "UAC_RC=%ERRORLEVEL%"\r
335goto :elev_done\r
336\r
337:elev_impossible\r
338echo.\r
339echo [setup-windows] SKIPPING the elevated half: this account cannot elevate.\r
340echo [setup-windows] UAC is disabled machine-wide ^(EnableLUA = 0^) and you are not an administrator,\r
341echo [setup-windows] so Windows offers no way to elevate - there is no prompt to accept. With UAC off,\r
342echo [setup-windows] 'Start-Process -Verb RunAs' is silently ignored and reports success, which is why\r
343echo [setup-windows] this used to look like a cancelled prompt.\r
344echo [setup-windows]\r
7840e3c2
MV
345echo [setup-windows] Not installed: Visual Studio and its MSVC toolchain, rsync, and the OpenSSH\r
346echo [setup-windows] server. Everything per-user above is unaffected.\r
cd774a19
MV
347echo [setup-windows]\r
348echo [setup-windows] To finish the box, either sign in to an administrator account and re-run this\r
349echo [setup-windows] script, or have an administrator run setup-windows-with-uac.ps1 there. Then\r
350echo [setup-windows] re-run setup-windows-no-uac.ps1 as yourself, so the per-user PATH and .gitconfig\r
351echo [setup-windows] land in YOUR profile rather than the administrator's.\r
352echo.\r
353set "UAC_RC=SKIPPED"\r
354\r
355:elev_done\r
3c096107
MV
356\r
357@rem --- Surface the elevated session's output (its window has already closed) ---\r
cd774a19
MV
358@rem\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
364if "%UAC_RC%"=="SKIPPED" goto :after_uac_log\r
3c096107
MV
365if exist "%UAC_LOG%" (\r
366 echo.\r
367 echo ===== elevated setup log ^(%UAC_LOG%^) =====\r
368 type "%UAC_LOG%"\r
369 echo ===== end of elevated setup log =====\r
370) else (\r
371 echo [setup-windows] WARNING: no elevated log found at "%UAC_LOG%".\r
cd774a19
MV
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
3c096107 374)\r
cd774a19 375:after_uac_log\r
3c096107 376\r
2bbc9b41
MV
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
382@rem\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
386@rem\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
392@rem\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
395powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-no-uac.ps1"\r
396if not "%ERRORLEVEL%"=="0" echo [setup-windows] WARNING: setup-windows-no-uac.ps1 reported a failure ^(see above^); continuing.\r
397\r
cd774a19
MV
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
402if "%UAC_RC%"=="SKIPPED" goto :uac_reported\r
3c096107
MV
403if not "%UAC_RC%"=="0" (\r
404 echo.\r
405 echo [setup-windows] ELEVATED SETUP FAILED ^(exit code %UAC_RC%^). See log above.\r
406 exit /b %UAC_RC%\r
407)\r
408echo.\r
409echo [setup-windows] Elevated setup completed successfully.\r
cd774a19
MV
410goto :uac_reported\r
411\r
412:uac_reported\r
413if "%UAC_RC%"=="SKIPPED" echo.\r
414if "%UAC_RC%"=="SKIPPED" echo [setup-windows] Per-user setup complete; the elevated half was skipped ^(see above^).\r
3c096107
MV
415\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
420\r
7840e3c2
MV
421@rem --- Find an interpreter by path, rather than trusting `py` ---\r
422@rem\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
426@rem\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
431@rem\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
437@rem\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
444set "PY_EXE="\r
445if exist "%LOCALAPPDATA%\Programs\Python\Launcher\py.exe" set "PY_EXE=%LOCALAPPDATA%\Programs\Python\Launcher\py.exe"\r
446if not defined PY_EXE if exist "%WINDIR%\py.exe" set "PY_EXE=%WINDIR%\py.exe"\r
447if 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
448\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
453if 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
456) else (\r
457 echo [setup-windows] Python: %PY_EXE%\r
458 "%PY_EXE%" -m pip install Pillow\r
459)\r
649b04df
MV
460\r
461if "%IS_ARM64%"=="1" (\r
462 echo.\r
463 echo [setup-windows] ARM64 notes:\r
464 echo [setup-windows] native ARM64: Git, Python, .NET SDK, PowerShell, VS Code, Windows Terminal,\r
7840e3c2
MV
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
649b04df
MV
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
474)\r
cd774a19
MV
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
477if "%ELEV%"=="NOLUA" (\r
478 echo [setup-windows] NOT on this box: everything needing elevation was skipped, including\r
7840e3c2 479 echo [setup-windows] Visual Studio's MSVC, the .NET SDK and CMake. The\r
cd774a19
MV
480 echo [setup-windows] architecture audit above lists what is really installed.\r
481)\r
7840e3c2
MV
482\r
483exit /b 0\r
484\r
485@rem ---------------------------------------------------------------------------\r
486@rem :x64only <winget-id> "<why not on ARM64>"\r
487@rem\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
492@rem\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
496:x64only\r
497if not "%IS_ARM64%"=="1" (\r
498 winget install %1\r
499 goto :eof\r
500)\r
501echo [setup-windows] Skipping %1 on ARM64: %~2\r
502goto :eof\r