]>
| Commit | Line | Data |
|---|---|---|
| 1 | <# | |
| 2 | setup-windows-steps.ps1 - every step the Windows setup can take, in one list. | |
| 3 | ||
| 4 | Dot-sourced, never run: it defines functions and nothing else, so loading it | |
| 5 | changes no state. It deliberately has no param block either - dot-sourcing | |
| 6 | binds one in the CALLER's scope, which would reset the caller's own -Only and | |
| 7 | -Skip to their defaults. | |
| 8 | ||
| 9 | Who reads it: | |
| 10 | setup-windows-plan.ps1 shows it as the plan before anything happens, | |
| 11 | and lets you choose; setup-windows.bat runs it first | |
| 12 | setup-windows.bat takes the Runner 'bat' steps, as the planner hands | |
| 13 | them over (it never reads this file itself) | |
| 14 | setup-windows-with-uac.ps1 takes the 'uac' steps - the elevated half | |
| 15 | setup-windows-no-uac.ps1 takes the 'user' steps - per-user configuration | |
| 16 | ||
| 17 | THIS IS THE ONE PLACE a step's availability is decided. -X64Only, with its | |
| 18 | reason, keeps a step off ARM64; -Admin keeps it off an account that cannot | |
| 19 | elevate. The planner shows both as "[-]" rows with the reason, the batch file | |
| 20 | prints the same reason in the log where the step would have run, and the two | |
| 21 | halves apply them again when run on their own. This replaces the :x64only | |
| 22 | subroutine that used to sit at the bottom of setup-windows.bat and the | |
| 23 | $Arm64Dropped table in setup-windows-no-uac.ps1. | |
| 24 | ||
| 25 | Adding a step: add a row below, in the order it runs, and gate the code that | |
| 26 | does it on the same Id in the script its Runner names. The scripts check that | |
| 27 | the two agree: an Id the catalog does not know is refused, and a catalog row | |
| 28 | that nothing implements is reported rather than silently dropped. | |
| 29 | #> | |
| 30 | ||
| 31 | function Get-SetupGroups { | |
| 32 | # Display order is run order. The letter is what you type in the planner, or | |
| 33 | # pass to -Only / -Skip, to take a whole group at once. | |
| 34 | [ordered]@{ | |
| 35 | U = 'Per-user installs (winget, except Claude Code)' | |
| 36 | M = 'Machine-wide installs (winget), then the WiX .NET tool' | |
| 37 | E = 'Elevated setup (setup-windows-with-uac.ps1)' | |
| 38 | C = 'Per-user configuration (setup-windows-no-uac.ps1), then Pillow' | |
| 39 | } | |
| 40 | } | |
| 41 | ||
| 42 | function New-SetupStep { | |
| 43 | param( | |
| 44 | [Parameter(Mandatory, Position = 0)] [string] $Id, | |
| 45 | [Parameter(Mandatory, Position = 1)] [ValidateSet('U', 'M', 'E', 'C')] [string] $Group, | |
| 46 | # Which script takes the step. | |
| 47 | [Parameter(Mandatory, Position = 2)] [ValidateSet('bat', 'uac', 'user')] [string] $Runner, | |
| 48 | # What the plan says the step does. For a winget step it follows the | |
| 49 | # package id, so it only needs to add what the id does not say. | |
| 50 | [string] $Text, | |
| 51 | [string] $Package, | |
| 52 | # Not on ARM64, because <this>. Shown in the plan and in the run log. | |
| 53 | [string] $X64Only, | |
| 54 | # Needs an account that can elevate: a machine-wide installer, or part | |
| 55 | # of the elevated half. | |
| 56 | [switch] $Admin, | |
| 57 | # Steps this one builds on. Advisory only - the planner notes it when | |
| 58 | # one of these is switched off, since it may already be installed. | |
| 59 | [string[]] $Needs = @() | |
| 60 | ) | |
| 61 | $shown = if (-not $Package) { $Text } elseif ($Text) { "$Package - $Text" } else { $Package } | |
| 62 | [pscustomobject]@{ | |
| 63 | Id = $Id | |
| 64 | Group = $Group | |
| 65 | Runner = $Runner | |
| 66 | Text = $shown | |
| 67 | Package = $Package | |
| 68 | X64Only = $X64Only | |
| 69 | Admin = [bool]$Admin | |
| 70 | Needs = $Needs | |
| 71 | } | |
| 72 | } | |
| 73 | ||
| 74 | function Get-SetupCatalog { | |
| 75 | # Every step, in the order setup-windows.bat takes them. The long-form | |
| 76 | # reasoning for each one stays in a comment beside the code that does it; | |
| 77 | # the text here is what a person deciding needs to see. | |
| 78 | param([bool] $IsArm64) | |
| 79 | ||
| 80 | $vs = if ($IsArm64) { | |
| 81 | 'Visual Studio 2026 Community, C++: MSVC for ARM64 + x86/x64, SDK 26100 (several GB)' | |
| 82 | } else { | |
| 83 | 'Visual Studio 2022 Community, C++: MSVC for x86/x64 + ARM64, SDK 26100 (several GB)' | |
| 84 | } | |
| 85 | $rust = if ($IsArm64) { | |
| 86 | 'Rust stable-aarch64-pc-windows-msvc (native), installed and made the default' | |
| 87 | } else { | |
| 88 | 'Rust stable-x86_64-pc-windows-msvc, installed and made the default' | |
| 89 | } | |
| 90 | ||
| 91 | @( | |
| 92 | # --- setup-windows.bat: per-user winget installs --- | |
| 93 | # Not winget: recent Anthropic.ClaudeCode releases are broken through it. | |
| 94 | New-SetupStep ClaudeCode U bat -Text 'Claude Code, native installer: irm https://claude.ai/install.ps1 | iex' | |
| 95 | New-SetupStep Brave U bat -Package Brave.Brave ` | |
| 96 | -X64Only 'no ARM64 build, and Edge is in-box and native' | |
| 97 | New-SetupStep Git U bat -Package Git.Git | |
| 98 | New-SetupStep PowerShell U bat -Package Microsoft.PowerShell | |
| 99 | New-SetupStep ProcessExplorer U bat -Package Microsoft.Sysinternals.ProcessExplorer | |
| 100 | New-SetupStep ProcessMonitor U bat -Package Microsoft.Sysinternals.ProcessMonitor | |
| 101 | New-SetupStep SDelete U bat -Package Microsoft.Sysinternals.SDelete | |
| 102 | New-SetupStep VSCode U bat -Package Microsoft.VisualStudioCode | |
| 103 | New-SetupStep WindowsTerminal U bat -Package Microsoft.WindowsTerminal | |
| 104 | New-SetupStep Python U bat -Package Python.Python.3.13 | |
| 105 | New-SetupStep WinMerge U bat -Package WinMerge.WinMerge ` | |
| 106 | -X64Only 'its ARM64 build is machine-scope only; unelevated winget gets the x64 one' | |
| 107 | New-SetupStep Ninja U bat -Package Ninja-build.Ninja -Text 'native; Visual Studio bundles an x64 one' | |
| 108 | New-SetupStep LLVM U bat -Package LLVM.LLVM -Text 'upstream Clang, a second compiler beside clang-cl' ` | |
| 109 | -X64Only 'this box carries MSVC alone; no second Clang to cross-check' | |
| 110 | New-SetupStep Rustup U bat -Package Rustlang.Rustup -Text 'the Rust toolchain manager, per-user; installs stable too' | |
| 111 | New-SetupStep iPerf3 U bat -Package ar51an.iPerf3 | |
| 112 | # On ARM64 too, like iperf3: an x86-only build, but bound by the link to | |
| 113 | # the device, not the CPU. See the comment beside it in setup-windows.bat. | |
| 114 | New-SetupStep PlatformTools U bat -Package Google.PlatformTools -Text 'Android SDK Platform-Tools: adb, fastboot (an x86 build)' | |
| 115 | New-SetupStep NASM U bat -Package NASM.NASM ` | |
| 116 | -X64Only 'x86/x64-only assembler; MSVC armasm64 is the native one' | |
| 117 | New-SetupStep VirtualBox U bat -Package Oracle.VirtualBox -Text 'for the Windows 7 test VM' ` | |
| 118 | -X64Only 'no ARM64 host build, so no Windows 7 x86 test VM either' | |
| 119 | ||
| 120 | # --- setup-windows.bat: machine-wide installers, despite running unelevated --- | |
| 121 | New-SetupStep DotNetSdk M bat -Admin -Package Microsoft.DotNet.SDK.10 -Text 'for the WiX tooling' | |
| 122 | New-SetupStep CMake M bat -Admin -Package Kitware.CMake -Text 'standalone, so a plain cmake works in any shell' | |
| 123 | New-SetupStep AndroidGPUInspector M bat -Admin -Package Google.AndroidGPUInspector ` | |
| 124 | -X64Only 'x64-only build, and it profiles Android devices, not this box' | |
| 125 | New-SetupStep OpenCppCoverage M bat -Admin -Package OpenCppCoverage.OpenCppCoverage -Text 'native line coverage' ` | |
| 126 | -X64Only 'it steps x86/x64 instructions, so it cannot cover an ARM64 binary' | |
| 127 | New-SetupStep WiX M bat -Needs DotNetSdk ` | |
| 128 | -Text 'WiX 5.0.2 as a .NET global tool, pinned: 6.0 and later carry a maintenance-fee EULA' | |
| 129 | ||
| 130 | # --- setup-windows-with-uac.ps1: the elevated half --- | |
| 131 | New-SetupStep OpenSshClient E uac -Admin -Text 'OpenSSH Client capability (ssh.exe, and the libcrypto.dll rsync''s ssh.exe needs)' | |
| 132 | New-SetupStep SshAgent E uac -Admin -Needs OpenSshClient -Text 'ssh-agent service: start automatically, and start it now' | |
| 133 | New-SetupStep OpenSshServer E uac -Admin -Text 'OpenSSH Server: sshd automatic and running, inbound TCP 22 on all profiles' | |
| 134 | New-SetupStep Rsync E uac -Admin -Needs OpenSshClient -Text 'rsync and its ssh.exe into C:\Tools\rsync, on the machine PATH' | |
| 135 | New-SetupStep VisualStudio E uac -Admin -Text $vs | |
| 136 | New-SetupStep VsClang E uac -Admin -Needs VisualStudio -Text 'Visual Studio Clang/LLVM toolset (clang-cl, for the ClangCL presets)' ` | |
| 137 | -X64Only 'by choice: this box carries MSVC alone' | |
| 138 | New-SetupStep VsWinXP E uac -Admin -Needs VisualStudio -Text 'Visual Studio v141 + Windows XP targeting toolset' ` | |
| 139 | -X64Only 'v141 has no ARM64-hosted compiler; no XP targeting from Arm' | |
| 140 | New-SetupStep Wdk E uac -Admin -Needs VisualStudio -Text 'Windows Driver Kit 10.0.26100 (pairs with SDK 26100)' ` | |
| 141 | -X64Only 'user-mode box; this WDK will not install beside VS 2026''s SDK' | |
| 142 | New-SetupStep Wpt E uac -Admin -Text 'Windows Performance Toolkit (xperf, wpr, WPA); the Windows ADK if it is missing' | |
| 143 | New-SetupStep VTune E uac -Admin -Text 'Intel VTune Profiler: report whether it is installed (installs nothing)' | |
| 144 | ||
| 145 | # --- setup-windows-no-uac.ps1: per-user configuration --- | |
| 146 | New-SetupStep WinMergePath C user -Needs WinMerge -Text 'WinMerge on the user PATH' ` | |
| 147 | -X64Only 'WinMerge is not installed on ARM64' | |
| 148 | New-SetupStep VsWhere C user -Needs VisualStudio -Text 'vswhere.exe on the user PATH' | |
| 149 | New-SetupStep BinSkim C user -Text 'BinSkim into %LOCALAPPDATA%\Programs\BinSkim, on the user PATH (100+ MB)' ` | |
| 150 | -X64Only 'the NuGet package publishes win-x64 only' | |
| 151 | New-SetupStep GitConfig C user -Needs Git -Text 'global git: identity if filled in, core.sshCommand to a Win32-OpenSSH client' | |
| 152 | New-SetupStep NinjaPath C user -Needs Ninja -Text 'the native ninja first on the user PATH' | |
| 153 | New-SetupStep LlvmPath C user -Needs LLVM -Text 'upstream LLVM on the user PATH' ` | |
| 154 | -X64Only 'LLVM is not installed on ARM64' | |
| 155 | New-SetupStep RustToolchain C user -Needs Rustup, VisualStudio -Text $rust | |
| 156 | New-SetupStep ArchAudit C user -Text 'architecture audit: native or emulated, for every tool (read-only)' | |
| 157 | ||
| 158 | # --- setup-windows.bat, after both halves --- | |
| 159 | New-SetupStep Pillow C bat -Needs Python -Text 'pip install Pillow into the newest Python' | |
| 160 | ) | |
| 161 | } | |
| 162 | ||
| 163 | function Get-SetupStepBlocker { | |
| 164 | # Why this step cannot run on this machine at all, or $null if it can. | |
| 165 | # Architecture first: an x64-only step is off ARM64 before elevation is even | |
| 166 | # consulted, which is the more useful reason to show. | |
| 167 | param($Step, [bool] $IsArm64, [string] $Elevation) | |
| 168 | if ($IsArm64 -and $Step.X64Only) { return "not on ARM64 - $($Step.X64Only)" } | |
| 169 | if ($Elevation -eq 'NOLUA' -and $Step.Admin) { | |
| 170 | return 'needs an administrator - this account cannot elevate' | |
| 171 | } | |
| 172 | $null | |
| 173 | } | |
| 174 | ||
| 175 | function Split-SetupNames { | |
| 176 | # `powershell -File` hands a whole "A,B,C" to an array parameter as ONE | |
| 177 | # string, so split here and accept either spelling. | |
| 178 | param([string[]] $Value) | |
| 179 | @($Value | ForEach-Object { $_ -split '[,\s]+' } | Where-Object { $_ }) | |
| 180 | } | |
| 181 | ||
| 182 | function Resolve-SetupStepNames { | |
| 183 | # Step ids (case-insensitive) or group letters, as typed, to step ids. | |
| 184 | # Anything else is an error: a mistyped -Skip that was quietly ignored would | |
| 185 | # install exactly what you asked it not to. | |
| 186 | param([string[]] $Names, [object[]] $Steps, [hashtable] $Aliases = @{}) | |
| 187 | $groups = @((Get-SetupGroups).Keys) | |
| 188 | foreach ($name in (Split-SetupNames $Names)) { | |
| 189 | if ($Aliases.ContainsKey($name)) { $name = $Aliases[$name] } | |
| 190 | $hit = @($Steps | Where-Object { $_.Id -eq $name }) | |
| 191 | if (-not $hit) { $hit = @($Steps | Where-Object { $_.Group -eq $name }) } | |
| 192 | if ($hit) { $hit.Id; continue } | |
| 193 | # A real group with no steps in this script's share of the catalog. | |
| 194 | if ($groups -contains $name) { continue } | |
| 195 | throw "Unknown step '$name'. The steps are: $($Steps.Id -join ', '). Groups: $($groups -join ', ')." | |
| 196 | } | |
| 197 | } | |
| 198 | ||
| 199 | function Resolve-SetupSelection { | |
| 200 | # The plan: one row per step, in run order, saying whether it is selected | |
| 201 | # and - when it cannot run on this machine at all - why not. | |
| 202 | # | |
| 203 | # -Only $null means "everything that can run"; an empty -Only means nothing. | |
| 204 | param( | |
| 205 | [object[]] $Steps, | |
| 206 | [AllowNull()] [string[]] $Only, | |
| 207 | [string[]] $Skip = @(), | |
| 208 | [hashtable] $Aliases = @{}, | |
| 209 | [bool] $IsArm64, | |
| 210 | [string] $Elevation | |
| 211 | ) | |
| 212 | $want = if ($null -eq $Only) { @($Steps.Id) } else { @(Resolve-SetupStepNames $Only $Steps $Aliases) } | |
| 213 | $drop = @(Resolve-SetupStepNames $Skip $Steps $Aliases) | |
| 214 | $named = @(Split-SetupNames $Only | ForEach-Object { if ($Aliases.ContainsKey($_)) { $Aliases[$_] } else { $_ } }) | |
| 215 | foreach ($s in $Steps) { | |
| 216 | $why = Get-SetupStepBlocker $s $IsArm64 $Elevation | |
| 217 | if ($why -and $named -contains $s.Id) { | |
| 218 | Write-Warning "$($s.Id) was asked for but cannot run here: $why" | |
| 219 | } | |
| 220 | [pscustomobject]@{ | |
| 221 | Id = $s.Id | |
| 222 | Step = $s | |
| 223 | Why = $why | |
| 224 | Selected = (-not $why) -and ($want -contains $s.Id) -and ($drop -notcontains $s.Id) | |
| 225 | } | |
| 226 | } | |
| 227 | } | |
| 228 | ||
| 229 | function Write-SetupPlan { | |
| 230 | # The short form of the plan, for the two halves: what this script will do | |
| 231 | # and what it will not, before it does any of it. | |
| 232 | param([object[]] $Plan, [string] $Title) | |
| 233 | Write-Host "`n==> $Title" -ForegroundColor Cyan | |
| 234 | foreach ($p in $Plan) { | |
| 235 | if ($p.Selected) { | |
| 236 | Write-Host (' run {0,-14} {1}' -f $p.Id, $p.Step.Text) | |
| 237 | } else { | |
| 238 | $why = if ($p.Why) { $p.Why } else { 'not selected' } | |
| 239 | Write-Host (' skip {0,-14} {1}' -f $p.Id, $why) -ForegroundColor DarkGray | |
| 240 | } | |
| 241 | } | |
| 242 | } | |
| 243 | ||
| 244 | function Confirm-SetupPlan { | |
| 245 | # $true to go ahead, $false if declined, $null if there is no one to ask. | |
| 246 | # | |
| 247 | # Never assumes yes on its own. A run with no console behind it - piped, | |
| 248 | # scheduled, a non-interactive ssh command - stops after printing the plan, | |
| 249 | # and says how to run it unattended (-Yes) on purpose. | |
| 250 | param([switch] $Yes, [int] $Count) | |
| 251 | if ($Yes) { return $true } | |
| 252 | if ([Console]::IsInputRedirected) { | |
| 253 | Write-Host ' No console to ask on, so nothing has been done. Re-run with -Yes to take' -ForegroundColor Yellow | |
| 254 | Write-Host ' the steps marked "run" unattended.' -ForegroundColor Yellow | |
| 255 | return $null | |
| 256 | } | |
| 257 | $answer = Read-Host " Take the $Count step(s) marked run? [Y/n]" | |
| 258 | if ($answer -match '^\s*(y|yes)?\s*$') { return $true } | |
| 259 | Write-Host ' Nothing done.' | |
| 260 | $false | |
| 261 | } |