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