fix(windows): keep install.ps1 pure ASCII so PowerShell can parse it

install.ps1 did not run. It failed to PARSE, dying with a cascade of syntax
errors before its first statement, and it has been in that state on main since
61a432c.

Windows PowerShell 5.1 decodes a BOM-less .ps1 as ANSI, not UTF-8. The three
em-dashes in this file are 3 UTF-8 bytes each, and cp1252 renders them as a
sequence ENDING IN A DOUBLE QUOTE. Two sat in comments and were harmless; the
third sat inside a string literal:

    "error: could not retire the existing $BinName - close all running"

where the injected quote closed the string early and left the rest of the line
as garbage expression, taking the enclosing if/foreach blocks with it.

This shipped broken because nothing executed the script. The smoke covers it in
Phase 13, but every Windows smoke run since the launcher removal aborted at an
earlier phase, and the VirusTotal check only ever scanned the bytes. Since
install.ps1 is now the sole Windows install AND update path, a parse error there
is a total loss of both.

Fixed as ASCII rather than by adding a BOM: the documented install pipes the
file straight into the parser (irm ... | iex), where a BOM would become part of
the string and break that path instead.

The bundle contract now rejects any non-ASCII codepoint in install.ps1 and names
it, so this cannot return silently.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-07-28 19:42:21 +02:00
1 parent c8a58cfca8
commit b0eae97c03
2 files changed
+16 -3

No files matched your search

+3 -3
View File
@@ -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
+13
View File
@@ -164,6 +164,19 @@ for archive, call, ui in (
# "<dest>.retired-<timestamp>", 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",