Обработка ошибок и retry в PowerShell — от ловушки нерабочего try/catch до exit code и типовых схем повтора
· Обновлено: · Го Комура · PowerShell, Windows, Обработка ошибок, Retry, Автоматизация, Улучшение эксплуатации, Скрипт, Планировщик заданий
История изменений (6 обновлений, последнее 30 Aug 2026)
Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.
- Локализован путь к несуществующему файлу в примере. Утверждения статьи не менялись.
- Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
- В начало статьи добавлен раздел «Карта знаний этой статьи». Понятия из текста и связи между ними собраны в кратком изложении, на схеме и на странице сведений. Утверждения самой статьи не менялись.
- Текст обновлён по итогам внешнего ревью (1283 замечания). Содержание отдельных правок смотрите в записях ниже.
- В одном абзаце уточнена ось классификации (официальные три категории описывают, насколько далеко останавливается выполнение; в статье основная ось — попадает ли ошибка в `try`/`catch`, то есть две категории). При первом упоминании идемпотентности добавлено пояснение, приведены конкретные примеры ошибок, завершающих инструкцию, и минимальный код; в основной текст вынесены имя экспериментальной функции `$PSNativeCommandUseErrorActionPreference` и то, что в 5.1 самой переменной нет.
- Классификация ошибок была описана неточно. Исправлено: сначала ошибки делятся на незавершающие и завершающие, а завершающие делятся дальше.
- Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.21620112)
Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.
Го Комура (2026). Обработка ошибок и retry в PowerShell — от ловушки нерабочего try/catch до exit code и типовых схем повтора. KomuraSoft LLC. https://comcomponent.com/ru/blog/powershell-error-handling-retry-design/
- DOI (зарегистрированный архив)
- 10.5281/zenodo.21620112
- DOI (последняя зарегистрированная версия)
- 10.5281/zenodo.21620113
«Ночной пакет завершился ошибкой, а в Планировщике заданий стоял успех (0x0), и никто этого не заметил». «Написал try/catch, а в catch выполнение не попадает». «Из-за кратковременного обрыва сети падает раз в месяц». Как только скрипт PowerShell выходит в эксплуатацию, такие обращения появляются почти неизбежно. От скрипта, который хорошо отрабатывает у вас вручную, до скрипта, который каждую ночь идёт без оператора, отделяет проектирование обработки ошибок и повторного запуска (retry).
Сложность в том, что модель ошибок PowerShell чуть иначе устроена, чем модель исключений в большинстве языков. «Ошибка есть, а выполнение идёт дальше» и «вроде перехватил в catch, а оно прошло мимо» почти всегда не баг, а поведение строго по спецификации PowerShell. Если писать, не зная этого механизма, получаются скрипты, которые проглатывают сбой и завершаются как успешные.
Статья рассчитана на специалистов ИТ-отделов и разработчиков, которые автоматизируют типовые внутренние процессы на PowerShell. По официальной документации разберём различие завершающих и незавершающих ошибок, проверку успеха нативных команд, проектирование exit code, по которому Планировщик заданий и мониторинг могут судить об успехе, и схему retry, которая выдерживает временные ошибки.
1. Сначала выводы
- Ошибки PowerShell делятся на незавершающие и завершающие (завершающие инструкцию / завершающие скрипт). Незавершающая ошибка выводит сообщение и продолжает конвейер; по умолчанию в try/catch она не попадает.1
- Способов считать категории два, поэтому сначала зафиксируем оси. В официальной документации три категории: незавершающая / завершающая инструкцию / завершающая скрипт. Это ось движка «насколько далеко остановить» (только конвейер / только эту инструкцию / весь стек вызовов).1 Автору же сначала нужно знать, попадёт ли ошибка в try/catch; на этой оси остаются две разновидности — незавершающая и завершающая. В статье основная ось — две разновидности, к трём категориям возвращаемся там, где это нужно.
- Стандартный приём: к команде, которую должен перехватить try/catch, добавляют
-ErrorAction Stop. Stop повышает незавершающую ошибку до завершающей, и её обрабатывает catch. Другой вариант — в начале скрипта задать$ErrorActionPreference(по умолчанию Continue) в Stop.12 -ErrorActionпереопределяет$ErrorActionPreferenceтолько для этой одной команды. Они не полностью симметричны:-ErrorActionуправляет только незавершающими ошибками.1- Сбой нативной команды (robocopy, git, внешний EXE) по умолчанию не становится ошибкой PowerShell. Ненулевой код завершения ставит
$?в$falseи попадает в$LASTEXITCODE, но ErrorRecord не создаётся и в catch ничего не попадает. Успех проверяйте по$LASTEXITCODE.1 - В PowerShell 7.4
$PSNativeCommandUseErrorActionPreferenceстала штатной функцией. При$trueненулевой код завершения порождает незавершающую ошибку, и вместе с$ErrorActionPreference = 'Stop'её можно перехватить в try/catch (по умолчанию$false).32 - Внутри catch
$_— это ErrorRecord.$_.Exceptionдаёт само исключение; если ошибку повысили, исходные сведения доступны через$_.Exception.ErrorRecord. Блок catch с указанием типа позволяет отдельно обработать только ожидаемые ошибки.14 - Об успехе или неудаче вовне обязательно сообщайте через exit code. Код завершения задаёт ключевое слово
exit; при запуске черезpwsh -File/powershell.exe -Fileэто значение становится кодом завершения процесса. Без exit нормальное завершение даёт 0, необработанное исключение — 1.56 - Три принципа retry: только временные ошибки, верхняя граница, идемпотентность (сколько раз ни выполнить одну и ту же обработку, результат не меняется; подробно в главе 6). Не маскируйте retry бизнес-ошибки, увеличивайте интервал по схеме экспоненциального backoff, проектируйте обработку так, чтобы повторный запуск не давал двойной обработки. Только когда выполнены все три условия, скрипт можно считать «допустимым для повторного запуска».
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 18, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
2. Два вида ошибок — почему try/catch не срабатывает
Ошибки PowerShell сначала делятся на незавершающие и завершающие. Завершающие дальше делятся на ошибки, завершающие инструкцию, и ошибки, завершающие скрипт, поэтому при подробном счёте получается три категории. Незавершающая ошибка только сообщает о проблеме и не останавливает конвейер; ошибка, завершающая инструкцию, останавливает только эту инструкцию и переходит к следующей; ошибка, завершающая скрипт, откатывает весь стек вызовов.1
На практике ловушкой оказываются незавершающие ошибки. Когда командлет вроде Get-Content или Get-ChildItem не смог обработать отдельный элемент входа, он обычно выдаёт именно незавершающую ошибку: красное сообщение появляется, выполнение идёт дальше и не попадает ни в try/catch, ни в trap.1
# [Ловушка] в catch не попадём ни разу, на экране будет и «Готово»
try {
Get-Content -Path 'C:\Data\несуществующий-файл.txt' # незавершающая ошибка
Write-Host 'Готово' # выполнится
}
catch {
Write-Host 'Сюда выполнение не дойдёт'
}
# [Стандартный приём] -ErrorAction Stop повышает ошибку до завершающей, и её обрабатывает catch
try {
Get-Content -Path 'C:\Data\несуществующий-файл.txt' -ErrorAction Stop
Write-Host 'Готово' # при ошибке не выполняется
}
catch {
Write-Host "Перехвачено: $($_.Exception.Message)"
}
Когда действуют -ErrorAction Stop или $ErrorActionPreference = 'Stop', движок оборачивает незавершающую ошибку в ActionPreferenceStopException и повышает её до завершающей. Точный механизм таков: внутри блока try до catch доходит уже эта повышенная ошибка.1
С другой стороны, есть ошибки, которые с самого начала завершающие (то есть попадают в catch без дополнительных мер). Это ошибки, завершающие инструкцию. Официальная документация указывает такие источники.1
- Вызов несуществующей команды (
CommandNotFoundException) - Сбой привязки параметров (
ParameterBindingException; например, в числовой параметр передали строку, которую нельзя преобразовать) - Метод .NET выбросил исключение (например,
[int]::Parse('abc')) - Командлет или advanced function сообщил через
$PSCmdlet.ThrowTerminatingError(), что этот вызов продолжать нельзя
Как следует из названия, останавливается «только эта инструкция», и скрипт продолжает выполнение со следующей.1
# Ошибка, завершающая инструкцию: эта инструкция останавливается, следующая выполняется
[int]::Parse('abc')
Write-Output 'Эта строка выполнится'
# Это завершающая ошибка, поэтому в catch попадает и без -ErrorAction Stop
try { [int]::Parse('abc') }
catch { Write-Warning "Перехвачено: $($_.Exception.Message)" }
Дополнительная сложность: одна и та же ситуация вроде «файла нет» в зависимости от реализации командлета оказывается то незавершающей ошибкой, то ошибкой, завершающей инструкцию. Различать это каждый раз со стороны автора нереально, поэтому на практике на строке, которую нужно перехватить, явно ставят -ErrorAction Stop и тем самым в обоих случаях гарантируют попадание в catch.
Идея «тогда всегда ставить $ErrorActionPreference = 'Stop'» верна наполовину. Для скрипта без оператора безопаснее остановиться и сообщить о сбое, чем проглотить ошибку и идти дальше, поэтому Stop в начале — хороший выбор по умолчанию. Но $ErrorActionPreference действует на текущую область видимости и на дочерние, а значит меняет поведение вызываемых модулей и функций; для завершающих действий, которым допустимо завершаться неудачей (удаление временных файлов и т. п.), нужно отдельно вернуть -ErrorAction SilentlyContinue.2
3. Что читать внутри catch — как разбирать ErrorRecord
В блоке catch в $_ лежит ErrorRecord. Сведения, которые стоит писать в журнал, берутся отсюда.14
try {
Copy-Item -Path $src -Destination $dest -ErrorAction Stop
}
catch [System.IO.IOException] {
# Catch с указанием типа отдельно обрабатывает только «ожидаемый сбой».
# Даже для повышенной ошибки движок сопоставляет исходный тип исключения
Write-Warning "Ошибка ввода-вывода: $($_.Exception.Message)"
}
catch {
# Неожиданное пишем в журнал вместе с контекстом и пробрасываем (не проглатываем)
$rec = $_ # $_ — это ErrorRecord
Write-Warning ('Тип: {0} / Позиция: {1} / Объект: {2}' -f `
$rec.Exception.GetType().FullName,
$rec.InvocationInfo.PositionMessage,
$rec.TargetObject)
throw # throw без аргументов пробрасывает ту же ошибку выше
}
finally {
# finally выполняется и при успехе, и при ошибке, и при остановке по Ctrl+C. Завершающие действия — сюда
if ($tempFile -and (Test-Path $tempFile)) { Remove-Item $tempFile -ErrorAction SilentlyContinue }
}
Смотреть стоит на три места.
$_.Exception— само исключение. Ошибка, повышенная через-ErrorAction Stop, оборачивается вActionPreferenceStopException, но при сопоставлении типов в catch движок смотрит на исходный тип (например,ItemNotFoundException), поэтому catch с указанием типа пишут как обычно. К исходному ErrorRecord можно пройти через$_.Exception.ErrorRecord.1$_.InvocationInfo.PositionMessageсодержит «в каком файле, в какой строке, какая команда». В журнале необслуживаемого запуска от наличия этой строки время расследования отличается на порядок.- Блок finally выполняется и при успехе try, и при ошибке, и при остановке по Ctrl+C. Закрытие соединений, удаление временных файлов и прочие завершающие действия размещайте в finally.7
На каком уровне перехватывать ошибку и где писать журнал — общий вопрос для разных языков. Принципы из статьи «Где размещать catch и логирование при обработке исключений» (перехватывать на границе, не проглатывать, не писать одно и то же в журнал дважды) к PowerShell применяются как есть.
4. Успех нативных команд — $?, $LASTEXITCODE и новая возможность 7.4
Ещё одна крупная дыра — нативные команды: robocopy, git, внутренние EXE компании. Внешние программы не участвуют в системе ошибок PowerShell и сообщают о сбое кодом завершения. Поведение по умолчанию такое.1
| Событие | Поведение (по умолчанию) |
|---|---|
| Ненулевой код завершения | $? становится $false, код завершения попадает в $LASTEXITCODE |
| Создание ErrorRecord | Не происходит (в $Error тоже не добавляется) |
| try/catch | Не срабатывает |
То есть try { robocopy ... } catch { ... } (по умолчанию) ничего не перехватывает. Успех нативной команды проверяйте через $LASTEXITCODE. $? — булево «успешна ли предыдущая операция»; для нативных команд оно $true только при коде завершения 0.1 В Windows PowerShell 5.1 $? иногда становился $false просто из-за того, что нативная команда писала в stderr; в PowerShell 7 это изменили: $false ставится только при ненулевом коде завершения. Запись в stderr сама по себе сбоем не считается — изменение ближе к реальному поведению.8
# Успех нативной команды определяем по $LASTEXITCODE
robocopy.exe 'D:\Reports' '\\fileserver\reports' /MIR /R:2 /W:5
if ($LASTEXITCODE -ge 8) {
# У robocopy коды 0–7 — успех (сведения о том, было ли копирование, и т. п.), 8 и выше — сбой
throw "Сбой robocopy (ExitCode=$LASTEXITCODE)"
}
Начиная с PowerShell 7.4 это поведение можно изменить через $PSNativeCommandUseErrorActionPreference. Функцию добавили как экспериментальную в 7.3 и сделали штатной (mainstream) в 7.4.3 При $true нативная команда с ненулевым кодом завершения порождает незавершающую ошибку с явным кодом завершения, и эта ошибка подчиняется $ErrorActionPreference. Вместе со Stop сбой внешней команды тоже попадает в try/catch.12
В среде, где соседствуют 5.1 и 7, всегда проверяйте, от какой версии вы исходите. Функция доступна в PowerShell 7.4 и новее; в 7.3 она была экспериментальной (имя функции — PSNativeCommandErrorActionPreference), поэтому нужны были Enable-ExperimentalFeature и перезапуск сессии.3 В Windows PowerShell 5.1 самой переменной нет: присвоение $true ничего не делает (просто создаётся новая переменная, ошибки нет, поэтому промах легко не заметить). Если один и тот же скрипт может пойти и на 5.1, и на 7, на эту функцию лучше не опираться и везде, включая следующие главы, единообразно проверять $LASTEXITCODE.
# PowerShell 7.4+: сбой внешней команды тоже обрабатывается в try/catch (по умолчанию $false)
$PSNativeCommandUseErrorActionPreference = $true
$ErrorActionPreference = 'Stop'
try {
git.exe fetch origin
}
catch {
Write-Warning "Сбой git: $($_.Exception.Message)"
throw
}
& {
# Для команд вроде robocopy, где ненулевой код не равен сбою, внутри блока скрипта
# временно отключаем настройку и проверяем успех по $LASTEXITCODE как раньше
# (после выхода из блока значение восстанавливается)
$PSNativeCommandUseErrorActionPreference = $false
robocopy.exe 'D:\Reports' '\\fileserver\reports' /MIR
if ($LASTEXITCODE -ge 8) { throw "Сбой robocopy (ExitCode=$LASTEXITCODE)" }
}
Пример с robocopy стоит в официальной документации не случайно: есть команды, которые используют ненулевой код завершения как нормальную информацию, поэтому при массовом включении настройки нужны участки-исключения.2 Там, где доступна только Windows PowerShell 5.1, функции нет — проверяйте успех единообразно через $LASTEXITCODE. Разница поведения 5.1 и 7 легко становится ловушкой при миграции, поэтому смотрите также «Различия Windows PowerShell 5.1 и PowerShell 7 и переход».
5. Проектирование exit code — чтобы Планировщик заданий и мониторинг могли судить об успехе
Ошибку перехватили — дальше нужно сообщить о ней вовне. По сути единственный способ, которым Планировщик заданий или средство мониторинга узнаёт об успехе скрипта, — код завершения процесса. Зафиксируем спецификацию точно.
exit <число>явно задаёт код завершения скрипта.exitтакже записывает значение в$LASTEXITCODE.59- При запуске через
pwsh -File(powershell.exe -File) значение, переданное в exit, становится кодом завершения процесса как есть. Без оператора exit нормальное завершение даёт 0, завершение из-за необработанного исключения — 1.56 - При запуске скрипта через
-Commandкод вродеexit 10внутри скрипта не сохраняется. Он сжимается до 0 или 1 по успеху последней команды (еслиexit 10написан прямо в командной строке, возвращается именно это значение). Если эксплуатация различает коды завершения скрипта, стандартный подход — запуск через-File.6
Если положить эту спецификацию в основу, каркас скрипта для необслуживаемого запуска выглядит так.
# Invoke-NightlyExport.ps1 — каркас, по которому Планировщик заданий может судить об успехе
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop' # Для необслуживаемого запуска по умолчанию: остановиться и сообщить
# Пишем полный протокол выполнения, включая stdout и ошибки, в журнал (-Append дописывает в дневной файл)
Start-Transcript -Path "C:\Logs\NightlyExport_$(Get-Date -Format yyyyMMdd).log" -Append
try {
Export-DailyData # Основная бизнес-обработка (вызов функции из модуля)
exit 0 # Явно фиксируем успех
}
catch [System.Net.WebException] {
Write-Warning "Ошибка связи: $($_.Exception.Message)"
exit 10 # Класс временных ошибок — задаче оставляем возможность настроить повтор
}
catch {
Write-Warning "Непредвиденная ошибка: $($_.Exception.Message)"
Write-Warning $_.InvocationInfo.PositionMessage
exit 1 # Постоянная ошибка — без повтора, смотрит человек
}
finally {
Stop-Transcript # В finally транскрипт закрывается даже при выходе через exit
}
Start-Transcript — командлет, который целиком записывает ввод и вывод сессии в текст и позволяет воспроизвести, что в тот момент было на экране, без ручной расстановки echo и перенаправлений.10 Его имеет смысл держать не вместо собственных функций журналирования, а вместе с ними — как последнюю страховку. Проектирование журналов и борьба с их разрастанием разобраны в «Прикладной PowerShell — безопасная автоматизация разбора журналов, архивирования и отчётности».
Хитрость в том, чтобы не усложнять назначение exit code. Достаточно гранулярности вроде: 0 = успех, 1 = постоянная ошибка (смотрит человек), десятки = временная ошибка (можно повторить). На этом уже можно строить проверку «Результата последнего запуска» в Планировщике заданий и в системах управления заданиями. Настройки на стороне задачи (повтор при сбое, как смотреть результат выполнения) — в «Задачи Планировщика заданий не запускаются или завершаются с 0x1 — как локализовать причину и спроектировать безопасную эксплуатацию».
6. Проектирование retry — отличаем временные ошибки от бизнес-ошибок
Наконец, повторный запуск. Ценность retry в том, чтобы автоматически поглощать временные ошибки и не будить людей ночью. Если вставить его небрежно, получаются уже другие аварии: бесконечные повторы постоянного сбоя или порча данных из-за двойной обработки. Принципов три.
- Повторять только временные ошибки. Ограничьтесь сбоями, которые может снять время: кратковременный обрыв сети, временная блокировка файла, ожидание запуска зависимой службы. Некорректный ввод, нехватку прав и ошибку настройки сразу завершайте неудачей и передавайте человеку через exit code и журнал.
- Задайте верхнюю границу и интервал. Определите предельное число попыток, интервал увеличивайте по схеме экспоненциального backoff (2 секунды, 4, 8…). Бить с постоянным интервалом сторону, которая уже в сбое, только мешает ей восстановиться.
- Сделайте обработку идемпотентной (безопасной при повторном запуске). И retry, и повтор из Планировщика заданий означают одно: «та же обработка выполняется ещё раз». Это предполагает приёмы вроде публикации результата через временный файл с последующим переименованием и записи обработанных ID, чтобы отсечь повторный импорт.
В виде шаблона это сводится к такой форме.
function Invoke-WithRetry {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [scriptblock] $Operation,
# Значение 0 или меньше завершилось бы успехом без единого запуска, поэтому требуем не меньше 1
[ValidateRange(1, 100)]
[int] $MaxAttempts = 4,
# Отрицательное значение даст отдельную ошибку в Start-Sleep при повторе, поэтому отсекаем уже на привязке параметра
[ValidateRange(0, 3600)]
[int] $BaseDelaySeconds = 2,
# Перечисляем только типы исключений, которые имеет смысл повторять (по умолчанию — связь и I/O)
[Type[]] $RetryableExceptions = @([System.IO.IOException], [System.Net.WebException])
)
for ($attempt = 1; $attempt -le $MaxAttempts; $attempt++) {
try {
# Сначала принимаем вывод в переменную и возвращаем только после успеха. Если делать
# return & $Operation напрямую, при исключении после частичного вывода этот частичный
# результат уйдёт вызывающему коду, и при успешном повторе те же данные придут дважды
$output = & $Operation
return $output
}
catch {
$ex = $_.Exception
$isRetryable = $RetryableExceptions | Where-Object { $ex -is $_ }
if (-not $isRetryable -or $attempt -eq $MaxAttempts) {
throw # Бизнес-ошибка либо исчерпан лимит попыток — завершаем неудачей как есть
}
# Ограничиваем сверху экспоненциально растущее ожидание (чтобы конфигурация
# с большим числом попыток не ждала слишком долго и не вышла за диапазон Start-Sleep)
$delay = [math]::Min($BaseDelaySeconds * [math]::Pow(2, $attempt - 1), 300)
Write-Warning "Сбой (попытка ${attempt}): $($ex.Message) — повтор через ${delay} с"
Start-Sleep -Seconds $delay
}
}
}
# Использование: целевая операция должна быть переведена в завершающую ошибку через -ErrorAction Stop
Invoke-WithRetry -Operation {
Copy-Item -Path '\\fileserver\out\daily.csv' -Destination 'D:\Work' -ErrorAction Stop
}
# Замечание при повторе Invoke-RestMethod / Invoke-WebRequest в PowerShell 7:
# в версии 7 сбой связи приходит не как WebException времён 5.1, а как исключение
# семейства HttpRequestException, поэтому с настройками по умолчанию повтор не сработает.
# Кроме того, постоянный HTTP-ответ вроде 404 приходит тем же типом, поэтому, получив
# ответ, нужно самим решить по коду статуса, стоит ли его повторять
Invoke-WithRetry -RetryableExceptions ([System.Net.Http.HttpRequestException]) -Operation {
# -SkipHttpErrorCheck принимает даже ответ с ошибкой без исключения, чтобы решить по коду, как поступить
$r = Invoke-WebRequest -Uri 'https://api.example.co.jp/orders' -TimeoutSec 30 -SkipHttpErrorCheck
if ($r.StatusCode -in 408, 429, 500, 502, 503, 504) {
# Бросаем как HttpRequestException только временные коды → будет повтор
throw [System.Net.Http.HttpRequestException]::new("Временная ошибка HTTP: $($r.StatusCode)")
}
if ($r.StatusCode -ge 400) {
throw "Постоянная ошибка HTTP: $($r.StatusCode)" # другой тип, поэтому не повторяется
}
$r.Content | ConvertFrom-Json
}
Ключевой момент: объекты retry выбираются явно, по типу исключения. Если написать «повторять всё, что попало в catch», то и постоянная ошибка вроде неверного параметра будет впустую пробовать четыре раза и ждать между попытками. После запуска в эксплуатацию реалистичный путь — постепенно добавлять в $RetryableExceptions типы временных ошибок, которые реально видны в журналах.
Сам Invoke-WithRetry довольно длинный, поэтому не вставляйте его в скрипт каждый раз, а сохраните целиком в файл вроде Retry.psm1 и подключайте через Import-Module. Так меньше шанс, что при копировании в catch останется старая версия (практика выноса в модуль — в «Проектирование параметров и модуляризация PowerShell»). Кроме того, логика retry и ветвления по ошибкам — как раз то место, где стоит писать тесты Pester («Тесты PowerShell на Pester — практическая схема, которая снижает риск сломать эксплуатационный скрипт»).
7. Практические приёмы (таблица решений)
| Вопрос | Варианты | Ориентир |
|---|---|---|
| Поведение при ошибке по умолчанию | Оставить Continue / в начале задать $ErrorActionPreference = ‘Stop’ | Для необслуживаемого запуска безопаснее «остановиться и сообщить». Интерактивный скрипт для расследования может оставаться на Continue2 |
| Место, которое нужно перехватить | Надеяться / явно указать -ErrorAction Stop | Командлеты чаще выдают незавершающие ошибки. На строке, которую хотите перехватить, указывайте Stop явно1 |
| Успех нативной команды | Не проверять / проверка по $LASTEXITCODE / $PSNativeCommandUseErrorActionPreference из 7.4 | В среде со смешанным 5.1 унифицируйте на $LASTEXITCODE. Только 7.4+ — новая функция плюс участки-исключения для robocopy и подобных32 |
| Сообщение об успехе вовне | Только журнал / спроектировать exit code и запускать через -File | Журнал — для людей, exit code — для машин; нужны оба. Запуск через -Command сжимает код завершения6 |
| Протокол выполнения | Только свой журнал / дополнительно Start-Transcript | Страховка, которая сохраняет и то, что свой журнал не подхватывает (например, стандартный вывод внешних команд)10 |
| Retry | Все ошибки / только временные + экспоненциальный backoff + идемпотентность | Retry бизнес-ошибки ведёт к аварии. Нужен комплект из верхней границы, интервала и идемпотентности |
8. Итоги
- Ошибки PowerShell делятся на незавершающие и завершающие; незавершающие по умолчанию в try/catch не попадают. Стандартный приём — явно указывать
-ErrorAction Stopдля команд, которые нужно перехватить. - По умолчанию
$ErrorActionPreferenceравен Continue. Для скрипта без оператора задайте Stop в начале — так структурно закрывается авария, когда сбой проглатывается и скрипт завершается как успешный. - Сбой нативной команды по умолчанию в catch не попадает. Проверяйте его через
$LASTEXITCODEили, начиная с PowerShell 7.4, задействуйте$PSNativeCommandUseErrorActionPreference. - В catch пишите в журнал тип исключения, сообщение и позицию из
$_(ErrorRecord), а завершающие действия размещайте в finally. finally выполняется и при Ctrl+C, и при exit. - Об успехе или сбое сообщайте вовне через exit code. При запуске через
-Fileзначениеexitстановится кодом завершения как есть, и Планировщик заданий или мониторинг могут судить об успехе. - Retry стройте по трём принципам: только временные ошибки, ограниченный сверху экспоненциальный backoff и идемпотентность. Постоянные ошибки сразу завершайте неудачей и передавайте человеку.
Похожие статьи
- Прикладной PowerShell — безопасная автоматизация разбора журналов, архивирования и отчётности
- Тесты PowerShell на Pester — практическая схема, которая снижает риск сломать эксплуатационный скрипт
- Задачи Планировщика заданий не запускаются или завершаются с 0x1 — как локализовать причину и спроектировать безопасную эксплуатацию
- Где размещать catch и логирование при обработке исключений
- Политика выполнения и подпись скриптов PowerShell
- Проектирование параметров и модуляризация PowerShell
Смежные области консультирования
KomuraSoft LLC (合同会社小村ソフト) занимается ревью обработки ошибок и проектирования retry для ночных пакетов и типовых скриптов, расследованием перемежающихся сбоев вроде «сбой есть, а его считают успехом» или «падает раз в месяц», а также повышением эксплуатационного качества существующих скриптов.
- Техническая консультация и ревью проекта
- Исследование дефектов и анализ первопричин
- Миграция и использование существующих активов
- Связаться с нами
Справочные ссылки
-
Microsoft Learn, about_Error_Handling. Три категории ошибок (незавершающая, завершающая инструкцию, завершающая скрипт); то, что незавершающая ошибка по умолчанию не попадает в catch/trap; механизм повышения через -ErrorAction Stop (ActionPreferenceStopException и $_.Exception.ErrorRecord); то, что catch с указанием типа сопоставляется с исходным типом исключения; семантика $? и $LASTEXITCODE; то, что ненулевой код завершения нативной команды по умолчанию не создаёт ErrorRecord; поведение $PSNativeCommandUseErrorActionPreference. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17
-
Microsoft Learn, about_Preference_Variables. То, что $ErrorActionPreference по умолчанию равен Continue; что параметр -ErrorAction имеет приоритет для отдельной команды; что настройка действует на текущую область видимости и на дочерние; что $PSNativeCommandUseErrorActionPreference по умолчанию равен $false; пример временного отключения настройки внутри блока скрипта для команд вроде robocopy, которые используют ненулевой код завершения как информацию. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, What’s New in PowerShell 7.4. То, что экспериментальная функция PSNativeCommandErrorActionPreference ($PSNativeCommandUseErrorActionPreference) стала штатной (mainstream) в PowerShell 7.4. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Everything you wanted to know about exceptions. Доступ к сведениям об исключении через $_ внутри блока catch; то, что команда с -ErrorAction Stop и ошибка Write-Error становятся обрабатываемыми в catch; шаблон освобождения ресурсов через try/finally. ↩ ↩2
-
Microsoft Learn, about_Language_Keywords. То, что ключевое слово exit задаёт код завершения и отражается в $LASTEXITCODE; что скрипт, запущенный через pwsh -File, возвращает числовой аргумент exit как код завершения; что без оператора exit нормальное завершение даёт 0, необработанное исключение — 1. ↩ ↩2 ↩3
-
Microsoft Learn, about_Pwsh. Как определяется код завершения при запуске через -File; то, что запуск через -Command превращает любой код завершения, кроме 0 и 1, в 1, поэтому для сохранения кода завершения нужен exit $LASTEXITCODE. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, about_Try_Catch_Finally. Синтаксис try/catch/finally, блоки catch с указанием типа и несколько catch, а также то, что блок finally выполняется при успехе, при ошибке, при остановке по Ctrl+C и даже при exit внутри catch. ↩
-
Microsoft Learn, Differences between Windows PowerShell 5.1 and PowerShell 7.x. То, что в PowerShell 7 $? больше не становится $false только из-за записи нативной команды в stderr, а только при ненулевом коде завершения. ↩
-
Microsoft Learn, about_Automatic_Variables. То, что $LASTEXITCODE хранит код завершения нативной программы или скрипта; что при вызове через pwsh -File ставится 1 при завершении из-за исключения, значение ключевого слова exit или 0 при нормальном завершении. ↩
-
Microsoft Learn, Start-Transcript. Запись команд сессии и вывода консоли в текстовый файл, дозапись через -Append, расположение и имя файла по умолчанию, остановка через Stop-Transcript. ↩ ↩2
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Политика выполнения PowerShell и подпись скриптов — как отказаться от Bypass в роли постоянной заглушки
Политика выполнения PowerShell — не граница безопасности, а защитный механизм. В статье разобраны различия RemoteSigned и других политик,...
Планировщик заданий не запускается или завершается с 0x1 — диагностика и надёжная эксплуатация
Разбираем, как вывести периодические задания Windows в стабильную эксплуатацию: учётная запись выполнения и тип входа, типичные причины з...
Как запустить PowerShell из C# (CSharp) и получить результат в виде объектов
Разбираем, как запустить PowerShell из C# и получить результат не строкой, а как PSObject: PowerShell SDK, AddCommand, AddParameter, Base...
Тесты PowerShell на Pester — практическая схема, которая делает эксплуатационные скрипты устойчивее к поломкам
Практические шаги, как тестировать PowerShell-скрипты на Pester v5: даты, файловые операции, удаление, Mock и запуск в CI выстраиваем без...
Практические команды PowerShell — мелкие приёмы для повседневной работы
Разбираем практические команды PowerShell для повседневной работы: где применять Measure-Object, Group-Object, Select-String, Compare-Obj...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Бизнес-приложения, интеграция оборудования и средства связи — от требований до разработки.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Почему в PowerShell написан try/catch, а выполнение не попадает в catch?
- Потому что большинство ошибок, которые выдают командлеты, — незавершающие (non-terminating error). try/catch перехватывает только завершающие ошибки: незавершающая ошибка выводит сообщение, продолжает выполнение и в catch не попадает. Стандартный приём — добавить -ErrorAction Stop к команде, которую нужно перехватить (или в начале скрипта задать $ErrorActionPreference = 'Stop'). Тогда незавершающая ошибка повышается до завершающей, и её можно обработать в try/catch.
- Как выбирать между $? и $LASTEXITCODE?
- $? — булево значение, успешно ли завершилась предыдущая операция; оно выставляется и для командлетов, и для нативных команд. $LASTEXITCODE — код завершения последней нативной программы (или скрипта, который вызвал exit); при ошибках командлетов он не меняется. Когда нужно судить об успехе внешней команды вроде robocopy или git, надёжнее опираться на $LASTEXITCODE: по нему видно и смысл кода завершения. Учтите, что ненулевой код завершения нативной команды по умолчанию в catch не попадает.
- Как Планировщику заданий определить, успешно ли отработал скрипт PowerShell?
- В конце скрипта (и в блоках catch) явно задайте код завершения ключевым словом exit, а задачу запускайте через pwsh -File (или powershell.exe -File) и смотрите значение «Результат последнего запуска». При запуске через -File значение, переданное в exit, становится кодом завершения процесса как есть; без exit нормальное завершение даёт 0, необработанное исключение — 1. При запуске через -Command любой код завершения, кроме 0 и 1, превращается в 1, поэтому если эксплуатация строится на кодах завершения, стандартный подход — запуск через -File.
- Для каких ошибок стоит делать retry?
- Только для временных ошибок, при которых повтор может изменить результат: кратковременный обрыв сети, временная блокировка файла, ожидание запуска зависимой службы и тому подобное. Бизнес-ошибки и постоянные ошибки — некорректные входные данные, нехватка прав, ошибка настройки — при повторе снова завершатся неудачей, поэтому их не повторяют: скрипт сразу завершается сбоем и сообщает человеку через журнал и exit code. Даже при retry задайте верхнюю границу по числу попыток и интервалу, увеличивайте интервал по схеме экспоненциального backoff и заранее проектируйте обработку идемпотентной, чтобы повторный запуск не приводил к двойной обработке.
- Что делает $PSNativeCommandUseErrorActionPreference в PowerShell 7.4?
- Это настройка, при которой нативная команда с ненулевым кодом завершения порождает ошибку PowerShell (незавершающую). Её добавили как экспериментальную функцию в PowerShell 7.3 и сделали штатной в 7.4 (по умолчанию $false). При $true поведение подчиняется $ErrorActionPreference, поэтому вместе со Stop сбой внешней команды можно перехватить в try/catch. Но есть команды вроде robocopy, которые используют ненулевой код завершения как обычную информацию, поэтому на таких участках значение нужно временно вернуть в $false.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.