diff --git a/install.ps1 b/install.ps1 index 60df83477..f455c983d 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,4 +1,4 @@ -# install.ps1 — One-line installer for codebase-memory-mcp (Windows). +# install.ps1 - One-line installer for codebase-memory-mcp (Windows). # # Usage: see README.md for install instructions. # @@ -103,7 +103,7 @@ foreach ($arg in $args) { # PROCESSOR_ARCHITEW6432, which is unset for 64-bit emulated processes. Fall back # to the env vars only if the .NET API is somehow unavailable. if ($env:CBM_ARCH) { - # Explicit override wins — used by CI/tests, and an escape hatch under x64 + # Explicit override wins - used by CI/tests, and an escape hatch under x64 # emulation on ARM64 where no in-process detection is reliable. $Arch = $env:CBM_ARCH } else { @@ -279,7 +279,7 @@ if (Test-Path -LiteralPath $Dest -PathType Leaf) { catch { Start-Sleep -Milliseconds 500 } } if (-not $renamed) { - Write-Host "error: could not retire the existing $BinName — close all running" -ForegroundColor Red + Write-Host "error: could not retire the existing $BinName - close all running" -ForegroundColor Red Write-Host " codebase-memory-mcp sessions and coding agents, then re-run." -ForegroundColor Red Remove-Item -Recurse -Force $TmpDir -ErrorAction SilentlyContinue exit 1 diff --git a/tests/test_windows_bundle_contract.sh b/tests/test_windows_bundle_contract.sh index 780c13027..532462d1b 100644 --- a/tests/test_windows_bundle_contract.sh +++ b/tests/test_windows_bundle_contract.sh @@ -164,6 +164,19 @@ for archive, call, ui in ( # ".retired-", then installs over the freed name. Without the # retire step every Windows update fails on a locked destination. installer = read("install.ps1") +# Windows PowerShell 5.1 decodes a BOM-less .ps1 as ANSI, so a UTF-8 em-dash +# arrives as three cp1252 characters ending in a double quote. Inside a string +# literal that quote closes it early and the whole script dies with a cascade of +# parse errors before its first statement runs. A BOM would fix the file on disk +# but corrupt the documented `irm ... | iex` path, which pipes the bytes +# straight into the parser -- so the shipped installer stays pure ASCII. +non_ascii = sorted({ch for ch in installer if ord(ch) > 0x7F}) +require( + not non_ascii, + "install.ps1 must be pure ASCII (found: " + + ", ".join(f"U+{ord(ch):04X}" for ch in non_ascii) + + ")", +) require( "$Dest = Join-Path $InstallDir $BinName" in installer, "install.ps1 must resolve the canonical install destination as $Dest",