Powershell write error exception

Ошибки в 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

#ошибки

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.

Index

  • Index
  • Basic terminology
    • Exception
    • Throw and Catch
    • The call stack
    • Terminating and non-terminating errors
    • Swallowing an exception
  • Basic command syntax
    • Throw
      • Write-Error -ErrorAction Stop
      • Cmdlet -ErrorAction Stop
    • Try/Catch
    • Try/Finally
    • Try/Catch/Finally
  • $PSItem
    • PSItem.ToString()
    • $PSItem.InvocationInfo
    • $PSItem.ScriptStackTrace
    • $PSItem.Exception
      • $PSItem.Exception.Message
      • $PSItem.Exception.InnerException
      • $PSItem.Exception.StackTrace
  • Working with exceptions
    • Catching typed exceptions
    • Catch multiple types at once
    • Throwing typed exceptions
      • Write-Error -Exception
      • The big list of .Net exceptions
    • Exceptions are objects
    • Re-throwing an exception
      • Re-throwing a new exception
      • $PSCmdlet.ThrowTerminatingError()
  • Try can create terminating errors
    • $PSCmdlet.ThrowTerminatingError() inside try/catch
    • Public function templates
  • Trap
  • Closing remarks

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 not deal with the issue. Trying to divide a number by zero or running out of memory are examples of something that will create an exception. Sometimes the author of the code you are using will create 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 is not caught by something, the script will stop 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 will be 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 will either be caught or it will terminate 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 will 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.

function Do-Something
{
    throw "Bad thing happened"
}

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

PS:> Do-Something

Bad thing happened
At line:1 char:1
+ throw "Bad thing happened"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (Bad thing happened:String) [], RuntimeException
    + FullyQualifiedErrorId : Bad thing happened

Write-Error -ErrorAction Stop

I mentioned that Write-Error does not throw a terminating error by default. If you specify -ErrorAction Stop then Write-Errorgenerates a terminating error that can be handled with a catch.

Write-Error -Message "Houston, we have a problem." -ErrorAction Stop

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

Cmdlet -ErrorAction Stop

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

Do-Something -ErrorAction Stop

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.

try
{
    Do-Something
}
catch
{
    Write-Output "Something threw an exception"
}

try
{
    Do-Something -ErrorAction Stop
}
catch
{
    Write-Output "Something threw an exception or used Write-Error"
}

The catch script only runs if there is a terminating error. If the try executes correctly, then it will skip over the catch.

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:

$command = [System.Data.SqlClient.SqlCommand]::New(queryString, connection)
$command.Connection.Open()
$command.ExecuteNonQuery()
$command.Connection.Close()

Any time you open or connect to a resource, you should close it. If the ExecuteNonQuery() throws an exception, the connection will not get closed. Here is the same code inside a try/finally block.

$command = [System.Data.SqlClient.SqlCommand]::New(queryString, connection)
try
{
    $command.Connection.Open()
    $command.ExecuteNonQuery()
}
finally
{
    $command.Connection.Close()
}

In this example, the connection will get closed if there is an error. It will also get closed if there is no error. The finally script will run every time.

Because you are not catching the exception, it will still get propagated up the call stack.

Try/Catch/Finally

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

$PSItem

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

Inside the catch block, there is 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.

[System.IO.File]::ReadAllText( '\testnofilefound.log')

PSItem.ToString()

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

catch
{
    Write-Output "Ran into an issue: $($PSItem.ToString())"
}

catch
{
    Write-Output "Ran into an issue: $PSItem"
}

$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.

PS:> $PSItem.InvocationInfo | Format-List *

MyCommand             : Get-Resource
BoundParameters       : {}
UnboundArguments      : {}
ScriptLineNumber      : 5
OffsetInLine          : 5
ScriptName            : C:blogthrowerror.ps1
Line                  :     Get-Resource
PositionMessage       : At C:blogthrowerror.ps1:5 char:5
                        +     Get-Resource
                        +     ~~~~~~~~~~~~
PSScriptRoot          : C:blog
PSCommandPath         : C:blogthrowerror.ps1
InvocationName        : Get-Resource

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

$PSItem.ScriptStackTrace

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

PS:> $PSItem.ScriptStackTrace
at Get-Resource, C:blogthrowerror.ps1: line 13
at Do-Something, C:blogthrowerror.ps1: line 5
at <ScriptBlock>, C:blogthrowerror.ps1: line 18

I am 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.

PS:> $PSItem.Exception.Message

Exception calling "ReadAllText" with "1" argument(s): "The network path was not found."

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 are calling catches an exception and throws a different exception. They will place the original exception inside the new exception.

PS:> $PSItem.Exception.InnerExceptionMessage
The network path was not found.

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.

at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize, Boolean checkHost)
at System.IO.File.InternalReadAllText(String path, Encoding encoding, Boolean checkHost)
at CallSite.Target(Closure , CallSite , Type , String )

You will only get this stack trace when the event is thrown from managed code. I am calling a .Net framework function directly so that is all we can see in this example. Generally when you are looking at a stack trace, you are 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.

try
{
    Do-Something -Path $path
}
catch [System.IO.FileNotFoundException]
{
    Write-Output "Could not find $path"
}
catch [System.IO.IOException]
{
     Write-Output "IO error with the file: $path"
}

The exception type is checked for each catch block until one is found that matches your exception. It is 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 will be invoked even if there are multiple matches.

If we had a System.IO.PathTooLongException then 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 is possible to catch multiple exception types with the same catch statement.

try
{
    Do-Something -Path $path -ErrorAction Stop
}
catch [System.IO.DirectoryNotFoundException],[System.IO.FileNotFoundException]
{
    Write-Output "The path or file was not found: [$path]"
}
catch [System.IO.IOException]
{
    Write-Output "IO error with the file: [$path]"
}

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:

throw "Could not find: $path"

Use an exception accelerator like this:

throw [System.IO.FileNotFoundException] "Could not find: $path"

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.

throw [System.IO.FileNotFoundException]::new()
throw [System.IO.FileNotFoundException]::new("Could not find path: $path")

If you are not yet using PowerShell 5.0, you will have to use the older New-Object approach.

throw (New-Object -TypeName System.IO.FileNotFoundException )
throw (New-Object -TypeName System.IO.FileNotFoundException -ArgumentList "Could not find path: $path")

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:

# with normal message
Write-Error -Message "Could not find path: $path" -Exception ([System.IO.FileNotFoundException]::new()) -ErrorAction Stop

# With message inside new exception
Write-Error -Exception ([System.IO.FileNotFoundException]::new("Could not find path: $path")) -ErrorAction Stop

# Pre PS 5.0
Write-Error -Exception ([System.IO.FileNotFoundException]"Could not find path: $path") -ErrorAction Stop

Write-Error -Message "Could not find path: $path" -Exception ( New-Object -TypeName System.IO.FileNotFoundException ) -ErrorAction Stop

Then we can catch it like this:

catch [System.IO.FileNotFoundException]
{
    Write-Log $PSItem.ToString()
}

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.

  • The big list of .Net exceptions

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 documentation for System.IO.FileNotFoundException, we will see that we can pass in a message and a file path.

[System.IO.FileNotFoundException]::new("Could not find file", $path)

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

catch [System.IO.FileNotFoundException]
{
    Write-Output $PSItem.Exception.FileName
}

You will have to consult the .Net documentation for other constructors and object properties.

Re-throwing an exception

If all you are 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.

catch
{
    Write-Log $PSItem.ToString()
    throw $PSItem
}

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

catch
{
    Write-Log $PSItem.ToString()
    throw
}

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 will hide 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.

catch
{
    throw [System.MissingFieldException]::new('Could not access field',$PSItem.Exception)
}

$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.

Unable to find the specified file.
At line:31 char:9
+         throw [System.IO.FileNotFoundException]::new()
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [], FileNotFoundException
    + FullyQualifiedErrorId : Unable to find the specified file.

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 does not tell them anything useful.

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

$PSCmdlet.ThrowTerminatingError(
    [System.Management.Automation.ErrorRecord]::new(
        ([System.IO.FileNotFoundException]"Could not find $Path"),
        'My.ID',
        [System.Management.Automation.ErrorCategory]::OpenError,
        $MyObject
    )
)

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

Get-Resource : Could not find C:Program Files (x86)Reference
AssembliesMicrosoftFramework.NETPortablev4.6System.IO.xml
At line:6 char:5
+     Get-Resource -Path $Path
+     ~~~~~~~~~~~~
    + CategoryInfo          : OpenError: (:) [Get-Resource], FileNotFoundException
    + FullyQualifiedErrorId : My.ID,Get-Resource

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.

catch
{
    $PSCmdlet.ThrowTerminatingError($PSItem)
}

This will change 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.

function Do-Something { 1/(1-1) }

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

&{ Do-Something; Write-Output "We did it. Send Email" }

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

try
{
    &{ Do-Something; Write-Output "We did it. Send Email" }
}
catch
{
    Write-Output "Notify Admin to fix error and send email"
}

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 will act 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 as a single line using $PSCmdlet.ThrowTerminatingError($PSitem) to deal with all exceptions leaving his functions.

function Do-Something
{
    [cmdletbinding()]
    param()

    process
    {
        try
        {
            ...
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError($PSitem)
        }
    }
}

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.

Trap

I focused on the try/catch aspect of exceptions. But there is 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 will get executed and then the normal code will continue. If multiple exceptions happen, then the trap will get called over and over.

trap
{
    Write-Log $PSItem.ToString()
}

throw [System.Exception]::new('first')
throw [System.Exception]::new('second')
throw [System.Exception]::new('third')

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

Adding proper exception handling to your scripts will not only make them more stable, but it will also make 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 am going to switch over to using Write-Error -Stop to generate errors in my code. I am also going to take Kirk’s advice and make ThrowTerminatingError my goto exception handler for every funciton.

SYNOPSIS

Writes an object to the error stream.

SYNTAX

NoException (Default)

Write-Error [-Message] <String> [-Category <ErrorCategory>] [-ErrorId <String>] [-TargetObject <Object>]
 [-RecommendedAction <String>] [-CategoryActivity <String>] [-CategoryReason <String>]
 [-CategoryTargetName <String>] [-CategoryTargetType <String>] [<CommonParameters>]

WithException

Write-Error -Exception <Exception> [[-Message] <String>] [-Category <ErrorCategory>] [-ErrorId <String>]
 [-TargetObject <Object>] [-RecommendedAction <String>] [-CategoryActivity <String>] [-CategoryReason <String>]
 [-CategoryTargetName <String>] [-CategoryTargetType <String>] [<CommonParameters>]

ErrorRecord

Write-Error -ErrorRecord <ErrorRecord> [-RecommendedAction <String>] [-CategoryActivity <String>]
 [-CategoryReason <String>] [-CategoryTargetName <String>] [-CategoryTargetType <String>] [<CommonParameters>]

DESCRIPTION

The `Write-Error` cmdlet declares a non-terminating error.
By default, errors are sent in the error stream to the host program to be displayed, along with output.

To write a non-terminating error, enter an error message string, an ErrorRecord object, or an Exception object.
Use the other parameters of `Write-Error` to populate the error record.

Non-terminating errors write an error to the error stream, but they do not stop command processing.
If a non-terminating error is declared on one item in a collection of input items, the command continues to process the other items in the collection.

To declare a terminating error, use the `Throw` keyword.
For more information, see about_Throw (../Microsoft.PowerShell.Core/About/about_Throw.md).

EXAMPLES

Example 1: Write an error for RegistryKey object

Get-ChildItem | ForEach-Object {
    if ($_.GetType().ToString() -eq "Microsoft.Win32.RegistryKey")
    {
        Write-Error "Invalid object" -ErrorId B1 -TargetObject $_
    }
    else
    {
        $_
    }
}

This command declares a non-terminating error when the `Get-ChildItem` cmdlet returns a `Microsoft.Win32.RegistryKey` object, such as the objects in the `HKLM:` or `HKCU:` drives of the PowerShell Registry provider.

Example 2: Write an error message to the console

Write-Error "Access denied."

This command declares a non-terminating error and writes an «Access denied» error.
The command uses the Message parameter to specify the message, but omits the optional Message parameter name.

Example 3: Write an error to the console and specify the category

Write-Error -Message "Error: Too many input values." -Category InvalidArgument

This command declares a non-terminating error and specifies an error category.

Example 4: Write an error using an Exception object

$E = [System.Exception]@{Source="Get-ParameterNames.ps1";HelpLink="https://go.microsoft.com/fwlink/?LinkID=113425"}
Write-Error -Exception $E -Message "Files not found. The $Files location does not contain any XML files."

This command uses an Exception object to declare a non-terminating error.

The first command uses a hash table to create the System.Exception object.
It saves the exception object in the `$E` variable.
You can use a hash table to create any object of a type that has a null constructor.

The second command uses the `Write-Error` cmdlet to declare a non-terminating error.
The value of the Exception parameter is the Exception object in the `$E` variable.

PARAMETERS

-Category

Specifies the category of the error.
The default value is NotSpecified .
The acceptable values for this parameter are:

  • NotSpecified
  • OpenError
  • CloseError
  • DeviceError
  • DeadlockDetected
  • InvalidArgument
  • InvalidData
  • InvalidOperation
  • InvalidResult
  • InvalidType
  • MetadataError
  • NotImplemented
  • NotInstalled
  • ObjectNotFound
  • OperationStopped
  • OperationTimeout
  • SyntaxError
  • ParserError
  • PermissionDenied
  • ResourceBusy
  • ResourceExists
  • ResourceUnavailable
  • ReadError
  • WriteError
  • FromStdErr
  • SecurityError
  • ProtocolError
  • ConnectionError
  • AuthenticationError
  • LimitsExceeded
  • QuotaExceeded
  • NotEnabled

For information about the error categories, see ErrorCategory Enumeration (https://go.microsoft.com/fwlink/?LinkId=143600).

Type: System.Management.Automation.ErrorCategory
Parameter Sets: NoException, WithException
Aliases:
Accepted values: NotSpecified, OpenError, CloseError, DeviceError, DeadlockDetected, InvalidArgument, InvalidData, InvalidOperation, InvalidResult, InvalidType, MetadataError, NotImplemented, NotInstalled, ObjectNotFound, OperationStopped, OperationTimeout, SyntaxError, ParserError, PermissionDenied, ResourceBusy, ResourceExists, ResourceUnavailable, ReadError, WriteError, FromStdErr, SecurityError, ProtocolError, ConnectionError, AuthenticationError, LimitsExceeded, QuotaExceeded, NotEnabled

Required: False
Position: Named
Default value: NotSpecified
Accept pipeline input: False
Accept wildcard characters: False

-CategoryActivity

Specifies the action that caused the error.

Type: System.String
Parameter Sets: (All)
Aliases: Activity

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-CategoryReason

Specifies how or why the activity caused the error.

Type: System.String
Parameter Sets: (All)
Aliases: Reason

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-CategoryTargetName

Specifies the name of the object that was being processed when the error occurred.

Type: System.String
Parameter Sets: (All)
Aliases: TargetName

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-CategoryTargetType

Specifies the type of the object that was being processed when the error occurred.

Type: System.String
Parameter Sets: (All)
Aliases: TargetType

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-ErrorId

Specifies an ID string to identify the error.
The string should be unique to the error.

Type: System.String
Parameter Sets: NoException, WithException
Aliases:

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-ErrorRecord

Specifies an error record object that represents the error.
Use the properties of the object to describe the error.

To create an error record object, use the `New-Object` cmdlet or get an error record object from the array in the `$Error` automatic variable.

Type: System.Management.Automation.ErrorRecord
Parameter Sets: ErrorRecord
Aliases:

Required: True
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-Exception

Specifies an exception object that represents the error.
Use the properties of the object to describe the error.

To create an exception object, use a hash table or use the `New-Object` cmdlet.

Type: System.Exception
Parameter Sets: WithException
Aliases:

Required: True
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-Message

Specifies the message text of the error.
If the text includes spaces or special characters, enclose it in quotation marks.
You can also pipe a message string to `Write-Error`.

Type: System.String
Parameter Sets: NoException
Aliases: Msg

Required: True
Position: 0
Default value: None
Accept pipeline input: True (ByValue)
Accept wildcard characters: False
Type: System.String
Parameter Sets: WithException
Aliases: Msg

Required: False
Position: 0
Default value: None
Accept pipeline input: True (ByValue)
Accept wildcard characters: False

-RecommendedAction

Specifies the action that the user should take to resolve or prevent the error.

Type: System.String
Parameter Sets: (All)
Aliases:

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-TargetObject

Specifies the object that was being processed when the error occurred.
Enter the object, a variable that contains the object, or a command that gets the object.

Type: System.Object
Parameter Sets: NoException, WithException
Aliases:

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

CommonParameters

This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see about_CommonParameters.

INPUTS

System.String

You can pipe a string that contains an error message to `Write-Error`.

OUTPUTS

Error object

`Write-Error` writes only to the error stream.
It does not return any objects.

NOTES

`Write-Error` does not change the value of the `$?` automatic variable, therefore it does not signal a terminating error condition.
To signal a terminating error, use the $PSCmdlet.WriteError() (/dotnet/api/system.management.automation.cmdlet.writeerror)method.

about_Automatic_Variables

about_Output_Streams

about_Redirection

Write-Debug

Write-Host

Write-Output

Write-Progress

Write-Verbose

Write-Warning

Содержание

  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 в качестве обработчика исключений для каждой функции.

Источник

Write-Error [-Category {NotSpecified | OpenError | CloseError | DeviceError | DeadlockDetected | InvalidArgument |InvalidData | InvalidOperation | InvalidResult | InvalidType | MetadataError | NotImplemented | NotInstalled |ObjectNotFound | OperationStopped | OperationTimeout | SyntaxError | ParserError | PermissionDenied | ResourceBusy| ResourceExists | ResourceUnavailable | ReadError | WriteError | FromStdErr | SecurityError | ProtocolError |ConnectionError | AuthenticationError | LimitsExceeded | QuotaExceeded | NotEnabled}] [-CategoryActivity <String>][-CategoryReason <String>] [-CategoryTargetName <String>] [-CategoryTargetType <String>] [-ErrorId <String>]-Message* <String> [-RecommendedAction <String>] [-TargetObject <Object>] [<CommonParameters>]
Write-Error [-Category {NotSpecified | OpenError | CloseError | DeviceError | DeadlockDetected | InvalidArgument |InvalidData | InvalidOperation | InvalidResult | InvalidType | MetadataError | NotImplemented | NotInstalled |ObjectNotFound | OperationStopped | OperationTimeout | SyntaxError | ParserError | PermissionDenied | ResourceBusy| ResourceExists | ResourceUnavailable | ReadError | WriteError | FromStdErr | SecurityError | ProtocolError |ConnectionError | AuthenticationError | LimitsExceeded | QuotaExceeded | NotEnabled}] [-CategoryActivity <String>][-CategoryReason <String>] [-CategoryTargetName <String>] [-CategoryTargetType <String>] [-ErrorId <String>]-Exception* <Exception> [-Message* <String>] [-RecommendedAction <String>] [-TargetObject <Object>][<CommonParameters>]
Write-Error [-CategoryActivity <String>] [-CategoryReason <String>] [-CategoryTargetName <String>][-CategoryTargetType <String>] -ErrorRecord* <ErrorRecord> [-RecommendedAction <String>] [<CommonParameters>]

The Write-Error cmdlet declares a non-terminating error. By default, errors are sent in the error stream to the host program to be displayed, along with output.

To write a non-terminating error, enter an error message string, an ErrorRecord object, or an Exception object. Use the other parameters of Write-Error to populate the error record.

Non-terminating errors write an error to the error stream, but they do not stop command processing. If a non-terminating error is declared on one item in a collection of input items, the command continues to process the other items in the collection.

To declare a terminating error, use the Throw keyword. For more information, see about_Throw (http://go.microsoft.com/fwlink/?LinkID=145153).

Parameters

-Category <ErrorCategory>

  • Default value is None
  • Accepts pipeline input False

Specifies the category of the error. The default value is NotSpecified. The acceptable values for this parameter are:

— NotSpecified

— OpenError

— CloseError

— DeviceError

— DeadlockDetected

— InvalidArgument

— InvalidData

— InvalidOperation

— InvalidResult

— InvalidType

— MetadataError

— NotImplemented

— NotInstalled

— ObjectNotFound

— OperationStopped

— OperationTimeout

— SyntaxError

— ParserError

— PermissionDenied

— ResourceBusy

— ResourceExists

— ResourceUnavailable

— ReadError

— WriteError

— FromStdErr

— SecurityError

— ProtocolError

— ConnectionError

— AuthenticationError

— LimitsExceeded

— QuotaExceeded

— NotEnabled

For information about the error categories, see ErrorCategory Enumerationhttp://go.microsoft.com/fwlink/?LinkId=143600 (http://go.microsoft.com/fwlink/?LinkId=143600) in MSDN.

-CategoryActivity <String>

  • Default value is None
  • Accepts pipeline input False

Specifies the action that caused the error.

-CategoryReason <String>

  • Default value is None
  • Accepts pipeline input False

Specifies how or why the activity caused the error.

-CategoryTargetName <String>

  • Default value is None
  • Accepts pipeline input False

Specifies the name of the object that was being processed when the error occurred.

-CategoryTargetType <String>

  • Default value is None
  • Accepts pipeline input False

Specifies the type of the object that was being processed when the error occurred.

-ErrorId <String>

  • Default value is None
  • Accepts pipeline input False

Specifies an ID string to identify the error. The string should be unique to the error.

-ErrorRecord <ErrorRecord>

  • This value is required
  • Default value is None
  • Accepts pipeline input False

Specifies an error record object that represents the error. Use the properties of the object to describe the error.

To create an error record object, use the New-Object cmdlet or get an error record object from the array in the $Error automatic variable.

-Exception <Exception>

  • This value is required
  • Default value is None
  • Accepts pipeline input False

Specifies an exception object that represents the error. Use the properties of the object to describe the error.

To create an exception object, use a hash table or use the New-Object cmdlet.

-Message <String>

  • This value is required
  • Default value is None
  • Accepts pipeline input ByValue

Specifies the message text of the error. If the text includes spaces or special characters, enclose it in quotation marks. You can also pipe a message string to Write-Error .

-RecommendedAction <String>

  • Default value is None
  • Accepts pipeline input False

Specifies the action that the user should take to resolve or prevent the error.

-TargetObject <Object>

  • Default value is None
  • Accepts pipeline input False

Specifies the object that was being processed when the error occurred. Enter the object, a variable that contains the object, or a command that gets the object.

<CommonParameters>

This cmdlet supports the common parameters: Verbose, Debug,ErrorAction, ErrorVariable, WarningAction, WarningVariable,OutBuffer, PipelineVariable, and OutVariable.

Inputs

System.String

You can pipe a string that contains an error message to Write-Error .

Outputs

Error object

Write-Error writes only to the error stream. It does not return any objects.

Examples
  1. Write an error for RegistryKey object:
    PS C:> Get-ChildItem | ForEach-Object { if ($_.GetType().ToString() -eq "Microsoft.Win32.RegistryKey") {Write-Error "Invalid object" -ErrorID B1 -Targetobject $_ } else {$_ } }
    

    This command declares a non-terminating error when the Get-ChildItem cmdlet returns a Microsoft.Win32.RegistryKey object, such as the objects in the HKLM: or HKCU: drives of the Windows PowerShell Registry provider.

  2. Write an error message to the console:
    PS C:> Write-Error "Access denied."
    

    This command declares a non-terminating error and writes an «Access denied» error. The command uses the Message parameter to specify the message, but omits the optional Message parameter name.

  3. Write an error to the console and specify the category:
    PS C:> Write-Error -Message "Error: Too many input values." -Category InvalidArgument
    

    This command declares a non-terminating error and specifies an error category.

  4. Write an error using an Exception object:
    PS C:> $E = [System.Exception]@{$e = [System.Exception]@{Source="Get-ParameterNames.ps1";HelpLink="http://go.micros
    oft.com/fwlink/?LinkID=113425"}HelpLink="http://go.microsoft.com/fwlink/?LinkID=113425"}
    PS C:>  Write-Error $E -Message "Files not found. The $Files location does not contain any XML files."
    

    This command uses an Exception object to declare a non-terminating error.

    The first command uses a hash table to create the System.Exception object. It saves the exception object in the $E variable. You can use a hash table to create any object of a type that has a null constructor.

    The second command uses the Write-Error cmdlet to declare a non-terminating error. The value of the Exception parameter is the Exception object in the $E variable.

Additional Notes
 

Error Handling is a very important concept in every programming language including PowerShell which gives us several possibilities to manage errors in code.

In this article, I will try to cover as many as possible error handling concepts with examples from my practice and one of the important concepts is writing errors in external Error Log text file.

Why Should We Bother Handling Errors In PowerShell

It is no fun to run any code or application full of errors and bugs as the matter a fact it is quite annoying so in order for users to have a pleasant experience handling the errors is one of the essentials in programming.

PowerShell is no exception to that way of thinking and programming ethics although it can take some additional coding and effort but trust me it is always worth it.

I would be more than happy to share my experience with error handling and even more satisfied if I can hear your tips from the field so we can all enrich our knowledge on this very important subject.

Here is a general approach to error handling and logging errors into the external text file. I have an example in the next subheading that further explain each step so everything is much more understandable.

  1. Identify which Commands need Error Handling in your Function, CmdLet or Script.
  2. Set the ErrorAction parameter to value Stop for the command that needs Error Handling. This will stop executing the command when an error occurs and it will show the error message. Basically, we are making non-terminating errors into terminating in order to be able to handle the error in the catch block.
  3. Command that has ErrorAction parameter set to value Stop is wrapped in Try { } block. So when Error occurs handling of the code will jump from the Try block to Catch block.
  4. In Catch { } block that immediately follows after Try {} block, the error is handled by writing to the error log file on the disk.
  5. We use our own Write-ErrorLog CmdLet inside Catch{ } block to write the error in a text file on the disk. (See the explanation and code here)
  6. We have an additional switch error parameter to decide whether we want to write an error in the log file or not. This is totally optional.
  7. Use the Finally { } block if needed.
  8. Test the whole setup by intentionally breaking the code while in the development phase.
  9. Since some of the CmdLets calls are Scheduled we have routine to check external Error Log text file at least once a week and investigate errors that are caught. This step is part of improving the overall quality of the written code.

Example Of PowerShell Error Handling

To show you Error Handling and implement previously defined steps I will use my own Get-CPUInfo CmdLet which is in the Common module of the Efficiency Booster PowerShell Project. Efficiency Booster PowerShell Project is a library of CmdLets that help us IT experts in day to day IT tasks.

In order to follow me along, I highly encourage you to download the zip file with the source code used in this example.

Here is the location of Get-CPUInfo script:
…[My] DocumentsWindowsPowerShellModules3common

Get-CPUInfo CmdLet script location

Let’s use steps defined in the previous subheading to this example.

Step 1. I have identified the command that needs Error Handling in Get-CPUInfo CmdLet and that is a call to Get-CimInstance CmdLet.

Get-CimInstance @params 

Step 2. So I have set up the ErrorAction parameter to the value ‘Stop‘ for Get-CimInstance CmdLet in order to force non-terminating errors into terminating and then to be able to handle such errors.

INFO: I use parameter splatting when running CmdLet. If you want to know more about parameter splating please read this article.

            $params = @{ 'ComputerName'=$computer;
                         'Class'='Win32_Processor';
                         'ErrorAction'='Stop'}
            $CPUInfos = Get-CimInstance @params | 
                            Select-Object   @{label="ServerName"; Expression={$_.SystemName}}, 
                                            @{label="CPU"; Expression={$_.Name}}, 
                                            @{label="CPUid"; Expression={$_.DeviceID}}, 
                                            NumberOfCores, 
                                            AddressWidth

Step 3. Wrap up the call to Get-CimInstance CmdLet into the Try Block in order to be able to handle the error in a catch block that follows.

         try {
            Write-Verbose "Start processing: $computer - $env - $logicalname"
            Write-Verbose "Start Win32_Processor processing..."
            $CPUInfos = $null
            $params = @{ 'ComputerName'=$computer;
                         'Class'='Win32_Processor';
                         'ErrorAction'='Stop'}
            $CPUInfos = Get-CimInstance @params | 
                            Select-Object   @{label="ServerName"; Expression={$_.SystemName}}, 
                                            @{label="CPU"; Expression={$_.Name}}, 
                                            @{label="CPUid"; Expression={$_.DeviceID}}, 
                                            NumberOfCores, 
                                            AddressWidth           
            Write-Verbose "Finish Win32_Processor processing..."                    
            foreach ($CPUInfo in $CPUInfos) {
                Write-Verbose "Start processing CPU: $CPUInfo"
                $properties = @{ 'Environment'=$env;
                                 'Logical name'=$logicalname;
                                 'Server name'=$CPUInfo.ServerName;
            	                 'CPU'=$CPUInfo.CPU;
            	                 'CPU ID'=$CPUInfo.CPUid;
            	                 'Number of CPU cores'=$CPUInfo.NumberOfCores; 
                                 '64 or 32 bits'=$CPUInfo.AddressWidth;
                                 'IP'=$ip;
                                 'Collected'=(Get-Date -UFormat %Y.%m.%d' '%H:%M:%S)}
                $obj = New-Object -TypeName PSObject -Property $properties
                $obj.PSObject.TypeNames.Insert(0,'Report.CPUInfo')
                Write-Output $obj
                Write-Verbose "Finish processing CPU: $CPUInfo"
            }
 Write-Verbose "Finish processing: $computer - $env - $logicalname"                       
        }

Step 4. When the error occurs in the try block it is handled in the Catch Block.
It is important to notice following in the catch block of code:

  • Get-CPUInfo CmdLet switch parameter $errorlog has been used to decide whether to log the errors in an external text file or not. This is completely optional.
  • Certain Error properties are collected using an automatic variable $_ ($PSItem is another name for the same variable). If you want to know more about which properties we collect please read here.
  • Collected data about the error that will be handled has been passed to another CmdLet Write-ErrorLog that will write the data in an external text log file. Please read here about Write-ErrorLog CmdLet.
catch {
            Write-Warning "Computer failed: $computer - $env - $logicalname CPU failed: $CPUInfos"
            Write-Warning "Error message: $_"
            if ( $errorlog ) {
                $errormsg = $_.ToString()
                $exception = $_.Exception
                $stacktrace = $_.ScriptStackTrace
                $failingline = $_.InvocationInfo.Line
                $positionmsg = $_.InvocationInfo.PositionMessage
                $pscommandpath = $_.InvocationInfo.PSCommandPath
                $failinglinenumber = $_.InvocationInfo.ScriptLineNumber
                $scriptname = $_.InvocationInfo.ScriptName
                Write-Verbose "Start writing to Error log."
                Write-ErrorLog -hostname $computer -env $env -logicalname $logicalname -errormsg $errormsg -exception $exception -scriptname $scriptname -failinglinenumber $failinglinenumber -failingline $failingline -pscommandpath $pscommandpath -positionmsg $pscommandpath -stacktrace $stacktrace
                Write-Verbose "Finish writing to Error log."
            }
        }

Step 5. I have already mentioned that Write-ErrorLog CmdLet has been used to write the error data into an external text log file. Read more about this CmdLet here.

Step 6. I did not need Finally { } block for this example.

Step 7. In the development phase, I was intentionally breaking Get-CPUInfo CmdLet to test my error handling code.

Step 8. If Get-CPUInfo CmdLet is part of the scheduled code I would look regularly Error log file and work on correcting all the bugs in the code produced by Get-CPUInfo CmdLet.

Chain Of Events When PowerShell Error Occurs

Let’s talk a little bit about what is happening when an error occurs in PowerShell and which events are triggered one after another.

  • Call to CmdLet is failing and error has just occurred.
  • Since we have ErrorAction parameter set to value Stop our non-terminating error has forced into terminating error so the execution of code stops.
  • The error that is failing in the CmdLet is written in the $Error automatic variable by the PowerShell.
  • We have forced the error to be terminating in order to be able to handle with try catch finally block of error handling.
  • Since our CmdLet call is wrapped in Try block and error is terminating PowerShell can trigger error handling looking for a Catch block.
  • We can have several Catch blocks for one try block. If we have a Catch block that handles the actual error number that block is executed.
  • Otherwise, PowerShell will look Catch block that handles all error numbers.
  • Optionally in the Catch block, we can have code that will write the error in the external text Error log file.
  • We can use the automatic variable $Error or $_ object to read Error data and write them in the external file.
  • If there is no Catch block PowerShell will look for Catch block in parent call if we have nested calls.
  • If there are no further Catch block or no Catch block at all that will handle error then PowerShell looks for Finally block to execute which is used to clean up resources as needed.
  • After executing Finally block error message will be sent to the error stream for further processing.

In further sections of this article, you can read in more detail about the many terms mentioned (ErrorAction, $Error, Try Catch Finally, Terminating, Non-Terminating, etc ) in this bulleted list in order to better understand them.

How To Write PowerShell Errors Into The External Log File

Here I will explain the code of Write-ErrorLog CmdLet that writes error data that occurs in CmdLets and handle the error data into an external text file.

Error data are written in the file named Error_Log.txt in folder PSlogs.

Location and name of external error text log file
Example of error logged in an external text file

Write-ErrorLog CmdLet is part of the Efficiency Booster PowerShell Project and if you want to download the source code of this CmdLet please click here.

Here is the location of Write-ErrorLog script which is part of the Utils module:
…[My] DocumentsWindowsPowerShellModules2utils

Write-ErrorLog CmdLet script location

Write-ErrorLog CmdLet Code

Here is the code of the whole Write-ErrorLog CmdLet.

<#
.SYNOPSIS
Writes errors that occur in powershell scripts into error log file.
.DESCRIPTION
Writes errors that occur in powershell scripts into error log file.
Error log file and error log folder will be created if doesn't exist.
Error log file name is Error_Log.txt and it has been saved into ..DocumentsPSlogs

.PARAMETER hostname
Name of the computer that is failing.

.PARAMETER env
Environment where computer is located. For example: Production, Acceptance, Test, Course etc.

.PARAMETER logicalname
Type of the server that is failing. For example: Application, Web, Integration, FTP, Scan, etc. 

.PARAMETER errormsg
Error message.

.PARAMETER exception
Error number.

.PARAMETER scriptname
Name of the powershell script that is failing.

.PARAMETER failinglinenumber
Line number in the script that is failing.

.PARAMETER failingline
Content of failing line.

.PARAMETER pscommandpath
Path to the powershell command.

.PARAMETER positionmsg
Error message position.

.PARAMETER stacktrace
Stack trace of the error.

.EXAMPLE
Write-ErrorLog -hostname "Server1" -env "PROD" -logicalname "APP1" -errormsg "Error Message" -exception "HResult 0789343" -scriptname "Test.ps1" -failinglinenumber "25" -failingline "Get-Service" -pscommandpath "Command pathc." -positionmsg "Position message" -stacktrace "Stack trace" 

.EXAMPLE
Help Write-ErrorLog -Full

.LINK 
Out-File
#>
Function Write-ErrorLog {
[CmdletBinding()]
param (

    [Parameter(Mandatory=$false,
                HelpMessage="Error from computer.")] 
    [string]$hostname,

    [Parameter(Mandatory=$false,
                HelpMessage="Environment that failed. (Test, Production, Course, Acceptance...)")] 
    [string]$env,

    [Parameter(Mandatory=$false,
                HelpMessage="Type of server that failed. (Application, Web, Integration...)")] 
    [string]$logicalname,
    
    [Parameter(Mandatory=$false,
                HelpMessage="Error message.")] 
    [string]$errormsg,
    
    [Parameter( Mandatory=$false,
                HelpMessage="Exception.")]
    [string]$exception,
    
    [Parameter(Mandatory=$false, 
                HelpMessage="Name of the script that is failing.")]
    [string]$scriptname,
     
    [Parameter(Mandatory=$false,
                HelpMessage="Script fails at line number.")]
    [string]$failinglinenumber,

    [Parameter(Mandatory=$false,
                HelpMessage="Failing line looks like.")]
    [string]$failingline,
    
    [Parameter(Mandatory=$false,
                HelpMessage="Powershell command path.")]
    [string]$pscommandpath,    

    [Parameter(Mandatory=$false,
                HelpMessage="Position message.")]
    [string]$positionmsg, 

    [Parameter(Mandatory=$false,
                HelpMessage="Stack trace.")]
    [string]$stacktrace
)
BEGIN { 
        
        $errorlogfile = "$homeDocumentsPSlogsError_Log.txt"
        $errorlogfolder = "$homeDocumentsPSlogs"
        
        if  ( !( Test-Path -Path $errorlogfolder -PathType "Container" ) ) {
            
            Write-Verbose "Create error log folder in: $errorlogfolder"
            New-Item -Path $errorlogfolder -ItemType "Container" -ErrorAction Stop
        
            if ( !( Test-Path -Path $errorlogfile -PathType "Leaf" ) ) {
                Write-Verbose "Create error log file in folder $errorlogfolder with name Error_Log.txt"
                New-Item -Path $errorlogfile -ItemType "File" -ErrorAction Stop
            }
        }
}
PROCESS {  

            Write-Verbose "Start writing to Error log file. $errorlogfile"
            $timestamp = Get-Date 
            #IMPORTANT: Read just first value from collection not the whole collection.
            "   " | Out-File $errorlogfile -Append
            "************************************************************************************************************" | Out-File $errorlogfile -Append
            "Error happend at time: $timestamp on a computer: $hostname - $env - $logicalname" | Out-File $errorlogfile -Append
            "Error message: $errormsg" | Out-File $errorlogfile -Append
            "Error exception: $exception" | Out-File $errorlogfile -Append
            "Failing script: $scriptname" | Out-File $errorlogfile -Append
            "Failing at line number: $failinglinenumber" | Out-File $errorlogfile -Append
            "Failing at line: $failingline" | Out-File $errorlogfile -Append
            "Powershell command path: $pscommandpath" | Out-File $errorlogfile -Append
            "Position message: $positionmsg" | Out-File $errorlogfile -Append
            "Stack trace: $stacktrace" | Out-File $errorlogfile -Append
            "------------------------------------------------------------------------------------------------------------" | Out-File $errorlogfile -Append                   
            
            Write-Verbose "Finish writing to Error log file. $errorlogfile"
}        
END { 

}
}
#region Execution examples
#Write-ErrorLog -hostname "Server1" -env "PROD" -logicalname "APP1" -errormsg "Error Message" -exception "HResult 0789343" -scriptname "Test.ps1" -failinglinenumber "25" -failingline "Get-Service" -pscommandpath "Command pathc." -positionmsg "Position message" -stacktrace "Stack trace" -Verbose
#endregion

Write-ErrorLog CmdLet Explained

Let’s make our hand’s a little bit “dirty” and dive into PowerShell code.

In the BEGIN block we:

  • Check if folder PSlogs exist in the (My) Documents folder of the current user.
    • If the PSlogs folder doesn’t exist then create the folder.
  • Check if file Error_Log.txt exists in the folder PSlogs.
    • If Error_Log.txt doesn’t exist then create the file.
  • Now we can move on to PROCESS block code.
BEGIN { 
        
        $errorlogfile = "$homeDocumentsPSlogsError_Log.txt"
        $errorlogfolder = "$homeDocumentsPSlogs"
        
        if  ( !( Test-Path -Path $errorlogfolder -PathType "Container" ) ) {
            
            Write-Verbose "Create error log folder in: $errorlogfolder"
            New-Item -Path $errorlogfolder -ItemType "Container" -ErrorAction Stop
        
            if ( !( Test-Path -Path $errorlogfile -PathType "Leaf" ) ) {
                Write-Verbose "Create error log file in folder $errorlogfolder with name Error_Log.txt"
                New-Item -Path $errorlogfile -ItemType "File" -ErrorAction Stop
            }
        }
}

In the PROCESS block:

  • We format the line of text that we want to write into the log file.
  • Then we pipe formatted text to Out-File CmdLet with the Append parameter to write that line of text in the file.
  • We repeat the process of formatting the line of text and appending of that line to the Error log file.
PROCESS {  

            Write-Verbose "Start writing to Error log file. $errorlogfile"
            $timestamp = Get-Date 
            #IMPORTANT: Read just first value from collection not the whole collection.
            "   " | Out-File $errorlogfile -Append
            "************************************************************************************************************" | Out-File $errorlogfile -Append
            "Error happend at time: $timestamp on a computer: $hostname - $env - $logicalname" | Out-File $errorlogfile -Append
            "Error message: $errormsg" | Out-File $errorlogfile -Append
            "Error exception: $exception" | Out-File $errorlogfile -Append
            "Failing script: $scriptname" | Out-File $errorlogfile -Append
            "Failing at line number: $failinglinenumber" | Out-File $errorlogfile -Append
            "Failing at line: $failingline" | Out-File $errorlogfile -Append
            "Powershell command path: $pscommandpath" | Out-File $errorlogfile -Append
            "Position message: $positionmsg" | Out-File $errorlogfile -Append
            "Stack trace: $stacktrace" | Out-File $errorlogfile -Append
            "------------------------------------------------------------------------------------------------------------" | Out-File $errorlogfile -Append                   
            
            Write-Verbose "Finish writing to Error log file. $errorlogfile"
} 

$PSItem or $_

$PSItem contains the current object in the pipeline object and we use it to read Error properties in this case.

Just a quick explanation of each Error property from the $_ ($PSItem) automatic variable that we collect and write in Logfile:

  • $_.ToString() – This is Error Message.
  • $_.Exception – This is Error Exception.
  • $_.InvocationInfo.ScriptName – This the PowerShell script name where Error occurred.
  • $_.InvocationInfo.ScriptLineNumber – This is line number within the PowerShell script where Error occurred.
  • $_.InvocationInfo.Line – This is the line of code within PowerShell script where Error occurred.
  • $_.InvocationInfo.PSCommandPath – This is the path to the PowerShell script file on the disk.
  • $_.InvocationInfo.PositionMessage – This is a formatted message indicating where the CmdLet appeared in the line.
  • $_.ScriptStackTrace – This is the Trace of the Stack.

As you can see on the screenshot below we collect really useful information about the Error that we handle in the Logfile. The pieces of information are presented in very neatly fashion so we can immediately see:

  • which error occurred,
  • what were the message and exception,
  • where the error occurred (script name, script location, line number and line of the code in the script)
  • even the call stack is shown if needed.

Here are the final result and an example of one formatted error logged in Error_Log.txt file.

************************************************************************************************************
Error happend at time: 09/11/2019 18:20:41 on a computer: APP01 -  - 
Error message: The WinRM client cannot process the request. If the authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then HTTPS transport must be used or the destination machine must be added to the TrustedHosts configuration setting. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be authenticated. You can get more information about that by running the following command: winrm help config.
Error exception: Microsoft.Management.Infrastructure.CimException: The WinRM client cannot process the request. If the authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then HTTPS transport must be used or the destination machine must be added to the TrustedHosts configuration setting. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be authenticated. You can get more information about that by running the following command: winrm help config.
   at Microsoft.Management.Infrastructure.Internal.Operations.CimAsyncObserverProxyBase`1.ProcessNativeCallback(OperationCallbackProcessingContext callbackProcessingContext, T currentItem, Boolean moreResults, MiResult operationResult, String errorMessage, InstanceHandle errorDetailsHandle)
Failing script: C:UsersdekibDocumentsWindowsPowerShellModules3commonGetCPUInfo.ps1
Failing at line number: 214
Failing at line:             $CPUInfos = Get-CimInstance @params | 

Powershell command path: C:UsersdekibDocumentsWindowsPowerShellModules3commonGetCPUInfo.ps1
Position message: C:UsersdekibDocumentsWindowsPowerShellModules3commonGetCPUInfo.ps1
Stack trace: at Get-CPUInfo, C:UsersdekibDocumentsWindowsPowerShellModules3commonGetCPUInfo.ps1: line 214
at , : line 1
------------------------------------------------------------------------------------------------------------

TIP: If your scripts are scheduled in Task Manager the best practice is to have a routine of regularly checking the Error Log file and investigate the errors that occurred since the last check. I am doing this once a week.

Errors In PowerShell

There are two types of Errors in PowerShell:

  • Terminating
  • Non-Terminating

Terminating Errors

Here are the important features of Terminating errors:

  • terminates execution of command or script
  • triggers Catch block and can be error handled by the Catch block.

Examples are syntax errors, non-existent CmdLets, or other fatal errors

Non-Terminating Errors

Here are important features of Non-Terminating Error:

  • A non-fatal error.
  • Allows execution to continue despite the failure that just occurred.
  • It doesn’t trigger the Catch block and cannot be Error Handled in the Catch block by default.

Examples are permission problems, file not found, etc.

How To Force Non-Terminating Errors Into Terminating

Use the ErrorAction parameter with value Stop to force non-terminating error into terminating as in the following example. The reason why we want to make non-termination error into terminating one is to be able to catch the error when occurs.

$AuthorizedUser = Get-Content .DocumentsWindowsPowerShellProfile.ps1 -ErrorAction Stop 

Basically, the workflow is as follows.

  • When an error occurs,
  • a non-terminating error has changed into terminating one since we have Stop value on ErrrorAction parameter,
  • then since terminating error has occurred try block will send the error handling to catch block where error can be processed and
  • optionally written to the external log,
  • optionally error handling can continue in the final block.

NOTE: ErrorAction parameter overrides temporarily ErrorActionPreference variable while the call to CmdLet has been processed.

How To Treat All Errors As Terminating

We use the ErrorActionPreference variable to treat all errors as terminating by setting to the value Stop in the:

  • script
  • or session

Write the following line of code at the begging of the script to treat all errors as terminating.

$ErrorActionPreference = Stop

Type in Windows PowerShell Console the same command to setup terminating errors for the session.

ErrorAction Common Parameter

ErrorAction parameter belongs to the set of common parameters that we can use with any CmdLet. If we set CmdLetBinding on Advanced Functions than PowerShell automatically makes common parameters available for that command.

This is a parameter that I always use with CmdLets that need error handling since the ErrorAction Parameter determines how the CmdLet responds to a non-terminating error. It has several values that we will talk about in a minute but the value that I like to use is Stop and it is used to make non-terminating errors into terminating errors as written in previous sections.

The ErrorAction parameter overrides the value of the $ErrorActionPreference variable when applied to the specific command.

Here are valid values:

  • Continue (Default)
    • This is the default setting. Display error then continues execution.
  • Stop
    • Display error, and stop the execution.
  • Inquire
    • Displays error message and the user is asked to continue with execution.
  • SilentlyContinue
    • No error message is displayed and execution is continued. However, the error message is added to the $Error automatic variable.
  • Ignore
    • The same as SilentlyContinue, No error message is displayed and execution is continued. However, Ignore does not add an error message to the $Error automatic variable.
  • Suspend
    • This one is for workflows. A workflow job is suspended to investigate what happened, then the workflow can be resumed.

$ErrorActionPreference Preference Variable Explained

$ErrorActionPreference preference variable determines how Windows PowerShell responds to a non-terminating error (an error that does not stop the cmdlet processing) in a script, cmdlet or at the command line

If we want to override the value of the ErrorActionPreference preference variable for the specific command we use the ErrorAction common parameter as explained here.

The valid values for $ErrorActionPreference preference variable are:

  • Continue (Default)
    • This is the default setting. Display error then continues execution.
  • Stop
    • Display error message, and stop the execution.
  • Inquire
    • Displays error message and the user is asked to continue with execution.
  • SilentlyContinue
    • No error message is displayed and execution is continued. However, the error message is added to the $Error automatic variable.
  • Suspend
    • This one is for workflows. A workflow job is suspended to investigate what happened, then the workflow can be resumed.

Error Handling With Try/Catch/Finally Blocks

Try, Catch, and Finally, blocks are used to handle terminating errors in the scripts, functions, and CmdLets. A non-terminating error does not trigger Try block and Windows PowerShell will not look for Catch block to handle the error. So we need to force a non-terminating error to become terminating error using ErrorAction parameter with value Stop whenever we call some CmdLet or Advanced function.

Try block is used as part of the code that PowerShell will monitor for errors. The workflow in the Try block is as follows:

  • Try block is the section of code that will be monitored for errors by Windows PowerShell.
  • When the error occurs within Try block the error is saved to $Error automatic variable first.
  • Windows PowerShell searches for a Catch block to handle the error if the error is terminating. If the Catch block has not been found in current scope Windows PowerShell will search for catch block in parent scopes for nested calls.
  • Then the Finally block is run if exists.
  • If there is no Catch block than the error is not handled and the error is written to the error stream.

One Try block can have several Catch Blocks that will handle different error types.

Catch block usually handles the error.

The Finally block is optional and can be only one. Usually, it is used to clean up and free the resources.

The syntax for Try, Catch, and Finally block:

try { } 
catch [],[]
   { } 
catch { } 
finally { }

Getting Error Information With $Error Automatic Variable

$Error automatic variable is an array of error objects (both terminating and the non-terminating) that occurred in the current PowerShell session. The most recent error that occurred is written with index 0 in the array as $Error[0]. If we have just opened the Windows PowerShell session the $Error variable is an empty array and ready to be used.

Check the number of Errors in $Error variable with the following code:

$Error.Count

To prevent the error from being written in $Error automatic variable set ErrorAction parameter to value Ignore.

$Error variable is a rich object that has many useful properties worth reading and helpful for further understanding of the error that just occurred.

Let’s see some useful properties and in section Write-ErrorLog CmdLet Explained I have explained to you some useful examples of properties that are interesting to be written in an external log file.

$error[0] | Get-Member
Methods and Properties of the Error object
$Error.CategoryInfo | Get-Member
Methods and Properties of CategoryInfo object
$Error[0].Exception
The exception of the error that occurred.
$Error.InvocationInfo | Get-Member
Methods and Properties of InvocationInfo object

Write-Error CmdLet

Write-Error CmdLet writes an object to the error stream.

Please read this article from Microsoft PowerShell documentation regarding this CmdLet.

Handling Errors from non-PowerShell processes

We can run applications from PowerShell script like for example, PsExec.exe or robocopy.exe and they are external processes for PowerShell. Since it is an external process, errors from it will not be caught by our try/catch blocks in PowerShell script. So how we will know whether our external process was successful or not.

Well, we can use the $LastExitCode PowerShell automatic variable.

PowerShell will write the exit code to the $LastExitCode automatic variable when the external process exits. Check the external tool’s documentation for exit code values but usually, 0 means success and 1 or greater values mean a failure.

Useful PowerShell Error Handling Articles

Here are some useful articles and resources:

  • Windows PowerShell Error Reporting
  • About Try Catch Finally
  • About CommonParameters
  • About Automatic Variables
  • About Preference Variables
  • Write-Error
  • About Throw
  • About Break
  • About Continue

Понравилась статья? Поделить с друзьями:
  • Powershell throw error
  • Powershell suppress error output
  • Powershell on error resume next
  • Powershell logging error
  • Powershell last error