]> vilimpoc.org git repositories - dotfiles/blame - setup-windows.bat
dotfiles: register UI.Xaml 2.8 for the account before installing Terminal
[dotfiles] / setup-windows.bat
CommitLineData
3c096107 1@echo off\r
0b412520
MV
2@rem The plan below hands back a few dozen STEP_/WHY_/PKG_ variables; keep them,\r
3@rem and everything else this script sets, out of the calling cmd session.\r
4setlocal\r
3c096107
MV
5\r
6@rem ---------------------------------------------------------------------------\r
452f525c 7@rem setup-windows.bat - provision a fresh Windows box for native development\r
649b04df
MV
8@rem\r
9@rem Runs on x64 and on ARM64 (Windows 11 on Arm). See the ARM64 block below for\r
10@rem what changes on Arm silicon.\r
0b412520
MV
11@rem\r
12@rem Before it does anything, it shows the plan - every step it and its two\r
13@rem halves can take, marked for this machine - and lets you choose. See "The\r
14@rem plan" below, or run `setup-windows.bat /?`.\r
649b04df
MV
15@rem ---------------------------------------------------------------------------\r
16\r
17@rem ---------------------------------------------------------------------------\r
18@rem Host architecture\r
19@rem\r
7840e3c2
MV
20@rem The environment variables are not enough, and the failure is silent. A 32-bit\r
21@rem cmd.exe under emulation reports x86 and stashes the real one in\r
22@rem PROCESSOR_ARCHITEW6432 - that pair is handled below - but an EMULATED x64\r
23@rem cmd.exe on an ARM64 box reports AMD64 with PROCESSOR_ARCHITEW6432 unset, and\r
24@rem nothing in the environment contradicts it. Launching this script from an x64\r
25@rem shell (Git Bash, for one) is enough to hit that, and measured here it gives:\r
26@rem PROCESSOR_ARCHITECTURE AMD64\r
27@rem HKLM\...\Session Manager\Environment ARM64 <- the real one\r
649b04df 28@rem\r
7840e3c2
MV
29@rem So take the machine-level registry value as the answer and keep the\r
30@rem environment pair only as the fallback. The registry key is per-machine and\r
31@rem not subject to per-process emulation, and it is not one of the redirected\r
0b412520
MV
32@rem hives. This is load-bearing now: the architecture decides which packages\r
33@rem are installed at all, so reading it wrong provisions the wrong machine.\r
7840e3c2 34@rem\r
0b412520 35@rem The value drives two things below, and nothing else - winget resolves the\r
649b04df
MV
36@rem installer architecture on its own (native first, then whatever the box can\r
37@rem emulate), so the ordinary installs need no help:\r
0b412520
MV
38@rem - the plan: every x64-only step, VirtualBox included, is marked unavailable\r
39@rem on ARM64 and so never selected (see setup-windows-steps.ps1)\r
649b04df 40@rem - the summary tells you which tools you got as emulated x64\r
3c096107 41@rem ---------------------------------------------------------------------------\r
649b04df
MV
42set "HOST_ARCH=%PROCESSOR_ARCHITECTURE%"\r
43if defined PROCESSOR_ARCHITEW6432 set "HOST_ARCH=%PROCESSOR_ARCHITEW6432%"\r
7840e3c2
MV
44@rem reg.exe by full path, and NOT through a pipe to find/findstr: both of those\r
45@rem are shadowable by anything earlier on the PATH, and a POSIX `find` on the\r
46@rem PATH turns this into a silent no-op that leaves the emulated answer standing.\r
47for /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
48set "IS_ARM64=0"\r
49if /i "%HOST_ARCH%"=="ARM64" set "IS_ARM64=1"\r
50echo [setup-windows] Host architecture: %HOST_ARCH%\r
3c096107 51\r
cd774a19
MV
52@rem ---------------------------------------------------------------------------\r
53@rem Can this account elevate? Worked out ONCE, here, because it gates two very\r
54@rem different things: the elevated half far below, and a handful of the "per-user"\r
55@rem winget installs just after this, which are per-user in name only.\r
56@rem\r
57@rem ALREADY - already elevated. IsInRole(Administrator) is false for an admin\r
58@rem running unelevated under UAC, so this means actually elevated,\r
59@rem not merely capable of it.\r
60@rem PROMPT - not elevated, UAC on, so elevation can be requested.\r
61@rem NOLUA - UAC is off machine-wide (EnableLUA = 0) AND this is not an\r
62@rem administrator. Elevation is impossible, not merely declined:\r
63@rem Windows has no prompt to offer. Note that with UAC off,\r
64@rem `Start-Process -Verb RunAs` does not fail - it is silently\r
65@rem ignored, runs the child with the caller's own token, and reports\r
66@rem success, which is why this needs detecting rather than trying.\r
67@rem ---------------------------------------------------------------------------\r
68set "ELEV=PROMPT"\r
69for /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
70echo [setup-windows] Elevation: %ELEV%\r
71if "%ELEV%"=="NOLUA" (\r
72 echo [setup-windows] This account cannot elevate ^(UAC off, not an administrator^). Packages that\r
0b412520
MV
73 echo [setup-windows] need a machine-wide install will be SKIPPED rather than left to fail; the\r
74 echo [setup-windows] plan below marks each one, and there is a summary before the elevated half.\r
cd774a19
MV
75)\r
76\r
7840e3c2
MV
77@rem ---------------------------------------------------------------------------\r
78@rem ARM64 is a lean build box, x64 is the full workstation\r
79@rem\r
80@rem The ARM64 machine here is a small VM, and everything it was installing\r
81@rem without a native build was going to run under Prism emulation anyway. So the\r
82@rem two architectures now provision different things on purpose: x64 keeps the\r
0b412520 83@rem full set, ARM64 installs ONE C/C++ compiler - MSVC from Visual Studio - and\r
7840e3c2 84@rem nothing that merely duplicates it or that exists only as an x64 binary.\r
0b412520
MV
85@rem Rust does not break that rule: it is a different language, not a second\r
86@rem C/C++ compiler, and its native aarch64 MSVC toolchain links through MSVC.\r
7840e3c2 87@rem\r
0b412520 88@rem Dropped on ARM64, and which script would otherwise take each one:\r
7840e3c2
MV
89@rem Brave.Brave here no ARM64 build; Edge is in-box and native\r
90@rem WinMerge.WinMerge here ARM64 build is machine-scope only\r
91@rem LLVM.LLVM here second Clang, dropped with VS's clang-cl\r
92@rem NASM.NASM here x86/x64 assembler; ARM64 uses MSVC's armasm64\r
93@rem Google.AndroidGPUInspector here x64-only, profiles Android devices\r
94@rem OpenCppCoverage.OpenCppCoverage here x86/x64 only; cannot instrument ARM64 binaries\r
95@rem VS Clang/LLVM component with-uac see $ClangComponents\r
96@rem Windows Driver Kit with-uac see the WDK step\r
97@rem BinSkim no-uac NuGet publishes win-x64 only\r
4776737b
MV
98@rem Google.AndroidStudio here no Windows ARM64 build\r
99@rem Android SDK no-uac Windows build tools are x86/x64 only\r
7840e3c2 100@rem\r
0b412520
MV
101@rem The decision itself is made in ONE place, setup-windows-steps.ps1, which\r
102@rem marks each of these -X64Only with its reason. The plan shows them as [-]\r
103@rem with that reason before anything runs, and :want prints it again in the log\r
104@rem where the step would have run, so the skip and its reason stay together.\r
105@rem ---------------------------------------------------------------------------\r
106\r
7840e3c2 107@rem ---------------------------------------------------------------------------\r
0b412520
MV
108@rem The plan: what this run will do, shown BEFORE any of it happens\r
109@rem\r
110@rem setup-windows-plan.ps1 lists every step this file and its two halves can\r
111@rem take - from the catalog in setup-windows-steps.ps1, marked for this host's\r
112@rem architecture and elevation - and lets you switch steps off and on. Nothing\r
113@rem below runs unless it was on that list and still selected when you pressed\r
114@rem Enter; with no console to ask on, it stops unless given -Yes. Arguments to\r
115@rem this script go straight to the planner:\r
116@rem setup-windows.bat -WhatIf show the plan, do nothing\r
117@rem setup-windows.bat -Skip VirtualBox,Brave start with those switched off\r
118@rem setup-windows.bat -Only E -Yes just the elevated half, no questions\r
119@rem\r
120@rem The choice comes back as a scratch .cmd of `set` lines, called here:\r
121@rem STEP_<Id>, WHY_<Id>, PKG_<Id> per step this file takes - see :want\r
122@rem UAC_STEPS, USER_STEPS comma lists passed to the halves as -Only\r
123@rem PLAN_RUN, PLAN_RC go on (1), or stop and exit with PLAN_RC\r
124@rem No file at all means the planner failed - it exits 0 even on a mistyped\r
125@rem parameter - so that stops the run too, before anything has been done.\r
126@rem ---------------------------------------------------------------------------\r
127set "PLAN_ARGS=%*"\r
128if "%~1"=="/?" set "PLAN_ARGS=-Help"\r
129if "%~1"=="-?" set "PLAN_ARGS=-Help"\r
130set "PLAN_CMD=%TEMP%\setup-windows-plan-%RANDOM%%RANDOM%.cmd"\r
131if exist "%PLAN_CMD%" del "%PLAN_CMD%"\r
132powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-plan.ps1" -HostArch "%HOST_ARCH%" -Elevation "%ELEV%" -OutFile "%PLAN_CMD%" %PLAN_ARGS%\r
133if not exist "%PLAN_CMD%" (\r
134 echo [setup-windows] No plan was made ^(see above^), so nothing has been done.\r
135 exit /b 1\r
136)\r
137call "%PLAN_CMD%"\r
138del "%PLAN_CMD%"\r
139if not "%PLAN_RUN%"=="1" exit /b %PLAN_RC%\r
140\r
141@rem --- Claude Code, from Anthropic's own installer rather than winget ---\r
142@rem\r
143@rem NOT `winget install Anthropic.ClaudeCode` any more: recent releases are\r
144@rem broken through that route. This is the documented native install,\r
145@rem `irm https://claude.ai/install.ps1 | iex`. The script downloads the current\r
146@rem release's claude.exe, checks it against the SHA-256 in that release's\r
147@rem manifest, and runs `claude install`, which puts the launcher in\r
148@rem %USERPROFILE%\.local\bin - per-user, no elevation. Re-running it updates to\r
149@rem the latest release.\r
150@rem\r
151@rem Two things are fixed up around it, both about which build it gets:\r
152@rem - It picks win32-arm64 or win32-x64 from $env:PROCESSOR_ARCHITECTURE and\r
153@rem nothing else - the variable the block at the top of this file shows to be\r
154@rem WRONG in an emulated x64 shell on ARM64, where it would hand you the\r
155@rem emulated build. So %HOST_ARCH%, the registry's answer, is written into it\r
156@rem for the installer's process first. That only corrects the value; anything\r
157@rem else in that process that reads it sees the truth too.\r
158@rem - It refuses a 32-bit PowerShell outright ("does not support 32-bit\r
159@rem Windows"), and a bare `powershell` from a 32-bit cmd.exe is exactly that.\r
160@rem %SystemRoot%\Sysnative reaches the 64-bit one from there - it exists only\r
161@rem for 32-bit processes - so it is used when present.\r
162@rem\r
163@rem A copy left behind by the old winget route is a second claude.exe, reached\r
164@rem through WinGet\Links on the user PATH, and it can win over this one. It is\r
165@rem reported, not removed: `winget uninstall Anthropic.ClaudeCode` is yours to run.\r
166@rem\r
167@rem The installer has no "already current" check - it downloads and reinstalls\r
168@rem the whole release every time - so setup-windows-current.ps1 asks first: is\r
169@rem the native claude.exe already the latest release, and the build for this\r
170@rem host? Only a clear yes skips it; a check that cannot decide installs.\r
171set "PS64=powershell"\r
172if exist "%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" set "PS64=%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe"\r
173call :want ClaudeCode || goto :after_claude\r
174"%PS64%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-current.ps1" -Step ClaudeCode -HostArch "%HOST_ARCH%"\r
175if "%ERRORLEVEL%"=="0" goto :claude_leftover\r
176echo [setup-windows] ClaudeCode: irm https://claude.ai/install.ps1 ^| iex ^(as %HOST_ARCH%^)\r
177"%PS64%" -NoProfile -ExecutionPolicy Bypass -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $env:PROCESSOR_ARCHITECTURE = '%HOST_ARCH%'; irm https://claude.ai/install.ps1 | iex"\r
178if not "%ERRORLEVEL%"=="0" echo [setup-windows] WARNING: the Claude Code installer failed ^(exit %ERRORLEVEL%^); see above. Continuing.\r
179:claude_leftover\r
180set "CLAUDE_WINGET="\r
181if exist "%LOCALAPPDATA%\Microsoft\WinGet\Links\claude.exe" set "CLAUDE_WINGET=1"\r
182for /d %%D in ("%LOCALAPPDATA%\Microsoft\WinGet\Packages\Anthropic.ClaudeCode_*") do set "CLAUDE_WINGET=1"\r
183if defined CLAUDE_WINGET (\r
184 echo [setup-windows] WARNING: Claude Code is ALSO installed through winget ^(Anthropic.ClaudeCode^), the\r
185 echo [setup-windows] route this script no longer uses. That claude.exe can win over the new one on\r
186 echo [setup-windows] the PATH. Remove it with: winget uninstall Anthropic.ClaudeCode\r
187)\r
188:after_claude\r
7840e3c2 189\r
2e281421 190@rem --- Non-admin (per-user) winget installs ---\r
649b04df
MV
191@rem\r
192@rem No --architecture anywhere on purpose. winget already picks the best\r
193@rem installer the manifest offers for this machine - arm64, else x64, else x86 -\r
194@rem and pinning it would turn "no native build, use the emulated one" into a\r
195@rem hard failure.\r
196@rem\r
197@rem These all publish real ARM64 installers, so on an ARM64 box every one of\r
198@rem them lands as a native ARM64 binary with no emulation involved.\r
199@rem\r
200@rem The Sysinternals packages are a multi-architecture ZIP rather than an\r
201@rem installer, so the ARM64 build is IN there but is not the default-named exe:\r
202@rem the zip holds procexp.exe (x86), procexp64.exe (x64) and procexp64a.exe\r
203@rem (ARM64). On ARM64, run the *64a.exe variants - plain procexp.exe is the x86\r
204@rem one and will run emulated.\r
0b412520
MV
205@rem\r
206@rem One :winget call per package, each named by its step in the plan, so any of\r
207@rem them can be switched off. Brave is x64-only: the emulated x64 browser is the\r
208@rem slowest thing on an ARM64 box, and Edge is in-box and native there.\r
209call :winget Brave Brave.Brave\r
210call :winget Git Git.Git\r
211call :winget PowerShell Microsoft.PowerShell\r
212call :winget ProcessExplorer Microsoft.Sysinternals.ProcessExplorer\r
213call :winget ProcessMonitor Microsoft.Sysinternals.ProcessMonitor\r
214call :winget SDelete Microsoft.Sysinternals.SDelete\r
215call :winget VSCode Microsoft.VisualStudioCode\r
dd9ba4de
MV
216\r
217@rem --- Windows Terminal, after registering the framework package it needs ---\r
218@rem\r
219@rem Terminal is an MSIX, and its manifest depends on the Microsoft.UI.Xaml.2.8\r
220@rem framework package (>= 8.2306.22001.0), which winget installs first, from the\r
221@rem 8.2310.30001.0 appx. That goes wrong when a NEWER build of the framework is\r
222@rem already on the machine but not registered for this account - measured on\r
223@rem the Windows 10 box, winget 1.29.380, with 8.2501.31001.0 staged and none\r
224@rem registered for the account running this:\r
225@rem - Windows stages nothing ("A higher version 8.2501.31001.0 of this package\r
226@rem is already installed", AppXDeploymentServer event 735) and reports the\r
227@rem stage as a success;\r
228@rem - winget then registers the 8.2310 package it believes it staged, by full\r
229@rem name, and there is no such package (0x80073CF1);\r
230@rem - which it prints as\r
231@rem Installer failed with exit code: 0x80070002 : The system cannot\r
232@rem find the file specified.\r
233@rem and Terminal itself is never attempted. On every run, since a failed\r
234@rem run changes nothing.\r
235@rem\r
236@rem So register the build that is already there for this account first - by\r
237@rem family name, which takes no version and no elevation. winget then finds the\r
238@rem dependency met and installs Terminal alone. Asked only when this account\r
239@rem has none registered, the case measured, so a re-run - Terminal and its\r
240@rem framework both in place - changes nothing.\r
241@rem\r
242@rem With none on the machine either, the registration fails with that same\r
243@rem 0x80073CF1, and that is not worth a line: it is the fresh box, where nothing\r
244@rem is in winget's way and it installs the framework itself. Any other failure\r
245@rem is reported, and winget still gets its turn.\r
246@rem\r
247@rem %PS64% because it is Windows PowerShell: the Appx module does not load in\r
248@rem PowerShell 7 here ("Operation is not supported on this platform").\r
249call :want WindowsTerminal || goto :after_terminal\r
250"%PS64%" -NoProfile -ExecutionPolicy Bypass -Command "$f = 'Microsoft.UI.Xaml.2.8'; if (-not (Get-AppxPackage $f)) { try { Add-AppxPackage -RegisterByFamilyName -MainPackage ($f + '_8wekyb3d8bbwe') -ErrorAction Stop; Write-Host ('[setup-windows] WindowsTerminal: registered ' + $f + ' ' + @(Get-AppxPackage $f)[0].Version + ', already on this machine, for this account.') } catch { if ($_.Exception.Message -notmatch '0x80073CF1') { Write-Host ('[setup-windows] WARNING: could not register ' + $f + ' for this account: ' + ($_.Exception.Message -split '\r?\n')[0]) } } }"\r
0b412520 251call :winget WindowsTerminal Microsoft.WindowsTerminal\r
dd9ba4de
MV
252:after_terminal\r
253\r
0b412520 254call :winget Python Python.Python.3.13\r
fbe899b6
MV
255\r
256@rem WinMerge is the one package here where winget's own preference order picks\r
257@rem the WRONG architecture, and it is worth knowing why: upstream publishes the\r
258@rem ARM64 build as a MACHINE-scope installer (WinMerge-<ver>-ARM64-Setup.exe) and\r
259@rem the x64 build as a PER-USER one (WinMerge-<ver>-x64-PerUser-Setup.exe). Run\r
260@rem unelevated, winget's scope preference beats its architecture preference and\r
261@rem you silently get the emulated x64 build.\r
262@rem\r
7840e3c2
MV
263@rem That asymmetry is why ARM64 no longer installs it at all: the only build a\r
264@rem normal user can install there is the emulated one, and asking for --architecture\r
265@rem arm64 just fails unless the run is elevated. VS Code's diff view and\r
266@rem `git difftool` cover the same ground natively.\r
0b412520 267call :winget WinMerge WinMerge.WinMerge\r
3c096107 268\r
649b04df
MV
269@rem --- Build drivers, standalone and native ---\r
270@rem\r
271@rem CMake and Ninja BOTH ship with Visual Studio, so this is not about making\r
272@rem them exist - it is about which binary you get and where it is on the PATH:\r
273@rem\r
274@rem - VS's cmake.exe is already ARM64, but it only lands on the PATH inside a\r
275@rem Developer Command Prompt. The build line further down assumes a plain\r
276@rem `cmake`, so install the standalone one to make that true in any shell.\r
277@rem - VS's bundled ninja.exe is x64 EVEN ON ARM64 (verified on this box), so it\r
278@rem runs under emulation. Ninja is re-invoked for every edge in the build\r
279@rem graph, which makes it the one tool here where emulation is actually paid\r
280@rem over and over, and ninja-winarm64.zip is a native build. Worth having.\r
281@rem\r
3e0aa9fc
MV
282@rem Which ninja wins inside a VS Developer Command Prompt: the native one. VS\r
283@rem adds its copy from Common7\Tools\vsdevcmd\ext\cmake.bat with\r
284@rem set "PATH=%PATH%;...\CMake\bin;...\CMake\Ninja"\r
285@rem which APPENDS, so the VS directories sit at the very end of the composed\r
286@rem PATH, behind every machine and user entry. setup-windows-no-uac.ps1 then\r
287@rem pins the native ninja's directory to the FRONT of the user PATH so the\r
288@rem margin does not depend on two append orders staying as they are.\r
cd774a19
MV
289@rem\r
290@rem Ninja here, CMake in the machine-wide group below: Ninja is a portable zip\r
291@rem winget unpacks into the user profile, CMake is an MSI that installs for the\r
292@rem whole machine.\r
0b412520 293call :winget Ninja Ninja-build.Ninja\r
649b04df 294\r
7840e3c2 295@rem --- Second, independent Clang: compiler diversity, on x64 only ---\r
649b04df 296@rem\r
7840e3c2
MV
297@rem On x64 the elevated half installs Visual Studio's Clang component (clang-cl).\r
298@rem This is a DIFFERENT LLVM: the upstream release, on its own schedule and\r
299@rem usually several major versions ahead of the one VS bundles, installed to\r
649b04df
MV
300@rem C:\Program Files\LLVM rather than inside the VS tree. Two Clang majors plus\r
301@rem MSVC over the same sources is the point - it is what catches the bugs a\r
302@rem single toolchain agrees with itself about.\r
303@rem\r
7840e3c2
MV
304@rem NOT ON ARM64, and not for lack of a native build - the manifest does resolve\r
305@rem to LLVM-<ver>-woa64.exe there. It is dropped because the ARM64 box carries\r
306@rem MSVC alone: with VS's clang-cl component gone too, a third compiler with\r
307@rem nothing to disagree with is just disk. Do the multi-compiler build on the\r
308@rem x64 machine, which keeps all three.\r
649b04df 309@rem\r
7840e3c2
MV
310@rem PATH ORDER MATTERS on x64 if you use it. Should this installer put its bin on\r
311@rem the PATH, a bare `clang-cl` resolves to the upstream one while CMake's\r
649b04df
MV
312@rem `-T ClangCL` toolset keeps using the VS copy - so the two can be selected\r
313@rem independently, but only if you know which one you are asking for. Prefer\r
314@rem -DCMAKE_CXX_COMPILER with a full path when you care.\r
0b412520
MV
315call :winget LLVM LLVM.LLVM\r
316\r
317@rem --- Rust, via rustup ---\r
318@rem\r
319@rem Per-user on both architectures: rustup-init keeps rustup and every\r
320@rem toolchain it manages under %USERPROFILE%\.cargo and \.rustup, and puts\r
321@rem .cargo\bin on the user PATH. The manifest publishes an aarch64 rustup-init,\r
322@rem so ARM64 gets a native rustup.\r
323@rem\r
324@rem winget runs it as `rustup-init -y -q`, which also installs a stable\r
325@rem toolchain for whatever host rustup detects. Which toolchain is the DEFAULT -\r
326@rem stable MSVC for this host, x86_64 or aarch64 - is then pinned explicitly by\r
327@rem the RustToolchain step in setup-windows-no-uac.ps1 rather than left to that\r
328@rem detection; see Set-RustDefaultToolchain there for why.\r
329@rem\r
330@rem The -msvc toolchains link with MSVC's link.exe, so building needs the Visual\r
331@rem Studio the elevated half installs. Installing rustup before it is fine:\r
332@rem with -y, rustup-init only warns "installing msvc toolchain without its\r
333@rem prerequisites" (rustup 1.29.1, src/cli/self_update/windows.rs) - it does not\r
334@rem go and install a Visual Studio of its own.\r
335@rem\r
336@rem Safe to re-run as it stands. rustup registers itself in Add/Remove Programs\r
337@rem under the key "Rustup" - the manifest's ProductCode - and rewrites its\r
338@rem DisplayVersion when it updates itself, so winget always sees the version\r
339@rem that is really there and only runs rustup-init again for a newer one.\r
340call :winget Rustup Rustlang.Rustup\r
649b04df 341\r
cd774a19
MV
342@rem x64-only manifest: winget falls back to the x64 installer on ARM64 and it runs\r
343@rem under Prism emulation. Harmless for what it does here - iperf3 is a network\r
344@rem benchmark bounded by the link, not by CPU.\r
0b412520
MV
345call :winget iPerf3 ar51an.iPerf3\r
346\r
347@rem Android SDK Platform-Tools: adb and fastboot. A portable zip, so per-user:\r
348@rem winget unpacks it under %LOCALAPPDATA%\Microsoft\WinGet\Packages and puts\r
349@rem that directory on the user PATH, rather than linking the exes one by one,\r
350@rem because adb.exe needs AdbWinApi.dll and AdbWinUsbApi.dll beside it.\r
351@rem\r
352@rem The manifest labels it x64, but every binary in the zip is 32-bit x86\r
353@rem (machine type 0x014C, checked on r37.0.1), and there is no ARM64 build. So it\r
354@rem runs under WOW64 on x64 and under x86 emulation on ARM64 - which, unlike x64\r
355@rem emulation, every Windows on Arm has. Kept on ARM64 anyway, for the iperf3\r
356@rem reason: adb is bound by the USB or TCP link to the device, not by the CPU.\r
357call :winget PlatformTools Google.PlatformTools\r
649b04df
MV
358\r
359@rem NASM assembles x86/x86-64 only - there is no ARM64 target in it and no ARM64\r
7840e3c2
MV
360@rem build of it. x64 keeps it; ARM64 does not, because an emulated assembler is\r
361@rem only worth carrying if you cross-compile x86/x64 here, and that work belongs\r
362@rem on the x64 box. The ARM64 assembler is armasm64.exe, which ships with MSVC.\r
363@rem\r
364@rem Its installer is also one of the two here that fall back to a per-user\r
365@rem directory when it cannot write to Program Files - C:\Users\<you>\AppData\\r
366@rem Local\bin\NASM, registered nowhere, so `winget uninstall` cannot find it\r
367@rem afterwards. Uninstall.exe in that directory is the only way back out.\r
0b412520
MV
368@rem\r
369@rem Registered nowhere also means winget cannot see it is installed, so it\r
370@rem reruns the installer on every run - measured, with the same 3.02 on both\r
371@rem sides. setup-windows-current.ps1 compares that per-user nasm.exe with the\r
372@rem version winget offers first, and only a match skips the install. A\r
373@rem machine-wide NASM is registered, so it is left to winget as usual.\r
374call :want NASM || goto :after_nasm\r
375"%PS64%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-current.ps1" -Step NASM -HostArch "%HOST_ARCH%"\r
376if not "%ERRORLEVEL%"=="0" call :winget NASM NASM.NASM\r
377:after_nasm\r
649b04df
MV
378\r
379@rem VirtualBox has no ARM64 Windows host build, and the x64 one cannot be made to\r
380@rem work by emulation: a hypervisor is kernel-mode, and its x64 driver will not\r
381@rem load on an ARM64 kernel. Installing it on ARM64 gets you a broken product and\r
0b412520 382@rem a failed driver install, so the plan marks it x64-only and says so.\r
649b04df
MV
383@rem\r
384@rem This is also what makes setup-windows-7-test-env.bat unreachable from an\r
385@rem ARM64 host: it prepares a Windows 7 *x86* guest, and neither VirtualBox nor\r
386@rem Hyper-V on ARM64 can run one.\r
325b494c
MV
387@rem\r
388@rem Nor is it installed when this Windows is itself a virtual machine, which the\r
389@rem planner reads from the firmware's SMBIOS strings: the test VMs belong on the\r
390@rem physical host, and nesting VirtualBox inside a guest needs nested\r
391@rem virtualization the outer hypervisor usually does not offer. The plan marks it\r
392@rem -NotInVM, with that reason, and names the hypervisor it found.\r
0b412520 393call :winget VirtualBox Oracle.VirtualBox\r
649b04df 394\r
cd774a19
MV
395@rem ---------------------------------------------------------------------------\r
396@rem Machine-wide installers, despite sitting in the "per-user" section\r
397@rem\r
398@rem These four are not per-user at all. On an account that cannot elevate they\r
399@rem fail on every run, with installer exit codes that say nothing useful:\r
400@rem Microsoft.DotNet.SDK.10 exit 5 (ERROR_ACCESS_DENIED)\r
401@rem Kitware.CMake exit 1603 (generic MSI failure)\r
402@rem Google.AndroidGPUInspector exit 1603\r
403@rem OpenCppCoverage exit 1 (its installer self-elevates)\r
404@rem\r
405@rem Grouped and skipped outright where elevation is impossible. Attempting a\r
406@rem guaranteed failure four times per run - and paying the download for it -\r
407@rem teaches nobody anything, and the four opaque error codes bury the one line\r
408@rem that matters. On an account that can elevate they run exactly as before.\r
0b412520
MV
409@rem The four are -Admin in setup-windows-steps.ps1, so on such an account the\r
410@rem plan shows them as unavailable, with the reason, and :want skips each one.\r
411@rem Re-run from an administrator account to get them.\r
cd774a19
MV
412@rem\r
413@rem NOTE none of these is a build-blocker: the .NET SDK is only here for the WiX\r
414@rem MSI tooling, CMake also ships inside Visual Studio, AGI profiles Android\r
415@rem devices, and OpenCppCoverage cannot instrument ARM64 binaries anyway.\r
7840e3c2
MV
416@rem\r
417@rem On ARM64 the group is just the first two - AGI and OpenCppCoverage are in the\r
418@rem x64-only set, so there they are skipped before elevation is even consulted.\r
0b412520
MV
419@rem AGI has no ARM64 build, and it profiles Android devices rather than\r
420@rem anything compiled here, so emulating it is not worth the disk.\r
cd774a19 421@rem ---------------------------------------------------------------------------\r
0b412520
MV
422call :winget DotNetSdk Microsoft.DotNet.SDK.10\r
423call :winget CMake Kitware.CMake\r
424call :winget AndroidGPUInspector Google.AndroidGPUInspector\r
cd774a19 425\r
4776737b
MV
426@rem --- Android Studio ---\r
427@rem\r
428@rem Machine-wide: the manifest's NSIS installer elevates itself and installs to\r
429@rem %ProgramFiles%\Android\Android Studio. x64 only - there is no ARM64 build -\r
430@rem so the plan keeps it off ARM64.\r
431@rem\r
432@rem Installed only when it is missing, which setup-windows-current.ps1 decides.\r
433@rem Android Studio updates itself, in the app, and the winget package is published\r
434@rem with UpgradeBehavior: deny and RequireExplicitUpgrade, so winget would never\r
435@rem upgrade it anyway. What the check prevents is the other case: a Studio that\r
436@rem was installed some other way - as on the x64 laptop, where winget lists it\r
437@rem only by its Add/Remove Programs key - is one winget cannot match to the\r
438@rem package, so `winget install` would run the installer over it.\r
439@rem\r
440@rem It is also where the per-user Android SDK step gets its JDK: Studio bundles\r
441@rem one (jbr), and the SDK's sdkmanager and Gradle both need Java.\r
442call :want AndroidStudio || goto :after_studio\r
443"%PS64%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-current.ps1" -Step AndroidStudio -HostArch "%HOST_ARCH%"\r
444if not "%ERRORLEVEL%"=="0" call :winget AndroidStudio Google.AndroidStudio\r
445:after_studio\r
446\r
3c096107 447@rem OpenCppCoverage: native (PE) line coverage for the C++ binaries. run-coverage-occ.py drives the\r
452f525c 448@rem pytest suite under it to produce an HTML report (the binaries under test build with PDBs,\r
3c096107 449@rem which it reads). The installer elevates via UAC.\r
649b04df
MV
450@rem\r
451@rem x86/x64 only, and unlike the other emulated tools that is a real limit rather\r
452@rem than a slowdown: it collects coverage by debugging the process under test and\r
7840e3c2
MV
453@rem stepping x86/x64 instructions, so it can cover x86/x64 binaries but NOT an\r
454@rem ARM64 one. That is what puts it in the x64-only set: on ARM64 it could only\r
455@rem ever cover the cross-compiled x64 build, which is a job for the x64 box.\r
0b412520 456call :winget OpenCppCoverage OpenCppCoverage.OpenCppCoverage\r
3c096107 457\r
aa6eb783
MV
458@rem --- WiX 5.0.2, pinned on purpose ---\r
459@rem WiX packages our proprietary software into MSIs, and the version is pinned to keep that\r
460@rem free of a fee. 5.0.2 is the last release distributed under the Microsoft Reciprocal\r
461@rem License alone. From 6.0 onward the package also carries OSMFEULA.txt, an Open Source\r
462@rem Maintenance Fee agreement: a monthly fee owed by anyone who uses the PREBUILT BINARIES\r
463@rem as part of revenue-generating activity and has annual gross revenue >= US$10,000.\r
464@rem\r
465@rem It is a fee for the binaries, not a restriction on what we ship - MS-RL is file-scoped\r
466@rem and never reached the MSIs WiX builds, under any version - but 5.0.2 owes nothing.\r
467@rem The 6.x/7.x SOURCE is still MS-RL too, so self-compiling is another way out; a pin is\r
468@rem the cheaper one. This replaces `winget install WiXToolset.WiXCLI`, which has no version\r
469@rem selector and so installs the latest (7.0.0 today, EULA and all).\r
470@rem\r
471@rem PIN THE MSBUILD SIDE TOO. A .wixproj referencing WixToolset.Sdk without a version\r
472@rem resolves to the latest - 7.x, same EULA - and nothing here constrains it. Pin it in the\r
473@rem project: <Project Sdk="WixToolset.Sdk/5.0.2">.\r
474@rem\r
475@rem dotnet.exe is called by full path: winget put the SDK on the machine PATH a few lines\r
476@rem ago, but this cmd session inherited its environment before that and cannot see it.\r
477@rem install-then-update is for re-runs - install fails once the tool is there, and update\r
478@rem then holds it at exactly 5.0.2 - which keeps this script idempotent like the rest.\r
649b04df 479@rem\r
0b412520
MV
480@rem But update reinstalls even when 5.0.2 is already what is there, so first look in the\r
481@rem global tool store, which keeps one directory per installed version under\r
482@rem %USERPROFILE%\.dotnet\tools\.store\wix. With 5.0.2 there and its shim in place, nothing\r
483@rem runs; install-then-update is left for a first install, or for pulling a tool that has\r
484@rem drifted to another version back to the pin.\r
485@rem\r
649b04df
MV
486@rem %ProgramFiles%\dotnet is the NATIVE SDK's root on every architecture, ARM64\r
487@rem included - what winget just installed. An x64 SDK installed alongside it on\r
488@rem an ARM64 box goes to %ProgramFiles%\dotnet\x64 instead, so fall back there\r
489@rem for a machine that only ever got the x64 one. Prefer the native root: the\r
490@rem ARM64 SDK runs the wix tool natively, and building an MSI is not something\r
491@rem worth doing through emulation.\r
0b412520
MV
492call :want WiX || goto :after_wix\r
493set "WIX_VERSION=5.0.2"\r
494set "WIX_SHIM=%USERPROFILE%\.dotnet\tools\wix.exe"\r
495if exist "%USERPROFILE%\.dotnet\tools\.store\wix\%WIX_VERSION%\" if exist "%WIX_SHIM%" (\r
496 echo [setup-windows] WiX: %WIX_VERSION% is already the installed version; not reinstalling.\r
497 goto :wix_report\r
498)\r
aa6eb783 499set "DOTNET_EXE=%ProgramFiles%\dotnet\dotnet.exe"\r
649b04df 500if not exist "%DOTNET_EXE%" set "DOTNET_EXE=%ProgramFiles%\dotnet\x64\dotnet.exe"\r
0b412520 501if not exist "%DOTNET_EXE%" echo [setup-windows] WARNING: dotnet.exe not found; skipping the WiX %WIX_VERSION% pin.\r
649b04df
MV
502if not exist "%DOTNET_EXE%" goto :after_wix\r
503\r
0b412520 504"%DOTNET_EXE%" tool install --global wix --version %WIX_VERSION% || "%DOTNET_EXE%" tool update --global wix --version %WIX_VERSION%\r
aa6eb783
MV
505\r
506@rem Report what the pin actually produced. By full path again, and because the shim lands in\r
507@rem a directory this session's PATH predates: expect "5.0.2+<commit>", not 7.x.\r
0b412520
MV
508:wix_report\r
509"%WIX_SHIM%" --version\r
aa6eb783 510\r
649b04df
MV
511:after_wix\r
512\r
3c096107
MV
513@rem ---------------------------------------------------------------------------\r
514@rem No package manager needed for the Windows build\r
515@rem\r
516@rem Just:\r
517@rem cd windows && cmake -B build -G "Visual Studio 17 2022" -A x64 && cmake --build build --config Release\r
649b04df
MV
518@rem\r
519@rem -A selects the TARGET, independently of the host. On an ARM64 box the same\r
520@rem generator cross-compiles all three, and MSVC's ARM64-hosted toolchain is used\r
521@rem for each - nothing is emulated:\r
522@rem -A ARM64 native ARM64 binaries\r
523@rem -A x64 x64 binaries (run here under emulation)\r
524@rem -A Win32 x86 binaries\r
525@rem Use the generator matching the Visual Studio the elevated half installed -\r
526@rem "Visual Studio 17 2022" on x64, "Visual Studio 18 2026" on ARM64 - or just\r
527@rem let CMake pick the newest it finds.\r
3c096107
MV
528@rem ---------------------------------------------------------------------------\r
529\r
649b04df 530@rem --- Elevated installs (Visual Studio, WDK, system tools) ---\r
3c096107
MV
531@rem The elevated script runs in its own window and logs to setup-windows-uac.log.\r
532@rem -PassThru + $p.ExitCode propagates its real exit code back through to ERRORLEVEL.\r
533set "UAC_LOG=%~dp0setup-windows-uac.log"\r
3c096107 534\r
cd774a19
MV
535@rem %ELEV% was worked out at the top of this script - see the comment there for\r
536@rem why `Start-Process -Verb RunAs` cannot be trusted to report this itself.\r
0b412520
MV
537@rem\r
538@rem It is handed exactly the elevated steps the plan left selected (-Only, as one\r
539@rem comma list, which it splits) and -Yes, since the question was asked before\r
540@rem anything ran. With none selected there is nothing to elevate FOR, so no\r
541@rem prompt is raised at all.\r
cd774a19 542if "%ELEV%"=="NOLUA" goto :elev_impossible\r
0b412520
MV
543if not defined UAC_STEPS goto :elev_not_selected\r
544\r
545@rem The last run's log goes only now, when a new one is about to replace it: a\r
546@rem run that leaves the elevated half out keeps the log of the one that did it.\r
547if exist "%UAC_LOG%" del "%UAC_LOG%"\r
cd774a19
MV
548if "%ELEV%"=="ALREADY" goto :elev_direct\r
549\r
0b412520
MV
550@rem Not elevated, UAC is on: request it. Expect a prompt - a Yes/No for an\r
551@rem administrator, and for a standard account one that asks for an\r
552@rem administrator's name and password, after which the elevated half runs as\r
553@rem THAT account.\r
554@rem\r
555@rem Process.Start with the runas verb, NOT Start-Process -Verb RunAs, which this\r
556@rem line used to be. Two reasons:\r
557@rem - When Start-Process fails - a cancelled prompt, most often - `$p` stays\r
558@rem empty and `exit $p.ExitCode` exited 0: a failed elevation reported as a\r
559@rem successful one.\r
560@rem - It cannot be told apart even when caught. Windows PowerShell's\r
561@rem Start-Process rethrows the failure as a plain InvalidOperationException\r
562@rem and drops the Win32Exception that says why (checked: a missing program\r
563@rem came back as exit 1, not 2). Process.Start keeps it, so the exit code\r
564@rem here is the real Windows error - 1223, ERROR_CANCELLED, for a declined\r
565@rem prompt - and the message is printed where it happens.\r
566@rem If the elevated process does start, its own exit code comes back as before.\r
567@rem Arguments is one string, passed through verbatim - the same hand-built form\r
568@rem Invoke-VsModify uses in the elevated half.\r
569@rem\r
570@rem NO QUOTE CHARACTERS INSIDE THE -Command STRING. This line used to wrap the\r
571@rem script path in "" pairs, and powershell.exe's command-line parser does not\r
572@rem read those the way they look: the first "" yields a quote but leaves the\r
573@rem quoted region, and the second - now outside it - yields nothing at all. The\r
574@rem path's CLOSING quote vanished. That went unnoticed while the path was the\r
575@rem last argument, because an unclosed quote simply runs to the end of the line.\r
576@rem Once -Only and -Yes followed it, they were swallowed into the -File path,\r
577@rem and the elevated PowerShell refused a script that did not exist (exit\r
578@rem -196608) before running a single line: no transcript, and a window that\r
579@rem closed at once. Measured with a stand-in that records its own command line:\r
580@rem -File "C:\...\setup-windows-with-uac.ps1 -Only OpenSshClient,... -Yes\r
581@rem So the path and the step list travel as environment variables, and the one\r
582@rem quote the child needs is [char]34, made inside PowerShell where no parser in\r
583@rem between can eat it.\r
cd774a19 584echo [setup-windows] Requesting elevation ^(expect a UAC prompt^)...\r
0b412520
MV
585set "UAC_SCRIPT=%~dp0setup-windows-with-uac.ps1"\r
586powershell -NoProfile -Command "try { $q = [char]34; $i = New-Object Diagnostics.ProcessStartInfo 'powershell'; $i.Verb = 'runas'; $i.UseShellExecute = $true; $i.Arguments = '-NoProfile -ExecutionPolicy Bypass -File ' + $q + $env:UAC_SCRIPT + $q + ' -Only ' + $env:UAC_STEPS + ' -Yes'; $p = [Diagnostics.Process]::Start($i); $p.WaitForExit(); exit $p.ExitCode } catch { $e = $_.Exception; while ($e -and $e -isnot [ComponentModel.Win32Exception]) { $e = $e.InnerException }; Write-Host ('[setup-windows] Could not start the elevated half: ' + $(if ($e) { $e.Message } else { $_.Exception.Message })); if ($e) { exit $e.NativeErrorCode } else { exit 1 } }"\r
3c096107 587set "UAC_RC=%ERRORLEVEL%"\r
cd774a19
MV
588goto :elev_done\r
589\r
590:elev_direct\r
591echo [setup-windows] Already running elevated; running the elevated half directly.\r
0b412520 592powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-with-uac.ps1" -Only %UAC_STEPS% -Yes\r
cd774a19
MV
593set "UAC_RC=%ERRORLEVEL%"\r
594goto :elev_done\r
595\r
0b412520
MV
596:elev_not_selected\r
597echo.\r
598echo [setup-windows] No elevated steps were selected, so no elevation is requested.\r
599set "UAC_RC=NOTSELECTED"\r
600goto :elev_done\r
601\r
cd774a19
MV
602:elev_impossible\r
603echo.\r
604echo [setup-windows] SKIPPING the elevated half: this account cannot elevate.\r
605echo [setup-windows] UAC is disabled machine-wide ^(EnableLUA = 0^) and you are not an administrator,\r
606echo [setup-windows] so Windows offers no way to elevate - there is no prompt to accept. With UAC off,\r
607echo [setup-windows] 'Start-Process -Verb RunAs' is silently ignored and reports success, which is why\r
608echo [setup-windows] this used to look like a cancelled prompt.\r
609echo [setup-windows]\r
7840e3c2
MV
610echo [setup-windows] Not installed: Visual Studio and its MSVC toolchain, rsync, and the OpenSSH\r
611echo [setup-windows] server. Everything per-user above is unaffected.\r
cd774a19
MV
612echo [setup-windows]\r
613echo [setup-windows] To finish the box, either sign in to an administrator account and re-run this\r
614echo [setup-windows] script, or have an administrator run setup-windows-with-uac.ps1 there. Then\r
615echo [setup-windows] re-run setup-windows-no-uac.ps1 as yourself, so the per-user PATH and .gitconfig\r
616echo [setup-windows] land in YOUR profile rather than the administrator's.\r
617echo.\r
618set "UAC_RC=SKIPPED"\r
619\r
620:elev_done\r
3c096107
MV
621\r
622@rem --- Surface the elevated session's output (its window has already closed) ---\r
cd774a19
MV
623@rem\r
624@rem A missing log is NOT self-explanatory, so do not guess at one cause. The\r
625@rem elevated script writes its transcript as almost its first act, so no log\r
0b412520
MV
626@rem means it never got as far as running. That has more than one cause: the\r
627@rem prompt was declined; it started unelevated and stopped on its own #Requires\r
628@rem line; or it was elevated and then refused to start in PowerShell's OWN\r
629@rem startup, before the script's first line. The last one is real: the lost\r
630@rem quote described at the launch line above produced it, and it was reported\r
631@rem here as a declined prompt when the prompt had in fact been accepted. The\r
632@rem exit code tells them apart, so report that instead of guessing.\r
cd774a19 633if "%UAC_RC%"=="SKIPPED" goto :after_uac_log\r
0b412520 634if "%UAC_RC%"=="NOTSELECTED" goto :after_uac_log\r
3c096107
MV
635if exist "%UAC_LOG%" (\r
636 echo.\r
637 echo ===== elevated setup log ^(%UAC_LOG%^) =====\r
638 type "%UAC_LOG%"\r
639 echo ===== end of elevated setup log =====\r
640) else (\r
641 echo [setup-windows] WARNING: no elevated log found at "%UAC_LOG%".\r
0b412520
MV
642 if "%UAC_RC%"=="1223" echo [setup-windows] The UAC prompt was declined or cancelled, so the elevated half never ran.\r
643 if not "%UAC_RC%"=="1223" if "%ELEV%"=="ALREADY" echo [setup-windows] The elevated half exited ^(code %UAC_RC%^) before writing its log; see its output above.\r
644 if not "%UAC_RC%"=="1223" if "%ELEV%"=="PROMPT" echo [setup-windows] The elevated half exited ^(code %UAC_RC%^) before it could write its log. Its window\r
645 if not "%UAC_RC%"=="1223" if "%ELEV%"=="PROMPT" echo [setup-windows] closes as it exits, so to read why, run it by hand from an Administrator prompt:\r
646 if not "%UAC_RC%"=="1223" if "%ELEV%"=="PROMPT" echo [setup-windows] powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-with-uac.ps1"\r
647 if "%UAC_RC%"=="-196608" echo [setup-windows] -196608 is PowerShell rejecting its own command line, e.g. a -File path that does not exist as given.\r
3c096107 648)\r
cd774a19 649:after_uac_log\r
3c096107 650\r
2bbc9b41
MV
651@rem --- Non-elevated PowerShell half ---\r
652@rem WinMerge on the user PATH, BinSkim, and the global git config (identity +\r
653@rem core.sshCommand -> a Win32-OpenSSH client, so git shares the Windows\r
654@rem ssh-agent). EDIT THE GIT IDENTITY at the top of setup-windows-no-uac.ps1\r
655@rem before the first run.\r
656@rem\r
657@rem Deliberately NOT elevated: every step writes per-user state (the HKCU PATH,\r
658@rem the .gitconfig under %USERPROFILE%), which the elevated half would write to\r
659@rem the administrator profile instead.\r
660@rem\r
661@rem AFTER the elevated half on purpose: core.sshCommand prefers the ssh.exe that\r
662@rem half unpacks beside rsync.exe - a push through the in-box client is capped\r
663@rem at ~17MB/s - and it can only prefer it once it is on disk. Run either way,\r
664@rem including when the elevated half failed above: nothing here depends on it,\r
665@rem and the fallback is the in-box client that Windows already has.\r
666@rem\r
667@rem Non-fatal: these are conveniences, and the elevated half is the part worth\r
668@rem the UAC prompt. A failure warns and provisioning continues.\r
0b412520
MV
669@rem\r
670@rem Handed the per-user steps the plan left selected, and -Yes, just as the\r
671@rem elevated half is; with none selected it is not started at all.\r
672if not defined USER_STEPS goto :user_not_selected\r
673powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-no-uac.ps1" -Only %USER_STEPS% -Yes\r
2bbc9b41 674if not "%ERRORLEVEL%"=="0" echo [setup-windows] WARNING: setup-windows-no-uac.ps1 reported a failure ^(see above^); continuing.\r
0b412520
MV
675goto :after_user_steps\r
676:user_not_selected\r
677echo [setup-windows] No per-user configuration steps were selected; setup-windows-no-uac.ps1 not run.\r
678:after_user_steps\r
2bbc9b41 679\r
cd774a19
MV
680@rem Skipping the elevated half is a reported, understood outcome on a box where\r
681@rem elevation is impossible - not a failure to exit non-zero over. The per-user\r
682@rem provisioning above did run, and re-running from an administrator account is\r
0b412520 683@rem the documented next step. Not selecting any of it is a choice, not a failure.\r
cd774a19 684if "%UAC_RC%"=="SKIPPED" goto :uac_reported\r
0b412520 685if "%UAC_RC%"=="NOTSELECTED" goto :uac_reported\r
3c096107
MV
686if not "%UAC_RC%"=="0" (\r
687 echo.\r
688 echo [setup-windows] ELEVATED SETUP FAILED ^(exit code %UAC_RC%^). See log above.\r
689 exit /b %UAC_RC%\r
690)\r
691echo.\r
692echo [setup-windows] Elevated setup completed successfully.\r
cd774a19
MV
693goto :uac_reported\r
694\r
695:uac_reported\r
696if "%UAC_RC%"=="SKIPPED" echo.\r
697if "%UAC_RC%"=="SKIPPED" echo [setup-windows] Per-user setup complete; the elevated half was skipped ^(see above^).\r
3c096107
MV
698\r
699@rem Removed: this doesn't work as well as I hoped, maybe try again later\r
700@rem -- Install Headroom ---\r
701@rem py -m pip install "headroom-ai[all]"\r
702@rem npm install headroom-ai\r
703\r
7840e3c2
MV
704@rem --- Find an interpreter by path, rather than trusting `py` ---\r
705@rem\r
706@rem `py -m pip install Pillow` used to be this line, and it failed with\r
707@rem 'py' is not recognized as an internal or external command\r
708@rem for two independent reasons, which look identical at the prompt:\r
709@rem\r
710@rem 1. PATH staleness. winget installed Python a few lines above, but this cmd\r
711@rem session inherited its environment before that, so nothing winget added\r
712@rem is visible here. Same problem the DOTNET_EXE lookup solves above, and it\r
713@rem would bite even where the launcher IS installed.\r
714@rem\r
715@rem 2. The launcher may simply not be there. Measured on the ARM64 box: every\r
716@rem other Python component registered - Core Interpreter, Executables,\r
717@rem Standard Library, pip Bootstrap, Tcl/Tk, Add to Path - and there is no\r
718@rem launcher entry and no py.exe anywhere on disk, while the Launcher\r
719@rem directory it would occupy is on the user PATH but does not exist.\r
720@rem\r
721@rem So resolve an interpreter by full path and prefer the launcher only when it\r
722@rem is real. python.exe is the thing that actually has to exist; `py` is a\r
723@rem convenience shim that picks between several of them, and there is exactly one\r
724@rem here. Reverse name order so the newest Python wins, and require python.exe\r
725@rem inside the directory - a half-removed version leaves the folder behind with\r
726@rem only Doc and Lib in it, which is exactly what this box has.\r
0b412520 727call :want Pillow || goto :after_pillow\r
7840e3c2
MV
728set "PY_EXE="\r
729if exist "%LOCALAPPDATA%\Programs\Python\Launcher\py.exe" set "PY_EXE=%LOCALAPPDATA%\Programs\Python\Launcher\py.exe"\r
730if not defined PY_EXE if exist "%WINDIR%\py.exe" set "PY_EXE=%WINDIR%\py.exe"\r
731if 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
732\r
733@rem Pillow publishes win_arm64 wheels alongside win_amd64, so the ARM64 Python\r
734@rem winget just installed gets a native wheel and never falls back to building\r
735@rem from source. Any package WITHOUT an ARM64 wheel does fall back, and that\r
736@rem source build needs the MSVC toolchain the elevated half installs.\r
737if not defined PY_EXE (\r
738 echo [setup-windows] WARNING: no Python interpreter found; skipping the Pillow install.\r
739 echo [setup-windows] Looked for the py launcher, then %LOCALAPPDATA%\Programs\Python\Python3*\python.exe.\r
740) else (\r
741 echo [setup-windows] Python: %PY_EXE%\r
742 "%PY_EXE%" -m pip install Pillow\r
743)\r
0b412520 744:after_pillow\r
649b04df
MV
745\r
746if "%IS_ARM64%"=="1" (\r
747 echo.\r
748 echo [setup-windows] ARM64 notes:\r
749 echo [setup-windows] native ARM64: Git, Python, .NET SDK, PowerShell, VS Code, Windows Terminal,\r
7840e3c2 750 echo [setup-windows] Sysinternals ^(the *64a.exe variants^), Claude Code, CMake,\r
0b412520
MV
751 echo [setup-windows] Ninja, rustup, and the SDK/WPT tools.\r
752 echo [setup-windows] compilers : MSVC from Visual Studio 2026, native ARM64 - the only C/C++\r
753 echo [setup-windows] compiler by design; clang-cl and upstream LLVM are x64-box only.\r
754 echo [setup-windows] Rust stable-aarch64-pc-windows-msvc, native, linking through MSVC.\r
755 echo [setup-windows] emulated : iperf3 ^(x64^) and adb ^(x86^), both bound by their link, not the CPU.\r
4776737b
MV
756 echo [setup-windows] not here : Brave, WinMerge, LLVM, NASM, AGI, OpenCppCoverage, BinSkim,\r
757 echo [setup-windows] the WDK and the Android SDK - dropped on ARM64; the plan above says why.\r
758 echo [setup-windows] unavailable : VirtualBox, the Windows 7 x86 test VM, Intel VTune, Android Studio,\r
649b04df
MV
759 echo [setup-windows] and the v141 / Windows XP targeting toolset.\r
760)\r
cd774a19
MV
761@rem The list above is what this script PROVIDES on ARM64, not necessarily what\r
762@rem landed on this run - so point at the audit, which reports the actual state.\r
763if "%ELEV%"=="NOLUA" (\r
764 echo [setup-windows] NOT on this box: everything needing elevation was skipped, including\r
7840e3c2 765 echo [setup-windows] Visual Studio's MSVC, the .NET SDK and CMake. The\r
cd774a19
MV
766 echo [setup-windows] architecture audit above lists what is really installed.\r
767)\r
7840e3c2
MV
768\r
769exit /b 0\r
770\r
771@rem ---------------------------------------------------------------------------\r
0b412520
MV
772@rem :want <StepId>\r
773@rem\r
774@rem Exit 0 if the plan selected this step, so the command after it runs.\r
775@rem Otherwise exit 1, first printing the reason if it is one that cannot run on\r
776@rem this machine at all. A step you simply switched off passes silently: the\r
777@rem plan said so a moment ago, and a line for each would bury the skips that\r
778@rem have a reason. Used as\r
779@rem call :want Id && <command>\r
780@rem call :want Id || goto :after_id\r
781@rem\r
782@rem It replaces :x64only and keeps that subroutine's rule: the reason is never\r
783@rem separated from the skip. The log line a future reader sees is the text the\r
784@rem plan showed, from setup-windows-steps.ps1 - still exactly one place to change\r
785@rem the policy - because the interesting case is reading the run log six months\r
786@rem later and wondering where NASM went.\r
787@rem\r
788@rem A step with no STEP_ line at all is one the catalog does not know, so the\r
789@rem two have drifted apart. It is refused rather than run: it was never on the\r
790@rem plan you agreed to.\r
791@rem\r
792@rem Delayed expansion, kept local, to read STEP_<Id> by a computed name and to\r
793@rem print the reason without cmd re-parsing an & | < > or paren inside it.\r
7840e3c2 794@rem ---------------------------------------------------------------------------\r
0b412520
MV
795:want\r
796setlocal EnableDelayedExpansion\r
797set "_state=!STEP_%~1!"\r
798if "!_state!"=="1" (endlocal & exit /b 0)\r
799if not defined STEP_%~1 (\r
800 echo [setup-windows] BUG: step %~1 is not in the plan - setup-windows-steps.ps1 and this file disagree. Not run.\r
801 endlocal & exit /b 1\r
7840e3c2 802)\r
0b412520
MV
803if defined WHY_%~1 echo [setup-windows] Skipping %~1: !WHY_%~1!\r
804endlocal & exit /b 1\r
805\r
806@rem ---------------------------------------------------------------------------\r
807@rem :winget <StepId> <package-id>\r
808@rem\r
809@rem Re-running is safe and cheap. On a package winget can see is installed,\r
810@rem `winget install` upgrades it when a newer version exists and otherwise does\r
811@rem nothing - its documented default ("check package installed status and try to\r
812@rem perform an upgrade if applicable"; only --force, never used here, installs\r
813@rem regardless). The installs winget cannot see are checked before they get here.\r
814@rem\r
815@rem winget install the package, if the plan selected the step. The package id is\r
816@rem written at the call site as well as in the catalog so that this file still\r
817@rem says what it installs. Should the two ever disagree, the plan showed you\r
818@rem something other than what is about to be installed, so it is not run.\r
819@rem ---------------------------------------------------------------------------\r
820:winget\r
821call :want %1 || exit /b 0\r
822setlocal EnableDelayedExpansion\r
823if /i not "!PKG_%~1!"=="%~2" (\r
824 echo [setup-windows] BUG: the plan showed %~1 as "!PKG_%~1!" but this file installs "%~2". Not run.\r
825 endlocal & exit /b 0\r
826)\r
827endlocal\r
828echo [setup-windows] %~1: winget install %~2\r
829winget install %2\r
830exit /b 0\r