]> vilimpoc.org git repositories - dotfiles/blame - setup-windows-steps.ps1
setup-windows: Msys2 step - MSYS2 with MinGW-w64 GCC, per-user, no elevation
[dotfiles] / setup-windows-steps.ps1
CommitLineData
0b412520
MV
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
325b494c
MV
18 reason, keeps a step off ARM64; -NotInVM, with its reason, keeps it off a
19 Windows that is itself a virtual machine; -Admin keeps it off an account that
20 cannot elevate. The planner shows each as a "[-]" row with the reason, the batch file
0b412520
MV
21 prints the same reason in the log where the step would have run, and the two
22 halves apply them again when run on their own. This replaces the :x64only
23 subroutine that used to sit at the bottom of setup-windows.bat and the
24 $Arm64Dropped table in setup-windows-no-uac.ps1.
25
26 Adding a step: add a row below, in the order it runs, and gate the code that
27 does it on the same Id in the script its Runner names. The scripts check that
28 the two agree: an Id the catalog does not know is refused, and a catalog row
29 that nothing implements is reported rather than silently dropped.
30#>
31
32function Get-SetupGroups {
33 # Display order is run order. The letter is what you type in the planner, or
34 # pass to -Only / -Skip, to take a whole group at once.
35 [ordered]@{
36 U = 'Per-user installs (winget, except Claude Code)'
37 M = 'Machine-wide installs (winget), then the WiX .NET tool'
38 E = 'Elevated setup (setup-windows-with-uac.ps1)'
39 C = 'Per-user configuration (setup-windows-no-uac.ps1), then Pillow'
40 }
41}
42
43function New-SetupStep {
44 param(
45 [Parameter(Mandatory, Position = 0)] [string] $Id,
46 [Parameter(Mandatory, Position = 1)] [ValidateSet('U', 'M', 'E', 'C')] [string] $Group,
47 # Which script takes the step.
48 [Parameter(Mandatory, Position = 2)] [ValidateSet('bat', 'uac', 'user')] [string] $Runner,
49 # What the plan says the step does. For a winget step it follows the
50 # package id, so it only needs to add what the id does not say.
51 [string] $Text,
52 [string] $Package,
53 # Not on ARM64, because <this>. Shown in the plan and in the run log.
54 [string] $X64Only,
325b494c
MV
55 # Not when this Windows is a guest in a virtual machine, because <this>.
56 # Shown in the plan and in the run log, like -X64Only.
57 [string] $NotInVM,
0b412520
MV
58 # Needs an account that can elevate: a machine-wide installer, or part
59 # of the elevated half.
60 [switch] $Admin,
61 # Steps this one builds on. Advisory only - the planner notes it when
62 # one of these is switched off, since it may already be installed.
63 [string[]] $Needs = @()
64 )
65 $shown = if (-not $Package) { $Text } elseif ($Text) { "$Package - $Text" } else { $Package }
66 [pscustomobject]@{
67 Id = $Id
68 Group = $Group
69 Runner = $Runner
70 Text = $shown
71 Package = $Package
72 X64Only = $X64Only
325b494c 73 NotInVM = $NotInVM
0b412520
MV
74 Admin = [bool]$Admin
75 Needs = $Needs
76 }
77}
78
79function Get-SetupCatalog {
80 # Every step, in the order setup-windows.bat takes them. The long-form
81 # reasoning for each one stays in a comment beside the code that does it;
82 # the text here is what a person deciding needs to see.
83 param([bool] $IsArm64)
84
85 $vs = if ($IsArm64) {
86 'Visual Studio 2026 Community, C++: MSVC for ARM64 + x86/x64, SDK 26100 (several GB)'
87 } else {
88 'Visual Studio 2022 Community, C++: MSVC for x86/x64 + ARM64, SDK 26100 (several GB)'
89 }
90 $rust = if ($IsArm64) {
91 'Rust stable-aarch64-pc-windows-msvc (native), installed and made the default'
92 } else {
93 'Rust stable-x86_64-pc-windows-msvc, installed and made the default'
94 }
95
96 @(
97 # --- setup-windows.bat: per-user winget installs ---
98 # Not winget: recent Anthropic.ClaudeCode releases are broken through it.
99 New-SetupStep ClaudeCode U bat -Text 'Claude Code, native installer: irm https://claude.ai/install.ps1 | iex'
100 New-SetupStep Brave U bat -Package Brave.Brave `
101 -X64Only 'no ARM64 build, and Edge is in-box and native'
102 New-SetupStep Git U bat -Package Git.Git
103 New-SetupStep PowerShell U bat -Package Microsoft.PowerShell
104 New-SetupStep ProcessExplorer U bat -Package Microsoft.Sysinternals.ProcessExplorer
105 New-SetupStep ProcessMonitor U bat -Package Microsoft.Sysinternals.ProcessMonitor
106 New-SetupStep SDelete U bat -Package Microsoft.Sysinternals.SDelete
107 New-SetupStep VSCode U bat -Package Microsoft.VisualStudioCode
108 New-SetupStep WindowsTerminal U bat -Package Microsoft.WindowsTerminal
109 New-SetupStep Python U bat -Package Python.Python.3.13
110 New-SetupStep WinMerge U bat -Package WinMerge.WinMerge `
111 -X64Only 'its ARM64 build is machine-scope only; unelevated winget gets the x64 one'
112 New-SetupStep Ninja U bat -Package Ninja-build.Ninja -Text 'native; Visual Studio bundles an x64 one'
113 New-SetupStep LLVM U bat -Package LLVM.LLVM -Text 'upstream Clang, a second compiler beside clang-cl' `
114 -X64Only 'this box carries MSVC alone; no second Clang to cross-check'
115 New-SetupStep Rustup U bat -Package Rustlang.Rustup -Text 'the Rust toolchain manager, per-user; installs stable too'
116 New-SetupStep iPerf3 U bat -Package ar51an.iPerf3
117 # On ARM64 too, like iperf3: an x86-only build, but bound by the link to
118 # the device, not the CPU. See the comment beside it in setup-windows.bat.
119 New-SetupStep PlatformTools U bat -Package Google.PlatformTools -Text 'Android SDK Platform-Tools: adb, fastboot (an x86 build)'
120 New-SetupStep NASM U bat -Package NASM.NASM `
121 -X64Only 'x86/x64-only assembler; MSVC armasm64 is the native one'
8ea42089
MV
122 # Not winget: its package runs the machine-wide installer into C:\msys64.
123 New-SetupStep Msys2 U bat -Text 'MSYS2 into %LOCALAPPDATA%\msys64 (no admin), with MinGW-w64 GCC: ucrt64 (x64) and mingw32 (i686, msvcrt, for XP targets)' `
124 -X64Only 'its base system is x86_64 (emulated on Arm), and the XP target it is here for is x64-box work'
0b412520 125 New-SetupStep VirtualBox U bat -Package Oracle.VirtualBox -Text 'for the Windows 7 test VM' `
325b494c
MV
126 -X64Only 'no ARM64 host build, so no Windows 7 x86 test VM either' `
127 -NotInVM 'the test VMs belong on the physical host, not nested in a guest'
0b412520
MV
128
129 # --- setup-windows.bat: machine-wide installers, despite running unelevated ---
130 New-SetupStep DotNetSdk M bat -Admin -Package Microsoft.DotNet.SDK.10 -Text 'for the WiX tooling'
131 New-SetupStep CMake M bat -Admin -Package Kitware.CMake -Text 'standalone, so a plain cmake works in any shell'
132 New-SetupStep AndroidGPUInspector M bat -Admin -Package Google.AndroidGPUInspector `
133 -X64Only 'x64-only build, and it profiles Android devices, not this box'
4776737b
MV
134 New-SetupStep AndroidStudio M bat -Admin -Package Google.AndroidStudio -Text 'only if missing; it updates itself' `
135 -X64Only 'Google publishes no Windows ARM64 build of Android Studio'
0b412520
MV
136 New-SetupStep OpenCppCoverage M bat -Admin -Package OpenCppCoverage.OpenCppCoverage -Text 'native line coverage' `
137 -X64Only 'it steps x86/x64 instructions, so it cannot cover an ARM64 binary'
138 New-SetupStep WiX M bat -Needs DotNetSdk `
139 -Text 'WiX 5.0.2 as a .NET global tool, pinned: 6.0 and later carry a maintenance-fee EULA'
140
141 # --- setup-windows-with-uac.ps1: the elevated half ---
142 New-SetupStep OpenSshClient E uac -Admin -Text 'OpenSSH Client capability (ssh.exe, and the libcrypto.dll rsync''s ssh.exe needs)'
143 New-SetupStep SshAgent E uac -Admin -Needs OpenSshClient -Text 'ssh-agent service: start automatically, and start it now'
144 New-SetupStep OpenSshServer E uac -Admin -Text 'OpenSSH Server: sshd automatic and running, inbound TCP 22 on all profiles'
145 New-SetupStep Rsync E uac -Admin -Needs OpenSshClient -Text 'rsync and its ssh.exe into C:\Tools\rsync, on the machine PATH'
146 New-SetupStep VisualStudio E uac -Admin -Text $vs
147 New-SetupStep VsClang E uac -Admin -Needs VisualStudio -Text 'Visual Studio Clang/LLVM toolset (clang-cl, for the ClangCL presets)' `
148 -X64Only 'by choice: this box carries MSVC alone'
149 New-SetupStep VsWinXP E uac -Admin -Needs VisualStudio -Text 'Visual Studio v141 + Windows XP targeting toolset' `
150 -X64Only 'v141 has no ARM64-hosted compiler; no XP targeting from Arm'
151 New-SetupStep Wdk E uac -Admin -Needs VisualStudio -Text 'Windows Driver Kit 10.0.26100 (pairs with SDK 26100)' `
152 -X64Only 'user-mode box; this WDK will not install beside VS 2026''s SDK'
153 New-SetupStep Wpt E uac -Admin -Text 'Windows Performance Toolkit (xperf, wpr, WPA); the Windows ADK if it is missing'
154 New-SetupStep VTune E uac -Admin -Text 'Intel VTune Profiler: report whether it is installed (installs nothing)'
155
156 # --- setup-windows-no-uac.ps1: per-user configuration ---
325b494c
MV
157 # First, so the PATH it fixes is in place before anything reports on it.
158 New-SetupStep LocalBinPath C user -Text '%USERPROFILE%\.local\bin on the user PATH (where Claude Code''s launcher lives)'
0b412520
MV
159 New-SetupStep WinMergePath C user -Needs WinMerge -Text 'WinMerge on the user PATH' `
160 -X64Only 'WinMerge is not installed on ARM64'
161 New-SetupStep VsWhere C user -Needs VisualStudio -Text 'vswhere.exe on the user PATH'
162 New-SetupStep BinSkim C user -Text 'BinSkim into %LOCALAPPDATA%\Programs\BinSkim, on the user PATH (100+ MB)' `
163 -X64Only 'the NuGet package publishes win-x64 only'
164 New-SetupStep GitConfig C user -Needs Git -Text 'global git: identity if filled in, core.sshCommand to a Win32-OpenSSH client'
165 New-SetupStep NinjaPath C user -Needs Ninja -Text 'the native ninja first on the user PATH'
166 New-SetupStep LlvmPath C user -Needs LLVM -Text 'upstream LLVM on the user PATH' `
167 -X64Only 'LLVM is not installed on ARM64'
168 New-SetupStep RustToolchain C user -Needs Rustup, VisualStudio -Text $rust
4776737b
MV
169 New-SetupStep AndroidSdk C user -Needs AndroidStudio `
170 -Text 'Android SDK, per-user: latest platform + build-tools; accepts the SDK licences' `
171 -X64Only 'its Windows build tools are x86/x64 only, as is Android Studio'
0b412520
MV
172 New-SetupStep ArchAudit C user -Text 'architecture audit: native or emulated, for every tool (read-only)'
173
174 # --- setup-windows.bat, after both halves ---
175 New-SetupStep Pillow C bat -Needs Python -Text 'pip install Pillow into the newest Python'
176 )
177}
178
179function Get-SetupStepBlocker {
180 # Why this step cannot run on this machine at all, or $null if it can.
181 # Architecture first: an x64-only step is off ARM64 before elevation is even
325b494c
MV
182 # consulted, which is the more useful reason to show. Then the machine
183 # itself, then the account.
184 param($Step, [bool] $IsArm64, [string] $Elevation, [string] $VirtualMachine)
0b412520 185 if ($IsArm64 -and $Step.X64Only) { return "not on ARM64 - $($Step.X64Only)" }
325b494c
MV
186 if ($VirtualMachine -and $Step.NotInVM) {
187 return "not in a virtual machine ($VirtualMachine) - $($Step.NotInVM)"
188 }
0b412520
MV
189 if ($Elevation -eq 'NOLUA' -and $Step.Admin) {
190 return 'needs an administrator - this account cannot elevate'
191 }
192 $null
193}
194
325b494c
MV
195function Get-SetupVirtualMachine {
196 # The hypervisor this Windows is running under as a guest, by name, or $null
197 # on physical hardware.
198 #
199 # Read from the SMBIOS strings the firmware reports - Win32_ComputerSystem's
200 # Manufacturer and Model - which every common hypervisor fills in with its own
201 # name. NOT from HypervisorPresent: that is true on a PHYSICAL box too as soon
202 # as Hyper-V, WSL2, Windows Sandbox or virtualization-based security is on,
203 # which is most Windows 11 machines - the host OS then runs in the root
204 # partition, above a hypervisor, without being anyone's guest.
205 #
206 # A lookup that fails says "physical": this only ever takes steps away, and a
207 # step wrongly offered on a VM is one you can switch off in the plan.
208 $cs = Get-CimInstance Win32_ComputerSystem -ErrorAction SilentlyContinue
209 if (-not $cs) { return $null }
210 $smbios = "$($cs.Manufacturer) | $($cs.Model)"
211 # Hyper-V's pair is matched whole: Microsoft is also the manufacturer of
212 # every Surface, and "Virtual Machine" is the model only in a guest. Azure
213 # guests report the same pair.
214 $known = [ordered]@{
215 'innotek|VirtualBox' = 'VirtualBox'
216 'VMware' = 'VMware'
217 '^Microsoft Corporation \| Virtual Machine$' = 'Hyper-V'
218 'QEMU|KVM|Bochs' = 'QEMU/KVM'
219 'Parallels' = 'Parallels'
220 '\bXen\b|HVM domU' = 'Xen'
221 'Amazon EC2' = 'Amazon EC2'
222 'Google Compute Engine' = 'Google Compute Engine'
223 }
224 foreach ($pattern in $known.Keys) {
225 if ($smbios -match $pattern) { return $known[$pattern] }
226 }
227 $null
228}
229
0b412520
MV
230function Split-SetupNames {
231 # `powershell -File` hands a whole "A,B,C" to an array parameter as ONE
232 # string, so split here and accept either spelling.
233 param([string[]] $Value)
234 @($Value | ForEach-Object { $_ -split '[,\s]+' } | Where-Object { $_ })
235}
236
237function Resolve-SetupStepNames {
238 # Step ids (case-insensitive) or group letters, as typed, to step ids.
239 # Anything else is an error: a mistyped -Skip that was quietly ignored would
240 # install exactly what you asked it not to.
241 param([string[]] $Names, [object[]] $Steps, [hashtable] $Aliases = @{})
242 $groups = @((Get-SetupGroups).Keys)
243 foreach ($name in (Split-SetupNames $Names)) {
244 if ($Aliases.ContainsKey($name)) { $name = $Aliases[$name] }
245 $hit = @($Steps | Where-Object { $_.Id -eq $name })
246 if (-not $hit) { $hit = @($Steps | Where-Object { $_.Group -eq $name }) }
247 if ($hit) { $hit.Id; continue }
248 # A real group with no steps in this script's share of the catalog.
249 if ($groups -contains $name) { continue }
250 throw "Unknown step '$name'. The steps are: $($Steps.Id -join ', '). Groups: $($groups -join ', ')."
251 }
252}
253
254function Resolve-SetupSelection {
255 # The plan: one row per step, in run order, saying whether it is selected
256 # and - when it cannot run on this machine at all - why not.
257 #
258 # -Only $null means "everything that can run"; an empty -Only means nothing.
259 param(
260 [object[]] $Steps,
261 [AllowNull()] [string[]] $Only,
262 [string[]] $Skip = @(),
263 [hashtable] $Aliases = @{},
264 [bool] $IsArm64,
325b494c
MV
265 [string] $Elevation,
266 # The hypervisor's name when this Windows is a guest (see
267 # Get-SetupVirtualMachine), $null or empty on physical hardware.
268 [string] $VirtualMachine
0b412520
MV
269 )
270 $want = if ($null -eq $Only) { @($Steps.Id) } else { @(Resolve-SetupStepNames $Only $Steps $Aliases) }
271 $drop = @(Resolve-SetupStepNames $Skip $Steps $Aliases)
272 $named = @(Split-SetupNames $Only | ForEach-Object { if ($Aliases.ContainsKey($_)) { $Aliases[$_] } else { $_ } })
273 foreach ($s in $Steps) {
325b494c 274 $why = Get-SetupStepBlocker $s $IsArm64 $Elevation $VirtualMachine
0b412520
MV
275 if ($why -and $named -contains $s.Id) {
276 Write-Warning "$($s.Id) was asked for but cannot run here: $why"
277 }
278 [pscustomobject]@{
279 Id = $s.Id
280 Step = $s
281 Why = $why
282 Selected = (-not $why) -and ($want -contains $s.Id) -and ($drop -notcontains $s.Id)
283 }
284 }
285}
286
287function Write-SetupPlan {
288 # The short form of the plan, for the two halves: what this script will do
289 # and what it will not, before it does any of it.
290 param([object[]] $Plan, [string] $Title)
291 Write-Host "`n==> $Title" -ForegroundColor Cyan
292 foreach ($p in $Plan) {
293 if ($p.Selected) {
294 Write-Host (' run {0,-14} {1}' -f $p.Id, $p.Step.Text)
295 } else {
296 $why = if ($p.Why) { $p.Why } else { 'not selected' }
297 Write-Host (' skip {0,-14} {1}' -f $p.Id, $why) -ForegroundColor DarkGray
298 }
299 }
300}
301
302function Confirm-SetupPlan {
303 # $true to go ahead, $false if declined, $null if there is no one to ask.
304 #
305 # Never assumes yes on its own. A run with no console behind it - piped,
306 # scheduled, a non-interactive ssh command - stops after printing the plan,
307 # and says how to run it unattended (-Yes) on purpose.
308 param([switch] $Yes, [int] $Count)
309 if ($Yes) { return $true }
310 if ([Console]::IsInputRedirected) {
311 Write-Host ' No console to ask on, so nothing has been done. Re-run with -Yes to take' -ForegroundColor Yellow
312 Write-Host ' the steps marked "run" unattended.' -ForegroundColor Yellow
313 return $null
314 }
315 $answer = Read-Host " Take the $Count step(s) marked run? [Y/n]"
316 if ($answer -match '^\s*(y|yes)?\s*$') { return $true }
317 Write-Host ' Nothing done.'
318 $false
319}