Ловушки PowerShell 5.1, которые вылезают только при запуске от админа
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 и то, что файл действительно появился.