Вообще я редко вижу смысл в том чтобы отлавливать ошибки в скриптах, но недавно ко мне попалась задача, где необходимо было обработать ошибки в скрипте PowerShell. Дело в том что данный скрипт использовался как часть работы System Center Orchestrator. Для этого я использовал Try/Catch/Finaly . Но все по порядку.
Немного про ошибки
Ошибки можно условно разделить на две больших категории.
- Прерывающие
- Не прерывающие
Первые завершают выполнение конвейера с ошибкой. Т.е. дальнейшие команды в конвейере не выполняются. Например, вы не правильно указали имя команды. Вторая категория лишь генерирует ошибку, но конвейер продолжает выполняться далее. Например, вы запросили содержимое не существующего каталога.
В любом случае всю информацию о всех ошибках можно поглядеть в переменной $error. Последняя ошибка идет с индексом 0, т.е. $error[0] — покажет последнюю ошибку. А $error[0].Exception описание последней ошибки.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
PS C:\> Get-Command NoCommand,Dir Get-Command : Имя "NoCommand" не распознано как имя командлета, функции, файла сценария или выполняемой программы. Пров ерьте правильность написания имени, а также наличие и правильность пути, после чего повторите попытку. строка:1 знак:1 + Get-Command NoCommand,Dir + ~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (NoCommand:String) [Get-Command], CommandNotFoundException + FullyQualifiedErrorId : CommandNotFoundException,Microsoft.PowerShell.Commands.GetCommandCommand CommandType Name ModuleName ----------- ---- ---------- Alias dir -> Get-ChildItem PS C:\> $error[0] Get-Command : Имя "NoCommand" не распознано как имя командлета, функции, файла сценария или выполняемой программы. Пров ерьте правильность написания имени, а также наличие и правильность пути, после чего повторите попытку. строка:1 знак:1 + Get-Command NoCommand,Dir + ~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (NoCommand:String) [Get-Command], CommandNotFoundException + FullyQualifiedErrorId : CommandNotFoundException,Microsoft.PowerShell.Commands.GetCommandCommand PS C:\> $error[0].Exception Имя "NoCommand" не распознано как имя командлета, функции, файла сценария или выполняемой программы. Проверьте правильн ость написания имени, а также наличие и правильность пути, после чего повторите попытку. |
Этот же пример, но в виде рисунка для наглядности.
Используем Try в PowerShell
Для того чтобы выяснить была ли в какой-то части кода ошибка необходимо использовать Try. Это помогает избавиться от неожиданного и некорректного завершение вашего скрипта. Позволяя так же корректно завершить его работу.
Синтаксис выглядит в общем случае так.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Try { часть кода в которой ищем ошибку } Catch { [тип ошибки, которую ищем] код, который будет выполнен когда ошибка будет найдена } Finally { код, который будет выполнен в любом случае } |
Однако использование Finally и определение типа ошибки — опционально и может не использоваться.
Для проверки напишем вот такой код где используем Try в PowerShell для того чтобы обработать ошибку деления на ноль и вывести информацию об ошибке.
1 2 3 4 5 6 7 8 9 |
try { [float](4/0) } catch { Write-Host "Error!!!" Write-Host $error[0].Exception } |
Как видно я произвожу деление на ноль в блоке try, т.е. совершаю прерывающую ошибку. Только прерывающие ошибку будут отловлены. А далее в блоке catch произвожу уже необходимые отладочные действия, т.е. произвожу обработку ошибки.. В моей исходной задаче я в данном блоке заносил информацию об ошибках в специальный лог файл, но давайте попробуем запустить мой данный пример.
1 2 3 4 5 6 7 8 9 10 |
PS C:\> .\test.ps1 Error!!! System.Management.Automation.RuntimeException: Попытка деления на нуль. ---> System.DivideByZeroException: Попытка делен ия на нуль. --- Конец трассировки внутреннего стека исключений --- в System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exce ption) в System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame) в System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame) в System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame) |
Увы в блоке try в PowerShell должна присутствовать прерывающая ошибка.
Преобразуем не прерывающую ошибку в прерывающую
Существует общий параметр для всех командлетов в PowerShell -ErrorAction. Данный параметр может принимать четыре значения
- Continue — выводит ошибку и продолжает выполнение
- SilentlyContinue — не выводит ошибку и продолжает выполнение
- Stop — завершает выполнение
- Inquire — спрашивает у пользователя как поступить
В нашем случае подходит действие stop. Если использовать параметр со значением -ErrorAction Stop, то при возникновении ошибки при выполнении команды, данная ошибка прерывает выполнение команды. Ниже пример скрипта, использующего -ErrorAction Stop.
1 2 3 4 5 6 7 8 9 |
try { Dir NoFolder -ErrorAction Stop } catch { Write-Host "Error!!!" Write-Host $error[0].Exception } |
Ниже результат выполнения данного скрипта
1 2 3 4 5 6 7 |
PS C:\> .\test.ps1 Error!!! System.Management.Automation.ItemNotFoundException: Не удается найти путь "C:\NoFolder", так как он не существует. в System.Management.Automation.SessionStateInternal.GetChildItems(String path, Boolean recurse, C mdletProviderContext context) в Microsoft.PowerShell.Commands.GetChildItemCommand.ProcessRecord() |
В моей исходной задаче try в PowerShell необходимо было использовать для всех команд в скрипте. Поскольку обработка ошибки была общей для любой ошибки в скрипте весь скрипт можно поместить в один Try, конечно это не совсем правильно, но зато просто. Чтобы каждый раз не писать -ErrorAction Stop. Можно воспользоваться переменной $ErrorActionPreference, которая имеет те же значения и сходна по действию, однако распространяет свое действие на все командлеты в скрипте.
1 2 3 4 5 6 7 8 9 10 11 |
$ErrorActionPreference = "stop" try { Dir Folder Get-Process -ComputerName TestPC } catch { Write-Host "Error!!!" Write-Host $error[0].Exception } |
Вместо заключения
Конечно, по началу вы мало будете задумываться об поиске ошибок, используя множество условных конструкций вы их минимизируете. Однако использование try в PowerShell позволит минимизировать случаи неожиданного завершения скрипта.
Функции-фильтры, с которыми вы только что познакомились, запускали команды Windows PowerShell и позволяли этим командам помещать выходные данные в конвейер. Таким образом, выходные данные этих команд превращались в выходные данные функций. Например, в предыдущих примерах сюда входили одно или два обращения к Get-WmiObject .