+@rem ---------------------------------------------------------------------------\r
+@rem The plan: what this run will do, shown BEFORE any of it happens\r
+@rem\r
+@rem setup-windows-plan.ps1 lists every step this file and its two halves can\r
+@rem take - from the catalog in setup-windows-steps.ps1, marked for this host's\r
+@rem architecture and elevation - and lets you switch steps off and on. Nothing\r
+@rem below runs unless it was on that list and still selected when you pressed\r
+@rem Enter; with no console to ask on, it stops unless given -Yes. Arguments to\r
+@rem this script go straight to the planner:\r
+@rem setup-windows.bat -WhatIf show the plan, do nothing\r
+@rem setup-windows.bat -Skip VirtualBox,Brave start with those switched off\r
+@rem setup-windows.bat -Only E -Yes just the elevated half, no questions\r
+@rem\r
+@rem The choice comes back as a scratch .cmd of `set` lines, called here:\r
+@rem STEP_<Id>, WHY_<Id>, PKG_<Id> per step this file takes - see :want\r
+@rem UAC_STEPS, USER_STEPS comma lists passed to the halves as -Only\r
+@rem PLAN_RUN, PLAN_RC go on (1), or stop and exit with PLAN_RC\r
+@rem No file at all means the planner failed - it exits 0 even on a mistyped\r
+@rem parameter - so that stops the run too, before anything has been done.\r
+@rem ---------------------------------------------------------------------------\r
+set "PLAN_ARGS=%*"\r
+if "%~1"=="/?" set "PLAN_ARGS=-Help"\r
+if "%~1"=="-?" set "PLAN_ARGS=-Help"\r
+set "PLAN_CMD=%TEMP%\setup-windows-plan-%RANDOM%%RANDOM%.cmd"\r
+if exist "%PLAN_CMD%" del "%PLAN_CMD%"\r
+powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-plan.ps1" -HostArch "%HOST_ARCH%" -Elevation "%ELEV%" -OutFile "%PLAN_CMD%" %PLAN_ARGS%\r
+if not exist "%PLAN_CMD%" (\r
+ echo [setup-windows] No plan was made ^(see above^), so nothing has been done.\r
+ exit /b 1\r
+)\r
+call "%PLAN_CMD%"\r
+del "%PLAN_CMD%"\r
+if not "%PLAN_RUN%"=="1" exit /b %PLAN_RC%\r
+\r
+@rem --- Claude Code, from Anthropic's own installer rather than winget ---\r
+@rem\r
+@rem NOT `winget install Anthropic.ClaudeCode` any more: recent releases are\r
+@rem broken through that route. This is the documented native install,\r
+@rem `irm https://claude.ai/install.ps1 | iex`. The script downloads the current\r
+@rem release's claude.exe, checks it against the SHA-256 in that release's\r
+@rem manifest, and runs `claude install`, which puts the launcher in\r
+@rem %USERPROFILE%\.local\bin - per-user, no elevation. Re-running it updates to\r
+@rem the latest release.\r
+@rem\r
+@rem Two things are fixed up around it, both about which build it gets:\r
+@rem - It picks win32-arm64 or win32-x64 from $env:PROCESSOR_ARCHITECTURE and\r
+@rem nothing else - the variable the block at the top of this file shows to be\r
+@rem WRONG in an emulated x64 shell on ARM64, where it would hand you the\r
+@rem emulated build. So %HOST_ARCH%, the registry's answer, is written into it\r
+@rem for the installer's process first. That only corrects the value; anything\r
+@rem else in that process that reads it sees the truth too.\r
+@rem - It refuses a 32-bit PowerShell outright ("does not support 32-bit\r
+@rem Windows"), and a bare `powershell` from a 32-bit cmd.exe is exactly that.\r
+@rem %SystemRoot%\Sysnative reaches the 64-bit one from there - it exists only\r
+@rem for 32-bit processes - so it is used when present.\r
+@rem\r
+@rem A copy left behind by the old winget route is a second claude.exe, reached\r
+@rem through WinGet\Links on the user PATH, and it can win over this one. It is\r
+@rem reported, not removed: `winget uninstall Anthropic.ClaudeCode` is yours to run.\r
+@rem\r
+@rem The installer has no "already current" check - it downloads and reinstalls\r
+@rem the whole release every time - so setup-windows-current.ps1 asks first: is\r
+@rem the native claude.exe already the latest release, and the build for this\r
+@rem host? Only a clear yes skips it; a check that cannot decide installs.\r
+set "PS64=powershell"\r
+if exist "%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" set "PS64=%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe"\r
+call :want ClaudeCode || goto :after_claude\r
+"%PS64%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-current.ps1" -Step ClaudeCode -HostArch "%HOST_ARCH%"\r
+if "%ERRORLEVEL%"=="0" goto :claude_leftover\r
+echo [setup-windows] ClaudeCode: irm https://claude.ai/install.ps1 ^| iex ^(as %HOST_ARCH%^)\r
+"%PS64%" -NoProfile -ExecutionPolicy Bypass -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $env:PROCESSOR_ARCHITECTURE = '%HOST_ARCH%'; irm https://claude.ai/install.ps1 | iex"\r
+if not "%ERRORLEVEL%"=="0" echo [setup-windows] WARNING: the Claude Code installer failed ^(exit %ERRORLEVEL%^); see above. Continuing.\r
+:claude_leftover\r
+set "CLAUDE_WINGET="\r
+if exist "%LOCALAPPDATA%\Microsoft\WinGet\Links\claude.exe" set "CLAUDE_WINGET=1"\r
+for /d %%D in ("%LOCALAPPDATA%\Microsoft\WinGet\Packages\Anthropic.ClaudeCode_*") do set "CLAUDE_WINGET=1"\r
+if defined CLAUDE_WINGET (\r
+ echo [setup-windows] WARNING: Claude Code is ALSO installed through winget ^(Anthropic.ClaudeCode^), the\r
+ echo [setup-windows] route this script no longer uses. That claude.exe can win over the new one on\r
+ echo [setup-windows] the PATH. Remove it with: winget uninstall Anthropic.ClaudeCode\r
+)\r
+:after_claude\r
+\r
+@rem --- Non-admin (per-user) winget installs ---\r
+@rem\r
+@rem No --architecture anywhere on purpose. winget already picks the best\r
+@rem installer the manifest offers for this machine - arm64, else x64, else x86 -\r
+@rem and pinning it would turn "no native build, use the emulated one" into a\r
+@rem hard failure.\r
+@rem\r
+@rem These all publish real ARM64 installers, so on an ARM64 box every one of\r
+@rem them lands as a native ARM64 binary with no emulation involved.\r
+@rem\r
+@rem The Sysinternals packages are a multi-architecture ZIP rather than an\r
+@rem installer, so the ARM64 build is IN there but is not the default-named exe:\r
+@rem the zip holds procexp.exe (x86), procexp64.exe (x64) and procexp64a.exe\r
+@rem (ARM64). On ARM64, run the *64a.exe variants - plain procexp.exe is the x86\r
+@rem one and will run emulated.\r
+@rem\r
+@rem One :winget call per package, each named by its step in the plan, so any of\r
+@rem them can be switched off. Brave is x64-only: the emulated x64 browser is the\r
+@rem slowest thing on an ARM64 box, and Edge is in-box and native there.\r
+call :winget Brave Brave.Brave\r
+call :winget Git Git.Git\r
+call :winget PowerShell Microsoft.PowerShell\r
+call :winget ProcessExplorer Microsoft.Sysinternals.ProcessExplorer\r
+call :winget ProcessMonitor Microsoft.Sysinternals.ProcessMonitor\r
+call :winget SDelete Microsoft.Sysinternals.SDelete\r
+call :winget VSCode Microsoft.VisualStudioCode\r
+\r
+@rem --- Windows Terminal, after registering the framework package it needs ---\r
+@rem\r
+@rem Terminal is an MSIX, and its manifest depends on the Microsoft.UI.Xaml.2.8\r
+@rem framework package (>= 8.2306.22001.0), which winget installs first, from the\r
+@rem 8.2310.30001.0 appx. That goes wrong when a NEWER build of the framework is\r
+@rem already on the machine but not registered for this account - measured on\r
+@rem the Windows 10 box, winget 1.29.380, with 8.2501.31001.0 staged and none\r
+@rem registered for the account running this:\r
+@rem - Windows stages nothing ("A higher version 8.2501.31001.0 of this package\r
+@rem is already installed", AppXDeploymentServer event 735) and reports the\r
+@rem stage as a success;\r
+@rem - winget then registers the 8.2310 package it believes it staged, by full\r
+@rem name, and there is no such package (0x80073CF1);\r
+@rem - which it prints as\r
+@rem Installer failed with exit code: 0x80070002 : The system cannot\r
+@rem find the file specified.\r
+@rem and Terminal itself is never attempted. On every run, since a failed\r
+@rem run changes nothing.\r
+@rem\r
+@rem So register the build that is already there for this account first - by\r
+@rem family name, which takes no version and no elevation. winget then finds the\r
+@rem dependency met and installs Terminal alone. Asked only when this account\r
+@rem has none registered, the case measured, so a re-run - Terminal and its\r
+@rem framework both in place - changes nothing.\r
+@rem\r
+@rem With none on the machine either, the registration fails with that same\r
+@rem 0x80073CF1, and that is not worth a line: it is the fresh box, where nothing\r
+@rem is in winget's way and it installs the framework itself. Any other failure\r
+@rem is reported, and winget still gets its turn.\r
+@rem\r
+@rem %PS64% because it is Windows PowerShell: the Appx module does not load in\r
+@rem PowerShell 7 here ("Operation is not supported on this platform").\r
+call :want WindowsTerminal || goto :after_terminal\r
+"%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
+call :winget WindowsTerminal Microsoft.WindowsTerminal\r
+:after_terminal\r
+\r
+call :winget Python Python.Python.3.13\r
+\r
+@rem WinMerge is the one package here where winget's own preference order picks\r
+@rem the WRONG architecture, and it is worth knowing why: upstream publishes the\r
+@rem ARM64 build as a MACHINE-scope installer (WinMerge-<ver>-ARM64-Setup.exe) and\r
+@rem the x64 build as a PER-USER one (WinMerge-<ver>-x64-PerUser-Setup.exe). Run\r
+@rem unelevated, winget's scope preference beats its architecture preference and\r
+@rem you silently get the emulated x64 build.\r
+@rem\r
+@rem That asymmetry is why ARM64 no longer installs it at all: the only build a\r
+@rem normal user can install there is the emulated one, and asking for --architecture\r
+@rem arm64 just fails unless the run is elevated. VS Code's diff view and\r
+@rem `git difftool` cover the same ground natively.\r
+call :winget WinMerge WinMerge.WinMerge\r
+\r
+@rem --- Build drivers, standalone and native ---\r
+@rem\r
+@rem CMake and Ninja BOTH ship with Visual Studio, so this is not about making\r
+@rem them exist - it is about which binary you get and where it is on the PATH:\r
+@rem\r
+@rem - VS's cmake.exe is already ARM64, but it only lands on the PATH inside a\r
+@rem Developer Command Prompt. The build line further down assumes a plain\r
+@rem `cmake`, so install the standalone one to make that true in any shell.\r
+@rem - VS's bundled ninja.exe is x64 EVEN ON ARM64 (verified on this box), so it\r
+@rem runs under emulation. Ninja is re-invoked for every edge in the build\r
+@rem graph, which makes it the one tool here where emulation is actually paid\r
+@rem over and over, and ninja-winarm64.zip is a native build. Worth having.\r
+@rem\r
+@rem Which ninja wins inside a VS Developer Command Prompt: the native one. VS\r
+@rem adds its copy from Common7\Tools\vsdevcmd\ext\cmake.bat with\r
+@rem set "PATH=%PATH%;...\CMake\bin;...\CMake\Ninja"\r
+@rem which APPENDS, so the VS directories sit at the very end of the composed\r
+@rem PATH, behind every machine and user entry. setup-windows-no-uac.ps1 then\r
+@rem pins the native ninja's directory to the FRONT of the user PATH so the\r
+@rem margin does not depend on two append orders staying as they are.\r
+@rem\r
+@rem Ninja here, CMake in the machine-wide group below: Ninja is a portable zip\r
+@rem winget unpacks into the user profile, CMake is an MSI that installs for the\r
+@rem whole machine.\r
+call :winget Ninja Ninja-build.Ninja\r
+\r
+@rem --- Second, independent Clang: compiler diversity, on x64 only ---\r
+@rem\r
+@rem On x64 the elevated half installs Visual Studio's Clang component (clang-cl).\r
+@rem This is a DIFFERENT LLVM: the upstream release, on its own schedule and\r
+@rem usually several major versions ahead of the one VS bundles, installed to\r
+@rem C:\Program Files\LLVM rather than inside the VS tree. Two Clang majors plus\r
+@rem MSVC over the same sources is the point - it is what catches the bugs a\r
+@rem single toolchain agrees with itself about.\r
+@rem\r
+@rem NOT ON ARM64, and not for lack of a native build - the manifest does resolve\r
+@rem to LLVM-<ver>-woa64.exe there. It is dropped because the ARM64 box carries\r
+@rem MSVC alone: with VS's clang-cl component gone too, a third compiler with\r
+@rem nothing to disagree with is just disk. Do the multi-compiler build on the\r
+@rem x64 machine, which keeps all three.\r
+@rem\r
+@rem PATH ORDER MATTERS on x64 if you use it. Should this installer put its bin on\r
+@rem the PATH, a bare `clang-cl` resolves to the upstream one while CMake's\r
+@rem `-T ClangCL` toolset keeps using the VS copy - so the two can be selected\r
+@rem independently, but only if you know which one you are asking for. Prefer\r
+@rem -DCMAKE_CXX_COMPILER with a full path when you care.\r
+call :winget LLVM LLVM.LLVM\r
+\r
+@rem --- Rust, via rustup ---\r
+@rem\r
+@rem Per-user on both architectures: rustup-init keeps rustup and every\r
+@rem toolchain it manages under %USERPROFILE%\.cargo and \.rustup, and puts\r
+@rem .cargo\bin on the user PATH. The manifest publishes an aarch64 rustup-init,\r
+@rem so ARM64 gets a native rustup.\r
+@rem\r
+@rem winget runs it as `rustup-init -y -q`, which also installs a stable\r
+@rem toolchain for whatever host rustup detects. Which toolchain is the DEFAULT -\r
+@rem stable MSVC for this host, x86_64 or aarch64 - is then pinned explicitly by\r
+@rem the RustToolchain step in setup-windows-no-uac.ps1 rather than left to that\r
+@rem detection; see Set-RustDefaultToolchain there for why.\r
+@rem\r
+@rem The -msvc toolchains link with MSVC's link.exe, so building needs the Visual\r
+@rem Studio the elevated half installs. Installing rustup before it is fine:\r
+@rem with -y, rustup-init only warns "installing msvc toolchain without its\r
+@rem prerequisites" (rustup 1.29.1, src/cli/self_update/windows.rs) - it does not\r
+@rem go and install a Visual Studio of its own.\r
+@rem\r
+@rem Safe to re-run as it stands. rustup registers itself in Add/Remove Programs\r
+@rem under the key "Rustup" - the manifest's ProductCode - and rewrites its\r
+@rem DisplayVersion when it updates itself, so winget always sees the version\r
+@rem that is really there and only runs rustup-init again for a newer one.\r
+call :winget Rustup Rustlang.Rustup\r
+\r
+@rem x64-only manifest: winget falls back to the x64 installer on ARM64 and it runs\r
+@rem under Prism emulation. Harmless for what it does here - iperf3 is a network\r
+@rem benchmark bounded by the link, not by CPU.\r
+call :winget iPerf3 ar51an.iPerf3\r
+\r
+@rem Android SDK Platform-Tools: adb and fastboot. A portable zip, so per-user:\r
+@rem winget unpacks it under %LOCALAPPDATA%\Microsoft\WinGet\Packages and puts\r
+@rem that directory on the user PATH, rather than linking the exes one by one,\r
+@rem because adb.exe needs AdbWinApi.dll and AdbWinUsbApi.dll beside it.\r
+@rem\r
+@rem The manifest labels it x64, but every binary in the zip is 32-bit x86\r
+@rem (machine type 0x014C, checked on r37.0.1), and there is no ARM64 build. So it\r
+@rem runs under WOW64 on x64 and under x86 emulation on ARM64 - which, unlike x64\r
+@rem emulation, every Windows on Arm has. Kept on ARM64 anyway, for the iperf3\r
+@rem reason: adb is bound by the USB or TCP link to the device, not by the CPU.\r
+call :winget PlatformTools Google.PlatformTools\r
+\r
+@rem NASM assembles x86/x86-64 only - there is no ARM64 target in it and no ARM64\r
+@rem build of it. x64 keeps it; ARM64 does not, because an emulated assembler is\r
+@rem only worth carrying if you cross-compile x86/x64 here, and that work belongs\r
+@rem on the x64 box. The ARM64 assembler is armasm64.exe, which ships with MSVC.\r
+@rem\r
+@rem Its installer is also one of the two here that fall back to a per-user\r
+@rem directory when it cannot write to Program Files - C:\Users\<you>\AppData\\r
+@rem Local\bin\NASM, registered nowhere, so `winget uninstall` cannot find it\r
+@rem afterwards. Uninstall.exe in that directory is the only way back out.\r
+@rem\r
+@rem Registered nowhere also means winget cannot see it is installed, so it\r
+@rem reruns the installer on every run - measured, with the same 3.02 on both\r
+@rem sides. setup-windows-current.ps1 compares that per-user nasm.exe with the\r
+@rem version winget offers first, and only a match skips the install. A\r
+@rem machine-wide NASM is registered, so it is left to winget as usual.\r
+call :want NASM || goto :after_nasm\r
+"%PS64%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-current.ps1" -Step NASM -HostArch "%HOST_ARCH%"\r
+if not "%ERRORLEVEL%"=="0" call :winget NASM NASM.NASM\r
+:after_nasm\r
+\r
+@rem VirtualBox has no ARM64 Windows host build, and the x64 one cannot be made to\r
+@rem work by emulation: a hypervisor is kernel-mode, and its x64 driver will not\r
+@rem load on an ARM64 kernel. Installing it on ARM64 gets you a broken product and\r
+@rem a failed driver install, so the plan marks it x64-only and says so.\r
+@rem\r
+@rem This is also what makes setup-windows-7-test-env.bat unreachable from an\r
+@rem ARM64 host: it prepares a Windows 7 *x86* guest, and neither VirtualBox nor\r
+@rem Hyper-V on ARM64 can run one.\r
+@rem\r
+@rem Nor is it installed when this Windows is itself a virtual machine, which the\r
+@rem planner reads from the firmware's SMBIOS strings: the test VMs belong on the\r
+@rem physical host, and nesting VirtualBox inside a guest needs nested\r
+@rem virtualization the outer hypervisor usually does not offer. The plan marks it\r
+@rem -NotInVM, with that reason, and names the hypervisor it found.\r
+call :winget VirtualBox Oracle.VirtualBox\r
+\r
+@rem ---------------------------------------------------------------------------\r
+@rem Machine-wide installers, despite sitting in the "per-user" section\r
+@rem\r
+@rem These four are not per-user at all. On an account that cannot elevate they\r
+@rem fail on every run, with installer exit codes that say nothing useful:\r
+@rem Microsoft.DotNet.SDK.10 exit 5 (ERROR_ACCESS_DENIED)\r
+@rem Kitware.CMake exit 1603 (generic MSI failure)\r
+@rem Google.AndroidGPUInspector exit 1603\r
+@rem OpenCppCoverage exit 1 (its installer self-elevates)\r
+@rem\r
+@rem Grouped and skipped outright where elevation is impossible. Attempting a\r
+@rem guaranteed failure four times per run - and paying the download for it -\r
+@rem teaches nobody anything, and the four opaque error codes bury the one line\r
+@rem that matters. On an account that can elevate they run exactly as before.\r
+@rem The four are -Admin in setup-windows-steps.ps1, so on such an account the\r
+@rem plan shows them as unavailable, with the reason, and :want skips each one.\r
+@rem Re-run from an administrator account to get them.\r
+@rem\r
+@rem NOTE none of these is a build-blocker: the .NET SDK is only here for the WiX\r
+@rem MSI tooling, CMake also ships inside Visual Studio, AGI profiles Android\r
+@rem devices, and OpenCppCoverage cannot instrument ARM64 binaries anyway.\r
+@rem\r
+@rem On ARM64 the group is just the first two - AGI and OpenCppCoverage are in the\r
+@rem x64-only set, so there they are skipped before elevation is even consulted.\r
+@rem AGI has no ARM64 build, and it profiles Android devices rather than\r
+@rem anything compiled here, so emulating it is not worth the disk.\r
+@rem ---------------------------------------------------------------------------\r
+call :winget DotNetSdk Microsoft.DotNet.SDK.10\r
+call :winget CMake Kitware.CMake\r
+call :winget AndroidGPUInspector Google.AndroidGPUInspector\r
+\r
+@rem --- Android Studio ---\r
+@rem\r
+@rem Machine-wide: the manifest's NSIS installer elevates itself and installs to\r
+@rem %ProgramFiles%\Android\Android Studio. x64 only - there is no ARM64 build -\r
+@rem so the plan keeps it off ARM64.\r
+@rem\r
+@rem Installed only when it is missing, which setup-windows-current.ps1 decides.\r
+@rem Android Studio updates itself, in the app, and the winget package is published\r
+@rem with UpgradeBehavior: deny and RequireExplicitUpgrade, so winget would never\r
+@rem upgrade it anyway. What the check prevents is the other case: a Studio that\r
+@rem was installed some other way - as on the x64 laptop, where winget lists it\r
+@rem only by its Add/Remove Programs key - is one winget cannot match to the\r
+@rem package, so `winget install` would run the installer over it.\r
+@rem\r
+@rem It is also where the per-user Android SDK step gets its JDK: Studio bundles\r
+@rem one (jbr), and the SDK's sdkmanager and Gradle both need Java.\r
+call :want AndroidStudio || goto :after_studio\r
+"%PS64%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-current.ps1" -Step AndroidStudio -HostArch "%HOST_ARCH%"\r
+if not "%ERRORLEVEL%"=="0" call :winget AndroidStudio Google.AndroidStudio\r
+:after_studio\r