From 812a7439cef4590dcbc0a62397f94b6e7b4a7787 Mon Sep 17 00:00:00 2001 From: merefield Date: Mon, 5 Oct 2026 19:04:29 +0100 Subject: [PATCH] FIX: support Windows PowerShell architecture detection in installer --- .github/workflows/ci.yml | 7 ++++- .github/workflows/release.yml | 7 ++++- README.md | 5 +++- install-release.ps1 | 37 +++++++++++++++++++---- test/install-release.ps1 | 55 +++++++++++++++++++++++++++++++++-- 5 files changed, 101 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e585e1..69010d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,7 +79,12 @@ jobs: if: runner.os == 'Linux' run: bats test/install-release.bats - - name: Test Windows release installer + - name: Test Windows release installer (PowerShell 7) if: runner.os == 'Windows' shell: pwsh run: ./test/install-release.ps1 + + - name: Test Windows release installer (Windows PowerShell 5.1) + if: runner.os == 'Windows' + shell: powershell + run: ./test/install-release.ps1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 76ec6a8..7427510 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -124,11 +124,16 @@ jobs: if: runner.os == 'Linux' run: bats test/install-release.bats - - name: Test Windows release installer + - name: Test Windows release installer (PowerShell 7) if: runner.os == 'Windows' shell: pwsh run: ./test/install-release.ps1 + - name: Test Windows release installer (Windows PowerShell 5.1) + if: runner.os == 'Windows' + shell: powershell + run: ./test/install-release.ps1 + release: name: Build and publish needs: [validate, test] diff --git a/README.md b/README.md index dc931be..e1821e5 100644 --- a/README.md +++ b/README.md @@ -282,7 +282,10 @@ To install a specific release, replace `vX.Y.Z` with its tag and add `CODEXOMETER_VERSION=vX.Y.Z` beside the bin-directory setting, or download the script and pass `--version vX.Y.Z`. -On Windows, download and run the PowerShell installer: +On Windows, download and run the PowerShell installer using either built-in +Windows PowerShell 5.1 (`powershell`) or PowerShell 7 (`pwsh`). There is no need +to upgrade PowerShell to install Codexometer. To check your shell version, run +`$PSVersionTable`. ```powershell $installer = Join-Path ([IO.Path]::GetTempPath()) "install-codexometer.ps1" diff --git a/install-release.ps1 b/install-release.ps1 index cbf0e3e..63ae044 100644 --- a/install-release.ps1 +++ b/install-release.ps1 @@ -33,6 +33,29 @@ function Fail { throw "codexometer installer: $Message" } +function Resolve-ReleaseArchitecture { + param( + [string]$OSArchitecture, + [string]$NativeArchitecture, + [string]$ProcessArchitecture + ) + + # Prefer the OS, not the shell architecture. WOW64 exposes the native + # architecture separately when a 32-bit shell runs on 64-bit Windows. + $architecture = $OSArchitecture + if ([string]::IsNullOrWhiteSpace($architecture)) { + $architecture = $NativeArchitecture + } + if ([string]::IsNullOrWhiteSpace($architecture)) { + $architecture = $ProcessArchitecture + } + switch ($architecture) { + { $_ -in @("X64", "AMD64") } { return "amd64" } + "ARM64" { return "arm64" } + default { Fail "unsupported or undetermined architecture: $architecture" } + } +} + $Version = Get-Setting $Version "CODEXOMETER_VERSION" "latest" $Repository = Get-Setting $Repository "CODEXOMETER_REPOSITORY" "merefield/codexometer" $GitHubUrl = (Get-Setting $GitHubUrl "CODEXOMETER_GITHUB_URL" "https://github.com").TrimEnd("/") @@ -63,12 +86,16 @@ if ($Version -ne "latest" -and $Version -cnotmatch $semanticTagPattern) { Fail "invalid semantic release tag: $Version" } -$architecture = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() -switch ($architecture) { - "X64" { $releaseArch = "amd64" } - "Arm64" { $releaseArch = "arm64" } - default { Fail "unsupported architecture: $architecture" } +$osArchitecture = "" +try { + $osArchitecture = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() +} catch { + # Some Windows PowerShell 5.1/.NET Framework environments do not expose + # this property (or its type). Use Windows' architecture variables instead. } +$releaseArch = Resolve-ReleaseArchitecture $osArchitecture ` + ([Environment]::GetEnvironmentVariable("PROCESSOR_ARCHITEW6432")) ` + ([Environment]::GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")) [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 $headers = @{ "User-Agent" = "codexometer-release-installer" } diff --git a/test/install-release.ps1 b/test/install-release.ps1 index 1009c9e..f25e812 100644 --- a/test/install-release.ps1 +++ b/test/install-release.ps1 @@ -36,13 +36,64 @@ function Invoke-Installer { if (-not [string]::IsNullOrWhiteSpace($RequestedVersion)) { $arguments += @("-Version", $RequestedVersion) } - $output = & $hostExecutable @arguments 2>&1 + # Windows PowerShell turns redirected native stderr into ErrorRecords. + # Expected failing child installers must reach the exit-code assertions. + $savedErrorActionPreference = $ErrorActionPreference + try { + $ErrorActionPreference = "Continue" + $output = & $hostExecutable @arguments 2>&1 + $status = $LASTEXITCODE + } finally { + $ErrorActionPreference = $savedErrorActionPreference + } return @{ - Status = $LASTEXITCODE + Status = $status Output = ($output | Out-String).Trim() } } +# Load only the architecture helper and its error helper, without running the +# installer. Exercise missing RuntimeInformation data even on modern .NET. +$parseTokens = $null +$parseErrors = $null +$installerAst = [Management.Automation.Language.Parser]::ParseFile($installer, [ref]$parseTokens, [ref]$parseErrors) +if ($parseErrors.Count -ne 0) { + throw "installer contains PowerShell syntax errors" +} +foreach ($name in @("Fail", "Resolve-ReleaseArchitecture")) { + $functionAst = $installerAst.Find({ + param($node) + $node -is [Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq $name + }, $true) + if ($null -eq $functionAst) { throw "missing installer function: $name" } + . ([scriptblock]::Create($functionAst.Extent.Text)) +} +foreach ($case in @( + @{ OS = "X64"; Native = ""; Process = "AMD64"; Want = "amd64" }, + @{ OS = "Arm64"; Native = ""; Process = "AMD64"; Want = "arm64" }, + @{ OS = ""; Native = ""; Process = "AMD64"; Want = "amd64" }, + @{ OS = ""; Native = ""; Process = "ARM64"; Want = "arm64" }, + @{ OS = ""; Native = "AMD64"; Process = "x86"; Want = "amd64" }, + @{ OS = ""; Native = "ARM64"; Process = "x86"; Want = "arm64" }, + @{ OS = ""; Native = "ARM64"; Process = "AMD64"; Want = "arm64" } +)) { + $actual = Resolve-ReleaseArchitecture $case.OS $case.Native $case.Process + if ($actual -ne $case.Want) { throw "architecture mapping failed: $actual, expected $($case.Want)" } +} +foreach ($case in @( + @{ OS = ""; Native = ""; Process = "" }, + @{ OS = ""; Native = ""; Process = "x86" }, + @{ OS = ""; Native = "IA64"; Process = "x86" }, + @{ OS = "Unknown"; Native = ""; Process = "AMD64" } +)) { + $rejected = $false + try { Resolve-ReleaseArchitecture $case.OS $case.Native $case.Process | Out-Null } catch { + if ($_.Exception.Message -notmatch 'unsupported or undetermined architecture') { throw } + $rejected = $true + } + if (-not $rejected) { throw "unsupported architecture was accepted" } +} + try { New-Item -ItemType Directory -Path $testRoot | Out-Null New-Item -ItemType Directory -Path $packageDirectory, $releaseDirectory, $latestDirectory -Force | Out-Null