Skip to content

ActionPreference.Inquire is inconsistent and needs some redesign #9755

Closed

Description

@KirkMunro

Transcript demonstrating the problem

As the title says, ErrorAction.Inquire needs work. For example, consider the following transcript:

PS C:\> $ErrorActionPreference = 'Inquire'
PS C:\> gps -id 9999999999999999

Action to take for this exception:
Cannot bind parameter 'Id'. Cannot convert value "9999999999999999" to type "System.Int32". Error: "Value was either too large or too small for an Int32."
[C] Continue  [I] Silently Continue  [B] Break  [S] Suspend  [?] Help (default is "C"): B
Get-Process : Cannot bind parameter 'Id'. Cannot convert value "9999999999999999" to type "System.Int32". Error: "Value was either too large or too small for an Int32."
At line:1 char:9
+ gps -id 9999999999999999
+         ~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-Process], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.GetProcessCommand

PS C:\> try {gps -id 9999999999999999} catch {}
PS C:\> gps -id ([int]::MaxValue)

Confirm
Cannot find a process with the process identifier 2147483647.
[Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"): H
gps : The running command stopped because the user selected the Stop option.
At line:1 char:1
+ gps -id ([int]::MaxValue)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [Get-Process], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ActionPreferenceStop,Microsoft.PowerShell.Commands.GetProcessCommand

PS C:\> $WarningPreference = 'Inquire'
PS C:\> Write-Warning 'boo'
WARNING: boo

Confirm
Continue with this operation?
[Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"): h
Write-Warning : The running command stopped because the user selected the Stop option.
At line:1 char:1
+ Write-Warning 'boo'
+ ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [Write-Warning], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ActionPreferenceStop,Microsoft.PowerShell.Commands.WriteWarningCommand

This transcript demonstrates the following issues:

  1. The message you receive when prompted as a result of ActionPreference.Inquire differs depending on the event that occurred. For terminating errors, users are properly asked what action they would like to take for the exception. For non-terminating errors, users are shown the heading "Confirm", with an error message if the Inquire prompt was displayed as a result of a non-terminating error, or a question asking users if they want to continue if the Inquire prompt was displayed as a result of a non-error message (warning, debug, verbose, progress).
  2. If a terminating error is the cause for the message, the prompt appears too late, such that it will not appear at all if a terminating error is suppressed by a naked catch statement. This makes it difficult for users to properly troubleshoot errors in the system, because they may have something being suppressed that they don't know about.
  3. The options users can choose from differ depending on whether or not the Inquire prompt appeared due to a terminating exception or a non-terminating message, even though the underlying actions taken from those options are the same.
  4. For non-terminating messages (non-terminating errors, or debug, verbose, warning or progress messages), the options are simply incorrect. There is no "Yes to All" functionality for these messages from Inquire. "Yes to All" is really "Silently Continue", except for these messages it's not silent at all. For everything except non-terminating errors, the message is displayed first. For non-terminating errors, the message is displayed after the Inquire prompt. So "Yes to All" aka "SilentlyContinue" is just broken.
  5. The usefulness of the [S] Suspend option becomes questionable once PR Break action preference #8205 is approved/merged. This point is a little forward thinking, but if we can enter the debugger in response to an error, terminating or not (something which is now possible as part of that PR), then the value of entering a nested prompt diminishes greatly and it would be more valueable to have a [D] Debug option that enters the debugger instead (this keeps the user in the current scope, lets them look at the location in the script, step through the code, continue execution, terminate the script, etc.).

Expected behavior

  1. Whether ActionPreference.Inquire is checked because of a terminating error or a non-terminating message...
    1. ...the options presented to users should be identical, as follows:
      [C] Continue [I] Silently Continue [S] Stop [D] Debug [?] Help (default is "C"):
      This includes the following changes:
      • [B] Break has been replaced with [S] Stop to match ActionPreference.Stop.
      • [S] Suspend has been replaced with [D] Debug to allow users to enter the debugger (this depends on PR Break action preference #8205).
    2. ...the prompt and the options presented to users should appear before the corresponding message is shown in all cases so that the behavior is consistent, and so that the message can be suppressed with [I] Silently Continue (which should work).
  2. For non-terminating messages resulting in ActionPreference.Inquire displaying a prompt...
    1. ...the prompt caption should state Action to take for this [error|warning]: or Action to take for this [verbose|debug|progress] message:.
    2. ...the prompt message should show the actual message that will be displayed.
  3. For terminating messages resulting in ActionPreference.Inquire displaying a prompt...
    1. ... the check should happen earlier (like the check to enter the debugger in PR Break action preference #8205) so that users can receive the prompt even for exceptions that don't appear.

With these changes in place, the transcript above would instead show as follows:

PS C:\> $ErrorActionPreference = 'Inquire'
PS C:\> gps -id 9999999999999999

Action to take for this exception:
Cannot bind parameter 'Id'. Cannot convert value "9999999999999999" to type "System.Int32". Error: "Value was either too large or too small for an Int32."
[C] Continue  [I] Silently Continue  [S] Stop  [D] Debug  [?] Help (default is "C"): s
Get-Process : Cannot bind parameter 'Id'. Cannot convert value "9999999999999999" to type "System.Int32". Error: "Value was either too large or too small for an Int32."
At line:1 char:9
+ gps -id 9999999999999999
+         ~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-Process], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.GetProcessCommand

PS C:\> try {gps -id 9999999999999999} catch {}

Action to take for this exception:
Cannot bind parameter 'Id'. Cannot convert value "9999999999999999999" to type "System.Int32". Error: "Value was either too large or too small
for an Int32."
[C] Continue  [I] Silently Continue  [S] Stop  [D] Debug  [?] Help (default is "C"): s
Get-Process : Cannot bind parameter 'Id'. Cannot convert value "9999999999999999999" to type "System.Int32". Error: "Value was either too large or too small for an Int32."
At line:1 char:14
+ try {gps -id 9999999999999999999} catch {}
+              ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidArgument: (:) [Get-Process], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.GetProcessCommand

PS C:\> gps -id ([int]::MaxValue)

Action to take for this error:
Cannot find a process with the process identifier 2147483647.
[C] Continue  [I] Silently Continue  [S] Stop  [D] Debug  [?] Help (default is "C"): s
gps : The running command stopped because the user selected the Stop option.
At line:1 char:1
+ gps -id ([int]::MaxValue)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [Get-Process], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ActionPreferenceStop,Microsoft.PowerShell.Commands.GetProcessCommand

PS C:\> $WarningPreference = 'Inquire'
PS C:\> Write-Warning 'boo'

Action to take for this warning:
boo
[C] Continue  [I] Silently Continue  [S] Stop  [D] Debug  [?] Help (default is "C"): s
Write-Warning : The running command stopped because the user selected the Stop option.
At line:1 char:1
+ Write-Warning 'boo'
+ ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [Write-Warning], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ActionPreferenceStop,Microsoft.PowerShell.Commands.WriteWarningCommand

Actual behavior

See transcript shown above.

Environment data

Name                           Value
----                           -----
PSVersion                      6.2.0
PSEdition                      Core
GitCommitId                    6.2.0
OS                             Microsoft Windows 10.0.17763
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Activity

  1. added
    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a
    on May 29, 2019
  2. KirkMunro commented on May 29, 2019

    @KirkMunro
    ContributorAuthor

    I'd like to fix these issues once #8205 is accepted/merged, assuming y'all are OK with them Steve Lee (@SteveL-MSFT). Since these changes pertain to interactive use only (ActionPreference.Inquire responses cannot be scripted), they would be non-breaking, and they would catch the ActionPreference.Inquire feature up to the latest functionality while fixing a bundle of issues in it.

  3. bpayette commented on May 29, 2019

    @bpayette
    Contributor

    Kirk Munro (@KirkMunro) First - thanks for the detailed write-up! Second - I haven't had coffee yet so I'm going to ask some stupid questions which you've probably already answered. My main question is why should the inquire prompt have the same behaviour for different kinds of messages? For example, offering "Yes to all" makes no sense for a terminating error but it definitely makes sense for non-terminating errors. The goal of Inquire was to inspect the messages and then take appropriate action based on that message - skip the message, skip all of the messages, halt execution or enter a subshell/debugger to examine the state of the system to see what's happening, etc. But the set of operations needs to change for different types of messages. What am I missing?

  4. KirkMunro commented on May 29, 2019

    @KirkMunro
    ContributorAuthor

    Thanks for coming back so quickly with questions, Bruce Payette (@bpayette). Since I don't necessarily have all of the history behind why things are the way they are, I often just look at the end user experience. I tried many ways to get Inquire with a response of Yes to All to actually suppress all further prompts without any success whatsoever. For example, I tried the following, with and without SupportsShouldProcess, and was always prompted for each item, in both PowerShell 5.1 and 6.2 (sorry that I don't have any older versions handy to test this with right now).

    PS C:\> $ErrorActionPreference = 'Inquire'
    PS C:\> & {[CmdletBinding(SupportsShouldProcess)]param(); gps -id 1234; gps -id 1234}
    
    Confirm
    Cannot find a process with the process identifier 1234.
    [Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"): a
    gps : Cannot find a process with the process identifier 1234.
    At line:1 char:51
    + ...  {[CmdletBinding(SupportsShouldProcess)]param(); gps -id 1234; gps -i ...
    +                                                      ~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (1234:Int32) [Get-Process], ProcessCommandException
    + FullyQualifiedErrorId : NoProcessFoundForGivenId,Microsoft.PowerShell.Commands.GetProcessCommand
    
    
    Confirm
    Cannot find a process with the process identifier 1234.
    [Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"): a
    gps : Cannot find a process with the process identifier 1234.
    At line:1 char:65
    + ... etBinding(SupportsShouldProcess)]param(); gps -id 1234; gps -id 1234}
    +                                                             ~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (1234:Int32) [Get-Process], ProcessCommandException
    + FullyQualifiedErrorId : NoProcessFoundForGivenId,Microsoft.PowerShell.Commands.GetProcessCommand
    

    I'm fine if there need to be some differences between terminating/non-terminating support based on what is possible, but those differences need to be rationalized for today (since this functionality was in version 1.0 and pre-dates the PowerShell debugger), the bugs need to be fixed, the functionality should tie into the debugger instead of having suspend, and the wording and choices needs to be cleaned up.

    I had thought what you described was the original intent until I ran a bunch of tests to figure out why I haven't tried using this functionality in many years, which usually stems from running into issues and realizing it's just broken). Even then, though, if it was working that way, that makes Inquire work like a conditional Confirm for non-terminating messages, vs like a light-weight debugger prompt for terminating messages? It's certainly confusing. I don't have my heart set on one approach or the other, as long as it is consistent at the least (with obvious technical differences) and rational.

    Here's an example showing how the wording presented right now for non-terminating errors is just odd:

    Confirm
    Cannot find a process with the process identifier 1234.
    [Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"):
    

    Confirm what? That it cannot find that process? And the help doesn't really help. Here's what that says:

    Y - Continue with only the next step of the operation.
    A - Continue with all the steps of the operation.
    H - Stop this command.
    S - Pause the current pipeline and return to the command prompt. Type "exit" to resume the pipeline.
    

    Is [Y] Yes really a step like it sounds in the description (i.e. "only the next step of the operation")? That's not accurate. Is [A] Yes to All really continuing "with all steps of the operation"? That's not entirely accurate either. Doesn't that just mean continue, like in the debugger?

    Plus where options can be consistent (i.e. Halt vs Break), they should be consistent.

    I'll think about this some more with the idea that users should be able to run a command without further prompts if desired. Maybe that should be an explicit option that is very clear in the choices: "Run to completion" (maybe ignoring Inquire prompts of any type at that point, not just for the specific message type that occurred).

  5. KirkMunro commented on May 29, 2019

    @KirkMunro
    ContributorAuthor

    Just to share more questions while they are top of mind, to drive this closer to a solution:

    1. What is the benefit of allowing users to Silently Continue an exception (suppressing the message on their screen)? When would you ever gain value from doing that based on ActionPreference.Inquire?
    2. Would this be a more rational, consistent set of options that could be applied both to terminating and non-terminating errors?
      [C] Continue [P] Continue without further prompts [S] Stop [D] Debug [?] Help (default is "C"):

    This answers your question about why they should have the same prompting (terminating vs non-terminating). You're right, Yes to All doesn't make sense for terminating. But Continue makes sense to both, and Continue without further prompts (or better wording) also makes sense to both.

    This also highlights the confusion with the wording, because what you guys meant by Silently Continue here may have been Continue without further prompts, but those are two different things, are they not? Because it wouldn't suppress errors like SilentlyContinue does...it would just stop the Inquire prompting from occurring.

  6. BrucePay commented on May 31, 2019

    @BrucePay
    Collaborator

    Kirk Munro (@KirkMunro)

    What is the benefit of allowing users to Silently Continue an exception

    If you're running a script and you're looking for the nth exception, skipping them until you get to the right one makes sense to me. But you could also Continue without much difference in effect.

    Would this be a more rational, consistent set of options that could be applied both ...

    "Continue without further prompts" doesn't make any sense. It's not going to continue the current operation because it was a terminating error. (BTW - the context of these prompts is the current command/pipeline, not the script running the pipeline.)

    Anyway, it sounds like there are significant bugs in the implementation. "We" :-) should probably fix the bugs first before deciding to change the behaviour based on a faulty impression of how things work.)

  7. KirkMunro commented on Jun 6, 2019

    @KirkMunro
    ContributorAuthor

    Bruce Payette (@BrucePay)

    ... it sounds like there are significant bugs in the implementation. "We" :-) should probably fix the bugs first before deciding to change the behaviour based on a faulty impression of how things work.

    I'm not completely in agreement with you on that point. I would suggest the following instead:

    1. "We" identify all of the intended current behaviours of ActionPreference.Inquire. I'm just talking about documentation here. Based on this discussion I think I'm closer to understanding the use cases, but it would be worth putting them to text to make sure it is all covered. You're right that we shouldn't make changes based on a faulty impression of how it works.
    2. "We" identify the current bugs in the implementation based on the intended current behaviours of the feature. There seem to be multiple significant issues right now.
    3. "We" revisit the original design and decide if it is still appropriate or not, talk about a new design, and based on that discussion determine if it is better to fix the current issues or just replace what's there with what we really want now.

    That last point is key for me because ActionPreference.Inquire pre-dates the PowerShell debugger, and I believe while it's design may have been well thought out for PowerShell 1.0, I feel it quickly became antiquated and of questionable value in PowerShell 2.0 and later. Since we're approaching PowerShell 7.0, it seems like a great time to turn this questionable-valued feature (that I even suggested in another discussion could probably be yanked from PowerShell, given it's very limited usefulness and buggy implementation) into something that people can and will use. I'd much prefer spending some time on an RFC on a new design that makes more sense for PowerShell 7 and implementing that design than waste time fixing bugs in a feature that "nobody uses" (I literally know no one -- 12 years of working with PowerShell in the community, and aside from when someone would run into ActionPreference.Inquire behavior as a result of -Debug, which has since been updated such that -Debug now just sets $DebugPreference to ActionPreference.Continue, I've never seen a single person explicitly use ActionPreference.Inquire).

    Thoughts?

  8. KirkMunro commented on Jun 10, 2019

    @KirkMunro
    ContributorAuthor

    Just to add a data point to this: I tweeted a question about ActionPreference.Inquire Thursday last week, which was retweeted a number of times, and not a single person responded. Literally, crickets, so I followed up on it, and have had some responses, confirming that they don't use the feature. It will be interesting to see if other responses show up or not...

  9. added this to the Future milestone on Jul 9, 2019
  10. 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.

  11. 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.

  12. 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.

  13. SamErde commented on Aug 13, 2024

    @SamErde

    Just to add a data point to this: I tweeted a question about ActionPreference.Inquire Thursday last week, which was retweeted a number of times, and not a single person responded. Literally, crickets, so I followed up on it, and have had some responses, confirming that they don't use the feature. It will be interesting to see if other responses show up or not...

    I came here looking for more information about how to effectively use -Inquire but it seems like [System.Management.Automation.Host.ChoiceDescription] and $Host.UI.PromptForChoice() may be more useful.

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