powershellerror-handlingdocumentationambiguity

Powershell Terminating Errors, Looking for Examples


As many of you know by now, the in official Microsoft Documentation terminating errors in Powershell have the following features:

  1. They can be intercepted using a try-catch block. Source
  2. When thrown, Windows PowerShell runtime permanently stops the execution of the pipeline. Source

However, if you test this, especially when using built-in cmdlets, the second feature doesn't always manifest. For example:

Even though the error generated by this command can be intercepted by try-catch, the second line executes.

Non-Existent Command
Write-Output "This Line still executes"

Proof of concept for next line execution:

Line |
  46 |  Non-Existent Command
     |  ~~~~~~~~~~~~
     | The term 'Non-Existent' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the spelling of the name, or if a   
     | path was included, verify that the path is correct and try again.
This Line still executes

Proof of concept that it can be intercepted with try-catch:

try {
    Non-Existent Command
}
catch {
    Write-Output "Caught ya!"
}

Result:

Caught ya!

I have found that this type of error goes by a couple of colloquial names Semi-Terminating (Answer 2), and Statement Terminating (Top Comment).

It seems like a lot of the examples often provided as script-terminating, are actually statement-terminating, and therefore require you to set -ErrorAction Stop, in order for them to halt the script.

Common Examples Include

Import-Module -Name "invalid"
Get-Content -Path "invalid"
Exit-PSSession "invalid"
Stop-Service "invalid"

Yet all of these have demonstrated statement-terminating behavior.

Do you know of any built-in cmdlets that actually throw a script-terminating error on their own?


Solution

  • You shouldn't find a binary cmdlet that will throw a "script terminating" error. If the cmdlet was wrongfully using throw instead of Cmdlet.ThrowTerminatingError it wouldn't be able to halt your script. Both are considered statement or pipeline terminating errors unless the error preference is Stop either via common-parameter (-ErrorAction) or preference variable ($ErrorActionPreference), as you have already noted in your question.

    Add-Type '
    using System;
    using System.Management.Automation;
    
    [Cmdlet(VerbsDiagnostic.Test, "Command")]
    public sealed class TestCommand : PSCmdlet
    {
        protected override void EndProcessing()
        {
            throw new Exception("some error");
        }
    }' -PassThru | Import-Module -Assembly { $_.Assembly }
    
    Test-Command
    'can continue here'
    

    The same applies for a Method in binary class:

    Add-Type '
    using System;
    
    public static class Test
    {
        public static void Testing()
        {
            throw new Exception("some error");
        }
    }'
    
    [Test]::Testing()
    'can continue here'
    

    However, in both cases, when in the context of a try block, the throwing statement terminates the block itself as shown in Try can create terminating errors:

    try {
        Test-Command
        'wont get here'
    }
    catch { }
    
    'can continue here'
    

    There is another option I'm leaving aside as I'd personally consider this a bad practice and wouldn't recommend anyone to do it but, for the sake of completion I'm adding it.

    If the cmdlet purposely set's the ErrorActionPreference variable, either via EngineIntrinsics.SessionState (Module Scope - does not update the caller's preference variable) or via PSCmdlet.SessionState (Caller's Scope - updates its preference variable), then you can get a script terminating error, similar to what you get when using throw in a PowerShell script (see next section).

    Add-Type '
    using System;
    using System.Management.Automation;
    
    [Cmdlet(VerbsDiagnostic.Test, "Command")]
    public sealed class TestCommand : PSCmdlet
    {
        protected override void EndProcessing()
        {
            EngineIntrinsics context = (EngineIntrinsics)GetVariableValue("ExecutionContext");
            context.SessionState.PSVariable.Set("ErrorActionPreference", ActionPreference.Stop);
            throw new Exception("some error");
        }
    }' -PassThru | Import-Module -Assembly { $_.Assembly }
    
    Test-Command
    'wont get here'
    

    The way you can halt a PowerShell script is with throw in a PowerShell function, a class or in the script itself. This is of course a pretty bad practice.

    function Test-Function {
        throw 'some error'
    }
    
    Test-Function
    'wont get here'
    

    Other way can be from an error that the runtime can't recover from, i.e.: Environment.FailFast, and probably others I'm not listing in this answer.