<# setup-windows-current.ps1 - is this step's program already current? setup-windows.bat asks this before the two installs that would otherwise run in full on every re-run, because neither the installer nor winget can tell that what is on the box is already the latest: ClaudeCode Anthropic's installer script always downloads the release (a couple of hundred MB) and reinstalls it; it has no "already current" check of its own. NASM its installer falls back to a per-user directory when it cannot write to Program Files, and registers nothing there - so winget cannot see it, and reruns the installer every time. Exit code 0 means already current: skip the install. Anything else means go ahead as before - and that includes this script failing (no network, a version string it does not recognise). A check that cannot decide must never be the reason something is left missing or out of date, so its fallback is the old behaviour, not a skip. Every other install in the setup was already safe to re-run; the README's "Re-running" note lists how each one decides. #> param( [Parameter(Mandatory)] [ValidateSet('ClaudeCode', 'NASM')] [string] $Step, # As setup-windows.bat worked it out, from the registry: AMD64, ARM64 or x86. [string] $HostArch ) # Continue, not Stop: this runs native tools that may write to stderr, and every # decision below is made explicitly. Anything that does throw lands in the catch # at the bottom and becomes "go ahead and install". $ErrorActionPreference = 'Continue' function Say([string] $Msg) { Write-Host "[setup-windows] $Msg" } function Get-PEMachine([string] $Path) { # The machine type in the COFF header, as Get-PEMachine in # setup-windows-no-uac.ps1 reads it: what the file will actually run as. $fs = [IO.File]::OpenRead($Path) $br = New-Object IO.BinaryReader($fs) try { $fs.Seek(0x3c, 'Begin') | Out-Null $fs.Seek($br.ReadInt32() + 4, 'Begin') | Out-Null switch ($br.ReadUInt16()) { 0x8664 { 'x64' } 0xAA64 { 'ARM64' } 0x014c { 'x86' } default { 'unknown' } } } finally { $br.Close(); $fs.Close() } } function Get-Version([string] $Text) { # The first dotted version number in some output, or $null. $m = [regex]::Match("$Text", '\d+(\.\d+)+') if ($m.Success) { $m.Value } else { $null } } function Test-ClaudeCodeCurrent { # Current means: the native install's claude.exe is the latest release AND # the build for this host. The second half matters on ARM64, where an install # run from an emulated shell got the x64 build - the same version, but the # wrong binary, and worth replacing. # # The launcher in .local\bin is the full binary, not a shim, so its PE header # is the build that runs. A copy from the old winget route is deliberately # NOT consulted: the batch file reports that one separately. $exe = Join-Path $env:USERPROFILE '.local\bin\claude.exe' if (-not (Test-Path $exe)) { Say 'ClaudeCode: no native install yet.'; return $false } $have = Get-Version (& $exe --version 2>$null | Out-String) # The same endpoint the installer asks, so "latest" means the same thing. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $latest = Get-Version (Invoke-RestMethod -Uri 'https://downloads.claude.ai/claude-code-releases/latest' -ErrorAction Stop) if (-not $have -or -not $latest) { Say "ClaudeCode: could not compare versions (installed '$have', latest '$latest'); running the installer." return $false } $want = if ($HostArch -eq 'ARM64') { 'ARM64' } else { 'x64' } $mach = Get-PEMachine $exe if ($have -ne $latest) { Say "ClaudeCode: $have installed, $latest available; updating." return $false } if ($mach -ne $want) { Say "ClaudeCode: $have is current, but it is the $mach build on an $HostArch host; reinstalling for this host." return $false } Say "ClaudeCode: $have is the latest release, $mach build; not reinstalling." $true } function Test-NasmCurrent { # Only the per-user fallback install needs this. A machine-wide NASM has an # Add/Remove Programs entry, so winget sees it and upgrades it only when # there is something newer - that case is left to winget. $exe = Join-Path $env:LOCALAPPDATA 'bin\NASM\nasm.exe' if (-not (Test-Path $exe)) { return $false } $have = Get-Version (& $exe -v 2>$null | Out-String) $show = winget show --id NASM.NASM --exact --accept-source-agreements --disable-interactivity 2>$null | Out-String $m = [regex]::Match($show, '(?m)^\s*Version:\s*(\S+)') $latest = if ($m.Success) { $m.Groups[1].Value } else { $null } if (-not $have -or -not $latest) { Say "NASM: could not compare versions (installed '$have', winget '$latest'); leaving it to winget." return $false } if ($have -ne $latest) { Say "NASM: $have installed per-user, $latest available; updating." return $false } Say "NASM: $have (per-user, which winget cannot see) is the version winget offers; not reinstalling." $true } try { $current = switch ($Step) { 'ClaudeCode' { Test-ClaudeCodeCurrent } 'NASM' { Test-NasmCurrent } } } catch { Say "${Step}: the version check failed ($($_.Exception.Message)); installing as usual." $current = $false } exit $(if ($current -eq $true) { 0 } else { 1 })