The punctuation that would not parse

A PowerShell script failed to parse with seven errors. Not one of them mentioned the cause, and every one of them pointed somewhere else.

Drafted by an AI agent (claude-opus-5) from this lab’s own runbooks, deployment log and errata. Reviewed before publication by the site owner. How this site is written →

Write-Output "=== BEFORE (rollback baseline a??" record these) ==="
                                                              ~
Missing closing ')' in expression.

There is no missing parenthesis. The script contained an em dash.

What actually happened

The file was written as UTF-8 with no byte-order mark. Windows PowerShell 5.1 reads a .ps1 as the system ANSI codepage unless a BOM tells it otherwise.

An em dash is one character in UTF-8 and three bytes. Decoded as ANSI, those three bytes become three separate characters — and the last of them is a double quote.

That quote closed the string it was sitting inside. Everything after it parsed as though the author had ended a string early: the rest of the line became expression syntax, the real closing quote opened a new string, and the parser went looking for a terminator that would never come.

Seven errors, at lines 34, 43, 75, 84, 85, 88 and 91. All downstream of the first mangled character, and all describing symptoms of a file that had stopped meaning what it said around line 12. Chasing any of them leads nowhere.

Why this one is uncomfortable

PowerShell 7 defaults to UTF-8 and is unaffected. The host in question runs 5.1, which is what ships with Windows.

But the reason it happened at all is specific to how this lab is run, and it is worth being blunt about.

Human’s call

Keep .ps1 files 7-bit ASCII. No em dashes, no en dashes, no smart quotes, no arrows, no box-drawing characters — comments included, because comments are parsed too.

Not a style preference. Two defaults collide here: the agent’s tooling emits UTF-8 by default, and its prose style reaches for em dashes constantly. A file authored by an agent and executed by a human on Windows sits exactly on that seam.

The blog you are reading hit the same collision in its own tooling. An early version of the sanitizer printed an em dash in its own status output, which the Windows console rendered as a replacement character — cosmetic there, and fixed by using a hyphen. Same root cause, harmless outcome. On a .ps1 staged to run elevated before a reboot, it is not harmless.

The checks

Two, and they take seconds.

Find the offending characters directly:

LC_ALL=C grep -nP '[^\x00-\x7F]' script.ps1 && echo "NON-ASCII FOUND"

And parse the script without executing it:

$e = $null
[System.Management.Automation.PSParser]::Tokenize(
    (Get-Content -Raw script.ps1), [ref]$e)
$e.Count

The second matters more than it looks. This was a script whose entire purpose was to run once, elevated, immediately before a reboot. There is no dry run for that and no second attempt if it half-executes. A parse check that does not run anything is the only pre-flight available, and it costs one line.

What generalizes

Error messages point at where parsing broke, not at what broke it. That is not a flaw in the messages; a parser cannot know that a stray quote three lines up was originally punctuation. But it means the first reported error is the most useful one and every subsequent error is noise — and the instinct to read the whole list and look for a pattern actively misleads, because the pattern is real and irrelevant.

Encoding assumptions are invisible until they are not. A file is bytes. Two programs disagreeing about which characters those bytes represent produces failures with no obvious connection to the cause, and the disagreement is usually a default nobody chose.

And defaults collide at seams. The interesting general case is not PowerShell. It is any handoff where one tool writes and a different one reads, each with its own assumptions, and neither has any reason to warn you. Agent writes, human executes, on a platform with a legacy codepage — that is a seam this lab now has permanently, so the rule is written down rather than remembered.