From 335ad49dfcf3840264a61791d84aa5ec35efb2d9 Mon Sep 17 00:00:00 2001 From: Kevin Cui Date: Tue, 9 Jun 2026 02:39:14 -0400 Subject: [PATCH 1/2] fix(install): resolve Windows arch from env, not OSArchitecture The previous code read [RuntimeInformation]::OSArchitecture, which can be absent on the type PowerShell resolves in Windows PowerShell 5.1: a shadowing assembly such as PSReadLine may define its own RuntimeInformation without that property. Under Set-StrictMode the missing-property read becomes a terminating PropertyNotFoundStrict error, so `irm https://cli.oomol.com/install.ps1 | iex` fails. Detect the architecture from PROCESSOR_ARCHITEW6432 (falling back to PROCESSOR_ARCHITECTURE) instead. These work on every PowerShell version and report the true host architecture, including the WOW64 case where the inbox 32-bit PowerShell 5.1 on ARM64 resolves to arm64. The switch gains an amd64 case because that is the token PROCESSOR_ARCHITECTURE reports on x64 Windows. Verified on real Windows PowerShell 5.1 and PowerShell 7.6. Signed-off-by: Kevin Cui --- contrib/install/install-ps1.test.ts | 81 +++++++++++++++++++++++++++++ contrib/install/install.ps1 | 26 +++++++-- 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/contrib/install/install-ps1.test.ts b/contrib/install/install-ps1.test.ts index 88b2227f..90d9dfc8 100644 --- a/contrib/install/install-ps1.test.ts +++ b/contrib/install/install-ps1.test.ts @@ -39,6 +39,24 @@ describe("install.ps1", () => { expect(scriptContent).toContain("win32-arm64"); }); + test("detects architecture from processor environment variables", () => { + expect(scriptContent).toContain("PROCESSOR_ARCHITEW6432"); + expect(scriptContent).toContain("PROCESSOR_ARCHITECTURE"); + // RuntimeInformation's OSArchitecture is missing on the shadowed type in + // Windows PowerShell 5.1 and throws under Set-StrictMode, so the script must + // not depend on it for architecture detection. + expect(scriptContent).not.toContain("OSArchitecture"); + }); + + test("maps every shipped Windows architecture token to a platform id", () => { + expect(scriptContent).toContain("\"arm64\""); + // AMD64 is the only token PROCESSOR_ARCHITECTURE reports on x64 Windows, so + // the amd64 case is load-bearing; without it every x64 host would fall through + // to the default branch and fail. + expect(scriptContent).toContain("\"amd64\""); + expect(scriptContent).toContain("\"x64\""); + }); + windowsPowerShellTest( "uses %APPDATA%\\oo\\downloads as the default Windows download directory", () => { @@ -182,6 +200,51 @@ describe("install.ps1", () => { expect(result.exitCode).toBe(7); }, ); + + const resolvePlatformCases = [ + { architew6432: "", processor: "AMD64", expected: "win32-x64" }, + // Inbox Windows PowerShell 5.1 on an ARM64 host runs as a 32-bit (WOW64) + // process, so PROCESSOR_ARCHITECTURE is x86 but PROCESSOR_ARCHITEW6432 carries + // the true host architecture; the platform must still resolve to arm64. + { architew6432: "ARM64", processor: "x86", expected: "win32-arm64" }, + { architew6432: "", processor: "ARM64", expected: "win32-arm64" }, + ]; + + for (const { architew6432, processor, expected } of resolvePlatformCases) { + windowsPowerShellTest( + `Resolve-Platform maps ARCHITEW6432='${architew6432}' ARCHITECTURE='${processor}' to ${expected}`, + () => { + const result = runPowerShellCommand( + [ + `$env:PROCESSOR_ARCHITEW6432 = '${escapePowerShellString(architew6432)}'`, + `$env:PROCESSOR_ARCHITECTURE = '${escapePowerShellString(processor)}'`, + `. '${escapePowerShellString(installScriptPath)}'`, + "Resolve-Platform", + ].join("; "), + ); + + expect(result.exitCode).toBe(0); + expect(decodeSpawnOutput(result.stdout).trim()).toBe(expected); + }, + ); + } + + windowsPowerShellTest( + "Resolve-Platform fails on an unsupported architecture", + () => { + const result = runPowerShellCommand( + [ + "$env:PROCESSOR_ARCHITEW6432 = ''", + "$env:PROCESSOR_ARCHITECTURE = 'x86'", + `. '${escapePowerShellString(installScriptPath)}'`, + "Resolve-Platform", + ].join("; "), + ); + + expect(result.exitCode).not.toBe(0); + expect(decodeSpawnOutput(result.stderr)).toContain("Unsupported Windows architecture"); + }, + ); }); function resolvePowerShellCommand(): string | undefined { @@ -220,3 +283,21 @@ function resolvePowerShellCommand(): string | undefined { function escapePowerShellString(value: string): string { return value.replaceAll("'", "''"); } + +function runPowerShellCommand(command: string) { + return Bun.spawnSync( + [ + powerShellCommand!, + "-NoLogo", + "-NoProfile", + "-Command", + command, + ], + { + env: process.env, + stderr: "pipe", + stdin: "ignore", + stdout: "pipe", + }, + ); +} diff --git a/contrib/install/install.ps1 b/contrib/install/install.ps1 index 31b68209..dcbd34f6 100644 --- a/contrib/install/install.ps1 +++ b/contrib/install/install.ps1 @@ -67,14 +67,32 @@ function Resolve-Platform { Assert-Windows - $architecture = ( - [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture - ).ToString().ToLowerInvariant() + # Detect the host architecture from environment variables rather than the .NET + # RuntimeInformation architecture API. That API can be absent on the type + # PowerShell resolves in Windows PowerShell 5.1 (a shadowing assembly such as + # PSReadLine can define its own RuntimeInformation without it), which throws under + # Set-StrictMode; on .NET Framework it also reports the emulated process + # architecture rather than the host. PROCESSOR_ARCHITEW6432 is populated only + # inside a 32-bit (WOW64) process and holds the true host architecture, so the + # inbox 32-bit Windows PowerShell 5.1 on an ARM64 host still resolves to arm64. + # Reading $env: under StrictMode is provider access and safely yields $null when + # the variable is unset. + $architecture = $env:PROCESSOR_ARCHITEW6432 + if ([string]::IsNullOrWhiteSpace($architecture)) { + $architecture = $env:PROCESSOR_ARCHITECTURE + } + + if ([string]::IsNullOrWhiteSpace($architecture)) { + Fail "Could not determine the Windows processor architecture." + } - switch ($architecture) { + switch ($architecture.ToLowerInvariant()) { "arm64" { return "win32-arm64" } + "amd64" { + return "win32-x64" + } "x64" { return "win32-x64" } From 8bfd6ba35f6759a5e023ff870c49a9c814f9ede7 Mon Sep 17 00:00:00 2001 From: Kevin Cui Date: Tue, 9 Jun 2026 02:45:25 -0400 Subject: [PATCH 2/2] test(install): isolate arch tests from OO_INSTALL_PLATFORM runPowerShellCommand forwarded the full process environment, so an externally set OO_INSTALL_PLATFORM would short-circuit Resolve-Platform and make the architecture-detection tests nondeterministic. Clear that variable in the spawned environment so the tests always exercise the real detection path. Signed-off-by: Kevin Cui --- contrib/install/install-ps1.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contrib/install/install-ps1.test.ts b/contrib/install/install-ps1.test.ts index 90d9dfc8..8b5d06ba 100644 --- a/contrib/install/install-ps1.test.ts +++ b/contrib/install/install-ps1.test.ts @@ -294,7 +294,10 @@ function runPowerShellCommand(command: string) { command, ], { - env: process.env, + // Neutralize OO_INSTALL_PLATFORM so a value inherited from the caller's + // environment cannot short-circuit Resolve-Platform and make the + // architecture-detection tests nondeterministic. + env: { ...process.env, OO_INSTALL_PLATFORM: "" }, stderr: "pipe", stdin: "ignore", stdout: "pipe",