Skip to content

Is there a way to distinguish between caught script- and statement-terminating errors? #6286

Closed

Description

@alx9r

I've been experimenting with an improved test fixture for PowerShell commands. I am hoping to find a way to distinguish between script- and statement-terminating errors (using the terminology from MicrosoftDocs/PowerShell-Docs#1583). A (very) simplified version of the core of the test fixture looks something like this:

function TestFixture
{
    param
    (
        [System.Management.Automation.CommandInfo]
        $CommandUnderTest
        # ...
    )
    # ...
    try
    {
        # ...
        UpstreamFixturePart $InputObject |
            & $CommandUnderTest @NamedArgs @ArgumentList |
            DownstreamFixture
        # ...
    }
    catch
    {
        # ...
    }
}

The obvious way to distinguish between a script- and statement-terminating error is to test control flow behavior when the error occurs (see #6098 for an example). The try {} around $CommandUnderTest in TestFixture is necessary to capture and record exceptions as they are thrown, and keep flow of control within the test fixture. Unfortunately, the presence of that try{} block precludes the using control flow behavior to distinguish script- from statement-terminating errors because both kinds of errors result in the same control flow (ie. both jump to the catch block).

I was hoping that .WasThrownByThrowStatement would provide enough information to distinguish between the two. Unfortunately per #6288 and #3647 (comment) that is not currently sufficient to distinguish.

  1. Is there a way to distinguish between script- and statement-terminating errors once they are caught in the scriptblock?
  2. Is there a way to preserve the distinction in control flow behavior between a statement- and script-terminating errors and retain control of flow in the fixture?
  3. Is there some other way to distinguish between script- and statement-terminating errors in this scenario?

Activity

  1. iSazonov commented on Mar 2, 2018

    @iSazonov
    Collaborator

    Related #4837, #4781
    Dup #3158

  2. alx9r commented on Mar 2, 2018

    @alx9r
    Author

    Ilya (@iSazonov) This issue is not a dup of #3158:

  3. alx9r commented on Mar 13, 2018

    @alx9r
    Author

    Michael Klement (@mklement0) If you have the time, could you take a look at this and see if you have any ideas for how to achieve this?

  4. BrucePay commented on Mar 14, 2018

    @BrucePay
    Collaborator

    Hi alx9r, There aren't really three kinds of errors as such, just three dispositions of one kind of error. For example, if a command writes an error to error output, it's non-terminating. If -ErrorAction Stop is specified, that non-terminating write becomes a statement-terminating error. If there is a trap or try/catch in scope, the statement-terminating error is thrown to the catch block. In this scenario, the same error object eventually becomes all three "types" as it flows through the system - it began as non-terminating, became terminating and then became an exception. (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.)

  5. alx9r commented on Mar 15, 2018

    @alx9r
    Author

    Bruce Payette (@BrucePay)

    If -ErrorAction Stop is specified, that non-terminating write becomes a statement-terminating error.

    In my experimentation Write-Error -ea Stop, for example, does not cause a statement-terminating error.
    Rather, Write-Error -ea Stop seems to always throw an exception. In my experimentation the effect of -ErrorAction has been consistent with being an implementation detail of the command to which it is passed. Do you mean $ErrorActionPreference = 'Stop' here?

    In this scenario, the same error object eventually becomes all three "types" as it flows through the system - it began as non-terminating, became terminating and then became an exception.

    I think I understand the escalation concept you are describing. I have not, however, come across any one error source that observably escalates from non-terminating to statement-terminating to exception in the idealized way you describe. Do you have an example of a command that observably behaves the way you describe? Or are you describing an unobservable internal escalation process here?


    I arrived at the following table empirically (test code here).

    ref. Statement In try{}? ErrorActionPreference Exception Thrown by statement?
    1 throw x not SilentlyContinue yes
    2 throw yes SilentlyContinue yes
    3 throw no SilentlyContinue no
    4 ThrowTerminatingError() yes x yes
    5 ThrowTerminatingError() no Stop yes
    6 ThrowTerminatingError() no Continue no
    7 Write-Error -ea Stop x x yes
    8 Write-Error x Stop yes
    9 Write-Error x Continue no

    x = does not matter

    Another way to ask my original question is as follows:

    Is there a way to distinguish between a caught exception thrown by throw (ie. ref. 1 and 2) and an exception thrown by ThrowTerminatingError() (ie. ref. 4)?

    In yet other words, is there a way to infer from a caught exception whether that exception would have been thrown in the absence of the try{}catch{} that caught it?

  6. mklement0 commented on Mar 15, 2018

    @mklement0
    Contributor

    alx9r: Nice writeup; to add to the general part:

    Indeed, to recap from the error-handling saga:

    • Preference variable $ErrorActionPreference = 'Stop' makes both nonterminating and statement-terminating errors throw an unhandled-by-default exception (for which I coined the term script-terminating error, which I'll use in the remainder of this post).

    • Common parameter -ErrorAction Stop, by contrast, only affects nonterminating errors and promotes them directly to script-terminating errors.

    This directly contradicts the docs, which do not distinguish between preference-variable and common-parameter use and categorically claim that Stop only affects nonterminating errors (which is only true with the common parameter).

    You can't help but wonder if the original intent was:

    • to never affect statement-terminating errors with preference variable $ErrorActionPreference = 'Stop', in line with the common parameter's behavior and the documentation.

    • to have both $ErrorActionPreference = 'Stop' and -ErrorAction Stop promote nonterminating errors to statement-terminating ones rather than to script-terminating ones.

      • This would explain why -ErrorAction Stop has no effect on statement-terminating errors: they already are statement-terminating errors; in other words: they already are in the desired target state.

    In other words: the original intent may have been to never terminate the script by default , except with the use of Throw, and to instead require use of try/catch or trap in order to effect termination.

    • The Throw documentation confusingly talks about generating a "terminating error" too, without pointing out the fundamental distinction from a statement-terminating error.
    • Additionally, the documentation's example use case shows use of Throw to enforce a mandatory parameter without prompting; of course, using this technique results in a script-terminating error, whereas cmdlets normally only generate statement-terminating errors, even in the face of incorrect syntax and, if the PowerShell instance was invoked with -NonInteractive, in the absence of mandatory parameters (declared without Throw).

    Bruce Payette (@BrucePay), can you shed light on this?

  7. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

  8. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

  9. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

  10. microsoft-github-policy-service commented on Nov 23, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes.

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

    Resolution-No ActivityIssue has had no activity for 6 months or moreWG-Enginecore PowerShell engine, interpreter, and runtime

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions