# Ловушки PowerShell 5.1, которые вылезают только при запуске от админа

Опубликовано 2026-09-23 · PowerShell, Windows, скрипты · https://home.tigran.manukyan.dev/ru/notes/powershell-51-traps.html

**TL;DR:** Подводные камни Windows PowerShell 5.1: UTF-8 без BOM читается как ANSI, неудачный каст [xml] не останавливает скрипт, $null превращается в пустую строку в .NET-вызовах, возврат через унарную запятую ломает пайплайн, имена переменных не различают регистр, а коды выхода внешних программ надо проверять.

Написал скрипт для чистки системы и гонял его в режиме dry run без прав администратора. Все настоящие баги вылезли при запуске от админа. Вот те, что стоит запомнить.

## Держите .ps1 в чистом ASCII

Windows PowerShell 5.1 читает UTF-8 без BOM в системной ANSI-кодировке (на русской Windows это cp1251). Один не-ASCII символ в регулярке превратился в кракозябры, и шаблон перестал совпадать.

## Неудачный каст не останавливает скрипт

`[xml]$x = $text` на кривых данных пишет ошибку и спокойно идёт дальше с пустым `$x`. Причём в текст ошибки попадает весь вход, а это могут быть мегабайты красного текста. Используйте `XmlDocument.LoadXml` в `try/catch` и обрезайте сообщение.

## $null превращается в "" в .NET-вызовах

Если передать `$null` в .NET-параметр типа `string`, уйдёт пустая строка. Когда API действительно нужен NULL, передавайте `[NullString]::Value`.

## Унарная запятая и пайплайн

Функция, которая заканчивается на `return ,$list`, отдаёт список одним объектом. Тогда `Get-Things | Where-Object { ... }` фильтрует весь массив как один элемент. Сначала присвойте результат переменной, а уже потом отправляйте в пайплайн.

## Имена переменных не различают регистр

Локальная `$folders = @()` внутри функции перекрыла `$Folders` уровня скрипта. Цикл прошёл по пустому массиву, и скрипт отчитался об успехе. Давайте спискам из конфига имена, которые ни с чем не пересекутся.

## Защитные проверки должны быть fail-closed

Начинайте с `$blocked = $true` и сбрасывайте флаг, только когда проверка полностью прошла. Моя первая версия делала наоборот, и ошибка парсинга пропустила бы скрипт дальше.

## Проверяйте коды выхода внешних программ

`pnputil /export-driver` падает, если целевой папки нет. Вывод уходил в `Out-Null`, так что этого никто не заметил, и следующая строка удалила пакет без бэкапа. Проверяйте `$LASTEXITCODE` *и* то, что файл действительно появился.
