-
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
Initial impl of RFC-277 - native exe throws on non-zero exit code #15757
Conversation
src/System.Management.Automation/engine/NativeCommandProcessor.cs
Outdated
Show resolved
Hide resolved
| Warning = 13, | ||
| Information = 14, | ||
| Confirm = 15, | ||
| NativeCommandThrow = 16, |
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.
I'm not sure if this preference needs to be defined here.
|
There are internal APIs to get/set pref vars in |
|
Assigning myself since I've been tasked with taking this work on, but since you've already started @rkeithhill maybe I can support you here instead |
|
@rjmholt That would be great! I'd love to have your input on this. I need to update this PR as I started it off from my memory of the RFC and now I see that (as @iSazonov points out), I need to be checking the value of |
src/System.Management.Automation/engine/NativeCommandProcessor.cs
Outdated
Show resolved
Hide resolved
| <value>Dictates what type of prompt should be displayed for the current nesting level</value> | ||
| </data> | ||
| <data name="NativeCommandUseErrorActionPreferenceDescription" xml:space="preserve"> | ||
| <value>If true, $ErrorActionPreference applies to native executable non-zero exit codes</value> |
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.
Could use some help with this wording.
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.
I think what you've got is pretty good -- hard to word. One alternative:
If true, $ErrorActionPreference will apply to native executables, so that non-zero exit codes will generate cmdlet-style errors governed by error action settings.
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.
@rjmholt Can you give an example of where the -ErrorAction parameter would come into play? I've tried iex "whoami -xyzzy" -ea Stop and icm { whoami -xyzzy } -ea Stop. If I check the value of $ErrorActionPreference with the iex string or the icm scriptbock, the -ErrorAction parameter does not impact the value or pref variable.
PS> icm {
"`$ErrorActionPreference is $ErrorActionPreference"
$PSNativeCommandUseErrorActionPreference=$true
whoami -xyzzy
} -ErrorAction Stop
$ErrorActionPreference is Continue
ERROR: Invalid argument/option - '-xyzzy'.
Type "WHOAMI /?" for usage.
NativeCommandException: Program "whoami.exe" ended with non-zero exit code 1.
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.
No you're right, there's no -ErrorAction parameter here. Just the preference variable
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.
Edited my suggestion above to correct it
cf30d87 to
fc7037e
Compare
src/System.Management.Automation/engine/NativeCommandProcessor.cs
Outdated
Show resolved
Hide resolved
src/System.Management.Automation/engine/NativeCommandProcessor.cs
Outdated
Show resolved
Hide resolved
| errorId: "NativeCommandThrow", | ||
| errorCategory: ErrorCategory.NotSpecified, | ||
| targetObject: this.NativeCommandName); | ||
| this.commandRuntime._WriteErrorSkipAllowCheck(errorRecord, isNativeError: true); |
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.
Since we have isNativeError flag why not move the new code to _WriteErrorSkipAllowCheck()?
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.
I'll look into that.
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.
Yeah this was kind of a complicated question to answer, but I think it's worth us taking a look
| <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"> |
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.
There is a ProgramFailedToExecute resource string in ParserStrings.resx but that doesn't seem to be quite right for this runtime error. Is CommandBaseStrings.resx the correct file for this resource sting?
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.
It seems it is not used at all.
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.
ProgramFailedToExecute is used in the same NativeCommandProcessor.cs file during the creation of another exception ApplicationFailedException that I thiink covers the general case of native command execution failure. This does make me wonder if someone looking at the code in the future is going to wonder why there's both an ApplicationFailedException and a NativeCommandException.
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.
There is still used ParserStrings.CantActivateDocumentInPowerShellCore with InterpreterError.NewInterpreterException.
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.
This does make me wonder if someone looking at the code in the future is going to wonder why there's both an ApplicationFailedException and a NativeCommandException.
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.
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.
Adding this in my PR
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.
I think we need to open new issue in Docs repo to explain ApplicationFailedException (PowerShell can not run normally an non-backgroud application) vs NativeCommandException (PowerShell could be able to run non-backgraund application but throw due to non zero return code).
| # when $PSNativeCommandUseErrorActionPreference is $true | ||
|
|
||
| Describe 'Native command error handling tests' -Tags 'CI' { | ||
| Context 'PSNativeCommandUseErrorActionPreference is $true' { |
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.
I'd combine tests for true and false (and maybe for $ErrorActionPreference values) and use TestCases.
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 only things I would add here are:
- Testing on the properties of the exception that we're supposed to implement, if they're available beyond the error message
- A test with a try/catch
- A test with a trap
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.
Adding these in my PR
Allow native errors from exit codes to throw properly
|
@rjmholt The current build checks seem to be stuck. Is there a way I restart them short of pushing a bogus commit? Nevermind - resolved the merge conflict and that has restarted the build. |
|
Test failures are very weird... /azp run |
|
@rjmholt There is a test failure in output rendering tests which I don't believe this PR should impact: It's balking on this last line here: Anything wrong with that? It's a bit different than how you skipped the tests for this PR when the exp feature is not enabled. Hmm, it seems that the following approach to skipping a test is way more prevalent in the tests vs what the Output Rendering test does: Describe "Get-HotFix Tests" -Tag CI {
BeforeAll {
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
$skip = $false
if (!$IsWindows) {
$skip = $true
}
else {
# skip the tests if there are no hotfixes returned
$qfe = Get-CimInstance Win32_QuickFixEngineering
if ($qfe.Count -eq 0) {
$skip = $true
}
}
$PSDefaultParameterValues["it:skip"] = $skip
}
AfterAll {
$global:PSDefaultParameterValues = $originalDefaultParameterValues
} |
| /* WarningPreference */ typeof(ActionPreference), | ||
| /* InformationPreference */ typeof(ActionPreference), | ||
| /* ConfirmPreference */ typeof(ConfirmImpact), | ||
| /* PSNativeCommandUseErrorActionPreference */ typeof(SwitchParameter), |
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.
This should be the same type as the builtin variable listing in InitialSessionState.cs
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.
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.
| /* PSNativeCommandUseErrorActionPreference */ typeof(SwitchParameter), | |
| /* PSNativeCommandUseErrorActionPreference */ typeof(bool), |
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.
Adding this in my PR
|
@rjmholt Woohoo! All checks pass! That said, we might want to think about whether the tests I've added are adequate. |
|
@rkeithhill - Thank you for all the hard work. I just spoke with @rjmholt and he thinking he might open a separate pr to add a couple of tests. |
|
Sounds good to me. I wouldn't be able to do anything until later this evening and even then I don't have a ton of time before I'm out of vacation next week. |
Native error tweaks + extra tests
|
Merged. |
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
| /// <see cref="System.Management.Automation.ActionPreferenceStopException"/>, | ||
| /// </remarks> | ||
| internal void _WriteErrorSkipAllowCheck(ErrorRecord errorRecord, ActionPreference? actionPreference = null, bool isNativeError = false) | ||
| internal void _WriteErrorSkipAllowCheck(ErrorRecord errorRecord, ActionPreference? actionPreference = null, bool isNativeStdErrCall = false) |
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.
| this.commandRuntime.PipelineProcessor.ExecutionFailed = true; | ||
|
|
||
| bool nativeCommandUseErrorActionPreference = ExperimentalFeature.IsEnabled(ExperimentalFeature.PSNativeCommandErrorActionPreferenceFeatureName) | ||
| && this.Command.Context.GetBooleanPreference( |
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.
Why do we need the "this" prefix? Can we cleanup all in the PR (new and changed code only)?
| InitialSessionState.DefaultPSNativeCommandUseErrorActionPreference, | ||
| out _); | ||
|
|
||
| if (!nativeCommandUseErrorActionPreference) |
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.
I'd prefer roll out the variable here and remove the help variable. This will simplify switching from experimental (less code changes).
|
|
||
| if (errorActionPref != ActionPreference.Ignore) | ||
| { | ||
| const string errorId = "ProgramFailedToComplete"; |
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.
Code convention for consts.
| const string errorId = "ProgramFailedToComplete"; | |
| const string ErrorId = "ProgramFailedToComplete"; |
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.
Didn't we discuss that for local consts camelCase is preferred?
|
|
||
| internal static readonly VariablePath InformationPreferenceVarPath = new VariablePath(InformationPreference); | ||
|
|
||
| internal const string PSNativeCommandUseErrorActionPreference = nameof(PSNativeCommandUseErrorActionPreference); |
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.
This can confuse a bit
| internal const string PSNativeCommandUseErrorActionPreference = nameof(PSNativeCommandUseErrorActionPreference); | |
| internal const string PSNativeCommandUseErrorActionPreferenceName = nameof(PSNativeCommandUseErrorActionPreference); |
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.
All the other variables are named this way though, so convention has established this
| <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"> |
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.
I think we need to open new issue in Docs repo to explain ApplicationFailedException (PowerShell can not run normally an non-backgroud application) vs NativeCommandException (PowerShell could be able to run non-backgraund application but throw due to non zero return code).
| 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> |
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.
| <value>Program "{0}" ended with non-zero exit code {1}.</value> | |
| <value>Program "{0}" ended with non-zero exit code: {1}.</value> |
| <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> |
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.
Probably need:
| <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> | |
| <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> |
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.
None of the other messages here end with .
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.
There are both cases. We need use right. But I don't know what case is right :-)
| It 'Non-zero exit code throws teminating error for $ErrorActionPreference = ''Stop''' { | ||
| $ErrorActionPreference = 'Stop' | ||
|
|
||
| { testexe -returncode 1 } | Should -Throw -ErrorId 'ProgramFailedToComplete' |
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 test shows the name is not good - the utility really executed and completed but return non-zero return code.
I think we need better name like NativeCommandReturnNonZeroReturnCode.
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.
Yeah that's a fair point. @theJasonHelmick, @rkeithhill we should consider renaming this error ID
| if ($ErrorActionPref -eq 'Stop') { | ||
| { testexe -returncode 0 } | Should -Not -Throw | ||
| } | ||
| else { | ||
| testexe -returncode 0 > $null | ||
| } |
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.
I wonder why do we need separate branch for Stop and why do we need to write to null?
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.
Yeah it looks like we should just merge these cases. Ideally we should have some test on the output and last exit code.
|
Opened rkeithhill#22 (@rkeithhill) |
|
Closing in favour of #15897 because I needed to rebase the branch and can't edit this one (@rkeithhill you can turn that on in future if you'd like) |


PR Summary
Implements a terminating error when a native executable returns a non-zero exit code similar to
set -eon Linux and macOS.PR Context
Implement RFC 277
PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:or[ WIP ]to the beginning of the title (theWIPbot will keep its status check atPendingwhile the prefix is present) and remove the prefix when the PR is ready.(which runs in a different PS Host).
This PR does impact the UseDeclaredVarsMoreThanAssignments PSSA rule.
PSNativeCommandUseErrorActionPreferenceneeds to be excluded from this rule.