Skip to content

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

Closed

Description

@alx9r

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.

Activity

  1. added
    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a
    on Feb 3, 2018
  2. iSazonov commented on Feb 3, 2018

    @iSazonov
    Collaborator

    I believe we terminating pipeline #2860 (comment)

  3. alx9r commented on Feb 3, 2018

    @alx9r
    Author

    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.

  4. mklement0 commented on Feb 25, 2018

    @mklement0
    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#.

  5. alx9r commented on Feb 25, 2018

    @alx9r
    Author

    Michael Klement (@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?

  6. mklement0 commented on Feb 26, 2018

    @mklement0
    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.

  7. alx9r commented on Feb 26, 2018

    @alx9r
    Author

    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.

  8. mklement0 commented on Feb 26, 2018

    @mklement0
    Contributor

    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.

  9. alx9r commented on Feb 26, 2018

    @alx9r
    Author

    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 Michael Klement (@mklement0)!

  10. alx9r commented on Mar 14, 2018

    @alx9r
    Author

    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.

  11. mklement0 commented on Mar 15, 2018

    @mklement0
    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get aResolution-AnsweredThe question is answered.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions