YoavKlein
YoavKlein

Reputation: 2703

Powershell CommandNotFoundException is terminating?

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?

Upvotes: 2

Views: 1989

Answers (2)

mklement0
mklement0

Reputation: 438208

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.

Upvotes: 5

Aditya Nair
Aditya Nair

Reputation: 572

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

Upvotes: 0

Related Questions