4

I'm a C# developer and sometimes PowerShell is just driving me mad.

I have the following code:

$script:ErrorActionPreference = 'Stop'

try {
    # Some code here
}
catch [Microsoft.PowerShell.Commands.WriteErrorException] {
    # Print error messages (without stacktrace)
    Write-Host -ForegroundColor Red $_.Exception.Message
    exit 1
}
catch [System.Management.Automation.RuntimeException] {
    # A thrown string
    Write-Host -ForegroundColor Red $_.Exception.Message
    Write-Host -ForegroundColor Red $_.ScriptStackTrace
    exit 1
}
catch {
    # Print proper exception message (including stack trace)
    Write-Host -ForegroundColor Red "$($_.Exception.GetType().Name): $($_.Exception.Message)"
    Write-Host -ForegroundColor Red $_.ScriptStackTrace
    exit 1
}

The idea is basically:

  1. If the exception comes from a call to Write-Error use the first catch block.
  2. If a string is thrown directly, use the second catch block.
  3. For any other exception, use the last catch block.

Now, my problem is with Write-Error and the first catch block:

  • If I call Write-Error within the try block, the second catch block is executed (even though the first one should be executed).
  • If I remove the second catch block and then call Write-Error, the correct (first) catch block is used.

Why is that?

I've checked whether WriteErrorException and RuntimeException are inheriting from each other: They don't (both inherit from SystemException but this shouldn't matter).

I've also verified that this behavior is the same in both PowerShell 5.1 and PowerShell Core (6.0).

9
  • What does your Write-Error look like? You can specify the exception thrown by it. When just doing Write-Error 'msg' -ea 1, no exception is thrown, but an error record is under $Error.ErrorRecord, which is pretty weird. The source of the parent error record is System.Management.Automation which might be the cause of your issue. Commented May 19, 2018 at 13:54
  • @TheIncorrigible1 It's just Write-Error 'some message'. It'll raise an exception because of $script:ErrorActionPreference = 'Stop' at the beginning of the script. Commented May 19, 2018 at 14:21
  • So it doesn't actually throw an exception, but an error record, that has a child error record, that has an exception. That might be your issue here Commented May 19, 2018 at 14:22
  • @TheIncorrigible1 I'm not sure on the terminology. When I use Write-Error I can catch something (doesn't matter to me what you want to call it) and I can catch the correct ... thing of type Microsoft.PowerShell.Commands.WriteErrorException if I remove the second catch block. Commented May 19, 2018 at 14:35
  • Add -Exception to your Write-Error argument list. PowerShell works with the ErrorRecord .net class instead of just Exception. You can look up ErrorRecord on the msdn. Commented May 19, 2018 at 14:36

1 Answer 1

1

Write-Error won't throw a terminating error by default, but it will with ErrorActionPreference set to Stop as you've mentioned. However, this changes the exception thrown to ActionPreferenceStopException which does inherit RuntimeException

You can still catch the WriteErrorException without the RuntimeException clause because the inner error record for the ActionPreferenceStopException contains the WriteErrorException

You can see what I mean by running this:

Write-Error 'this is a test' -ErrorAction Stop
$error[0].ErrorRecord.Exception.GetType()
# IsPublic IsSerial Name                        BaseType
# -------- -------- ----                        --------
# True     True     WriteErrorException         System.SystemException

But with the RuntimeException clause, it will get picked up first because RuntimeException is the closest matching exception type.

To workaround this you'd need to either throw a more specific exception or test $_ within the RuntimeException clause. Here's the latter

$script:ErrorActionPreference = 'Stop'

try {
    # Some code here
}
catch [Microsoft.PowerShell.Commands.WriteErrorException] {
    # Print error messages (without stacktrace)
    Write-Host -ForegroundColor Red $_.Exception.Message
    exit 1
}
catch [System.Management.Automation.RuntimeException] {
    if ($_.Exception -is [Microsoft.PowerShell.Commands.WriteErrorException]) {
        # Print error messages (without stacktrace)
        Write-Host -ForegroundColor Red $_.Exception.Message
        exit 1
    }

    # A thrown string
    Write-Host -ForegroundColor Red $_.Exception.Message
    Write-Host -ForegroundColor Red $_.ScriptStackTrace
    exit 1
}
catch {
    # Print proper exception message (including stack trace)
    Write-Host -ForegroundColor Red "$($_.Exception.GetType().Name): $($_.Exception.Message)"
    Write-Host -ForegroundColor Red $_.ScriptStackTrace
    exit 1
}

You could also add a ActionPreferenceStopException clause and test for $_ there.

Edit: Actually, unless you really want to use Write-Error, you'd be better off just throwing an exception similar to how you would in C#. So instead of Write-Error, use:

throw [System.InvalidOperationException]::new('This is my message')
Sign up to request clarification or add additional context in comments.

4 Comments

I'm sorry but you code doesn't work the way you think it should work. Have you tried it with calling Write-Error?
My bad, looks like the ActionPreferenceException gets unwrapped when it's passed to the catch block. I've updated the example, the only difference was changing $_.ErrorRecord.Exception to $_.Exception in the -in statement.
Looks better. Now, if you throw a real exception (throw [System.InvalidOperationException]::new('This is my message')), you still end up in the second catch block. What about that?
@SebastianKrysmanski That's interesting. If you add a catch clause for InvalidOperationException it will work as expected, but your right it does trigger the second clause without that. My guess is that it's due to all exceptions needing to be wrapped in a RuntimeException of some sort under the hood. That seems like a bug, and it may be best to avoid catching RuntimeException directly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.