- Обработка ошибок с исключениями в Powershell с Try и Catch
- Как Powershell обрабатывает ошибки
- Автоматические переменные $Error
- Свойства объекта $Error
- Методы объекта $Error
- Критические ошибки (Terminating Errors)
- Не критические ошибки (Non-Terminating Errors)
- Параметр ErrorVariable
- Обработка некритических ошибок
- Приоритет ошибок с $ErrorActionPreference
- Использование параметра ErrorAction
- Обработка критических ошибок и исключений с Try, Catch и Finally
- Catch для всех типов исключений
- Создание отдельных исключений
- Выброс своих исключений
- Выброс с throw
- Использование Write-Error
- Windows PowerShell Error Records
- Error Identifier
- Error Category
- Replacement Error Message
Обработка ошибок с исключениями в Powershell с Try и Catch
02 октября 2020
В Powershell существует несколько уровней ошибок и несколько способов их обработать. Проблемы одного уровня (Non-Terminating Errors) можно решить с помощью привычных для Powershell команд. Другой уровень ошибок (Terminating Errors) решается с помощью исключений (Exceptions) стандартного, для большинства языков, блока в виде Try, Catch и Finally.
Навигация по посту
Как Powershell обрабатывает ошибки
До рассмотрения основных методов посмотрим на теоретическую часть.
Автоматические переменные $Error
В Powershell существует множество переменных, которые создаются автоматически. Одна из таких переменных — $Error хранит в себе все ошибки за текущий сеанс PS. Например так я выведу количество ошибок и их сообщение за весь сеанс:
При отсутствии каких либо ошибок мы бы получили пустой ответ, а счетчик будет равняться 0:
Переменная $Error являет массивом и мы можем по нему пройтись или обратиться по индексу что бы найти нужную ошибку:
Свойства объекта $Error
Так же как и все что создается в Powershell переменная $Error так же имеет свойства (дополнительную информацию) и методы. Названия свойств и методов можно увидеть через команду Get-Member:
Например, с помощью свойства InvocationInfo, мы можем вывести более структурный отчет об ошибки:
Методы объекта $Error
Например мы можем очистить логи ошибок используя clear:
Критические ошибки (Terminating Errors)
Критические (завершающие) ошибки останавливают работу скрипта. Например это может быть ошибка в названии командлета или параметра. В следующем примере команда должна была бы вернуть процессы «svchost» дважды, но из-за использования несуществующего параметра ‘—Error’ не выполнится вообще:
Не критические ошибки (Non-Terminating Errors)
Не критические (не завершающие) ошибки не остановят работу скрипта полностью, но могут вывести сообщение об этом. Это могут быть ошибки не в самих командлетах Powershell, а в значениях, которые вы используете. На предыдущем примере мы можем допустить опечатку в названии процессов, но команда все равно продолжит работу:
Как видно у нас появилась информация о проблеме с первым процессом ‘svchost111’, так как его не существует. Обычный процесс ‘svchost’ он у нас вывелся корректно.
Параметр ErrorVariable
Если вы не хотите использовать автоматическую переменную $Error, то сможете определять свою переменную индивидуально для каждой команды. Эта переменная определяется в параметре ErrorVariable:
Переменная будет иметь те же свойства, что и автоматическая:
Повторное использование логина и пароля в Powershell с Get-Credential и их шифрование
Обработка некритических ошибок
У нас есть два способа определения последующих действий при ‘Non-Terminating Errors’. Это правило можно задать локально и глобально (в рамках сессии). Мы сможем полностью остановить работу скрипта или вообще отменить вывод ошибок.
Приоритет ошибок с $ErrorActionPreference
Еще одна встроенная переменная в Powershell $ErrorActionPreference глобально определяет что должно случится, если у нас появится обычная ошибка. По умолчанию это значение равно ‘Continue’, что значит «вывести информацию об ошибке и продолжить работу»:
Если мы поменяем значение этой переменной на ‘Stop’, то поведение скриптов и команд будет аналогично критичным ошибкам. Вы можете убедиться в этом на прошлом скрипте с неверным именем процесса:
Т.е. скрипт был остановлен в самом начале. Значение переменной будет храниться до момента завершения сессии Powershell. При перезагрузке компьютера, например, вернется значение по умолчанию.
Ниже значение, которые мы можем установить в переменной $ErrorActionPreference:
- Continue — вывод ошибки и продолжение работы;
- Inquire — приостановит работу скрипта и спросит о дальнейших действиях;
- SilentlyContinue — скрипт продолжит свою работу без вывода ошибок;
- Stop — остановка скрипта при первой ошибке.
Самый частый параметр, который мне приходится использовать — SilentlyContinue:
Использование параметра ErrorAction
Переменная $ErrorActionPreference указывает глобальный приоритет, но мы можем определить такую логику в рамках команды с параметром ErrorAction. Этот параметр имеет больший приоритет чем $ErrorActionPreference. В следующем примере, глобальная переменная определяет полную остановку скрипта, а в параметр ErrorAction говорит «не выводить ошибок и продолжить работу»:
Кроме ‘SilentlyContinue’ мы можем указывать те же параметры, что и в переменной $ErrorActionPreference.
Значение Stop, в обоих случаях, делает ошибку критической.
Обработка критических ошибок и исключений с Try, Catch и Finally
Когда мы ожидаем получить какую-то ошибку и добавить логику нужно использовать Try и Catch. Например, если в вариантах выше мы определяли нужно ли нам отображать ошибку или останавливать скрипт, то теперь сможем изменить выполнение скрипта или команды вообще. Блок Try и Catch работает только с критическими ошибками и в случаях если $ErrorActionPreference или ErrorAction имеют значение Stop.
Например, если с помощью Powershell мы пытаемся подключиться к множеству компьютеров один из них может быть выключен — это приведет к ошибке. Так как эту ситуацию мы можем предвидеть, то мы можем обработать ее. Процесс обработки ошибок называется исключением (Exception).
Синтаксис и логика работы команды следующая:
Блок try мониторит ошибки и если она произойдет, то она добавится в переменную $Error и скрипт перейдет к блоку Catch. Так как ошибки могут быть разные (нет доступа, нет сети, блокирует правило фаервола и т.д.) то мы можем прописывать один блок Try и несколько Catch:
Сам блок finally — не обязательный и используется редко. Он выполняется самым последним, после try и catch и не имеет каких-то условий.
Catch для всех типов исключений
Как и было показано выше мы можем использовать блок Catch для конкретного типа ошибок, например при проблемах с доступом. Если в этом месте ничего не указывать — в этом блоке будут обрабатываться все варианты ошибок:
Такой подход не рекомендуется использовать часто, так как вы можете пропустить что-то важное.
Мы можем вывести в блоке catch текст ошибки используя $PSItem.Exception:
Переменная $PSItem хранит информацию о текущей ошибке, а глобальная переменная $Error будет хранит информацию обо всех ошибках. Так, например, я выведу одну и ту же информацию:
Создание отдельных исключений
Что бы обработать отдельную ошибку сначала нужно найти ее имя. Это имя можно увидеть при получении свойств и методов у значения переменной $Error:
Так же сработает и в блоке Catch с $PSItem:
Для вывода только имени можно использовать свойство FullName:
Далее, это имя, мы вставляем в блок Catch:
Так же, как и было описано выше мы можем усложнять эти блоки как угодно указывая множество исключений в одном catch.
Создание и изменение в Powershell NTFS разрешений ACL
Выброс своих исключений
Иногда нужно создать свои собственные исключения. Например мы можем запретить добавлять через какой-то скрипт названия содержащие маленькие буквы или сотрудников без указания возраста и т.д. Способов создать такие ошибки — два и они тоже делятся на критические и обычные.
Выброс с throw
Throw — выбрасывает ошибку, которая останавливает работу скрипта. Этот тип ошибок относится к критическим. Например мы можем указать только текст для дополнительной информации:
Если нужно, то мы можем использовать исключения, которые уже были созданы в Powershell:
Использование Write-Error
Команда Write-Error работает так же, как и ключ ErrorAction. Мы можем просто отобразить какую-то ошибку и продолжить выполнение скрипта:
При необходимости мы можем использовать параметр ErrorAction. Значения этого параметра были описаны выше. Мы можем указать значение ‘Stop’, что полностью остановит выполнение скрипта:
Отличие команды Write-Error с ключом ErrorAction от обычных команд в том, что мы можем указывать исключения в параметре Exception:
В Exception мы так же можем указывать сообщение. При этом оно будет отображаться в переменной $Error:
Windows PowerShell Error Records
Cmdlets must pass an System.Management.Automation.ErrorRecord object that identifies the error condition for terminating and non-terminating errors.
The System.Management.Automation.ErrorRecord object contains the following information:
- The exception that describes the error. Often, this is an exception that the cmdlet caught and converted into an error record. Every error record must contain an exception.
If the cmdlet did not catch an exception, it must create a new exception and choose the exception class that best describes the error condition. However, you do not need to throw the exception because it can be accessed through the System.Management.Automation.ErrorRecord.Exception property of the System.Management.Automation.ErrorRecord object.
An error identifier that provides a targeted designator that can be used for diagnostic purposes and by Windows PowerShell scripts to handle specific error conditions with specific error handlers. Every error record must contain an error identifier (see Error Identifier).
An error category that provides a general designator that can be used for diagnostic purposes. Every error record must specify an error category (see Error Category).
An optional replacement error message and a recommended action (see Replacement Error Message).
Optional invocation information about the cmdlet that threw the error. This information is specified by Windows PowerShell (see Invocation Message).
The target object that was being processed when the error occurred. This might be the input object, or it might be another object that your cmdlet was processing. For example, for the command remove-item -recurse c:\somedirectory , the error might be an instance of a FileInfo object for «c:\somedirectory\lockedfile». The target object information is optional.
Error Identifier
When you create an error record, specify an identifier that designates the error condition within your cmdlet. Windows PowerShell combines the targeted identifier with the name of your cmdlet to create a fully qualified error identifier. The fully qualified error identifier can be accessed through the System.Management.Automation.ErrorRecord.FullyQualifiedErrorId property of the System.Management.Automation.ErrorRecord object. The error identifier is not available by itself. It is available only as part of the fully qualified error identifier.
Use the following guidelines to generate error identifiers when you create error records:
Make error identifiers specific to an error condition. Target the error identifiers for diagnostic purposes and for scripts that handle specific error conditions with specific error handlers. A user should be able to use the error identifier to identify the error and its source. Error identifiers also enable reporting for specific error conditions from existing exceptions so that new exception subclasses are not required.
In general, assign different error identifiers to different code paths. The end-user benefits from specific identifiers. Often, each code path that calls System.Management.Automation.Cmdlet.WriteError or System.Management.Automation.Cmdlet.Throwterminatingerror* has its own identifier. As a rule, define a new identifier when you define a new template string for the error message, and vice-versa. Do not use the error message as an identifier.
When you publish code using a particular error identifier, you establish the semantics of errors with that identifier for your complete product support lifecycle. Do not reuse it in a context that is semantically different from the original context. If the semantics of this error change, create and then use a new identifier.
You should generally use a particular error identifier only for exceptions of a particular CLR type. If the type of the exception or the type of the target object changes, create and then use a new identifier.
Choose text for your error identifier that concisely corresponds to the error that you are reporting. Use standard .NET Framework naming and capitalization conventions. Do not use white space or punctuation. Do not localize error identifiers.
Do not dynamically generate error identifiers in a non-reproducible way. For example, do not incorporate error information such as a process ID. Error identifiers are useful only if they correspond to the error identifiers seen by other users who are experiencing the same error condition.
Error Category
When you create an error record, specify the category of the error using one of the constants defined by the System.Management.Automation.ErrorCategory enumeration. Windows PowerShell uses the error category to display error information when users set the $ErrorView variable to «CategoryView» .
Avoid using the System.Management.Automation.ErrorCategory NotSpecified constant. If you have any information about the error or about the operation that caused the error, choose the category that best describes the error or the operation, even if the category is not a perfect match.
The information displayed by Windows PowerShell is referred to as the category-view string and is built from the properties of the System.Management.Automation.Errorcategoryinfo class. (This class is accessed through the error System.Management.Automation.ErrorRecord.CategoryInfo property.)
The following list describes the information displayed:
TargetName: By default, the name of the object the cmdlet was processing when the error occurred. Or, another cmdlet-defined string.
TargetType: By default, the type of the target object. Or, another cmdlet-defined string.
Activity: By default, the name of the cmdlet that created the error record. Or, some other cmdlet-defined string.
Reason: By default, the exception type. Or, another cmdlet-defined string.
Replacement Error Message
When you develop an error record for a cmdlet, the default error message for the error comes from the default message text in the System.Exception.Message property. This is a read-only property whose message text is intended only for debugging purposes (according to the .NET Framework guidelines). We recommend that you create an error message that replaces or augments the default message text. Make the message more user-friendly and more specific to the cmdlet.
The replacement message is provided by an System.Management.Automation.ErrorDetails object. Use one of the following constructors of this object because they provide additional localization information that can be used by Windows PowerShell.
ErrorDetails(Cmdlet, String, String, Object[]): Use this constructor if your template string is a resource string in the same assembly in which the cmdlet is implemented or if you want to load the template string through an override of the System.Management.Automation.Cmdlet.GetResourceString method.
ErrorDetails(Assembly, String, String, Object[]): Use this constructor if the template string is in another assembly and you do not load it through an override of System.Management.Automation.Cmdlet.GetResourceString.
The replacement message should conform to the .NET Framework design guidelines for writing exception messages with a small difference. The guidelines state that exception messages should be written for developers. These replacement messages should be written for the cmdlet user.