The existing help topics that touch on error handling ([about_Throw](https://doc…s.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_throw),
[about_CommonParameters](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_CommonParameters),
[about_Preference_Variables](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Preference_Variables), [about_Trap](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Trap), [about_Try_Catch_Finally](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Try_Catch_Finally)
):
* have sown **longstanding confusion due to conflating the two distinct types of terminating errors**:
* _statement_-terminating errors, as reported by cmdlets in certain non-recoverable situations (via the `.ThrowTerminatingError()` method) and by expressions in which a .NET exception / a PS runtime error occurs.
* _script_-terminating errors (fatal errors), as either triggered by `Throw` or by escalating one of the other error types via error-action preference-variable / parameter value `Stop`.
* have always contained the **incorrect claim that the error-action preference / parameter values only pertain to _non-terminating errors_** - which is true for the `-ErrorAction` _parameter_, but not the `$ErrorActionPreference` _preference variable_ - see https://github.com/PowerShell/PowerShell/issues/4292
* Arguably, `-ErrorAction` too should apply to _all_ error types (as appropriate), though that would certainly be a breaking change.
It's time to:
* correct the existing topics
* provide a conceptual `about_Error_Handling` topic, as @GoateePFE suggests - see #1424
**Below is my understanding of how PowerShell's error handling actually works as of Windows PowerShell v5.1 / PowerShell Core v6.0.0-beta.9**, which can serve as a starting point for `about_Error_Handling`, along with links to issues to related problems.
Do tell me if and where I got things wrong.
The sheer complexity of PowerShell's current error handling is problematic, though I do realize that making changes in this area is a serious backward-compatibility concern.
---
* **Types of errors:**
* **True PowerShell errors:**
* **_Non-terminating_ errors** are issued by cmdlets or functions to **signal failure with respect to specific inputs while continuing to process further (pipeline) input by default**.
* This allows potentially long-running commands to run to completion, despite _partial_ failure. The errors reported can then be inspected via the error records collected in automatic variable `$Error`, allowing reprocessing of only the failed objects later (see below).
* Note, however, that is possible for _all_ input objects to cause nonterminating errors, amounting to complete failure overall.
* **Most cmdlet errors are non-terminating errors**; e.g.: `'/NoSuch', '/' | Get-Item` reports an error for non-existent path `/NoSuch`, but continues processing with valid path `/`.
* **_Terminating_ errors:**
* **Important:** In the context of _remoting_ - whether explicit (e.g., via `Invoke-Command`) or implicit (e.g., via modules using implicit remoting) - terminating errors (of either kind) are converted to non-terminating ones.
* **Types** of terminating errors:
* **_Script_-terminating errors**:
* By default, they **abort the entire enclosing _script_ as well as any calling scripts**. On the command line, a single statement or a list of statements submitted together can be thought of as an implicit script.
* Only a `try` / `catch` or `Trap` statement can prevent hat.
* The **only way to directly trigger** such an error is with the **`Throw`** keyword.
* Note: Script-terminating errors are in effect _fatal_ errors from the perspective of your code, if unhandled, - see https://github.com/PowerShell/PowerShell/issues/14819#issuecomment-786121832 for a technical discussion.
* **_Statement_-terminating errors** are the (statement-level) counterparts to non-terminating errors: they **terminate the enclosing _statement_ (pipeline or expression)** and are issued to signal that **a statement encountered a problem that doesn't allow it to meaningfully start or continue processing**.
* **Important**:
* Statement-terminating errors truly only terminate the statement at hand. **By default, the enclosing script continues to run.**
* The **statement that is terminated is only the _immediately enclosing_ statement**; therefore, for instance, a statement-terminating error that occurs in the (`-Process`) script block of a `ForEach-Object` call does NOT terminate the pipeline as a whole - see below for an example.
* **Situations** in which statement-terminating errors occur:
* PowerShell's fundamental inability to even invoke a command generates a statement-terminating runtime error, namely:
* A **non-existent command**:
`nosuch -l # no such command exists`
* A cmdlet or function call with **invalid syntax**, such as incorrect parameter names or missing or mistyped parameter values:
`Get-Item -Foo # -Foo is an invalid parameter name`
`Select-Object -First notanumber # -First expects an [int]`
* Exception: If the mistyped value is bound via the _pipeline_, the resulting error is _non-terminating_ see [@alx9r's example](https://github.com/PowerShell/PowerShell-Docs/issues/1583#issuecomment-369643007).
* Attempting to **call another script that fails to parse** (that is syntactically invalid).
* While rare, cmdlet invocations that succeed syntactically can themselves generate statement-terminating errors, such as based on the contents of parameter values (not caught by parameter validation) or the state of the environment.
* Strict-mode violations (such as trying to access a nonexistent variable when [`Set-StrictMode`](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/set-strictmode)` -Version 1` or higher is in effect).
* Expressions can trigger statement-terminating errors too, namely:
* via PowerShell **expression runtime errors**; e.g.:
`1 / 0`
* via **exceptions thrown by .NET method calls**; e.g.:
`[int]::Parse('foo')`
* via statement-terminating errors that occurs inside a `(...)` subexpression (but not inside `$(...)` or `@(...)`, which are independent statement contexts); e.g.: `(Get-Item -Foo) + 'hi'` is a statement composed of a single expression that is terminated as a whole; by contrast, `$(Get-Item -Foo) + 'hi'` is a statement composed of an expression and an embedded statement, so only the `$(...)` part is terminated - `+ 'hi'` is still executed.
* As an aside: Better names for operators `$()` and `@()` would therefore have been [array] sub*statement* operators rather than [array] sub*expression* operators.
* **Failures signaled by _external utilities_** (command-line / console applications such as `findstr.exe` on Windows and terminal-based utilities such as `awk` on Unix) via their **exit codes** are **non-terminating** - in fact, external utilities reporting nonzero exit codes are **not errors in a PowerShell sense**.
* Note that the only standardized information that a utility's exit code carries is whether it succeeded overall (exit code `0`) or failed overall (_nonzero_ exit code), with no further distinction.
* The exit code of the most recently executed external utility is stored in the automatic `$LASTEXITCODE` variable - see below.
* Stderr output from external utilities is NOT considered error output - see below.
* **Default error _actions_, _logging_ and _success indicators_**:
* Default **actions** when errors occur:
* a **_non_-terminating** error:
* does not terminate the current statement (pipeline): processing continues with further input objects, if present.
* is output to PowerShell's error stream (stream number 2), which in the console prints in red with detailed context information; preference variable `$ErrorView` can be used to change to a different format.
* Note: The current default format is multi-line and "noisy", and there's a suggestion to change that: see https://github.com/PowerShell/PowerShell/issues/3647
* is logged in the automatic `$Error` collection (see below)
* a **_statement_-terminating** error:
* terminates the current statement, but the script continues processing.
* Important: The statement that is terminated is only the _immediately enclosing statement_; therefore, for instance, a statement-terminating error that occurs in the (`-Process`) script block of a `ForEach-Object` call does NOT terminate the pipeline as a whole - it only terminates the script-block invocation at hand.
* Example: `1, 2 | ForEach-Object -Process { Get-Item -Foo } -End { 'pipeline ran to completion' }`
* The `Get-Item -Foo` calls cause statement-terminating errors, but the statements they terminate are the instances of the `-Process` script blocks, so pipeline processing continues, and `pipeline ran to completion` prints.
* is output to PowerShell's error stream.
* is logged in the automatic `$Error` collection.
* a **_script_-terminating** error:
* terminates the entire script
* is output to PowerShell's error stream
* is logged in the automatic `$Error` collection (which only matters if the session isn't terminated as a whole)
* an **external utility signaling failure** by exit code / producing stderr output:
* does not terminate the current statement (pipeline).
* While stderr output is sent to PowerShell's error stream, it is not formatted like an error in the console, and by itself does not imply that an error occurred.
* Stderr output is NOT logged in the automatic `$Error` collection
* Default **success indicators**:
* Automatic Boolean variable `$?` reflects whether the most recent statement, including calls to external utilities, experienced any error:
* `$True`, if none, and `$False`, if _at least one_ error occurred (because, in the case of non-terminating errors, multiple errors may have occurred).
* In short: `$?` returning `$False` tells you only that _some_ error occurred:
* You cannot infer how many inputs experienced failure, which can range from 1 to all of them.
* You cannot infer (from $? alone) whether a _terminating_ error occurred or not.
* Caveat: Because `$?` is set by _every_ statement, its value is only meaningful immediately after the statement of interest.
* `$?` is NOT set / set as expected in the following cases:
* If a (by definition _terminating_) error was handled with a `Try/Catch` or `Trap` statement - unless the `Catch` handler is _empty_. (_Non-terminating_ errors cannot be caught that way.)
* With a `Catch` or `Finally` block present, the success of whatever statement appears last inside of them, if any, determines what `$?` is set to, with a (non-empty) `Finally` block taking precedence.
* If the error is a non-terminating error and the command originating the error is enclosed in `(...)` - which turns the command into an _expression_ - it is then the expression's _own_ success that is reflected in `$?`:
`(Get-Item /NoSuch); $?` yields `$True`, because the expression itself - whose sole purpose was to wrap the command - technically succeeded.
`(Get-Item -Foo); $?` yields `$False`, because the *statement-terminating* error terminated the expression _as a whole_.
* See https://github.com/PowerShell/PowerShell/issues/3359
* When calling external utilities, automatic variable `$LASTEXITCODE` _complements_ `$?` by containing the specific exit code set by the most recently executed external utility.
* `$?` is set to `$True` if the utility's exit code was `0`, and to `$False` otherwise.
* Note that while `$?` is only meaningful immediately after the statement of interest, `$LASTEXITCODE` remains relevant until the next external-utility call is made; either way, however, it is preferable to save the value in another variable if it needs to be inspected later.
* Note: Currently, PowerShell lacks operators that allow _chaining_ of commands based on whether they indicate success or not, such as the `&&` and `||` control operators in Bash:
* A feature suggestion can be found here: https://github.com/PowerShell/PowerShell/issues/3241
* Default **logging**:
* Errors are **logged in memory** (for the duration of the session) in the **global automatic `$Error` variable in reverse chronological order** (most recent error first; i.e., `$Error[0]` refers to the most recent error):
* `$Error` is a collection of type `[System.Collections.ArrayList]` and the errors are stored as error records of type `[System.Management.Automation.ErrorRecord]` that wrap the underlying .NET exception, which all errors ultimately are (instances of `[System.Exception]` or a derived type).
* To inspect `$Error` items, pipe them to `Format-List -Force` (direct output would result in the same format as when the error originally occurred); e.g.: `$Error[0] | Format-List -Force`
* You can clear the collection anytime with `$Error.Clear()`
* Errors are NOT logged in the following circumstances:
* When _non-terminating_ errors occur in a cmdlet / advanced function to which `-ErrorAction Ignore` was passed (see below).
* What external utilities print to stderr is not considered error output, therefore it is NOT logged in `$Error` (see below).
* **Modifying the default error actions and logging behavior:**
* **PowerShell commands and expressions**:
* Via **common parameter `-ErrorAction`** or **preference variable `$ErrorActionPreference`**:
* Per the documentation as of PowerShell Core v6.0.0-beta.5, specifying an error action should only affect non-terminating errors.
* While this is true with respect to the `-ErrorAction` common _parameter_, it is incorrect with respect to the `$ErrorActionPreference` _preference variable_, which affects terminating errors as well (see https://github.com/PowerShell/PowerShell/issues/4292).
* _Caveat_: a `try` / `catch` statement at a higher level can apparently _preempt_ `$ErrorActionPreference` - see https://github.com/PowerShell/PowerShell/issues/5693
* **Arguably, the `-ErrorAction` common parameter should affect terminating errors too, but changing that would be a major breaking change.**
* **In the context of _remoting_, terminating errors (of either type) are converted to non-terminating ones.**
* Caveat: When invoking functions from a module that uses _implicit remoting_, neither `-ErrorAction` nor `$ErrorActionPreference` work as expected:
* `-ErrorAction` is applied _remotely_, which means that whatever errors occur is invariably converted to a non-terminating error _locally_.
* The caller's `$ErrorActionPreference` value (unless it happens to be the _global_ variable value) is _not_ seen by the implicitly remoting module (which is typically an auto-generated script module), because modules have their own namespaces that only inherit from the global scope. The current inability of a script module to opt into the caller's preferences (without cumbersome workarounds) is discussed here: https://github.com/PowerShell/PowerShell/issues/4568
* The supported **action values** are:
* **`Continue`** ... _non-terminating errors only_: output errors and log them, but continue processing the current statement (non-terminating errors).
* **`Stop`** ... _non-terminating errors_ and _statement-terminating errors via `$ErrorActionPreference` only_: escalate the error to a script-terminating one.
* **`SilentlyContinue`** ... like `Continue`, but silence error output, while still logging errors. Via `$ErrorActionPreference` only, also applies to both types of terminating errors (processing continues).
* **`Ignore`** (`-ErrorAction` _parameter_ only) ... _non-terminating errors only_: like `SilentlyContinue`, but without logging errors in `$Error`. Due to `Ignore` not being supported via `$ErrorActionPreference`, n/a to terminating errors.
* Due to a bug, you can currently set `$ErrorActionPreference` to `Ignore`, even though you shouldn't be able to: https://github.com/PowerShell/PowerShell/issues/4348
* Due to another bug, passing `-ErrorAction Ignore` to an _advanced function_ currently causes a spurious statement-terminating error - see https://github.com/PowerShell/PowerShell/issues/1759
* **`Inquire`** ... prompt the user for the desired action, including the option to temporarily enter a nested session for debugging. Via `$ErrorActionPreference` only, also applies to both types of terminating errors.
* **`Suspend`** (workflows only) ... automatically suspends a workflow job to allow for investigation.
* **Ad-hoc**, when calling **cmdlet/advanced functions**, you can pass **common parameter `-ErrorAction`** to modify the behavior of non-terminating errors (only!).
* As stated, the `-ErrorAction` parameter has no impact on _terminating_ errors - in line with documented, but debatable behavior.
* **Scope-wide** (including descendant scopes, unless overridden there), you can set preference variable `$ErrorActionPreference`, which sets the scope's default behavior for all occurrences of non-terminating behaviors, and - against documented behavior - also for terminating errors.
* Caveat: Advanced functions defined in script _modules_ do _not_ see the caller's preference variables by default, and making them do so is currently quite cumbersome - see https://github.com/PowerShell/PowerShell/issues/4568
* As stated, cmdlets invoked via an implicit-remoting modules also do not see the caller's preference variables.
* The `-ErrorAction` parameter takes precedence over the `$ErrorActionPreference` variable.
* Non-terminating errors:
* may **alternatively be silenced with `2>$null`**, analogous to silencing stderr output from external utilities (see below). _Terminating_ errors _ignore_ `2>$null`
* Note that error output suppressed with `2>$null` or redirected to file with `2>$file` is nonetheless still recorded in the `$Error` collection, by design - see https://github.com/PowerShell/PowerShell/issues/4572
* may additionally be **collected _command-scoped_ in a user variable**, via **common parameter` -ErrorVariable`**, _unless_ `-ErrorAction Ignore` is also used. Note that use of `-ErrorVariable` does not affect the error _output_ behavior; e.g., with error action `Continue` in effect, non-terminating errors still print to the console, whether or not you use `-ErrorAction`.
* **Catching terminating errors** with **`Try` / `Catch`** or **`Trap`** statements:
* Important: Only terminating errors (of either type) can be caught this way, and `Try` / `Catch` and `Trap` are effective irrespective of the current `$ErrorActionPreference` value (and any `-ErrorAction` common parameter, which fundamentally only applies to non-terminating errors).
* Inside a `Catch` block:
* Automatic variable `$_` contains the `[System.Management.Automation.ErrorRecord]` instance representing the terminating error at hand.
* You can use just `Throw` (without an argument) to re-throw the error at hand.
* You may define multiple `Catch` blocks by applying optional filters based on the underlying .NET exception types - see `Get-Help about_Try_Catch_Finally`.
* Error logging: Errors caught this way are still logged in the `$Error` collection, but there is debate around that - see https://github.com/PowerShell/PowerShell/issues/3768
* **External utilities:**
* Given that an external utility signaling failure by returning a nonzero exit code is not a true PowerShell error:
* A failure signaled this way **cannot be escalated to a script-terminating error** via the `$ErrorActionPreference` variable.
* **Error messages (printed to stderr) are formatted like regular output and are NOT logged in the automatic `$Error` collection variable, nor do they affect how automatic variable `$?` is set** (that is solely based the utility's exit code).
* The rationale is that external utilities, unlike PowerShell commands, have only _two_ output streams at their disposal: _stdout_ for success (data) output, and _stderr_ for everything else, and while error messages are printed to stderr, other non-data output (warnings, status information) is sent there too, so you cannot make the assumption that all stderr output represents errors.
* However, you can **capture stderr output** for later inspection:
* Using output redirection `2>` allows you to suppress stderr output or send it to a file:
`whoami invalidarg 2>$null` suppresses stderr output;
`whoami invalidarg 2>err.txt ` captures stderr output in file `err.txt`
* Caveat: Due to a _bug_ still present as of PowerShell Core v6.0.0-beta.5, when you use any `2>` redirection (redirection of PowerShell's error stream):
* Stderr lines are unexpectedly collected in the `$Error` collection, despite the redirection, and even though stderr output is by default NOT collected in `$Error`.
* This can accidentally trigger a script-terminating error if `$ErrorActionPreference = 'Stop'` happens to be in effect.
* See https://github.com/PowerShell/PowerShell/issues/4002
* There is currently no convenient mechanism for collections stderr lines in a _variable_, but, as a workaround, you can use redirection `2>&1` to merge stderr output into PowerShell's success output stream (interleaved with stdout output), which allows you to filter out the stderr messages later by type, because PowerShell wraps the stderr lines in `[System.Management.Automation.ErrorRecord]` instances; e.g.:
* `$stdout, $stderr = ($(whoami; whoami invalidarg) 2>&1).Where({ $_ -isnot [System.Management.Automation.ErrorRecord] }, 'Split')`; you can then convert the `$stderr` array to _strings_ with `$stderr = $stderr | % ToString`.
* https://github.com/PowerShell/PowerShell/issues/4332 proposes adding a feature for directly collecting stderr lines in a variable, using a syntax such as `2>&var`
* **Reporting custom errors** in functions and scripts:
* **Guidelines** for when to use non-terminating errors vs. statement-terminating errors in the context of creating cmdlets are in MSDN topic [Cmdlet Error Reporting](https://msdn.microsoft.com/en-us/library/ms714412(v=vs.85).aspx); a pragmatic summary is in [this Stack Overflow answer]( http://stackoverflow.com/a/39949027/45375) of mine.
* **Non-terminating errors:**
* The `Write-Error` cmdlet is designed to generate non-terminating errors; e.g.:
`Write-Error "Not an existing file: $_"` wraps the message in a `[System.Management.Automation.ErrorRecord]` instance and outputs it to the error stream.
* However, `Write-Error` currently neglects to set `$?` to `$False` in the caller's scope, the way a compiled cmdlet does when reporting a non-terminating error - see https://github.com/PowerShell/PowerShell/issues/3629
* Thus, after calling a script or function, `$?` may mistakenly contain `$True` even when `Write-Error` calls were made.
* The workaround - available in advanced functions only (those whose `param()` block is decorated with the `[CmdletBinding()]` attribute) - is to use the `$PSCmdlet.WriteError()` method - see example below.
* **Terminating errors**:
* **_Statement_-terminating errors**:
* PowerShell has **NO keyword- or cmdlet-based mechanism for generating a statement-terminating error**.
* The workaround - available in advanced functions only - is to use `$PSCmdlet.ThrowTerminatingError()`:
* Note: Despite the similarity in name with the `Throw` keyword (statement), this truly only generates a _statement_-terminating error.
* See example below.
* On a side note: Sometimes it is desirable to terminate _upstream_ cmdlets, without terminating the pipeline _as a whole_, the way `Select-Object -First <n>` does, for instance. As of Windows PowerShell v5.1 / PowerShell Core v6.0.0-beta.5, there is no way to do this, but such a feature has been suggested here: https://github.com/PowerShell/PowerShell/issues/3821
* **_Script_-terminating errors:**
* Use the `Throw` keyword.
* _Any_ object can be thrown, including none. In the context of a `Catch` handler (as part of a `Try` / `Catch` statement), not specifying an object causes the current exception to be re-thrown.
* Unless the object thrown already is of type `[System.Management.Automation.ErrorRecord]`, PowerShell automatically wraps the object in an instance of that type and stores the object thrown in that instance's `.TargetObject` property.
---
**Example use of `$PSCmdlet.WriteError()` in an advanced function so as to create a _non_-terminating error** (to work around the issue that `Write-Error` doesn't set`$?` to `$False` in the caller's context):
```powershell
# PSv5+, using the static ::new() method to call the constructor.
# The specific error message and error category are sample values.
& { [CmdletBinding()] param() # quick mock-up of an advanced function
$PSCmdlet.WriteError([System.Management.Automation.ErrorRecord]::new(
# The underlying .NET exception: if you pass a string, as here,
# PS creates a [System.Exception] instance.
"Couldn't process this object.",
$null, # error ID
[System.Management.Automation.ErrorCategory]::InvalidData, # error category
$null) # offending object
)
}
# PSv4-, using New-Object:
& { [CmdletBinding()] param() # quick mock-up of an advanced function
$PSCmdlet.WriteError((
New-Object System.Management.Automation.ErrorRecord "Couldn't process this object.",
$null,
([System.Management.Automation.ErrorCategory]::InvalidData),
$null
))
}
```
**Example use of `$PSCmdlet.ThrowTerminatingError()` to create a _statement_-terminating error:**
```powershell
# PSv5+, using the static ::new() method to call the constructor.
# The specific error message and error category are sample values.
& { [CmdletBinding()] param() # quick mock-up of an advanced function
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
# The underlying .NET exception: if you pass a string, as here,
# PS creates a [System.Exception] instance.
"Something went wrong; cannot continue pipeline",
$null, # error ID
[System.Management.Automation.ErrorCategory]::InvalidData, # error category
$null # offending object
)
)
}
# PSv4-, using New-Object:
& { [CmdletBinding()] param() # quick mock-up of an advanced function
$PSCmdlet.ThrowTerminatingError((
New-Object System.Management.Automation.ErrorRecord "Something went wrong; cannot continue pipeline",
$null, # a custom error ID (string)
([System.Management.Automation.ErrorCategory]::InvalidData), # the PS error category
$null # the target object (what object the error relates to)
))
}
```