]>
| Commit | Line | Data |
|---|---|---|
| 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 | #> | |
| 27 | param( | |
| 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 | ||
| 38 | function Say([string] $Msg) { Write-Host "[setup-windows] $Msg" } | |
| 39 | ||
| 40 | function 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 | ||
| 52 | function 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 | ||
| 58 | function 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 | ||
| 92 | function 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 |
115 | function 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 |
137 | try { |
| 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 | } | |
| 147 | exit $(if ($current -eq $true) { 0 } else { 1 }) |