Raise error powershell

Ошибки в Powershell можно разделить на критические (завершающие работу скрипта) и нет. В первом случае мы будем использовать блок Try и Catch для обхода таких ситуаций. Разберем работу с ошибками в Powershell на примерах.

В Powershell существует несколько уровней ошибок и несколько способов их обработать. Проблемы одного уровня (Non-Terminating Errors) можно решить с помощью привычных для Powershell команд. Другой уровень ошибок (Terminating Errors) решается с помощью исключений (Exceptions) стандартного, для большинства языков, блока в виде Try, Catch и Finally. 

Как Powershell обрабатывает ошибки

До рассмотрения основных методов посмотрим на теоретическую часть.

Автоматические переменные $Error

В Powershell существует множество переменных, которые создаются автоматически. Одна из таких переменных — $Error хранит в себе все ошибки за текущий сеанс PS. Например так я выведу количество ошибок и их сообщение за весь сеанс:

Get-TestTest
$Error
$Error.Count

Переменная $Error в Powershell

При отсутствии каких либо ошибок мы бы получили пустой ответ, а счетчик будет равняться 0:

Счетчик ошибок с переменной $Error в Powershell

Переменная $Error являет массивом и мы можем по нему пройтись или обратиться по индексу что бы найти нужную ошибку:

$Error[0]

foreach ($item in $Error){$item}

Вывод ошибки по индексу в Powershell c $Error

Свойства объекта $Error

Так же как и все что создается в Powershell переменная $Error так же имеет свойства (дополнительную информацию) и методы. Названия свойств и методов можно увидеть через команду Get-Member:

$Error | Get-Member

Свойства переменной $Error в Powershell

Например, с помощью свойства InvocationInfo, мы можем вывести более структурный отчет об ошибки:

$Error[0].InvocationInfo

Детальная информация об ошибке с $Error в Powershell

Методы объекта $Error

Например мы можем очистить логи ошибок используя clear:

$Error.clear()

Очистка логов об ошибке в Powershell с $Error

Критические ошибки (Terminating Errors)

Критические (завершающие) ошибки останавливают работу скрипта. Например это может быть ошибка в названии командлета или параметра. В следующем примере команда должна была бы вернуть процессы «svchost» дважды, но из-за использования несуществующего параметра ‘—Error’ не выполнится вообще:

'svchost','svchost' | % {Get-Process -Name $PSItem} --Error 

Критические ошибки в Powershell Terminating Errors

Не критические ошибки (Non-Terminating Errors)

Не критические (не завершающие) ошибки не остановят работу скрипта полностью, но могут вывести сообщение об этом. Это могут быть ошибки не в самих командлетах Powershell, а в значениях, которые вы используете. На предыдущем примере мы можем допустить опечатку в названии процессов, но команда все равно продолжит работу:

'svchost111','svchost' | % {Get-Process -Name $PSItem}

Не критические ошибки в Powershell Non-Terminating Errors

Как видно у нас появилась информация о проблеме с первым процессом ‘svchost111’, так как его не существует. Обычный процесс ‘svchost’ он у нас вывелся корректно.

Параметр ErrorVariable

Если вы не хотите использовать автоматическую переменную $Error, то сможете определять свою переменную индивидуально для каждой команды. Эта переменная определяется в параметре ErrorVariable:

'svchost111','svchost' | % {Get-Process -Name $PSItem } -ErrorVariable my_err_var
$my_err_var

Использование ErrorVariable в Powershell

Переменная будет иметь те же свойства, что и автоматическая:

$my_err_var.InvocationInfo

Свойства  ErrorVariable в Powershell

Обработка некритических ошибок

У нас есть два способа определения последующих действий при ‘Non-Terminating Errors’. Это правило можно задать локально и глобально (в рамках сессии). Мы сможем полностью остановить работу скрипта или вообще отменить вывод ошибок.

Приоритет ошибок с $ErrorActionPreference

Еще одна встроенная переменная в Powershell $ErrorActionPreference глобально определяет что должно случится, если у нас появится обычная ошибка. По умолчанию это значение равно ‘Continue’, что значит «вывести информацию об ошибке и продолжить работу»:

$ErrorActionPreference

Определение $ErrorActionPreference в Powershell

Если мы поменяем значение этой переменной на ‘Stop’, то поведение скриптов и команд будет аналогично критичным ошибкам. Вы можете убедиться в этом на прошлом скрипте с неверным именем процесса:

$ErrorActionPreference = 'Stop'
'svchost111','svchost' | % {Get-Process -Name $PSItem}

Определение глобальной переменной $ErrorActionPreference в Powershell

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

Ниже значение, которые мы можем установить в переменной $ErrorActionPreference:

  • Continue — вывод ошибки и продолжение работы;
  • Inquire — приостановит работу скрипта и спросит о дальнейших действиях;
  • SilentlyContinue — скрипт продолжит свою работу без вывода ошибок;
  • Stop — остановка скрипта при первой ошибке.

Самый частый параметр, который мне приходится использовать — SilentlyContinue:

$ErrorActionPreference = 'SilentlyContinue'
'svchost111','svchost' | % {Get-Process -Name $PSItem}

Игнорирование ошибок в Powershell с ErrorActionPreference и SilentlyContinue

Использование параметра ErrorAction

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

$ErrorActionPreference = 'Stop'
'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'SilentlyContinue'}

Использование параметра ErrorAction в ошибках с Powershell

Кроме ‘SilentlyContinue’ мы можем указывать те же параметры, что и в переменной $ErrorActionPreference. 

Значение Stop, в обоих случаях, делает ошибку критической.

Обработка критических ошибок и исключений с Try, Catch и Finally

Когда мы ожидаем получить какую-то ошибку и добавить логику нужно использовать Try и Catch. Например, если в вариантах выше мы определяли нужно ли нам отображать ошибку или останавливать скрипт, то теперь сможем изменить выполнение скрипта или команды вообще. Блок Try и Catch работает только с критическими ошибками и в случаях если $ErrorActionPreference или ErrorAction имеют значение Stop.

Например, если с помощью Powershell мы пытаемся подключиться к множеству компьютеров один из них может быть выключен — это приведет к ошибке. Так как эту ситуацию мы можем предвидеть, то мы можем обработать ее. Процесс обработки ошибок называется исключением (Exception).

Синтаксис и логика работы команды следующая:

try {
    # Пытаемся подключиться к компьютеру
}
catch [Имя исключения 1],[Имя исключения 2]{
    # Раз компьютер не доступен, сделать то-то
}
finally {
    # Блок, который выполняется в любом случае последним
}

Блок try мониторит ошибки и если она произойдет, то она добавится в переменную $Error и скрипт перейдет к блоку Catch. Так как ошибки могут быть разные (нет доступа, нет сети, блокирует правило фаервола и т.д.) то мы можем прописывать один блок Try и несколько Catch:

try {
    # Пытаемся подключится
}
catch ['Нет сети']['Блокирует фаервол']{
    # Записываем в файл
}
catch ['Нет прав на подключение']{
    # Подключаемся под другим пользователем
}

Сам блок finally — не обязательный и используется редко. Он выполняется самым последним, после try и catch и не имеет каких-то условий.

Catch для всех типов исключений

Как и было показано выше мы можем использовать блок Catch для конкретного типа ошибок, например при проблемах с доступом. Если в этом месте ничего не указывать — в этом блоке будут обрабатываться все варианты ошибок:

try {
   'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'Stop'}
}
catch {
   Write-Host "Какая-то неисправность" -ForegroundColor RED
}

Игнорирование всех ошибок с try и catch в Powershell

Такой подход не рекомендуется использовать часто, так как вы можете пропустить что-то важное.

Мы можем вывести в блоке catch текст ошибки используя $PSItem.Exception:

try {
   'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'Stop'}
}
catch {
   Write-Host "Какая-то неисправность" -ForegroundColor RED
   $PSItem.Exception
}

Переменная PSITem в блоке try и catch в Powershell

Переменная $PSItem хранит информацию о текущей ошибке, а глобальная переменная $Error будет хранит информацию обо всех ошибках. Так, например, я выведу одну и ту же информацию:

$Error[0].Exception

Вывод сообщения об ошибке в блоке try и catch в Powershell

Создание отдельных исключений

Что бы обработать отдельную ошибку сначала нужно найти ее имя. Это имя можно увидеть при получении свойств и методов у значения переменной $Error:

$Error[0].Exception | Get-Member

Поиск имени для исключения ошибки в Powershell

Так же сработает и в блоке Catch с $PSItem:

Наименование ошибок для исключений в Powershell

Для вывода только имени можно использовать свойство FullName:

$Error[0].Exception.GetType().FullName

Вывод типа ошибок и их названия в Powershell

Далее, это имя, мы вставляем в блок Catch:

try {
   'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'Stop'}
}
catch [Microsoft.PowerShell.Commands.ProcessCommandException]{
   Write-Host "Произошла ошибка" -ForegroundColor RED
   $PSItem.Exception
}

Указываем исключение ошибки в блоке Try Catch Powershell

Так же, как и было описано выше мы можем усложнять эти блоки как угодно указывая множество исключений в одном catch.

Выброс своих исключений

Иногда нужно создать свои собственные исключения. Например мы можем запретить добавлять через какой-то скрипт названия содержащие маленькие буквы или сотрудников без указания возраста и т.д. Способов создать такие ошибки — два и они тоже делятся на критические и обычные.

Выброс с throw

Throw — выбрасывает ошибку, которая останавливает работу скрипта. Этот тип ошибок относится к критическим. Например мы можем указать только текст для дополнительной информации:

$name = 'AD.1'

if ($name -match '.'){
   throw 'Запрещено использовать точки в названиях'
}

Выброс ошибки с throw в Powershell

Если нужно, то мы можем использовать исключения, которые уже были созданы в Powershell:

$name = 'AD.1'

if ($name -like '*.*'){
   throw [System.IO.FileNotFoundException]'Запрещено использовать точки в названиях'
}

Выброс ошибки с throw в Powershell

Использование Write-Error

Команда Write-Error работает так же, как и ключ ErrorAction. Мы можем просто отобразить какую-то ошибку и продолжить выполнение скрипта:

$names = @('CL1', 'AD.1', 'CL3')

foreach ($name in $names){
   if ($name -like '*.*'){
      Write-Error -Message 'Обычная ошибка'
   }
   else{
      $name
   }
}

Использование Write-Error для работы с исключениями в Powershell

При необходимости мы можем использовать параметр ErrorAction. Значения этого параметра были описаны выше. Мы можем указать значение ‘Stop’, что полностью остановит выполнение скрипта:

$names = @('CL1', 'AD.1', 'CL3')

foreach ($name in $names){
   if ($name -like '*.*'){
      Write-Error -Message 'Обычная ошибка' -ErrorAction 'Stop'
   }
   else{
      $name
   }
}

Использование Write-Error и Stop в Powershell

Отличие команды Write-Error с ключом ErrorAction от обычных команд в том, что мы можем указывать исключения в параметре Exception:

Write-Error -Message 'Обычная ошибка' -ErrorAction 'Stop'

Write-Error -Message 'Исключение' -Exception [System.IO.FileNotFoundException] -ErrorAction 'Stop'

Свойства Write-Errror в Powershell

В Exception мы так же можем указывать сообщение. При этом оно будет отображаться в переменной $Error:

Write-Error -Exception [System.IO.FileNotFoundException]'Моё сообщение'

Свойства Write-Errror в Powershell 

Теги:

#powershell

#ошибки

Содержание

  1. Everything you wanted to know about exceptions
  2. Basic terminology
  3. Exception
  4. Throw and Catch
  5. The call stack
  6. Terminating and non-terminating errors
  7. Swallowing an exception
  8. Basic command syntax
  9. Throw
  10. Write-Error -ErrorAction Stop
  11. Cmdlet -ErrorAction Stop
  12. Try/Catch
  13. Try/Finally
  14. Try/Catch/Finally
  15. $PSItem
  16. PSItem.ToString()
  17. $PSItem.InvocationInfo
  18. $PSItem.ScriptStackTrace
  19. $PSItem.Exception
  20. $PSItem.Exception.Message
  21. $PSItem.Exception.InnerException
  22. $PSItem.Exception.StackTrace
  23. Working with exceptions
  24. Catching typed exceptions
  25. Catch multiple types at once
  26. Throwing typed exceptions
  27. Write-Error -Exception
  28. The big list of .NET exceptions
  29. Exceptions are objects
  30. Re-throwing an exception
  31. Re-throwing a new exception
  32. $PSCmdlet.ThrowTerminatingError()
  33. Try can create terminating errors
  34. $PSCmdlet.ThrowTerminatingError() inside try/catch
  35. Public function templates
  36. Closing remarks
  37. Все, что вы хотели знать об исключениях
  38. Базовая терминология
  39. Исключение
  40. Throw и Catch
  41. Стек вызовов
  42. Неустранимые и устранимые ошибки
  43. Игнорирование исключения
  44. Основной синтаксис команды
  45. Ключевое слово throw
  46. Параметр -ErrorAction Stop командлета Write-Error
  47. Параметр -ErrorAction Stop в командлете
  48. Try и Catch
  49. Try и Finally
  50. Try, catch и finally
  51. $PSItem
  52. PSItem.ToString()
  53. $PSItem.InvocationInfo
  54. $PSItem.ScriptStackTrace
  55. $PSItem.Exception
  56. $PSItem.Exception.Message
  57. $PSItem.Exception.InnerException
  58. $PSItem.Exception.StackTrace
  59. Работа с исключениями
  60. Перехват типизированных исключений
  61. Одновременный перехват нескольких типов
  62. Вызов типизированных исключений
  63. Параметр -Exception командлета Write-Error
  64. Большой список исключений .NET
  65. Исключения как объекты
  66. Повторный вызов исключения
  67. Повторный вызов нового исключения
  68. $PSCmdlet.ThrowTerminatingError()
  69. Try как источник неустранимой ошибки
  70. Метод $PSCmdlet.ThrowTerminatingError() в try/catch
  71. Шаблоны общих функций
  72. Ключевое слово trap
  73. Заключительное слово

Everything you wanted to know about exceptions

Error handling is just part of life when it comes to writing code. We can often check and validate conditions for expected behavior. When the unexpected happens, we turn to exception handling. You can easily handle exceptions generated by other people’s code or you can generate your own exceptions for others to handle.

The original version of this article appeared on the blog written by @KevinMarquette. The PowerShell team thanks Kevin for sharing this content with us. Please check out his blog at PowerShellExplained.com.

Basic terminology

We need to cover some basic terms before we jump into this one.

Exception

An Exception is like an event that is created when normal error handling can’t deal with the issue. Trying to divide a number by zero or running out of memory are examples of something that creates an exception. Sometimes the author of the code you’re using creates exceptions for certain issues when they happen.

Throw and Catch

When an exception happens, we say that an exception is thrown. To handle a thrown exception, you need to catch it. If an exception is thrown and it isn’t caught by something, the script stops executing.

The call stack

The call stack is the list of functions that have called each other. When a function is called, it gets added to the stack or the top of the list. When the function exits or returns, it is removed from the stack.

When an exception is thrown, that call stack is checked in order for an exception handler to catch it.

Terminating and non-terminating errors

An exception is generally a terminating error. A thrown exception is either be caught or it terminates the current execution. By default, a non-terminating error is generated by Write-Error and it adds an error to the output stream without throwing an exception.

I point this out because Write-Error and other non-terminating errors do not trigger the catch .

Swallowing an exception

This is when you catch an error just to suppress it. Do this with caution because it can make troubleshooting issues very difficult.

Basic command syntax

Here is a quick overview of the basic exception handling syntax used in PowerShell.

Throw

To create our own exception event, we throw an exception with the throw keyword.

This creates a runtime exception that is a terminating error. It’s handled by a catch in a calling function or exits the script with a message like this.

Write-Error -ErrorAction Stop

I mentioned that Write-Error doesn’t throw a terminating error by default. If you specify -ErrorAction Stop , Write-Error generates a terminating error that can be handled with a catch .

Thank you to Lee Dailey for reminding about using -ErrorAction Stop this way.

Cmdlet -ErrorAction Stop

If you specify -ErrorAction Stop on any advanced function or cmdlet, it turns all Write-Error statements into terminating errors that stop execution or that can be handled by a catch .

Try/Catch

The way exception handling works in PowerShell (and many other languages) is that you first try a section of code and if it throws an error, you can catch it. Here is a quick sample.

The catch script only runs if there’s a terminating error. If the try executes correctly, then it skips over the catch . You can access the exception information in the catch block using the $_ variable.

Try/Finally

Sometimes you don’t need to handle an error but still need some code to execute if an exception happens or not. A finally script does exactly that.

Take a look at this example:

Anytime you open or connect to a resource, you should close it. If the ExecuteNonQuery() throws an exception, the connection isn’t closed. Here is the same code inside a try/finally block.

In this example, the connection is closed if there’s an error. It also is closed if there’s no error. The finally script runs every time.

Because you’re not catching the exception, it still gets propagated up the call stack.

Try/Catch/Finally

It’s perfectly valid to use catch and finally together. Most of the time you’ll use one or the other, but you may find scenarios where you use both.

$PSItem

Now that we got the basics out of the way, we can dig a little deeper.

Inside the catch block, there’s an automatic variable ( $PSItem or $_ ) of type ErrorRecord that contains the details about the exception. Here is a quick overview of some of the key properties.

For these examples, I used an invalid path in ReadAllText to generate this exception.

PSItem.ToString()

This gives you the cleanest message to use in logging and general output. ToString() is automatically called if $PSItem is placed inside a string.

$PSItem.InvocationInfo

This property contains additional information collected by PowerShell about the function or script where the exception was thrown. Here is the InvocationInfo from the sample exception that I created.

The important details here show the ScriptName , the Line of code and the ScriptLineNumber where the invocation started.

$PSItem.ScriptStackTrace

This property shows the order of function calls that got you to the code where the exception was generated.

I’m only making calls to functions in the same script but this would track the calls if multiple scripts were involved.

$PSItem.Exception

This is the actual exception that was thrown.

$PSItem.Exception.Message

This is the general message that describes the exception and is a good starting point when troubleshooting. Most exceptions have a default message but can also be set to something custom when the exception is thrown.

This is also the message returned when calling $PSItem.ToString() if there was not one set on the ErrorRecord .

$PSItem.Exception.InnerException

Exceptions can contain inner exceptions. This is often the case when the code you’re calling catches an exception and throws a different exception. The original exception is placed inside the new exception.

I will revisit this later when I talk about re-throwing exceptions.

$PSItem.Exception.StackTrace

This is the StackTrace for the exception. I showed a ScriptStackTrace above, but this one is for the calls to managed code.

You only get this stack trace when the event is thrown from managed code. I’m calling a .NET framework function directly so that is all we can see in this example. Generally when you’re looking at a stack trace, you’re looking for where your code stops and the system calls begin.

Working with exceptions

There is more to exceptions than the basic syntax and exception properties.

Catching typed exceptions

You can be selective with the exceptions that you catch. Exceptions have a type and you can specify the type of exception you want to catch.

The exception type is checked for each catch block until one is found that matches your exception. It’s important to realize that exceptions can inherit from other exceptions. In the example above, FileNotFoundException inherits from IOException . So if the IOException was first, then it would get called instead. Only one catch block is invoked even if there are multiple matches.

If we had a System.IO.PathTooLongException , the IOException would match but if we had a InsufficientMemoryException then nothing would catch it and it would propagate up the stack.

Catch multiple types at once

It’s possible to catch multiple exception types with the same catch statement.

Thank you /u/Sheppard_Ra for suggesting this addition.

Throwing typed exceptions

You can throw typed exceptions in PowerShell. Instead of calling throw with a string:

Use an exception accelerator like this:

But you have to specify a message when you do it that way.

You can also create a new instance of an exception to be thrown. The message is optional when you do this because the system has default messages for all built-in exceptions.

If you’re not using PowerShell 5.0 or higher, you must use the older New-Object approach.

By using a typed exception, you (or others) can catch the exception by the type as mentioned in the previous section.

Write-Error -Exception

We can add these typed exceptions to Write-Error and we can still catch the errors by exception type. Use Write-Error like in these examples:

Then we can catch it like this:

The big list of .NET exceptions

I compiled a master list with the help of the Reddit/r/PowerShell community that contains hundreds of .NET exceptions to complement this post.

I start by searching that list for exceptions that feel like they would be a good fit for my situation. You should try to use exceptions in the base System namespace.

Exceptions are objects

If you start using a lot of typed exceptions, remember that they are objects. Different exceptions have different constructors and properties. If we look at the FileNotFoundException documentation for System.IO.FileNotFoundException , we see that we can pass in a message and a file path.

And it has a FileName property that exposes that file path.

You should consult the .NET documentation for other constructors and object properties.

Re-throwing an exception

If all you’re going to do in your catch block is throw the same exception, then don’t catch it. You should only catch an exception that you plan to handle or perform some action when it happens.

There are times where you want to perform an action on an exception but re-throw the exception so something downstream can deal with it. We could write a message or log the problem close to where we discover it but handle the issue further up the stack.

Interestingly enough, we can call throw from within the catch and it re-throws the current exception.

We want to re-throw the exception to preserve the original execution information like source script and line number. If we throw a new exception at this point, it hides where the exception started.

Re-throwing a new exception

If you catch an exception but you want to throw a different one, then you should nest the original exception inside the new one. This allows someone down the stack to access it as the $PSItem.Exception.InnerException .

$PSCmdlet.ThrowTerminatingError()

The one thing that I don’t like about using throw for raw exceptions is that the error message points at the throw statement and indicates that line is where the problem is.

Having the error message tell me that my script is broken because I called throw on line 31 is a bad message for users of your script to see. It doesn’t tell them anything useful.

Dexter Dhami pointed out that I can use ThrowTerminatingError() to correct that.

If we assume that ThrowTerminatingError() was called inside a function called Get-Resource , then this is the error that we would see.

Do you see how it points to the Get-Resource function as the source of the problem? That tells the user something useful.

Because $PSItem is an ErrorRecord , we can also use ThrowTerminatingError this way to re-throw.

This changes the source of the error to the Cmdlet and hide the internals of your function from the users of your Cmdlet.

Try can create terminating errors

Kirk Munro points out that some exceptions are only terminating errors when executed inside a try/catch block. Here is the example he gave me that generates a divide by zero runtime exception.

Then invoke it like this to see it generate the error and still output the message.

But by placing that same code inside a try/catch , we see something else happen.

We see the error become a terminating error and not output the first message. What I don’t like about this one is that you can have this code in a function and it acts differently if someone is using a try/catch .

I have not ran into issues with this myself but it is corner case to be aware of.

$PSCmdlet.ThrowTerminatingError() inside try/catch

One nuance of $PSCmdlet.ThrowTerminatingError() is that it creates a terminating error within your Cmdlet but it turns into a non-terminating error after it leaves your Cmdlet. This leaves the burden on the caller of your function to decide how to handle the error. They can turn it back into a terminating error by using -ErrorAction Stop or calling it from within a try<. >catch <. >.

Public function templates

One last take a way I had with my conversation with Kirk Munro was that he places a try<. >catch <. >around every begin , process and end block in all of his advanced functions. In those generic catch blocks, he has a single line using $PSCmdlet.ThrowTerminatingError($PSItem) to deal with all exceptions leaving his functions.

Because everything is in a try statement within his functions, everything acts consistently. This also gives clean errors to the end user that hides the internal code from the generated error.

I focused on the try/catch aspect of exceptions. But there’s one legacy feature I need to mention before we wrap this up.

A trap is placed in a script or function to catch all exceptions that happen in that scope. When an exception happens, the code in the trap is executed and then the normal code continues. If multiple exceptions happen, then the trap is called over and over.

I personally never adopted this approach but I can see the value in admin or controller scripts that log any and all exceptions, then still continue to execute.

Adding proper exception handling to your scripts not only make them more stable, but also makes it easier for you to troubleshoot those exceptions.

I spent a lot of time talking throw because it is a core concept when talking about exception handling. PowerShell also gave us Write-Error that handles all the situations where you would use throw . So don’t think that you need to be using throw after reading this.

Now that I have taken the time to write about exception handling in this detail, I’m going to switch over to using Write-Error -Stop to generate errors in my code. I’m also going to take Kirk’s advice and make ThrowTerminatingError my goto exception handler for every function.

Источник

Все, что вы хотели знать об исключениях

Обработка ошибок — это лишь часть процесса написания кода. Зачастую можно проверить, выполняются ли условия для ожидаемого поведения. При возникновении непредвиденной ситуации необходимо переключиться на обработку исключений. Вы можете легко обрабатывать исключения, созданные кодом других разработчиков, или создавать собственные исключения, которые смогут обработать другие.

Оригинал этой статьи впервые был опубликован в блоге автора @KevinMarquette. Группа разработчиков PowerShell благодарит Кевина за то, что он поделился с нами этими материалами. Читайте его блог — PowerShellExplained.com.

Базовая терминология

Прежде чем двигаться дальше, необходимо разобраться с основными терминами.

Исключение

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

Throw и Catch

Когда происходит исключение, мы говорим, что оно вызвано. Чтобы обработать вызванное исключение, его необходимо перехватить. Если вызывается исключение, которое не перехватывается никакими объектами, выполнение скрипта прекращается.

Стек вызовов

Стек вызовов — это список функций, которые вызывали друг друга. При вызове функции она добавляется в стек или вверх списка. При выходе из функции или возврате ею значения она удаляется из стека.

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

Неустранимые и устранимые ошибки

Исключение обычно является неустранимой ошибкой. Вызванное исключение либо перехватывается, либо завершает текущее выполнение. По умолчанию устранимая ошибка генерируется Write-Error и приводит к добавлению ошибки в выходной поток без вызова исключения.

Обращаю внимание на это потому, что Write-Error и другие устранимые ошибки не активируют catch .

Игнорирование исключения

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

Основной синтаксис команды

Ниже приведен краткий обзор основного синтаксиса обработки исключений, используемого в PowerShell.

Ключевое слово throw

Чтобы вызвать собственное событие исключения, воспользуйтесь ключевым словом throw .

Это приведет к созданию исключения во время выполнения, которое является неустранимой ошибкой. Оно обрабатывается с помощью catch в вызывающей функции или приводит к выходу из скрипта с сообщением следующего вида.

Параметр -ErrorAction Stop командлета Write-Error

Я упоминал, что по умолчанию Write-Error не вызывает неустранимую ошибку. Если указать -ErrorAction Stop , то Write-Error создает неустранимую ошибку, которую можно обработать с помощью catch .

Благодарю Ли Дейли за напоминание о том, что -ErrorAction Stop можно использовать таким образом.

Параметр -ErrorAction Stop в командлете

Если указать -ErrorAction Stop в любой расширенной функции или командлете, все инструкции Write-Error будут преобразованы в неустранимые ошибки, которые приводят к остановке выполнения или могут быть обработаны с помощью catch .

Try и Catch

Принцип обработки исключений в PowerShell (и многих других языках) состоит в том, что сначала к разделу кода применяется try , а если происходит ошибка, к нему применяется catch . Приведем краткий пример.

Скрипт catch выполняется только в том случае, если произошла неустранимая ошибка. Если try выполняется правильно, catch пропускается. Можно получить доступ к информации об исключении в блоке catch с помощью переменной $_ .

Try и Finally

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

Взгляните на этот пример.

Всякий раз, когда вы открываете ресурс или подключаетесь к нему, его следует закрыть. Если ExecuteNonQuery() вызывает исключение, соединение не закрывается. Вот тот же код в блоке try/finally .

В этом примере соединение закрывается при возникновении ошибки. Оно также закрывается при отсутствии ошибок. При этом всякий раз выполняется скрипт finally .

Так как исключение не перехватывается, оно по-прежнему распространяет в стек вызовов.

Try, catch и finally

Вполне допустимо использовать catch и finally вместе. В большинстве случаев используется либо один, либо другой скрипт, но вам могут встретиться сценарии, в которых используются они оба.

$PSItem

Теперь, когда мы разобрались с основами, можно изучить вопрос подробнее.

В блоке catch существует автоматическая переменная ( $PSItem или $_ ) типа ErrorRecord , содержащая сведения об исключении. Ниже приведен краткий обзор некоторых ключевых свойств.

В этих примерах для создания такого исключения я использовал недопустимый путь в ReadAllText .

PSItem.ToString()

Этот метод позволяет получить максимально понятное сообщение, которое можно использовать при ведении журнала и выводе общего результата. ToString() вызывается автоматически, если в строку помещена переменная $PSItem .

$PSItem.InvocationInfo

Это свойство содержит дополнительные сведения, собираемые PowerShell о функции или скрипте, которые вызвали исключение. Ниже приведено свойство InvocationInfo из примера созданного мною исключения.

Здесь приведены важные сведения: имя ScriptName , строка кода Line и номер строки ScriptLineNumber , из которой инициирован вызов.

$PSItem.ScriptStackTrace

Это свойство показывает порядок вызовов функций, ведущих к коду, в котором создано исключение.

Я выполняю вызовы функций в пределах одного скрипта, но таким же образом можно отслеживать функции при использовании нескольких скриптов.

$PSItem.Exception

Это, собственно, и есть вызванное исключение.

$PSItem.Exception.Message

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

Это еще одно сообщение, возвращаемое при вызове $PSItem.ToString() , если для ErrorRecord не задан другой его вариант.

$PSItem.Exception.InnerException

Исключения могут содержать внутренние исключения. Это часто случается, когда вызываемый код перехватывает одно исключение и вызывает другое. Исходное исключение помещается в новое исключение.

Я вернусь к этому вопросу позже, когда буду рассказывать о повторном вызове исключений.

$PSItem.Exception.StackTrace

Это свойство StackTrace для исключения. Я продемонстрировал принцип работы свойства ScriptStackTrace выше, но это предназначено для вызовов управляемого кода.

Эта трассировка стека выполняется только в случае, если событие вызвано из управляемого кода. Я вызываю функцию .NET Framework напрямую, так что это все, что можно увидеть в этом примере. Как правило, трассировка стека просматривается, чтобы найти место остановки кода и начало системных вызовов.

Работа с исключениями

Для работы с исключениями недостаточно базового синтаксиса и основных свойств.

Перехват типизированных исключений

Исключения можно перехватывать избирательно. Исключения имеют тип, задав который можно перехватить только исключения определенного типа.

Каждый блок catch проверяется на наличие исключения заданного типа, пока не будет найден тот, в котором оно создается. Важно понимать, что исключения могут наследоваться от других исключений. В приведенном выше примере FileNotFoundException наследуется от IOException . Поэтому, если исключение IOException было первым, будет вызвано именно оно. Даже если совпадений несколько, вызывается только один блок catch.

При наличии исключения System.IO.PathTooLongException исключение IOException распознается как совпадение, но при наличии исключения InsufficientMemoryException перехватывать его нечем, поэтому оно распространится по стеку.

Одновременный перехват нескольких типов

С помощью одной инструкции catch можно перехватывать несколько типов исключений одновременно.

Благодарю /u/Sheppard_Ra за предложение добавить этот раздел.

Вызов типизированных исключений

В PowerShell можно вызывать типизированные исключения. Вместо вызова throw со строкой:

используйте ускоритель исключений следующим образом:

Но в таком случае необходимо указать сообщение.

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

Если вы не используете PowerShell 5.0 или более поздних версий, необходимо использовать устаревший подход с применением New-Object .

Как упоминалось в предыдущем разделе, используя типизированное исключение, вы (или другие пользователи) можете перехватывать исключения по типу.

Параметр -Exception командлета Write-Error

Эти типизированные исключения можно добавить в Write-Error и при этом перехватывать исключения с помощью catch по их типу. Используйте командлет Write-Error , как показано в следующих примерах.

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

Большой список исключений .NET

В дополнение к этой публикации я (при помощи Сообщество Reddit/r/PowerShell) составил основной список, который содержит сотни исключений .NET.

Для начала я ищу в этом списке исключения, которые хорошо подходят для моей ситуации. Следует попытаться использовать исключения в базовом пространстве имен System .

Исключения как объекты

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

У него есть свойство FileName , которое предоставляет путь к этому файлу.

Сведения о других конструкторах и свойствах объектов см. в документации по Документация по .NET.

Повторный вызов исключения

Если блок catch используется только для вызова того же исключения с помощью throw , перехватывать его с помощью catch не стоит. Перехватывать с помощью catch следует только исключение, которое планируется обработать или использовать для выполнения какого-либо действия.

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

Интересно, что throw можно вызвать из catch , и тогда текущее исключение будет вызвано повторно.

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

Повторный вызов нового исключения

Если вы перехватили исключение, но хотите вызвать другое, следует вложить исходное исключение в новое. Это позволяет объекту на более низком уровне стека получить к нему доступ как к $PSItem.Exception.InnerException .

$PSCmdlet.ThrowTerminatingError()

Единственное, что мне не нравится в использовании throw для необработанных исключений, заключается в том, что в качестве причины ошибки в сообщении об ошибке указывается инструкция throw и номер проблемной строки.

Если сообщение об ошибке сообщает о том, что скрипт работает неправильно из-за вызова throw в строке 31, оно не подходит для отображения пользователям скрипта. Оно не сообщает им ничего полезного.

Декстер Дхами отметил, что для устранения этой проблемы я могу использовать ThrowTerminatingError() .

Если предположить, что метод ThrowTerminatingError() вызван внутри функции с именем Get-Resource , сообщение об ошибке будет таким, как показано ниже.

Вы заметили, что в качестве источника проблемы в нем указана функция Get-Resource ? Теперь пользователь узнает что-то полезное.

Так как значением $PSItem является ErrorRecord , можно таким же образом использовать ThrowTerminatingError для повторного вызова.

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

Try как источник неустранимой ошибки

Кирк Манро указывает, что некоторые исключения являются неустранимыми ошибками только при выполнении внутри блока try/catch . В этом предоставленном им примере исключение во время выполнения вызывается вследствие деления на ноль.

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

Однако если поместить этот же код в try/catch , то происходит нечто иное.

Ошибка становится неустранимой, а первое сообщение не выводится. Не нравится мне в этом то, что при наличии такого кода в функции поведение становится другим, если использовать try/catch .

Сам я не сталкивался с такой проблемой, но это крайний случай, о котором следует помнить.

Метод $PSCmdlet.ThrowTerminatingError() в try/catch

Одна из особенностей $PSCmdlet.ThrowTerminatingError() заключается в том, что в командлете этот метод вызывает неустранимую ошибку, но после выхода из него ошибка становится устранимой. Из-за этого тому, кто вызывает функцию, приходится решать, как следует обрабатывать такую ошибку. В этом случае можно превратить ее снова в неустранимую ошибку с помощью -ErrorAction Stop или воспользоваться вызовом из try<. >catch <. >.

Шаблоны общих функций

В одной из последних бесед с Кирком Манро мы говорили о том, что он помещает каждый блок begin , process и end во всех своих расширенных функциях в блок try<. >catch <. >. В этих универсальных блоках catch у него есть одна строка с методом $PSCmdlet.ThrowTerminatingError($PSItem) для обработки всех исключений, вызываемых его функциями.

Поскольку в его функциях все находится в операторе try , весь код работает единообразно. Это также позволяет выводить для пользователя понятные сообщения об ошибках, в которых скрыты данные о внутреннем коде.

Ключевое слово trap

Я уделил много внимания особенностям блока try/catch . Однако прежде чем закрывать эту тему, нужно упомянуть об одной устаревшей функции.

Ключевое слово trap помещается в скрипт или функцию для перехвата всех исключений, возникающих в соответствующей области. При возникновении исключения выполняется код в ловушке trap , а затем продолжается выполнение стандартного кода. Если происходит несколько исключений, ловушка вызывается снова и снова.

Я лично никогда не применял этот подход. Однако, судя по скриптам администраторов и контроллеров, которые заносят в журнал абсолютно все исключения, а затем продолжают выполнение, могу сказать, что в нем есть своя польза.

Заключительное слово

Добавление соответствующей обработки исключений в скрипты не только делает эти скрипты более стабильными, но и упрощает устранение таких исключений.

Я уделил так много внимания throw , так как это основное понятие, которым мы оперируем при обсуждении обработки исключений. В PowerShell также предоставляется командлет Write-Error , справляющийся со всеми ситуациями, в которых следовало бы использовать throw . Так что изложенное в этой статье вовсе не означает, что вам обязательно необходимо использовать throw .

Теперь, когда я подробно рассказал об обработке исключений, я собираюсь заняться вопросом использования Write-Error -Stop для создания ошибок в коде. Я также собираюсь воспользоваться советом Кирка и перейти на использование ThrowTerminatingError в качестве обработчика исключений для каждой функции.

Источник

You need to understand how PowerShell handles errors and exceptions if you wish to create a script that behaves as you expect. It’s a fact of life that on occasions your PowerShell scripts will throw errors.

Contents

  1. Non-terminating errors
  2. Terminating errors
  3. Turning non-terminating errors into terminating errors
  4. Wrap-up
  • Author
  • Recent Posts

Adam Bertram is a 20-year IT veteran, Microsoft MVP, blogger, and trainer.

No one writes perfect code or can account for the slew of environmental changes that may be going on to write a PowerShell script that executes correctly 100% of the time.

The first thing you’ll need to realize when working with errors in PowerShell is that there’s not just one kind of «error,» there are two: terminating and non-terminating errors. You will undoubtedly come across both.

Non-terminating errors

Non-terminating errors are still «errors» in PowerShell but not quite as severe as terminating ones. Non-terminating errors aren’t as serious because they do not halt script execution. Moreover, you can silence them, unlike terminating errors. You can create non-terminating errors with the Write-Error cmdlet. This cmdlet writes text to the error stream.

Write Error

Write Error

You can also manipulate non-terminating errors with the common ErrorAction and ErrorVariable parameters on all cmdlets and advanced functions. For example, if you’ve created an advanced function that contains a Write-Error reference, you can temporarily silence this as shown below.

function Do-Thing {
    [CmdletBinding()]
    param()
    Write-Error 'An error occurred'
}
## Execute on its own
PS> Do-Thing

Write Error output

Write Error output

I can silence this error with the built-in ErrorAction parameter and choose to SilentlyContinue as well.

Do-Thing -ErrorAction SilentlyContinue

You see when executing the above code, it will return nothing.

Terminating errors

Terminating errors are those that halt script execution. These errors are meant to be more serious than non-terminating errors. The most common way to create a terminating error is to use the throw command. The throw command «throws» an exception, which is essentially the same thing as a terminating error. For example, the below code will not return anything. You might expect that it would return «the string variable did not equal foo» because I did not include an else condition. However, it does not, because throw halts execution.

$string = 'foo'
if ($string -eq 'foo') {
    throw 'string equaled foo when it was not supposed to'
}
Write-Output 'the string variable did not equal foo'

Terminating error

Terminating error

Unlike with non-terminating errors, the ErrorAction or ErrorVariable parameters cannot handle terminating errors. Only try/catch blocks can handle them. You can use this pair of blocks together to form a net of sorts around a piece of code. When a try block throws a terminating error, it will immediately halt execution of any further code inside of that try block and begin execution of the code inside of the catch block. The example below shows this behavior.

try {
    $string = 'foo'
    if ($string -eq 'foo') {
        throw 'string equaled foo when it was not supposed to'
    }
    Write-Output 'the string variable did not equal foo'
} catch {
    Write-Output 'we ended up in the catch block'
}

When run, the code returns the string «we ended up in the catch block.» Without try/catch blocks, the code will simply write out a terminating error (exception) to the host as the first example shows. Here, we did not see any red error text but just the «we ended up in the catch block» string. This is how the code handles terminating errors.

Turning non-terminating errors into terminating errors

Sometimes you have no control over which errors are terminating vs. non-terminating. And you may want to ensure that when a script runs, it always stops for every error regardless of the type. To consider all errors as terminating errors, you can manipulate the $ErrorActionPreference automatic variable. This variable controls what happens when a non-terminating error occurs. The default setting for this variable is Continue. However, if you want to ensure every non-terminating error halts script execution just like a terminating error, you can change this to Stop as the example below shows.

$ErrorActionPreference = 'Stop'

$string = 'foo'
if ($string -eq 'foo') {
    Write-Error 'string equaled foo when it was not supposed to'
}
Write-Output 'we are past the error here'

By executing the above code, you’ll see that it returns both the error and the output. This is because Write-Error returns a non-terminating error or sends a string to the error stream. However, when setting $ErrorActionPreference to Stop, you’ll see that the code never returns «we are past the error here» because it halts script execution.

Subscribe to 4sysops newsletter!

Wrap-up

Understanding how errors occur and how to invoke them at appropriate times is an important skill to master in PowerShell. Proper error handling can make the difference between a script that blows up and a well-structured one that is much more robust.

avatar

There are a few different kinds of errors in PowerShell, and it can be a little bit of a minefield on occasion.
There are always two sides to consider, too: how you write code that creates errors, and how you handle those errors in your own code.
Let’s have a look at some ways to effectively utilise the different kinds of errors you can work with in PowerShell, and how to handle them.

Different Types of Errors

Terminating Errors

Terminating errors are mostly an «exactly what it sounds like» package in general use.
They’re very similar to C#’s Exception system, although PowerShell uses ErrorRecord as its base error object.
Their common characteristics are as follows:

  • Trigger try/catch blocks
  • Return control to the caller, along with the exception, if they’re not handled using a try/catch block.

There are a couple different ways to get a terminating error in PowerShell, and each has its own small differences that make them more useful in a tricky situation.

Throw

First is your typical throw statement, which simply takes an exception message as its only input.

throw "Something went wrong"

throw creates an exception and an ErrorRecord (PowerShell’s more detailed version of an exception) in a single, simple statement, and wraps them together.
Its exceptions display the entered error message, a generic RuntimeException, and the errorID matches the entered exception message.
The line number and position displayed from a throw error message point back to the throw statement itself.
throw exceptions are affected by -ErrorAction parameters of their advanced functions.

throw should generally be used for simple runtime errors, where you need to halt execution, but typically only if you plan to catch it yourself later.
There is no guarantee a user will not elect to simply ignore your error with -ErrorAction Ignore and continue with their lives if it is passed up from your functions.

ThrowTerminatingError()

Next, we have a more advanced version of the throw statement. ThrowTerminatingError() is a method that comes directly from PowerShell’s PSCmdlet .NET class.
It is accessible only in advanced functions via the $PSCmdlet variable.

using namespace System.Management.Automation;
$Exception = [Exception]::new("error message")
$ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
    $Exception,
    "errorID",
    [System.Management.Automation.ErrorCategory]::NotSpecified,
    $TargetObject # usually the object that triggered the error, if possible
)
$PSCmdlet.ThrowTerminatingError($ErrorRecord)

As you can see, these require a little more manual labor.
You can bypass some of this by deliberately using the throw statement and then later catching the generated ErrorRecord to pass it into the ThrowTerminatingError() method directly.
This saves a decent amount of the code involved, and offloads the work to PowerShell’s engine instead.
However, this will still involve essentially the same work being done, without necessarily giving you a chance to specify the finer details of the error record.

ThrowTerminatingError() creates a terminating error (as the name implies), however unlike throw its errors are unaffected by -ErrorAction parameters applied to the parent function.
It will also never reveal the code around it.
When you see an error thrown via this method, it will always display only the line where the command was called, never the code within the command that actually threw the error.

ThrowTerminatingError() should generally be used for serious errors, where continuing to process in spite of the error may lead to corrupted data, and you want any specified -ErrorAction parameter to be ignored.

Non-Terminating Errors

Non-terminating errors are PowerShell’s concession demanded by the fact that it is a shell that supports pipelines.
Not all errors warrant halting all ongoing actions, and only need to inform the user that a minor error occurred, but continue processing the remainder of the items.

Unlike terminating errors, non-terminating errors:

  • Do not trigger try/catch blocks.
  • Do not affect the script’s control flow.

Write-Error

Write-Error is a bit like the non-terminating version of throw, albeit more versatile.
It too creates a complete ErrorRecord, which has a NotSpecified category and some predefined Exception and ErrorCode properties that both refer to Write-Error.

This is a very simple and effective way to write a non-terminating error, but it is somewhat limited in its use.
While you can override its predefined default exception type, error code, and essentially everything about the generated ErrorRecord, Write-Error is similar to throw in that it too points directly back to the line where Write-Error itself was called.
This can often make the error display very unclear, which is rarely a good thing.

Write-Error is a great way to emit straightforward, non-terminating errors, but its tendency to muddy the error display makes it difficult to recommend for any specific uses.
However, as it is itself a cmdlet, it is also possible to make it create terminating errors as well, by using the -ErrorAction Stop parameter argument.

WriteError()

WriteError() is the non-terminating cousin of ThrowTerminatingError(), and is also available as one of the members of the automatic $PSCmdlet variable in advanced functions.
It behaves similarly to its cousin in that it too hides the code that implements it and instead points back to the point where its parent function was called as the source point of the error.
This is often preferable when dealing with expected errors such as garbled input or arguments, or simple «could not find the requested item» errors in some cases.

$Exception = [Exception]::new("error message")
$ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
    $Exception,
    "errorID",
    [System.Management.Automation.ErrorCategory]::NotSpecified,
    $TargetObject # usually the object that triggered the error, if possible
)
$PSCmdlet.WriteError($ErrorRecord)

Emit Errors Responsibly

There are no true hard-and-fast rules here.
If one solution works especially well for what you happen to be putting together, don’t let me deter you.
However, I would urge you to consider both the impact to your users as well as yourself or your team before selecting a single-minded approach to error creation.
Not all error scenarios are created equal, so it’s very rare that I would recommend using one type over all others.

WriteError() is generally preferred over its cmdlet form Write-Error, because it offers more control over how you emit the error.
Both tend to be used for expected and technically avoidable errors that occur, but the $PSCmdlet method has the additional benefit of not divulging implementation details unnecessarily.

If you expect the error to occur sometimes, I would generally advise using WriteError() unless the error’s presence significantly impairs your cmdlet’s ability to process the rest of its input, in which case I would use ThrowTerminatingError().

throw is generally nice as a quick way to trigger a try/catch block in your own code, but I would also recommend using it for: those truly unexpected errors, that last-ditch catch clause after several more specific catch blocks have been put in place and bypassed.
At this point, a final ending catch block that simply triggers a throw $_ call to re-throw the caught exception into the parent scope and quickly pass execution back to the caller.
It’s a very quick and very effective way to have the error be noticed, but should usually be kept internal to the function more than anything else, except in the last scenario mentioned.
Use of Write-Error -ErrorAction Stop may potentially also be substituted for that purpose if you personally prefer.

Thanks for reading!

Welcome to my Getting Started with Windows PowerShell series!

Why Handle Errors?

Error handling may take some extra time to create, however, it can really save you time in the long run. Let’s say you have a script that performs the following tasks:

  • Takes information from a database / file and stores it in $userAccounts

  • Uses a loop to perform processing on each $user in $userAccounts

    • Update a user’s AD information if the user is in the list

    • Disable any accounts that are not in the list

The script runs, and seems to be working great for a few weeks. Then, all of a sudden, the database server goes down, and $userAccounts is empty. The script runs, doesn’t see any users, and what likely happens next is all user accounts are deleted. While this example is a little extreme, it is something that can happen. There are also a lot of in-between cases that can make error handling worth it. It’s a good mindset to have when you’re writing scripts as you can write to incorporate it off the bat.

Errors in PowerShell

Errors in PowerShell are stored in the automatic variable $error. You can see how many errors you’ve encountered in your session by using:

$error.count

This is essentially an array of errors, to access the first value you can use:

$error[0]

Let’s take a look at $error[0] more closely, via:

$error[0] | Get-Member

We can view information on the command that raised the error via:

$error[0].InvocationInfo

It looks like the line property contains the full command we used when the error was encountered.

$error[0].InvocationInfo.Line

The exception that raised the error can be accessed via:

$error[0].Exception

We can also get more information about the exception via:

$error[0].Exception | Get-Member

Here you can see the TypeName: [System.UnauthorizedAccessException], and the various methods and properties of the exception. The exception TypeName is used later when we catch specific errors.

Looks like to get the exception’s message in string format we’d use:

$error[0].Exception.Message

Now that we’ve dug into the automatic variable $error, let’s move on to the different types of errors we can encounter.

Terminating Errors

Terminating errors in PowerShell mean that the script can no longer continue to run with the information it has encountered, or has been given. If you do not have a way to catch the error, it will likely display the nasty red error text you’ve likely seen before. Terminating errors halt anything running in the pipeline. This terminates output, aside from the error message of the terminating error. 

Here’s an example of a terminating error that was caused by calling a command that does not exist:

Get-TerminatingError

Non-Terminating Errors

Non-terminating errors in PowerShell do not stop the pipeline from executing. These errors are handled internally by the Cmdlet, and are not able to be captured via error handling. There are ways, however, to make PowerShell treat non-terminating errors like terminating errors. That way we can use error handling to capture what’s going on.

Here’s an example of a non-terminating error (access denied to a subfolder), when attempting to list all folders and subfolders in «C:Windowsappcompat».

Get-ChildItem -Path 'C:Windowsappcompat' -Recurse

Force Non-Terminating Errors to Terminate

You can force non-terminating errors to become terminating errors in PowerShell. There are a couple ways you can do this. Why would you want to do this, you ask? Since non-terminating errors cannot be handled by us, we need to force them to terminate, and gain the functionality of handling the error ourselves. This is not always needed, but it is good to know that you can do it if you come across a use case for yourself.

$errorActionPreference

You can do it at a global way for the session via the $ErrorActionPreference variable. This is a special variable in PowerShell that allows you to control what happens when a non-terminating error is encountered.

Here are the values for $ErrorActionPreference.

  • Stop

    • Display error, and stop execution.

  • Inquire

    • Display error, and ask to continue.

  • Continue (Default)

    • This is the default setting. Display error, then continue execution.

  • Suspend

    • This one is for workflows. A workflow job is suspended to investigate what happened, then the workflow can be resumed.

  • SilentlyContinue

    • No error is displayed, execution is continued.

Let’s see this in action.

Get-ChildItem -Path 'C:Windowsappcompat' -Recurse;Write-Host 'Test'

As you can see, with a non-terminating error, the next command in the sequence is executed. Therefore we see ‘Test’. Let’s set the $errorActionPreference automatic variable to Stop, and re-run the same command.

$ErrorActionPreference = 'Stop'
Get-ChildItem -Path 'C:Windowsappcompat' -Recurse;Write-Host 'Test'

Use the Command’s -ErrorAction Parameter

Cmdlet’s and functions/scripts/modules that use [cmdletbinding()] enable utilization of the -ErrorAction common parameter. This parameter allows you to specify different actions to take when an error is encountered. 

  • Stop

    • Display error, and stop execution.

  • Inquire

    • Display error, and ask to continue.

  • Continue (Default)

    • This is the default setting. Display error, then continue execution.

  • Suspend

    • This one is for workflows. A workflow job is suspended to investigate what happened, then the workflow can be resumed.

  • SilentlyContinue

    • No error is displayed, execution is continued.

  • Ignore

    • The same as SilentlyContinue, but as SilentlyContinue still adds the message to the $error automatic variable, Ignore does not do this.

Let’s set our $errorActionPreference to Continue, and then look at using the Cmdlet Get-ChildItem‘s -ErrorAction parameter.

We’re setting $errorActionPreference back to Continue as earlier we set it to Stop for our other example. Continue is the default value.

$ErrorActionPreference = 'Continue'
Get-ChildItem -Path 'C:Windowsappcompat' -Recurse -ErrorAction Stop;Write-Host 'Test'

Error Handling

There are a few different ways to to handle errors in PowerShell. The best way of course, is to never let them happen in the first place! To do that, it is good to have some data validation in place via an if statement, or whichever method suites your needs.

If a terminating error occurs, we can use Try/Catch/Finally blocks to perform different actions. If it is non-terminating, we can force it to become a terminating error, and then choose how to continue.

Validation

The simplest method of validation is the if statement. 

If you’d like to run these examples yourself, go ahead and fire up the PowerShell ISE. Save a file in C:PowerShell as part11.ps1. 

An if statement is constructed as follows:

if (condition -eq $true) {

Do-Stuff

} else {

Do-Other-Stuff

}

The condition can be anything that resolves to a true value. That includes running a command that output (other than and error) occurs! Check this out:

if (Get-ChildItem Z: -ErrorAction SilentlyContinue) {

    Write-Host 'I can list the contents of Z:!'

} else {

    Write-Host 'I cannot list the contents of Z:!'

}

This does not return true, as expected, and thus we see the message from the code in the else portion of the if statement. We use the -ErrorAction common parameter with the value SilentlyContinue to suppress the error from being displayed to the end user of the script. No need in this case, as we’re simply using the command for validation.

You can also use variables with the if statement to see if they are blank. 

$myVariable = $null

if ($myVariable) {

    Write-Host "We have information! Let's do stuff."

} else {

    Write-Host "`$myVariable is empty :("

}

The variable is empty, so the code in the else portion of the if statement is executed.

Try/Catch/Finally

The Try, Catch, and Finally blocks in PowerShell allow us to capture terminating errors. 

The Try block contains the code you’d like to execute, and catch any potential errors that happen.

The Catch block contains the code you’d like to execute after a terminating error has occurred. The current error will be accessible via the automatic variable $_.

The Finally block contains the code you’d like to run after the event has occurred. This is good for cleanup tasks. 

It is worth noting that finally block is not required.

Here is an example using Try/Catch/Finally:

Try {
    
    $command = 'Invoke-FakeCommand'

    Write-Host "Attempting to run: [Invoke-Expression -Command $command]"`n

    Invoke-Expression -Command $command

}

Catch {

    Write-Host $_.Exception.Message`n

}

Finally {

    Write-Host "Clean up: `$commmand = `$null"`n

    $commmand = $null

}

The code in the Try block executes and we see the output of Write-Host. We then see the error message that occurs, as our Catch block is writing out $_.Exception.Message. As we learned earlier, that is the string value of the exception that raised the error.

We then see the output from the Write-Host command in our Finally block. We use the finally block to free up the $command variable.

This version of Try/Catch will catch any terminating errors that are raised. To capture specific exceptions, you’ll want to use the exception’s TypeName.

Catch Specific Errors

Let’s take a look at the following:

Try {

    Get-ThisWontWork

}

Catch [System.Management.Automation.CommandNotFoundException] {
    
    Write-Host "Command not found!"`n -ForegroundColor Red 
    Write-Host "Message: [$($_.Exception.Message)"] -ForegroundColor Red -BackgroundColor DarkBlue
    
}

The exception was caught, and the code in the Catch block was executed.

Now let’s try this…

Try {
    
    Get-ChildItem -Path Z: -ErrorAction Stop
    Get-ThisWontWork

}

Catch [System.Management.Automation.CommandNotFoundException] {
    
    Write-Host "Command not found!"`n -ForegroundColor Red 
    Write-Host "Message: [$($_.Exception.Message)"] -ForegroundColor Red -BackgroundColor DarkBlue
    
}

Ahh! An error that wasn’t handled! Luckily we can add multiple catch blocks.

Let’s add another Catch block.

Catch {

Write-Host $_.Exception.Message

}
Try {
    
    Get-ChildItem -Path Z: -ErrorAction Stop
    Get-ThisWontWork

}

Catch [System.Management.Automation.CommandNotFoundException] {
    
    Write-Host "Command not found!"`n -ForegroundColor Red 
    Write-Host "Message: [$($_.Exception.Message)"] -ForegroundColor Red -BackgroundColor DarkBlue
    
}


Catch {

   Write-Host $_.Exception.Message

}

Now the exception from Get-ChildItem command is caught since the catch-all Catch block code is executed.

Getting Error Information

It can be handy to have a shortcut that shows you error information, which you can use to create specific Catch blocks. To do this, I created a function that I call in the Catch block which utilizes some validation itself! Here is the function:

function Get-ErrorInformation {
    [cmdletbinding()]
    param($incomingError)

    if ($incomingError -and (($incomingError| Get-Member | Select-Object -ExpandProperty TypeName -Unique) -eq 'System.Management.Automation.ErrorRecord')) {

        Write-Host `n"Error information:"`n
        Write-Host `t"Exception type for catch: [$($IncomingError.Exception | Get-Member | Select-Object -ExpandProperty TypeName -Unique)]"`n 

        if ($incomingError.InvocationInfo.Line) {
        
            Write-Host `t"Command                 : [$($incomingError.InvocationInfo.Line.Trim())]"
        
        } else {

            Write-Host `t"Unable to get command information! Multiple catch blocks can do this :("`n

        }

        Write-Host `t"Exception               : [$($incomingError.Exception.Message)]"`n
        Write-Host `t"Target Object           : [$($incomingError.TargetObject)]"`n
    
    }

    Else {

        Write-Host "Please include a valid error record when using this function!" -ForegroundColor Red -BackgroundColor DarkBlue

    }

}

Here is the full code I’ll be running for this example:

function Get-ErrorInformation {
    [cmdletbinding()]
    param($incomingError)

    if ($incomingError -and (($incomingError| Get-Member | Select-Object -ExpandProperty TypeName -Unique) -eq 'System.Management.Automation.ErrorRecord')) {

        Write-Host `n"Error information:"`n
        Write-Host `t"Exception type for catch: [$($IncomingError.Exception | Get-Member | Select-Object -ExpandProperty TypeName -Unique)]"`n 

        if ($incomingError.InvocationInfo.Line) {
        
            Write-Host `t"Command                 : [$($incomingError.InvocationInfo.Line.Trim())]"
        
        } else {

            Write-Host `t"Unable to get command information! Multiple catch blocks can do this :("`n

        }

        Write-Host `t"Exception               : [$($incomingError.Exception.Message)]"`n
        Write-Host `t"Target Object           : [$($incomingError.TargetObject)]"`n
    
    }

    Else {

        Write-Host "Please include a valid error record when using this function!" -ForegroundColor Red -BackgroundColor DarkBlue

    }

}

Try {
   
    Get-ChildItem -Path Z: -ErrorAction Stop

    Get-ThisWontWork

}

Catch [System.Management.Automation.CommandNotFoundException] {

    Write-Host 'Command not found Catch block executed!' 

}

Catch {

   Get-ErrorInformation -incomingError $_

}

So now, if we wanted to catch this exception on it’s own, we would need to add a catch block for [System.Management.Automation.DriveNotFoundException].

Catch [System.Management.Automation.CommandNotFoundException] {

Write-Host 'Command not found Catch block executed!' 

}

Let’s add that to our code and run the following:

function Get-ErrorInformation {
    [cmdletbinding()]
    param($incomingError)

    if ($incomingError -and (($incomingError| Get-Member | Select-Object -ExpandProperty TypeName -Unique) -eq 'System.Management.Automation.ErrorRecord')) {

        Write-Host `n"Error information:"`n
        Write-Host `t"Exception type for catch: [$($IncomingError.Exception | Get-Member | Select-Object -ExpandProperty TypeName -Unique)]"`n 

        if ($incomingError.InvocationInfo.Line) {
        
            Write-Host `t"Command                 : [$($incomingError.InvocationInfo.Line.Trim())]"
        
        } else {

            Write-Host `t"Unable to get command information! Multiple catch blocks can do this :("`n

        }

        Write-Host `t"Exception               : [$($incomingError.Exception.Message)]"`n
        Write-Host `t"Target Object           : [$($incomingError.TargetObject)]"`n
    
    }

    Else {

        Write-Host "Please include a valid error record when using this function!" -ForegroundColor Red -BackgroundColor DarkBlue

    }

}

Try {
   
    Get-ChildItem -Path Z: -ErrorAction Stop

    Get-ThisWontWork

}

Catch [System.Management.Automation.CommandNotFoundException] {

    Write-Host 'Command not found Catch block executed!' 

}

Catch [System.Management.Automation.DriveNotFoundException] {

    Write-Host 'Get-ChildItem drive not found Catch block executed!'

}

Catch {

   Get-ErrorInformation -incomingError $_

}

There we go! Now all our errors are handled, minus the ones we don’t know about yet.

Homework

  • Figure out why when multiple Catch blocks are used, it doesn’t pass along the execution information (unable to get command/line).

    • Let me know why this is!

  • Where else could I have added error handling in any of these examples?

I hope you’ve enjoyed the series so far! As always, leave a comment if you have any feedback or questions!

-Ginger Ninja

[Back to Top]

Murphy’s Law is an old saying that promises, «Anything that can go wrong will go wrong.» Coders and programmers know this saying particularly well. If you’ve spent any time writing code, you understand why. What coder doesn’t know the feeling of writing the perfect script that accomplishes just what you need, but some external variable pops up that causes it to malfunction?

In this blog post, we’re going to discuss the process for anticipating potential errors, mistakes and problems with your scripts and code: error handling. We’ll look at a small, simple program and insert some key lines and functions that will show you how to spot errors and identify them, rather than let your programs and machines churn along with faulty inputs.

What is a PowerShell Exception?

Quick Definition: A PowerShell exception is an error that happens while running PowerShell scripts, errors that PowerShell needs to be handled for it. PowerShell will try to handle errors on its own, but exceptions are, as the name may suggest, exceptions to its ability to do so. When an exception occurs, the phrase used is «throw». Handling a «thrown» exception means «catching» it, which is telling the script what to do. If a thrown exception isn’t caught, the script stops.

What is PowerShell Error Handling?

Quick Definition: Error handling in PowerShell is a process of analyzing your code for where exceptions could occur, for what exceptions could happen, and then writing code that anticipates and handles those exceptions.

An Overview of PowerShell Error Handling

In this video, Don Jones covers error handling in PowerShell. Something that sets a good script apart from a truly great one is its ability to respond to errors that you anticipated ahead of time. Watch as Don demonstrates the fine art of error handling.

When is Error Handling Necessary?

One of the things that really sets apart a good script from a great script is the script’s ability to respond to errors that you can anticipate ahead of time. It’s called Error Handling.

If you’re writing code or programming behavior for a computer, doing error handling can help in a number of ways. First of all, written well, a program can inform you what’s wrong — like a patient explaining symptoms to their doctor. Not only that, a program that stops at an exception won’t waste time and resources continuing a process that’s doomed to failure.

Don’t mistake error handling with faulty scripting. Errors in code can be a problem too, but usually it’s easy to spot those before running the program. Most people know the old «missing semicolon ruins day» problem. Error handling is about finding externalities — variables that you and your program don’t have control over while the program is doing its job.

Since the goal of error handling is to anticipate errors and then deal with them as they occur rather than just allow PowerShell to explode, the first step is spending some time with the program. Take a look at the following script and try to spot where you think errors might occur.

param (
        $computername = 'localhost'
)
   $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername
   $system = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computername
   $props = @ {        'ComputerName'=$computername;
                'OSVersion'=$os.caption;
                'TotalRAM'=$system.TotalPhysicalMemory;
                'Manufacturer'=$system.Manufacturer}
   $obj = New-Object =TypeName PowerShellObject -Property $props
   Write-Output $obj

Now, because this is a short one, it’s reasonably safe to assume that errors are going to happen on one of these two lines – if they happen anywhere:

   $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername
   $system = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computername

These are really the only two lines that are doing anything. They’re «leaving» the code and working with data from an external source. They’re working with an external entity. Some of the errors that we can anticipate include the computer in question not being available when we try to query it, or it could be that we don’t have permission to query it. There are a few things that could go wrong, but they’ll likely happen on those lines.

How to Code PowerShell Error Handling

There are three big steps to writing error handling in PowerShell: first is to identify where an error may occur: which command. Then, you put that command inside a try { } block. Third, you put inside a catch { } block what should be done if the error happens. We’ll walk through these three steps next.

First of all, choose one command — typically one. While it’s possible to do error handling for a block of commands, it’s usually best to limit your focus. Plus, the method we’ll be demonstrating below works best on one command. So, choose your one command and wrap it in a try-catch block.

   $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername

That’s part of the first step to writing PowerShell error handling: identify the command you think might cause an error. The second step is to put it inside a try { } block. Although the third step is to put inside the catch { } block whatever we want to do in the event of an error occurring, there’s another tweak we have to do first.

Most PowerShell commands are going to need a little bit of extra work. Still inside the try { } block, you’ll have to add a parameter to your function called ErrorAction and set it to «Stop».

try {
   $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername 
} catch {
}

Sometimes you’ll see that as -EA, which is what we’ll abbreviate it as going forward. Now, it’s possible you might want to take a different action based on the type of error that occurs. And so, you can capture the error in an -ErrorVariable. We’re going to call it «x».

try {
   $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername -EA Stop 
} catch {
}

Sometimes you’ll see that abbreviated -EV, which is again what we’ll abbreviate it as going forward. Also, it’s important to note that the variable name for the error does not include a dollar sign ($).

Next, we have to ask ourselves what we want to do about it when an error occurs? Well, let’s say we want to write the computer name that failed to a log file. To do that, we take the computer name variable $computername, and pump it to a file. And Append it to whatever else is in there:

try {
   $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername -EA Stop -EV x
} catch {
   
}

Maybe we also want to display a warning on the screen. We can make up our own error message, but for now let’s keep it pretty boilerplate: «Error talking to $computername : $x»

try {
   $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername -EA Stop -EV x
} catch {
   $computername | out-File c:errors.txt -Append
   
}

Note, be sure to keep that additional space behind the first variable «$computername». And, by using double quotation marks, we can include these variables and they’ll be expanded into their values.

How to Optimize Your PowerShell Error Handling

The trick to good PowerShell error handling is applying logic and reasoning to your code. Really good error handling anticipates errors and thinks about their consequences. Before we move on, let’s take a look at what our changes so far have gotten us:

try {
   $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername -EA Stop -EV x
} catch {
   $computername | out-File c:errors.txt -Append
   Write-Warning "Error talking to $computername : $x"
}
$system = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computername
   $props = @ {        'ComputerName'=$computername;
                'OSVersion'=$os.caption;
                'TotalRAM'=$system.TotalPhysicalMemory;
                'Manufacturer'=$system.Manufacturer}
   $obj = New-Object =TypeName PowerShellObject -Property $props
   Write-Output $obj

But let’s think about this. If the computer doesn’t respond to the first query,

try {
   
   $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername -EA Stop -EV x
} catch {
   $computername | out-File c:errors.txt -Append
   Write-Warning "Error talking to $computername : $x"
}

Then, down inside the catch { }, we’ll tell the program to set that variable to false if and when we get an error:

try {
   $everything_is_ok = $true
   $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername -EA Stop -EV x
} catch {
   $computername | out-File c:errors.txt -Append
   Write-Warning "Error talking to $computername : $x"
}

And then, wrap the output in an if statement regarding that variable:

$system = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computername
   $props = @ {        'ComputerName'=$computername;
                'OSVersion'=$os.caption;
                'TotalRAM'=$system.TotalPhysicalMemory;
                'Manufacturer'=$system.Manufacturer}
   $obj = New-Object =TypeName PowerShellObject -Property $props
   Write-Output $obj

So what is this doing? What it’s telling our program is that if our first function fails, the lines inside our catch { } will execute. When that happens, our variable becomes false, and none of the subsequent lines wrapped inside the if { } will ever execute. After all, if the first query failed, there’s no point in trying the second one – it’ll almost certainly fail too.

Once again, this is a small example. But hopefully it helps illustrate one of the approaches you can take to uncovering not just the first thrown exception in a program, but what to do about subsequent functions too.

How to Test Your PowerShell Error Handling

It’s always a good idea to check your code, both for success states and failure states, and this program is no different. If you can do it from your own device, go ahead and save the above code and give it a test. The program we’ve written is going to search for a server «localhost», which should obviously be discoverable on the network. If you’re like us, running the program results in success.

Of course, it’s not enough that your successes succeed correctly. You also want your failures to fail correctly. The way we can test for the errors we’ve anticipated is by changing our first parameter, «localhost» to the name of a server that definitely doesn’t exist on the network. We went with «NOTONLINE».

After we run that, eventually there will be an error. We see: «Error talking to NOTONLINE» followed by the exception that occurred: «The RPC server is unavailable.»

Not only that, but we can pull up errors.txt and see that the name of that computer was successfully logged to the file.

The thing to remember about Error Handling in PowerShell is the -ErrorAction. That tells the command, «Look, if a problem happens, rather than just keep going and trying to continue, I want you to stop and throw a complete exception — a trappable exception.» Without the -EA Stop, no part of the try-catch block would function.

In fact, removing it shows quite a different result. We’ll leave our «NOTONLINE» server name, remove the -EA Stop from our code, and try running the code again.

If you’re doing this too, you might find that your Windows Management Instrumentation takes a while to time out. But once it finally does, you should see an error that’s far less manageable. That’s because this time the error comes directly from the command. It’s not a warning generated by the code which detected an error, it’s a full-on uncaught exception. Not only that, but the second command tried to run, and a different WMI error came through for that. Because the input for the second function was an error, the output of it is gibberish.

The difference should be obvious: if you can avoid a full-blown error and instead catch every exception as a warning and halt the functions, you do a lot for your program. Not only do you prevent time, energy, and resources from being wasted, but you also increase the chances of pinpointing where the error occurred and how you can fix it.

If you leave PowerShell to its own devices, it’ll try to carry out your commands as best it can, with increasingly faulty data coming from error after error. Instead, tell it you want it to stop when there’s an error so that you can fix what caused it. You should see now that the -ErrorAction Stop is the key to making ErrorHandling work in PowerShell.

Wrapping Up

If you’re looking to master PowerShell, CBT Nuggets has training that will do much more than one blog post could. Here, we only had time to address some of the top-level ideas of PowerShell error handling, but consider this PowerShell 7 training that features 9 hours of training and 104 videos about scripts, and automation.

Понравилась статья? Поделить с друзьями:
  • Raise error file does not start with riff id
  • Raise error could not locate runnable browser webbrowser error could not locate runnable browser
  • Raise application error sqlerrm
  • Raise application error oracle пример
  • Raise application error oracle описание