Capturing multiple error stream events from a single PowerShell cmdlet call

One of the most common ways to implement PowerShell error handling is to set the ErrorActionPreference variable (or ErrorAction parameter) to Stop, and capture these errors with a surrounding try/catch block.

This works perfectly fine in many situations. However one problematic use is when a cmdlet needs to write multiple unique errors to the error stream. One specific example is New-AzResourceGroupDeployment, which can write multiple validation or deployment errors if they are encountered.

If you use the standard try/catch approach here then the cmdlet stops after writing the first error (by design– because you set the action preference). This post provides some tips to handle this scenario to ensure you don’t miss the additional error stream records.

Reproducing the problem

The following is a PowerShell function example that writes multiple errors to the error stream, using Write-Error.

function Invoke-MultipleErrorTest
{
  <#
    .SYNOPSIS
      Writes multiple errors to the error stream.
    
    .DESCRIPTION
      Writes multiple errors to the error stream.
    
    .EXAMPLE
      PS C:\ Invoke-MultipleErrorTest
      Writes the test errors to the stream.
  #>
  [CmdletBinding()]
  Param
  (
  )
  Process
  {
    Write-Error -Message 'First error: the operation failed.'
  
    Write-Error -Message 'Second error: the operation failed: subcode/reasons'
  
    Write-Error -Message 'Third error: another error was detected.'
  }
}

Once I copy and paste that function definition into a PowerShell prompt, we can easily reproduce the problem by setting the error action preference to stop. This tells the pipeline to treat these non-terminating errors as terminating errors which immediately stops the pipeline.

try
{
  Invoke-MultipleErrorTest -ErrorAction Stop
  # only the first error is returned.
  # catch block will be hit.
}
catch
{
  Write-Output "Catch block reached. Error: $_"
}

Results for Windows PowerShell 5.1 and PowerShell Core 7.x

Catch block reached. Error: First error: the operation failed.

Only one error record was written to the stream before our cmdlet stopped processing. Our cmdlet then trigged the surrounding try/catch block and we have lost additional error record context that probably would have been helpful.

Solution

There are multiple ways to approach this error handling scenario, but a good starting point is to leverage the common ErrorVariable parameter and drop the try/catch block.

If we save the errors for this cmdlet invocation directly into an ErrorVariable, we can see if errors have occurred by checking the .Count property on this object after the call.

One thing I also find helpful is to merge the error records found in the ErrorVariable into a single summary and then either Throw or Write-Error the summary message. Here is an example of this pattern:

Invoke-MultipleErrorTest -ErrorAction SilentlyContinue -ErrorVariable 'testErrors'

if ($testErrors -ne $null -and $testErrors.Count -gt 0)
{
  $uniqueErrorsFound = New-Object -TypeName System.Collections.Generic.List[System.String]

  foreach ($err in $testErrors)
  {
    $errStr = $err.ToString().Trim()
    
    if ($uniqueErrorsFound.Contains($errStr) -eq $false)
    {
      $uniqueErrorsFound.Add($errStr)
    }
  }
  
  $errorSummary = [System.String]::Join([System.Environment]::NewLine, $uniqueErrorsFound)
  
  throw "Invoke-MultipleErrorTest returned these error(s): $errorSummary"
}

Below are the results for when we execute this code block. Both results are from a single throw statement, but they include all three of the errors generated from our sample function.

Results for Windows PowerShell 5.1

Invoke-MultipleErrorTest returned these error(s): First error: the operation failed.
Second error: the operation failed: subcode/reasons
Third error: another error was detected.
At line:17 char:3
+ throw "Invoke-MultipleErrorTest returned these error(s): $errorSumm ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Invoke-Multiple...r was detected.:String) [], RuntimeException
+ FullyQualifiedErrorId : Invoke-MultipleErrorTest returned these error(s): First error: the operation failed.
Second error: the operation failed: subcode/reasons
Third error: another error was detected.

Results for PowerShell Core 7.x

Exception:
Line |
17 | throw "Invoke-MultipleErrorTest returned these error(s): $errorSumm …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Invoke-MultipleErrorTest returned these error(s): First error: the operation failed.
Second error: the operation failed: subcode/reasons
Third error: another error was detected.

Same result in both Windows PowerShell and PowerShell Core, aside from the error formatting changes.

Throw vs Write-Error

One thing I want to close with is a reminder on when to use Throw and when to use Write-Error in your own code.

Use the Throw statement when you are certain that the error condition is serious and should immediately halt the pipeline from further processing.

Use the Write-Error cmdlet to write an error to the error stream, but additional processing may still be able to continue.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s