~/home

PowerShell 5.1 traps that only show up in the admin run

· windows, powershell

TL;DR: Windows PowerShell 5.1 pitfalls: BOM-less UTF-8 scripts are read as ANSI, a failed [xml] cast does not stop the script, $null becomes an empty string in .NET calls, unary-comma returns break pipelines, variable names are case-insensitive, and native exit codes must be checked.

I wrote a cleanup script and tested it with non-admin dry runs. Every real bug showed up in the elevated run. These are the ones worth remembering.

Keep .ps1 files pure ASCII

Windows PowerShell 5.1 reads a UTF-8 file without a BOM in the system's ANSI code page. One non-ASCII character in a regex turned into three garbage characters and the pattern stopped matching.

A failed cast doesn't stop the script

[xml]$x = $text on bad input writes an error and carries on with $x empty. The error message also includes the whole input, which can be megabytes of red text. Use XmlDocument.LoadXml in try/catch and truncate the message.

$null becomes "" in .NET calls

Passing $null to a .NET string parameter sends an empty string. When the API really needs NULL, pass [NullString]::Value.

The unary comma and the pipeline

A function that ends in return ,$list sends the list as one object. Get-Things | Where-Object { ... } then filters the whole array as a single item. Assign to a variable first, then pipe.

Variables are case-insensitive

A local $folders = @() inside a function hid the script-level $Folders. The loop ran over an empty array and the script reported success. Give config lists names that can't collide.

Guards must fail closed

Start with $blocked = $true and clear it only when the check fully completes. My first version did the opposite, and a parse failure would have let it proceed.

Check native exit codes

pnputil /export-driver fails if the target folder doesn't exist. Piped to Out-Null, nobody noticed, and the next line deleted the package without a backup. Check $LASTEXITCODE and that the file landed.