]> vilimpoc.org git repositories - dotfiles/blame - setup-windows-current.ps1
dotfiles: register UI.Xaml 2.8 for the account before installing Terminal
[dotfiles] / setup-windows-current.ps1
CommitLineData
0b412520
MV
1<#
2 setup-windows-current.ps1 - is this step's program already current?
3
4776737b
MV
4 setup-windows.bat asks this before the installs that would otherwise run in
5 full on every re-run, because neither the installer nor winget can tell that
6 what is on the box is already the latest:
0b412520 7
4776737b
MV
8 ClaudeCode Anthropic's installer script always downloads the release
9 (a couple of hundred MB) and reinstalls it; it has no
10 "already current" check of its own.
11 NASM its installer falls back to a per-user directory when it
12 cannot write to Program Files, and registers nothing there -
13 so winget cannot see it, and reruns the installer every time.
14 AndroidStudio an install that did not come from winget is one winget
15 cannot match to its package, so it would install over it.
16 And Android Studio updates itself; winget never upgrades it.
0b412520
MV
17
18 Exit code 0 means already current: skip the install. Anything else means go
19 ahead as before - and that includes this script failing (no network, a version
20 string it does not recognise). A check that cannot decide must never be the
21 reason something is left missing or out of date, so its fallback is the old
22 behaviour, not a skip.
23
24 Every other install in the setup was already safe to re-run; the README's
25 "Re-running" note lists how each one decides.
26#>
27param(
4776737b 28 [Parameter(Mandatory)] [ValidateSet('ClaudeCode', 'NASM', 'AndroidStudio')] [string] $Step,
0b412520
MV
29 # As setup-windows.bat worked it out, from the registry: AMD64, ARM64 or x86.
30 [string] $HostArch
31)
32
33# Continue, not Stop: this runs native tools that may write to stderr, and every
34# decision below is made explicitly. Anything that does throw lands in the catch
35# at the bottom and becomes "go ahead and install".
36$ErrorActionPreference = 'Continue'
37
38function Say([string] $Msg) { Write-Host "[setup-windows] $Msg" }
39
40function Get-PEMachine([string] $Path) {
41 # The machine type in the COFF header, as Get-PEMachine in
42 # setup-windows-no-uac.ps1 reads it: what the file will actually run as.
43 $fs = [IO.File]::OpenRead($Path)
44 $br = New-Object IO.BinaryReader($fs)
45 try {
46 $fs.Seek(0x3c, 'Begin') | Out-Null
47 $fs.Seek($br.ReadInt32() + 4, 'Begin') | Out-Null
48 switch ($br.ReadUInt16()) { 0x8664 { 'x64' } 0xAA64 { 'ARM64' } 0x014c { 'x86' } default { 'unknown' } }
49 } finally { $br.Close(); $fs.Close() }
50}
51
52function Get-Version([string] $Text) {
53 # The first dotted version number in some output, or $null.
54 $m = [regex]::Match("$Text", '\d+(\.\d+)+')
55 if ($m.Success) { $m.Value } else { $null }
56}
57
58function Test-ClaudeCodeCurrent {
59 # Current means: the native install's claude.exe is the latest release AND
60 # the build for this host. The second half matters on ARM64, where an install
61 # run from an emulated shell got the x64 build - the same version, but the
62 # wrong binary, and worth replacing.
63 #
64 # The launcher in .local\bin is the full binary, not a shim, so its PE header
65 # is the build that runs. A copy from the old winget route is deliberately
66 # NOT consulted: the batch file reports that one separately.
67 $exe = Join-Path $env:USERPROFILE '.local\bin\claude.exe'
68 if (-not (Test-Path $exe)) { Say 'ClaudeCode: no native install yet.'; return $false }
69
70 $have = Get-Version (& $exe --version 2>$null | Out-String)
71 # The same endpoint the installer asks, so "latest" means the same thing.
72 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
73 $latest = Get-Version (Invoke-RestMethod -Uri 'https://downloads.claude.ai/claude-code-releases/latest' -ErrorAction Stop)
74 if (-not $have -or -not $latest) {
75 Say "ClaudeCode: could not compare versions (installed '$have', latest '$latest'); running the installer."
76 return $false
77 }
78 $want = if ($HostArch -eq 'ARM64') { 'ARM64' } else { 'x64' }
79 $mach = Get-PEMachine $exe
80 if ($have -ne $latest) {
81 Say "ClaudeCode: $have installed, $latest available; updating."
82 return $false
83 }
84 if ($mach -ne $want) {
85 Say "ClaudeCode: $have is current, but it is the $mach build on an $HostArch host; reinstalling for this host."
86 return $false
87 }
88 Say "ClaudeCode: $have is the latest release, $mach build; not reinstalling."
89 $true
90}
91
92function Test-NasmCurrent {
93 # Only the per-user fallback install needs this. A machine-wide NASM has an
94 # Add/Remove Programs entry, so winget sees it and upgrades it only when
95 # there is something newer - that case is left to winget.
96 $exe = Join-Path $env:LOCALAPPDATA 'bin\NASM\nasm.exe'
97 if (-not (Test-Path $exe)) { return $false }
98
99 $have = Get-Version (& $exe -v 2>$null | Out-String)
100 $show = winget show --id NASM.NASM --exact --accept-source-agreements --disable-interactivity 2>$null | Out-String
101 $m = [regex]::Match($show, '(?m)^\s*Version:\s*(\S+)')
102 $latest = if ($m.Success) { $m.Groups[1].Value } else { $null }
103 if (-not $have -or -not $latest) {
104 Say "NASM: could not compare versions (installed '$have', winget '$latest'); leaving it to winget."
105 return $false
106 }
107 if ($have -ne $latest) {
108 Say "NASM: $have installed per-user, $latest available; updating."
109 return $false
110 }
111 Say "NASM: $have (per-user, which winget cannot see) is the version winget offers; not reinstalling."
112 $true
113}
114
4776737b
MV
115function Test-AndroidStudioCurrent {
116 # Installed at all counts as current, because Android Studio keeps itself
117 # current: it updates in the app (Help > Check for Updates), and the winget
118 # package is published with UpgradeBehavior: deny and RequireExplicitUpgrade,
119 # so winget would not upgrade it anyway. The check matters for an install
120 # that did not come from winget - measured on the x64 laptop, where winget
121 # lists Studio only as "ARP\Machine\X64\Android Studio", unmatched to
122 # Google.AndroidStudio - since `winget install` would then run the installer
123 # over it. Looked for both ways: the Add/Remove Programs entry its installer
124 # writes, and studio64.exe where that installer puts it.
125 $arp = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
126 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
127 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue |
128 Where-Object { $_.DisplayName -eq 'Android Studio' }
129 $exe = @((Join-Path $env:ProgramFiles 'Android\Android Studio\bin\studio64.exe'),
130 (Join-Path $env:LOCALAPPDATA 'Programs\Android Studio\bin\studio64.exe')) | Where-Object { Test-Path $_ }
131 if (-not $arp -and -not $exe) { Say 'AndroidStudio: not installed yet.'; return $false }
132 $ver = @($arp)[0].DisplayVersion
133 Say "AndroidStudio: $(if ($ver) { $ver } else { 'an existing install' }) is already here, and it updates itself (Help > Check for Updates); not reinstalling."
134 $true
135}
136
0b412520
MV
137try {
138 $current = switch ($Step) {
4776737b
MV
139 'ClaudeCode' { Test-ClaudeCodeCurrent }
140 'NASM' { Test-NasmCurrent }
141 'AndroidStudio' { Test-AndroidStudioCurrent }
0b412520
MV
142 }
143} catch {
144 Say "${Step}: the version check failed ($($_.Exception.Message)); installing as usual."
145 $current = $false
146}
147exit $(if ($current -eq $true) { 0 } else { 1 })