-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Initial impl of RFC-277 - native exe throws on non-zero exit code #15757
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
Changes from all commits
3bf31dc
aa6d8dc
fc7037e
879c138
b270a58
2d87745
9ba3982
e60a4c0
67cdd1c
79fdcfd
73d0535
1ca8d91
949608e
31f958d
a9be5fc
9a40ae4
3990835
68e0506
f00e494
20ffbe5
a9c54e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3,21 +3,22 @@ | |||||
|
|
||||||
| #pragma warning disable 1634, 1691 | ||||||
|
|
||||||
| using System.Collections; | ||||||
| using System.Collections.Concurrent; | ||||||
| using System.Collections.Generic; | ||||||
| using System.ComponentModel; | ||||||
| using System.Diagnostics; | ||||||
| using System.Diagnostics.CodeAnalysis; | ||||||
| using System.Globalization; | ||||||
| using System.IO; | ||||||
| using System.ComponentModel; | ||||||
| using System.Management.Automation.Internal; | ||||||
| using System.Management.Automation.Runspaces; | ||||||
| using System.Runtime.InteropServices; | ||||||
| using System.Runtime.Serialization; | ||||||
| using System.Text; | ||||||
| using System.Collections; | ||||||
| using System.Threading; | ||||||
| using System.Management.Automation.Internal; | ||||||
| using System.Xml; | ||||||
| using System.Runtime.InteropServices; | ||||||
| using Dbg = System.Management.Automation.Diagnostics; | ||||||
| using System.Runtime.Serialization; | ||||||
| using System.Globalization; | ||||||
| using System.Diagnostics.CodeAnalysis; | ||||||
| using System.Collections.Concurrent; | ||||||
| using System.Collections.Generic; | ||||||
|
|
||||||
| namespace System.Management.Automation | ||||||
| { | ||||||
|
|
@@ -130,6 +131,120 @@ internal ProcessOutputObject(object data, MinishellStream stream) | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// This exception is used by the NativeCommandProcessor to indicate an error | ||||||
| /// when a native command retuns a non-zero exit code. | ||||||
| /// </summary> | ||||||
| [Serializable] | ||||||
| public class NativeCommandExitException : RuntimeException | ||||||
| { | ||||||
| // NOTE: | ||||||
| // When implementing the native error action preference integration, | ||||||
| // reusing ApplicationFailedException was contemplated. | ||||||
| // However, rather than possibly reusing a type already used in another scenario | ||||||
| // it was decided instead to use a fresh type to minimize the chance of conflating the two scenarios. | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Gets the path of the native command. | ||||||
| /// </summary> | ||||||
| public string Path { get; private set; } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Gets the exit code returned by the native command. | ||||||
| /// </summary> | ||||||
| public int ExitCode { get; private set; } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Gets the native command's process Id. | ||||||
| /// </summary> | ||||||
| public int ProcessId { get; private set; } | ||||||
|
|
||||||
| #region Constructors | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Initializes a new instance of the <see cref="NativeCommandExitException"/> class with information on the native | ||||||
| /// command, a specified error message and a specified error ID. | ||||||
| /// </summary> | ||||||
| /// <param name="path">The full path of the native command.</param> | ||||||
| /// <param name="exitCode">The exit code returned by the native command.</param> | ||||||
| /// <param name="processId">The process ID of the process before it ended.</param> | ||||||
| /// <param name="message">The error message.</param> | ||||||
| /// <param name="errorId">The error ID.</param> | ||||||
| internal NativeCommandExitException(string path, int exitCode, int processId, string message, string errorId) | ||||||
| : base(message) | ||||||
| { | ||||||
| SetErrorId(errorId); | ||||||
| SetErrorCategory(ErrorCategory.NotSpecified); | ||||||
| Path = path; | ||||||
| ExitCode = exitCode; | ||||||
| ProcessId = processId; | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Initializes a new instance of the <see cref="NativeCommandExitException"/> class. | ||||||
| /// </summary> | ||||||
| public NativeCommandExitException() : base() { } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Initializes a new instance of the <see cref="NativeCommandExitException"/> class with a specified error message. | ||||||
| /// </summary> | ||||||
| /// <param name="message"> | ||||||
| /// A localized error message. | ||||||
| /// </param> | ||||||
| public NativeCommandExitException(string message) : base(message) { } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Initializes a new instance of the <see cref="NativeCommandExitException"/> class with a specified error message | ||||||
| /// and a reference to the inner exception that is the cause of this exception. | ||||||
| /// </summary> | ||||||
| /// <param name="message"> | ||||||
| /// Localized error message. | ||||||
| /// </param> | ||||||
| /// <param name="innerException"> | ||||||
| /// Inner exception. | ||||||
| /// </param> | ||||||
| public NativeCommandExitException(string message, Exception innerException) : base(message, innerException) { } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Initializes a new instance of the <see cref="NativeCommandExitException"/> class with serialized data. | ||||||
| /// </summary> | ||||||
| /// <param name="info"></param> | ||||||
| /// <param name="context"></param> | ||||||
| protected NativeCommandExitException(SerializationInfo info, StreamingContext context) | ||||||
| : base(info, context) | ||||||
| { | ||||||
| if (info == null) | ||||||
| { | ||||||
| throw new PSArgumentNullException(nameof(info)); | ||||||
| } | ||||||
|
|
||||||
| Path = info.GetString(nameof(Path)); | ||||||
| ExitCode = info.GetInt32(nameof(ExitCode)); | ||||||
| ProcessId = info.GetInt32(nameof(ProcessId)); | ||||||
| } | ||||||
|
|
||||||
| #endregion Constructors | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Serializes the exception data. | ||||||
| /// </summary> | ||||||
| /// <param name="info">Serialization information.</param> | ||||||
| /// <param name="context">Streaming context.</param> | ||||||
| public override void GetObjectData(SerializationInfo info, StreamingContext context) | ||||||
| { | ||||||
| if (info == null) | ||||||
| { | ||||||
| throw new PSArgumentNullException(nameof(info)); | ||||||
| } | ||||||
|
|
||||||
| base.GetObjectData(info, context); | ||||||
|
|
||||||
| info.AddValue(nameof(Path), Path); | ||||||
| info.AddValue(nameof(ExitCode), ExitCode); | ||||||
| info.AddValue(nameof(ProcessId), ProcessId); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Provides way to create and execute native commands. | ||||||
| /// </summary> | ||||||
|
|
@@ -762,8 +877,51 @@ internal override void Complete() | |||||
| } | ||||||
|
|
||||||
| this.Command.Context.SetVariable(SpecialVariables.LastExitCodeVarPath, _nativeProcess.ExitCode); | ||||||
| if (_nativeProcess.ExitCode != 0) | ||||||
| this.commandRuntime.PipelineProcessor.ExecutionFailed = true; | ||||||
| if (_nativeProcess.ExitCode == 0) | ||||||
| { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| this.commandRuntime.PipelineProcessor.ExecutionFailed = true; | ||||||
|
|
||||||
| bool nativeCommandUseErrorActionPreference = ExperimentalFeature.IsEnabled(ExperimentalFeature.PSNativeCommandErrorActionPreferenceFeatureName) | ||||||
| && this.Command.Context.GetBooleanPreference( | ||||||
|
Comment on lines
+885
to
+888
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need the "this" prefix? Can we cleanup all in the PR (new and changed code only)? |
||||||
| SpecialVariables.PSNativeCommandUseErrorActionPreferenceVarPath, | ||||||
| InitialSessionState.DefaultPSNativeCommandUseErrorActionPreference, | ||||||
| out _); | ||||||
|
|
||||||
| if (!nativeCommandUseErrorActionPreference) | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer roll out the variable here and remove the help variable. This will simplify switching from experimental (less code changes). |
||||||
| { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| ActionPreference errorActionPref = | ||||||
| this.Command.Context.GetEnumPreference( | ||||||
| SpecialVariables.ErrorActionPreferenceVarPath, | ||||||
| ActionPreference.SilentlyContinue, | ||||||
| out _); | ||||||
|
|
||||||
| if (errorActionPref != ActionPreference.Ignore) | ||||||
| { | ||||||
| const string errorId = "ProgramFailedToComplete"; | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code convention for consts.
Suggested change
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Didn't we discuss that for local consts camelCase is preferred? |
||||||
|
|
||||||
| string errorMsg = | ||||||
| StringUtil.Format( | ||||||
| CommandBaseStrings.ProgramFailedToComplete, | ||||||
| this.NativeCommandName, | ||||||
| _nativeProcess.ExitCode); | ||||||
|
|
||||||
| var exception = | ||||||
| new NativeCommandExitException( | ||||||
| this.Path, | ||||||
| _nativeProcess.ExitCode, | ||||||
| _nativeProcess.Id, | ||||||
| errorMsg, | ||||||
| errorId); | ||||||
|
|
||||||
| var errorRecord = new ErrorRecord(exception, errorId, ErrorCategory.NotSpecified, null); | ||||||
| this.commandRuntime._WriteErrorSkipAllowCheck(errorRecord); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| catch (Win32Exception e) | ||||||
|
|
@@ -1087,7 +1245,7 @@ private void ProcessOutputRecord(ProcessOutputObject outputValue) | |||||
| ErrorRecord record = outputValue.Data as ErrorRecord; | ||||||
| Dbg.Assert(record != null, "ProcessReader should ensure that data is ErrorRecord"); | ||||||
| record.SetInvocationInfo(this.Command.MyInvocation); | ||||||
| this.commandRuntime._WriteErrorSkipAllowCheck(record, isNativeError: true); | ||||||
| this.commandRuntime._WriteErrorSkipAllowCheck(record, isNativeStdErrCall: true); | ||||||
| } | ||||||
| else if (outputValue.Stream == MinishellStream.Output) | ||||||
| { | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -255,6 +255,11 @@ internal static class SpecialVariables | |||||
|
|
||||||
| internal static readonly VariablePath InformationPreferenceVarPath = new VariablePath(InformationPreference); | ||||||
|
|
||||||
| internal const string PSNativeCommandUseErrorActionPreference = nameof(PSNativeCommandUseErrorActionPreference); | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can confuse a bit
Suggested change
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the other variables are named this way though, so convention has established this |
||||||
|
|
||||||
| internal static readonly VariablePath PSNativeCommandUseErrorActionPreferenceVarPath = | ||||||
| new VariablePath(PSNativeCommandUseErrorActionPreference); | ||||||
|
|
||||||
| #endregion Preference Variables | ||||||
|
|
||||||
| // Native command argument passing style | ||||||
|
|
@@ -321,25 +326,29 @@ internal static class SpecialVariables | |||||
| /* PSCommandPath */ typeof(string), | ||||||
| }; | ||||||
|
|
||||||
| internal static readonly string[] PreferenceVariables = { | ||||||
| SpecialVariables.DebugPreference, | ||||||
| SpecialVariables.VerbosePreference, | ||||||
| SpecialVariables.ErrorActionPreference, | ||||||
| SpecialVariables.WhatIfPreference, | ||||||
| SpecialVariables.WarningPreference, | ||||||
| SpecialVariables.InformationPreference, | ||||||
| SpecialVariables.ConfirmPreference, | ||||||
| }; | ||||||
|
|
||||||
| internal static readonly Type[] PreferenceVariableTypes = { | ||||||
| /* DebugPreference */ typeof(ActionPreference), | ||||||
| /* VerbosePreference */ typeof(ActionPreference), | ||||||
| /* ErrorPreference */ typeof(ActionPreference), | ||||||
| /* WhatIfPreference */ typeof(SwitchParameter), | ||||||
| /* WarningPreference */ typeof(ActionPreference), | ||||||
| /* InformationPreference */ typeof(ActionPreference), | ||||||
| /* ConfirmPreference */ typeof(ConfirmImpact), | ||||||
| }; | ||||||
| internal static readonly string[] PreferenceVariables = | ||||||
| { | ||||||
| SpecialVariables.DebugPreference, | ||||||
| SpecialVariables.VerbosePreference, | ||||||
| SpecialVariables.ErrorActionPreference, | ||||||
| SpecialVariables.WhatIfPreference, | ||||||
| SpecialVariables.WarningPreference, | ||||||
| SpecialVariables.InformationPreference, | ||||||
| SpecialVariables.ConfirmPreference, | ||||||
| SpecialVariables.PSNativeCommandUseErrorActionPreference, | ||||||
| }; | ||||||
|
|
||||||
| internal static readonly Type[] PreferenceVariableTypes = | ||||||
| { | ||||||
| /* DebugPreference */ typeof(ActionPreference), | ||||||
| /* VerbosePreference */ typeof(ActionPreference), | ||||||
| /* ErrorPreference */ typeof(ActionPreference), | ||||||
| /* WhatIfPreference */ typeof(SwitchParameter), | ||||||
| /* WarningPreference */ typeof(ActionPreference), | ||||||
| /* InformationPreference */ typeof(ActionPreference), | ||||||
| /* ConfirmPreference */ typeof(ConfirmImpact), | ||||||
| /* PSNativeCommandUseErrorActionPreference */ typeof(bool), | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we follow history pattern and do not use "PS" prefix?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was decided in the RFC though |
||||||
| }; | ||||||
|
|
||||||
| // The following variables are created in every session w/ AllScope. We avoid creating local slots when we | ||||||
| // see an assignment to any of these variables so that they get handled properly (either throwing an exception | ||||||
|
|
@@ -406,5 +415,6 @@ internal enum PreferenceVariable | |||||
| Warning = 13, | ||||||
| Information = 14, | ||||||
| Confirm = 15, | ||||||
| PSNativeCommandUseErrorAction = 16, | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -156,6 +156,15 @@ | |||||
| <data name="PauseHelpMessage" xml:space="preserve"> | ||||||
| <value>Pause the current pipeline and return to the command prompt. Type "{0}" to resume the pipeline.</value> | ||||||
| </data> | ||||||
| <data name="ProgramFailedToComplete" xml:space="preserve"> | ||||||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a ProgramFailedToExecute resource string in
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems it is not used at all.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is still used ParserStrings.CantActivateDocumentInPowerShellCore with InterpreterError.NewInterpreterException.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Worth adding a comment in the elements added in this PR to note that despite those things pre-existing, we added new ones to be specifically for the native error action preference feature to ensure it didn't accidentally clobber any existing concepts.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding this in my PR
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need to open new issue in Docs repo to explain |
||||||
| <!-- NOTE: | ||||||
| This string was added for the native command error action preference integration feature. | ||||||
| ParserStrings already declares a ProgramFailedToExecute string, | ||||||
| however that is used for ApplicationFailedExceptions thrown when the NativeCommandProcessor fails in an unexpected way. | ||||||
| In this case, we have a more specific error for the native command scenario, so the two are not conflated. | ||||||
| --> | ||||||
| <value>Program "{0}" ended with non-zero exit code {1}.</value> | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| </data> | ||||||
| <data name="ShouldProcessMessage" xml:space="preserve"> | ||||||
| <value>Performing the operation "{0}" on target "{1}".</value> | ||||||
| </data> | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -186,6 +186,9 @@ | |||||
| <data name="NestedPromptLevelDescription" xml:space="preserve"> | ||||||
| <value>Dictates what type of prompt should be displayed for the current nesting level</value> | ||||||
| </data> | ||||||
| <data name="PSNativeCommandUseErrorActionPreferenceDescription" xml:space="preserve"> | ||||||
| <value>If true, $ErrorActionPreference applies to native executables, so that non-zero exit codes will generate cmdlet-style errors governed by error action settings</value> | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably need:
Suggested change
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None of the other messages here end with
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are both cases. We need use right. But I don't know what case is right :-) |
||||||
| </data> | ||||||
| <data name="WhatIfPreferenceDescription" xml:space="preserve"> | ||||||
| <value>If true, WhatIf is considered to be enabled for all commands.</value> | ||||||
| </data> | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change is not related to the PR. Please revert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it's directly related to it. We now call this API from multiple native calls, but that parameter was only added for a specific scenario (the stderr scenario).
The actual issue is that we'll likely be removing it soon as the experimental feature that disables it is stabilised.