А АWednesday, 22 November 2017

Обработка непрерывающих ошибок в PowerShell.

Здравствуйте.

Совсем недавно меня спросили как можно обработать ошибку выполнения в PowerShell. Вопрос был про Errors Handling. И вопрос звучал на анлийском. Что-то я помнил, но смутно, так как плотно обработчики ошибок писать не доводилось. Поэтому пошел в сеть, нашел отличный материал по теме, и весь его протестировал. Материал богатый поэтому разбиваю его на две части. Уверен что вам тоже будет полезно.

Для начала определимся, что такое обработка ошибок вообще. В общем случае ошибка - это поведение программы или скрипта, отличное от запланированного. Совсем избежать ошибок не очень возможно, поэтому надо предвидеть, где они могут возникнуть и писать код так, чтобы при возникновении ошибки можно было перехватить ее, проанализировать и определить дальнейшее поведение скрипта.  Именно это обычно и подразумевается под обработкой ошибок.

В PowerShell ошибки делятся на два типа: прерывающие (Terminating) и непрерывающие (Non-Terminating). Как следует из названия, непрерывающие ошибки позволяют продолжить выполнение команды, тогда как при возникновении прерывающей ошибки дальнейшее продолжение выполнения команды невозможно. К примеру, у нас есть файл со списком служб, которые необходимо перезапустить следующей командой:
Get-Content -Path C:\Files\services.txt | Restart-Service

Предположим, что перезапуск одной из перечисленных служб по какой либо причине невозможен. Тем не менее можно продолжать выполнение задачи, поскольку остальные службы доступны и их можно перезапустить. Это пример непрерывающей ошибки.

А теперь представьте, что у нас нет прав на открытие этого файла, и соответственно прочитать список служб мы не можем. В этой ситуации продолжение работы невозможно, т.е. это прерывающая ошибка.

PowerShell позволяет обрабатывать оба эти типа ошибок. Большинство ошибок в PowerShell непрерывающие, и сегодня речь пойдет о том, как их обрабатывать.

Обработка непрерывающих ошибок

Для получения ошибки возьмем службу с ″оригинальным″ названием Service. Поскольку службы этой на сервере нет, то обращение к ней стабильно будет генерировать ошибку. Запросим данные о нескольких службах командой:
Get-Service service,spooler

PS D:\Courses!!!\Done!\PowerShell\scripts> Get-Service service,spooler
Get-Service : Не удается найти службу с именем службы "service".
строка:1 знак:1
+ Get-Service service,spooler     + CategoryInfo          : ObjectNotFound: (service:String) [Get-Service], ServiceCommandException     + FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.GetServiceCommand
 Status   Name               DisplayName                          
------   ----               -----------                          
Running  spooler            Диспетчер печати     
                
Как видно из примера, PowerShell не нашел службу Service, о чем выдал ошибку и затем продолжил выполнение команды. Давайте разберемся, почему команда повела себя именно так и как это поведение изменить.

За поведение команды при возникновении ошибки отвечает параметр ErrorAction, который может принимать одно из пяти значений:
• Continue;
• SilentlyContinue;
• Stop;
• Ignore;
• Inquire.


Примечание. Еще у ErrorAction может быть значение Suspend. Но это значение может применяться только к рабочим процессам (workflows), поэтому в рамках данной статьи речь о нем не пойдет.

Значение Continue означает, что при возникновении ошибки информация об этом будет выведена на экран (отправлена в поток вывода Error) и добавлена в автоматическую переменную $Error, после чего выполнение команды будет продолжено. Надо сказать, что Continue — это действие, определенное в сеансе по умолчанию, поэтому его можно не указывать явно.

PS D:\Courses!!!\Done!\PowerShell\scripts> Get-Service service,spooler -ErrorAction Continue
Get-Service : Не удается найти службу с именем службы "service".
строка:1 знак:1
+ Get-Service service,spooler -ErrorAction Continue     + CategoryInfo          : ObjectNotFound: (service:String) [Get-Service], ServiceCommandException     + FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.GetServiceCommand

Status   Name               DisplayName                          
------   ----               -----------                          
Running  spooler            Диспетчер печати                     


При значении SilentlyContinue информация об ошибке добавляется в переменную $Error, но не выводится на экран. При этом команда продолжает выполняться дальше, также как и в предыдущем случае.

PS D:\Courses!!!\Done!\PowerShell\scripts> Get-Service service,spooler -ErrorAction SilentlyContinue
Status   Name               DisplayName                          
------   ----               -----------                          
Running  spooler            Диспетчер печати                     


Значение Stop останавливает дальнейшее выполнение команды при возникновении ошибки. И наоборот, значение Ignore полностью игнорирует возникновение ошибки, при этом не выводится сообщение на экран и не производится запись в $Error. Это значение появилось в PowerShell 3.0.

PS D:\Courses!!!\Done!\PowerShell\scripts> Get-Service service,spooler -ErrorAction Stop
Get-Service : Не удается найти службу с именем службы "service".
строка:1 знак:1
+ Get-Service service,spooler -ErrorAction Stop     + CategoryInfo          : ObjectNotFound: (service:String) [Get-Service], ServiceCommandException     + FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.GetServiceCommand


Inquire — наиболее интересное значение ErrorAction. Если задать это значение, то при возникновении ошибки предлагается на выбор несколько действий: продолжить (Yes), продолжить не смотря на эту и все последующие ошибки (Yes to All), остановить (Halt) или приостановить (Suspend) выполнение команды.

Самый необычный эффект дает Suspend, при выборе которого открывается параллельный сеанс (Nested Namespace). Определить его можно по значку >>. Nested Namespace представляет из себя дочерний процесс, в котором можно полноценно работать — выполнять команды, запускать скрипты и т.п. Этот режим удобно использовать для отладки скриптов, например можно по быстрому исправить причину ошибки и продолжить выполнение. Для выхода из Nested Namespace достаточно набрать exit и выбрать необходимое действие.

PS D:\Courses!!!\Done!\PowerShell\scripts> Get-Service service,spooler -ErrorAction Inquire
Подтверждение
Не удается найти службу с именем службы "service".
[Y] Да - Y  [A] Да для всех - A  [H] Прервать команду - H  [S] Приостановить - S  [?] Справка
(значением по умолчанию является "Y"):s
PS D:\Courses!!!\Done!\PowerShell\scripts>> Get-Service service,spooler -ErrorAction Inquire
Подтверждение
Не удается найти службу с именем службы "service".
[Y] Да - Y  [A] Да для всех - A  [H] Прервать команду - H  [S] Приостановить - S  [?] Справка
(значением по умолчанию является "Y"):y
Get-Service : Не удается найти службу с именем службы "service".
строка:1 знак:1
+ Get-Service service,spooler -ErrorAction Inquire     + CategoryInfo          : ObjectNotFound: (service:String) [Get-Service], ServiceCommandException     + FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.GetServiceCommand
Status   Name               DisplayName                          
------   ----               -----------                          
Running  spooler            Диспетчер печати                     


Примечание. У параметра ErrorAction есть алиас — EA. Кроме того, вместо названия параметра можно указывать числовые значения: 0 (SilentlyContinue), 1 (Stop), 2 (Continue), 3 (Inquire). Так например, вместо:
Get-Service service,spooler -ErrorAction SilentlyContinue
можно написать так:
Get-Service service,spooler -EA 0

Переменные для обработки ошибок

Как я уже говорил, если не указывать параметр ErrorAction, то для команды действует режим обработки ошибок, определенный в сеансе. Этот режим задается переменной $ErrorActionPreference, которая по умолчанию имеет значение Continue. При желании можно переопределить режим для всего сеанса, задав переменной $ErrorActionPreference нужное значение.

 PS D:\Courses!!!\Done!\PowerShell\scripts>> $ErrorActionPreference
Continue

Все ошибки PowerShell сохраняет в автоматическую переменную $Error. Это глобальная переменная, которая представляет из себя массив строк, содержащий записи обо всех ошибках в текущем сеансе. Каждая новая ошибка добавляется в начало массива, соответственно для просмотра последней ошибки надо обратиться к самому первому элементу массива $Error[0].
$Error имеет свои свойства и методы, которые можно использовать. Например, посмотреть общее количество ошибок в текущем сеансе можно командой $Error.Count, а очистить список — командой $Error.Clear().

 PS D:\Courses!!!\Done!\PowerShell\scripts>> $Error[0]
Не удается преобразовать значение "SilentContinue" в тип "System.Management.Automation.ActionPreference". Ошибка: "Не удается сопоставить пустое имя идентификатора SilentContinue с допустимым именем перечислителя.  Укажите одно из следующих имен перечислителя и попробуйте еще раз: SilentlyContinue, Stop, Continue, Inquire, Ignore"
строка:1 знак:1
+ $ErrorActionPreference = "SilentContinue"
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException

PS D:\Courses!!!\Done!\PowerShell\scripts>> $Error.Count
8
PS D:\Courses!!!\Done!\PowerShell\scripts>> $Error.Clear()
PS D:\Courses!!!\Done!\PowerShell\scripts>> $Error.Count
0

Переменная $Error не безразмерна, по умолчанию она хранит не более 256 ошибок. При превышении этого количества наиболее старые ошибки будут затираться. При необходимости количество записей в переменной $Error можно увеличить, изменив значение другой переменной $MaximumErrorCount.

Кроме $Error для хранения ошибок допускается задавать собственные переменные. Сделать это можно с помощью параметра ErrorVariable, например так:
Get-Service service,spooler -ErrorAction SilentlyContinue -ErrorVariable var

Обратите внимание, что имя переменной в команде задается без знака $, хотя в дальнейшем к ней обращаемся как к обычной переменной $var.

PS D:\Courses!!!\Done!\PowerShell\scripts>> Get-Service service,spooler -ErrorAction SilentlyContinue -ErrorVariable var
Status   Name               DisplayName                          
------   ----               -----------                          
Running  spooler            Диспетчер печати                     
PS D:\Courses!!!\Done!\PowerShell\scripts>> $var
Get-Service : Не удается найти службу с именем службы "service".
строка:1 знак:1
+ Get-Service service,spooler -ErrorAction SilentlyContinue -ErrorVariable var
CategoryInfo          : ObjectNotFound: (service:String) [Get-Service], ServiceCommandException
    + FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.GetServiceCommand

В отличие от глобальной переменной $Error заданные вручную переменные хранят только ошибки той команды, в которой они определены. Кроме того, по умолчанию эти переменные каждый раз перезаписываются и поэтому хранят только последнюю ошибку. Если вы хотите, чтобы новые ошибки добавлялись в переменную, не перезаписывая ее содержимое, то перед именем переменной надо поставить знак +, например +var.

В следующей части речь пойдет про обработку прерывающих ошибок (exceptions).

No comments:

Post a Comment

А что вы думаете по этому поводу?

Версия на печать

Популярное