Skip to content

How, exactly, does PowerShell decide whether .ThrowTerminatingError() terminates only the statement? #6098

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
alx9r opened this issue Feb 3, 2018 · 10 comments
Labels
Issue-Question ideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a Resolution-Answered The question is answered.

Comments

@alx9r
Copy link

alx9r commented Feb 3, 2018

Consider the following code:

function a {
    param ( [Parameter(ValueFromPipeline)]$x )
    process {
        $PSCmdlet.ThrowTerminatingError(
            [System.Management.Automation.ErrorRecord]::new(
                'exception message',
                'errorId',
                [System.Management.Automation.ErrorCategory]::InvalidOperation,
                $null
            )
        )
    }
}

Invoking

a
Write-Host 'statement after'

outputs

a : exception message
At C:\test1.ps1:15 char:1
+ a
+ ~
+ CategoryInfo          : InvalidOperation: (:) [a], Exception
+ FullyQualifiedErrorId : errorId,a

statement after

which seems to be consistent with .ThrowTerminatingError() resulting in a "statement-terminating error".

On the other hand, invoking

try
{
    a
    Write-Host 'statement after'
}
catch
{
    Write-Host 'catch'
}

outputs catch which indicates that, in this case, .ThrowTerminatingError() terminates more than just the statement.

What is happening with flow of control in the code with the try{} block? Does PowerShell search the whole call stack for a try{}catch{}? Are there circumstances aside from a wrapping try{} that results in .ThrowTerminatingError terminating more than just the statement?

FWIW, this arose trying to understand MicrosoftDocs/PowerShell-Docs#1583.

@iSazonov iSazonov added the Issue-Question ideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a label Feb 3, 2018
@iSazonov
Copy link
Collaborator

iSazonov commented Feb 3, 2018

I believe we terminating pipeline #2860 (comment)

@alx9r
Copy link
Author

alx9r commented Feb 3, 2018

I believe we terminating pipeline #2860 (comment)

If it were that simple, then the output of

try
{
    1 | a
    Write-Host 'statement after'
}
catch
{
    Write-Host 'catch'
}

would be statement after not catch. The output is catch indicating that more happened with flow of control than merely terminating the pipeline established by 1 | a.

@mklement0
Copy link
Contributor

You should view this from the perspective of the try / catch statement:

If a terminating error occurs - whether script- or statement-terminating - the entire try block is exited at the point the error occurs and execution resumes in the catch block.

(A nonterminating error, by contrast, is not caught.)

Except for the terminating-vs.-nonterminating error distinction, this is consistent with the usual try / catch semantics in, say, C#.

@alx9r
Copy link
Author

alx9r commented Feb 25, 2018

@mklement0

Just to make sure we're on the same page, the situation is at least this complicated:

Error Reported by inside try{} no try {}
Write-Error continue with next statement continue with next statement
throw jump to catch{} stop execution
$PSCmdlet.ThrowTerminatingError jump to catch{} continue with next statement

It seems to me that $PSCmdlet.ThrowTerminating() is neither consistently "non-terminating" nor consistently "terminating".

Except for the terminating-vs.-nonterminating error distinction, this is consistent with the usual try / catch semantics in, say, C#.

I'm not proficient at C#. Are there C# statements that continue to the next statement if there is no surrounding try{} block but jump to catch{} otherwise? That's what $PSCmdlet.ThrowTerminating() seems to do. I would consider PowerShell throw's behavior consistent with usual try/catch semantics, but not $PSCmdlet.ThrowTerminating().

If a terminating error occurs - whether script- or statement-terminating - the entire try block is exited at the point the error occurs and execution resumes in the catch block.

This much is evident from the repro in my OP. But it doesn't really answer these questions from my OP:

Does PowerShell search the whole call stack for a try{}catch{}? Are there circumstances aside from a wrapping try{} that results in .ThrowTerminatingError terminating more than just the statement?

@mklement0
Copy link
Contributor

So far, I don't see anything that contradicts my explanation.

It seems to me that $PSCmdlet.ThrowTerminating() is neither consistently "non-terminating" nor consistently "terminating".

Nothing is - by design - consistently terminating vs. nonterminating in PowerShell:

The point of the $ErrorActionPreference preference variable / -ErrorAction parameter is to control the "terminatingness" of the behavior as needed (albeit in an inconsistent manner, and inconsistent with the documentation, as discussed in the linked docs issue).

Does PowerShell search the whole call stack for a try{}catch{}?

From what I can tell, yes.

Try this simple example (all scripts are assumed to reside in the current location):

Script t2.ps1:

Get-Item -NoSuchParam   # provoke statement-terminating error
"With the defaults, I'm pretty sure I won't execute, due to the try / catch on the call stack"

Script t1.ps1:

try {
  ./t2.ps1
  "What are you doing here?"
} catch {
  'ouch'
}

Running ./t1.ps yields:

ouch

That is, the statement-terminating error inside ./t2.ps1 aborted both the t2.ps - due to the try / catch on the call stack - and, bubbling up, the the try block, ultimately executing only the catch block in t1.ps1.

Are there circumstances aside from a wrapping try{} that results in .ThrowTerminatingError terminating more than just the statement?

Not that I'm aware of, but do let us know if you find such cases.

@alx9r
Copy link
Author

alx9r commented Feb 26, 2018

Nothing is - by design - consistently terminating vs. nonterminating in PowerShell:

This might be down to semantics, but throw consistently terminates in my experience.

Try this simple example...

The trouble with that approach to answering this question is that it proves what happens in that particular set of circumstances and not much else. I can get a more applicable version of that kind of information from unit testing commands that use $PSCmdlet.ThrowTerminatingError(). But that information doesn't tell me where to expect surprises the way an analytical answer often does. It seems like an analytical answer to this question is possible.

@mklement0
Copy link
Contributor

mklement0 commented Feb 26, 2018

This might be down to semantics, but throw consistently terminates in my experience.

  • Except if you use try / catch (so, yes, by default throw is runspace-terminating).

  • Yes, throw is the "apex error" (what I call a script-terminating error / runspace-terminating error) - it directly throws a by default uncaught exception that terminates the runspace.

  • You can promote other types of errors (nonterminating / statement-terminating) to "apex errors" via $ErrorActionPreference = 'Stop' / -ErrorAction Stop.

  • In the end, you can catch them with try / catch , anywhere up the call stack.

    • If they're already statement-terminating errors, however, you needn't promote them - try / catch will catch them as-is (just as it catches script-terminating errors, but unlike nonterminating errors).
  • If you don't, they terminate the runspace.

If you find a scenario that doesn't fit this model (which is based on my experiments), let us know.

The trouble with that approach to answering this question is that it proves what happens in that particular set of circumstances and not much else. I can get a more applicable version of that kind of information from unit testing commands that use $PSCmdlet.ThrowTerminatingError(). But that information doesn't tell me where to expect surprises the way an analytical answer often does. It seems like an analytical answer to this question is possible.

I don't know what you mean.

@alx9r
Copy link
Author

alx9r commented Feb 26, 2018

If you find a scenario that doesn't fit this model (which is based on my experiments), let us know.

You model seems to fit. I think you almost have me convinced. :)

Thanks for your help @mklement0!

@alx9r
Copy link
Author

alx9r commented Mar 14, 2018

BrucePay wrote in PowerShell/PowerShell#6286(comment):

Note that the automatic change from statement-terminating error to exception in the presence of trap or try/catch is a fundamental semantic for PowerShell.

@mklement0
Copy link
Contributor

(Replacing my earlier comment; got confused)

To think of this in terms of exceptions is a bit confusing, because the use of exceptions is an implementation detail, given that PowerShell's native "error currency" is error records ([System.Management.Automation.ErrorRecord] instances).

  • Both trap and try / catch allow you to intercept statement-terminating errors and script-terminating errors - which in the presence of these constructs are ignored by default, unless you take explicit action : to truly generate a script-terminating error (unhandled exception), you must, in a trap block, explicitly execute break, and, in a catch block, explicitly execute Throw.

  • By contrast, using $ErrorActionPreference = 'Stop' is the only way to directly promote both nonterminating and statement-terminating errors directly to script-terminating errors (unhandled exceptions) (the equivalent of using Throw).
    While you can also catch those with trap and try / catch, in their absence such promoted errors will terminate the script.

    • In other words: $ErrorActionPreference = 'Stop' doesn't just promote nonterminating errors to statement-terminating errors, it promotes both types to script-terminating errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue-Question ideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a Resolution-Answered The question is answered.
Projects
None yet
Development

No branches or pull requests

3 participants