Powershell CommandNotFoundException is terminating?

Viewed 537

When executing such a script:

nonexistingcommand
echo "Hello"

I get:

nonexistingcommand : The term 'nonexistingcommand' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the 
name, or if a path was included, verify that the path is correct and try again.
At D:\Playground\powershell\Test.ps1:2 char:1
+ nonexistingcommand
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (nonexistingcommand:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException
 
Hello

So it seems that the CommandNotFoundException is a non-terminating error. So why if I

try {
 nonexistingcommand
}
catch [System.Management.Automation.CommandNotFoundException] {
  throw
}
echo "Hello"

In this case, it will exit and not print "Hello".

Why is that?

2 Answers

Unfortunately, PowerShell has two types of terminating errors:

  • Statement-terminating errors, which by default only terminate the enclosing statement.

    • By default, execution resumes, namely with the next statement (echo "Hello" in your case)

    • CommandNotFoundException is an instance of such an error.

    • By contrast, non-terminating errors continue processing even of the originating statement, if further pipeline input is available (e.g., Get-ChildItem NoSuchDir, \ emits a non-terminating error for non-existing dir. NoSuchDir, then goes on to successfully report on existing dir. \)

  • Script-terminating (thread-terminating) errors, which terminate the enclosing script and the entire call stack.

    • Such errors are triggered by the throw statement called from PowerShell code, never by binary (compiled) cmdlets.

The try { ... } catch { ... } finally { ... } statement does not distinguish between these two subtypes: it catches them both.

  • Given that your catch block contains an argument-less throw statement, which implicitly relays the error that triggering error, you're effectively turning the statement-terminating error into a script-terminating one, so execution ends there.

Similarly, setting the $ErrorActionPreference preference variable to 'Stop' causes all types of errors (emitted by PowerShell commands[1]), including non-terminating ones, to abort execution overall; in other words: both non-terminating and statement-terminating errors are promoted to script-terminating ones.


For a comprehensive overview of PowerShell's surprisingly complex error handling, see GitHub docs issue #1583.


[1] At least in local, foreground invocations of external programs in consoles (terminals), stderr output is by default not routed via PowerShell's error stream, and therefore not affected by $ErrorActionPreference.
However, in PowerShell v7.1 and below, including in Windows PowerShell, using a 2> redirection does route stderr through PowerShell's error stream, which can have unexpected side effect. This problem will be fixed in v7.2.
Note that the behavior is PowerShell host-specific and applies to host ConsoleHost (use $host to get host information). Other hosts still invariably route stderr via the error stream, which notably affects remoting and background jobs.

The above error that states command was not found is a terminating error. When such an error is encountered, powershell pipeline stops execution. Only the statements within catch block are executed.

To know the difference between terminating and non-terminating errors, checkout this link: https://www.tutorialspoint.com/what-is-terminating-and-non-terminating-errors-in-powershell

If some statements are to be executed even if you encounter a terminating error, specify them within finally:

try {
 nonexistingcommand
}
catch [System.Management.Automation.CommandNotFoundException] {
  throw
}
finally {
  echo "Hello"
}

The above will print Hello

Related