Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/System.Management.Automation/engine/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,46 @@ internal ActionPreference ErrorActionPreferenceVariable
}
}

internal ActionPreference NativeCommandExceptionPreferenceVariable
{
get
{
bool defaultUsed = false;
return this.GetEnumPreference<ActionPreference>(
SpecialVariables.NativeCommandExceptionPreferenceVarPath,
InitialSessionState.defaultNativeCommandExceptionPreference,
out defaultUsed);
}
set
{
this.EngineSessionState.SetVariable(
SpecialVariables.NativeCommandExceptionPreferenceVarPath,
LanguagePrimitives.ConvertTo(value, typeof(ActionPreference), CultureInfo.InvariantCulture),
true,
CommandOrigin.Internal);
}
}

internal ActionPreference NativeCommandPipeFailPreferenceVariable
{
get
{
bool defaultUsed = false;
return this.GetEnumPreference<ActionPreference>(
SpecialVariables.NativeCommandPipeFailPreferenceVarPath,
InitialSessionState.defaultNativeCommandPipeFailPreference,
out defaultUsed);
}
set
{
this.EngineSessionState.SetVariable(
SpecialVariables.NativeCommandPipeFailPreferenceVarPath,
LanguagePrimitives.ConvertTo(value, typeof(ActionPreference), CultureInfo.InvariantCulture),
true,
CommandOrigin.Internal);
}
}

internal ActionPreference WarningActionPreferenceVariable
{
get
Expand Down
16 changes: 16 additions & 0 deletions src/System.Management.Automation/engine/InitialSessionState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4820,6 +4820,8 @@ .ForwardHelpCategory Cmdlet

internal const ActionPreference defaultDebugPreference = ActionPreference.SilentlyContinue;
internal const ActionPreference defaultErrorActionPreference = ActionPreference.Continue;
internal const ActionPreference defaultNativeCommandExceptionPreference = ActionPreference.Continue;
internal const ActionPreference defaultNativeCommandPipeFailPreference = ActionPreference.SilentlyContinue;
internal const ActionPreference defaultProgressPreference = ActionPreference.Continue;
internal const ActionPreference defaultVerbosePreference = ActionPreference.SilentlyContinue;
internal const ActionPreference defaultWarningPreference = ActionPreference.Continue;
Expand Down Expand Up @@ -4876,6 +4878,20 @@ .ForwardHelpCategory Cmdlet
ScopedItemOptions.None,
new ArgumentTypeConverterAttribute(typeof(ActionPreference))
),
new SessionStateVariableEntry(
SpecialVariables.NativeCommandExceptionPreference,
defaultNativeCommandExceptionPreference,
RunspaceInit.NativeCommandExceptionPreferenceDescription,
ScopedItemOptions.None,
new ArgumentTypeConverterAttribute(typeof(ActionPreference))
),
new SessionStateVariableEntry(
SpecialVariables.NativeCommandPipeFailPreference,
defaultNativeCommandPipeFailPreference,
RunspaceInit.NativeCommandPipeFailPreferenceDescription,
ScopedItemOptions.None,
new ArgumentTypeConverterAttribute(typeof(ActionPreference))
),
new SessionStateVariableEntry(
SpecialVariables.ProgressPreference,
defaultProgressPreference,
Expand Down
73 changes: 73 additions & 0 deletions src/System.Management.Automation/engine/MshCommandRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3313,6 +3313,79 @@ internal ActionPreference InformationPreference

internal PagingParameters PagingParameters { get; set; }

private ActionPreference _nativeCommandException = InitialSessionState.defaultNativeCommandExceptionPreference;
private bool _isNativeCommandExceptionPreferenceCached = false;
/// <summary>
/// ErrorActNativeCommandException tells the command what to do when throwing a native command failure exception
/// </summary>
/// <exception cref="System.Management.Automation.ExtendedTypeSystemException">
/// (get-only) An error occurred accessing $NativeCommandException.
/// </exception>
internal ActionPreference NativeCommandException
{
get
{
// Setting CommonParameters.ErrorAction has highest priority
if (IsNativeCommandExceptionSet)
return _nativeCommandException;

// Do not exit if debugging
if (Debug)
return ActionPreference.Continue;

// fall back to $NativeCommandException
if (!_isNativeCommandExceptionPreferenceCached)
{
bool defaultUsed = false;
_errorAction = Context.GetEnumPreference<ActionPreference>(SpecialVariables.NativeCommandExceptionPreferenceVarPath, _nativeCommandException, out defaultUsed);
_isNativeCommandExceptionPreferenceCached = true;
}
return _nativeCommandException;
}
set
{
_nativeCommandException = value;
IsNativeCommandExceptionSet = true;
} // set
}

internal bool IsNativeCommandExceptionSet { get; private set; } = false;

private ActionPreference _nativeCommandPipeFail = InitialSessionState.defaultNativeCommandPipeFailPreference;
private bool _isNativeCommandPipeFailPreferenceCached = false;
/// <summary>
/// ErrorActNativeCommandException tells the command what to do when throwing a native command failure exception
/// </summary>
/// <exception cref="System.Management.Automation.ExtendedTypeSystemException">
/// (get-only) An error occurred accessing $NativeCommandException.
/// </exception>
internal ActionPreference NativeCommandPipeFail
{
get
{
// Setting CommonParameters.ErrorAction has highest priority
if (IsNativeCommandPipeFailSet)
return _nativeCommandPipeFail;

// fall back to $NativeCommandException
if (!_isNativeCommandPipeFailPreferenceCached)
{
bool defaultUsed = false;
_errorAction = Context.GetEnumPreference<ActionPreference>(SpecialVariables.NativeCommandPipeFailPreferenceVarPath, _nativeCommandException, out defaultUsed);
_isNativeCommandPipeFailPreferenceCached = true;
}
return _nativeCommandPipeFail;
}
set
{
_nativeCommandPipeFail = value;
IsNativeCommandPipeFailSet = true;
} // set
}

internal bool IsNativeCommandPipeFailSet { get; private set; } = false;


#endregion Preference

#region Continue/Confirm
Expand Down
36 changes: 32 additions & 4 deletions src/System.Management.Automation/engine/NativeCommandProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ internal NativeCommandProcessor(ApplicationInfo applicationInfo, ExecutionContex
_inputWriter = new ProcessInputWriter(Command);
}

/// <summary>
/// Handle failure exit code
/// </summary>
public bool HandleFailure { get; set; }

/// <summary>
/// Gets the NativeCommand associated with this command processor.
/// </summary>
Expand Down Expand Up @@ -371,7 +376,12 @@ internal override void ProcessRecord()
/// The pipeline is stopping
/// </exception>
/// <exception cref="ApplicationFailedException">
/// The native command could not be run
/// The native command was not succesful: either it failed to run or exited with a non-zero exit status
/// ApplicationFailedToCompleteException is thrown for a non-zero exit status if the relevant prreferences are set
/// ApplicationFailedToRunException indicates an exception occured trying to run the command.
/// </exception>
/// <exception cref="ExitException">
/// The comand exited with a non-zero exit status, and the relevat preferences are enabled
/// </exception>
private void InitNativeProcess()
{
Expand Down Expand Up @@ -574,7 +584,7 @@ private void InitNativeProcess()
string message = StringUtil.Format(ParserStrings.ProgramFailedToExecute,
this.NativeCommandName, exceptionToRethrow.Message,
this.Command.MyInvocation.PositionMessage);
ApplicationFailedException appFailedException = new ApplicationFailedException(message, exceptionToRethrow);
ApplicationFailedToRunException appFailedException = new ApplicationFailedToRunException(message, exceptionToRethrow);

// There is no need to set this exception here since this exception will eventually be caught by pipeline processor.
// this.commandRuntime.PipelineProcessor.ExecutionFailed = true;
Expand Down Expand Up @@ -660,6 +670,7 @@ private void ConsumeAvailableNativeProcessOutput(bool blocking)
internal override void Complete()
{
Exception exceptionToRethrow = null;
int exitCode=0;
try
{
if (_isRunningInBackground == false)
Expand Down Expand Up @@ -711,7 +722,11 @@ internal override void Complete()

this.Command.Context.SetVariable(SpecialVariables.LastExitCodeVarPath, _nativeProcess.ExitCode);
if (_nativeProcess.ExitCode != 0)
{
this.commandRuntime.PipelineProcessor.ExecutionFailed = true;
if(HandleFailure)
exitCode=_nativeProcess.ExitCode;
}
}
}
catch (Win32Exception e)
Expand Down Expand Up @@ -745,16 +760,29 @@ internal override void Complete()
string message = StringUtil.Format(ParserStrings.ProgramFailedToExecute,
this.NativeCommandName, exceptionToRethrow.Message,
this.Command.MyInvocation.PositionMessage);
ApplicationFailedException appFailedException = new ApplicationFailedException(message, exceptionToRethrow);
ApplicationFailedToRunException appFailedException = new ApplicationFailedToRunException(message, exceptionToRethrow);

// There is no need to set this exception here since this exception will eventually be caught by pipeline processor.
// this.commandRuntime.PipelineProcessor.ExecutionFailed = true;

throw appFailedException;
}
if (exitCode != 0)
{
ActionPreference errorPreference=this.Command.Context.NativeCommandExceptionPreferenceVariable;
// terminate with an ExitException
if (errorPreference==ActionPreference.Stop)
throw new ExitException(exitCode);
// throw a normal non-terminating (runtime) exception
string message = StringUtil.Format(ParserStrings.ProgramFailedToComplete,
this.NativeCommandName, $"Exit Code {exitCode}\n",
this.Command.MyInvocation.PositionMessage);
ApplicationFailedToCompleteException appFailedException =
new ApplicationFailedToCompleteException(message);
throw appFailedException;
}
}


#region Process cleanup with Child Process cleanup

/// <summary>
Expand Down
12 changes: 12 additions & 0 deletions src/System.Management.Automation/engine/SpecialVariables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ internal static class SpecialVariables
internal const string ErrorActionPreference = "ErrorActionPreference";
internal static readonly VariablePath ErrorActionPreferenceVarPath = new VariablePath(ErrorActionPreference);

internal const string NativeCommandExceptionPreference = "NativeCommandExceptionPreference";
internal static readonly VariablePath NativeCommandExceptionPreferenceVarPath = new VariablePath(NativeCommandExceptionPreference);

internal const string NativeCommandPipeFailPreference = "NativeCommandPipeFailPreference";
internal static readonly VariablePath NativeCommandPipeFailPreferenceVarPath = new VariablePath(NativeCommandPipeFailPreference);

internal const string ProgressPreference = "ProgressPreference";
internal static readonly VariablePath ProgressPreferenceVarPath = new VariablePath(ProgressPreference);

Expand Down Expand Up @@ -279,6 +285,8 @@ internal static class SpecialVariables
SpecialVariables.WarningPreference,
SpecialVariables.InformationPreference,
SpecialVariables.ConfirmPreference,
SpecialVariables.NativeCommandExceptionPreference,
SpecialVariables.NativeCommandPipeFailPreference,
};

internal static readonly Type[] PreferenceVariableTypes = {
Expand All @@ -289,6 +297,8 @@ internal static class SpecialVariables
/* WarningPreference */ typeof(ActionPreference),
/* InformationPreference */ typeof(ActionPreference),
/* ConfirmPreference */ typeof(ConfirmImpact),
/* NativePreference */ typeof(ActionPreference),
/* NativePreference */ typeof(ActionPreference),
};

// The following variables are created in every session w/ AllScope. We avoid creating local slots when we
Expand Down Expand Up @@ -356,5 +366,7 @@ internal enum PreferenceVariable
Warning = 13,
Information = 14,
Confirm = 15,
NativeCommandException = 15,
NativeCommandPipeFail = 15,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2032,6 +2032,16 @@ private void SetPreferenceVariables()
_localsTuple.SetPreferenceVariable(PreferenceVariable.Confirm,
_commandRuntime.Confirm ? ConfirmImpact.Low : ConfirmImpact.None);
}
if(_commandRuntime.IsNativeCommandExceptionSet)
{
_localsTuple.SetPreferenceVariable(PreferenceVariable.NativeCommandException,
_commandRuntime.NativeCommandException);
}
if(_commandRuntime.IsNativeCommandPipeFailSet)
{
_localsTuple.SetPreferenceVariable(PreferenceVariable.NativeCommandPipeFail,
_commandRuntime.NativeCommandPipeFail);
}
}

#region StopProcessing functionality for script cmdlets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,34 @@ private static CommandProcessorBase AddCommand(PipelineProcessor pipe,
commandProcessor.AddParameter(cpi);
}
}

if(isNativeCommand)
{
// Native commands should not throw errors where the exit code is tested (if/switch/loop) and expessions
// Notes:
// Might be optimised to check the pipelineAst once as the pipeline is constructed.
// Would need to be done in both InvokePipeline and in GetSteppablePipeline.
// Might be optimised to use a member variable on CommandBaseAst instead of the Ast checks below.
// e.g. A NativeFailureHandling member, populated by the parser when building the Ast.
// n.b. Would want to avoid giveing a different result for the OutNullCommand optimisation in InvokePipeline
PipelineAst pipelineAst=commandBaseAst.Parent as PipelineAst;
Diagnostics.Assert(pipelineAst != null, "the command should be in a pipeline");
var pipeContext=pipelineAst.Parent;
if (!(pipeContext is IfStatementAst || pipeContext is LabeledStatementAst || pipeContext is ExpressionAst))
{
NativeCommandProcessor nativeCommandProcessor=commandProcessor as NativeCommandProcessor;
Diagnostics.Assert(nativeCommandProcessor != null, "the native command processor hould be a NativeCommandProcessor");
ActionPreference errorPreference=context.NativeCommandPipeFailPreferenceVariable;
// check for native commadn failure on every command in the pipeline for preference Stop
// or only the last command in the pipeline for preference Continue
nativeCommandProcessor.HandleFailure =
errorPreference==ActionPreference.Stop
||
errorPreference==ActionPreference.Continue&&
commandBaseAst==
pipelineAst.PipelineElements[pipelineAst.PipelineElements.Count-1];
}

}
string helpTarget;
HelpCategory helpCategory;
if (commandProcessor.IsHelpRequested(out helpTarget, out helpCategory))
Expand Down
3 changes: 3 additions & 0 deletions src/System.Management.Automation/resources/ParserStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,9 @@ Possible matches are</value>
<data name="ProgramFailedToExecute" xml:space="preserve">
<value>Program '{0}' failed to run: {1}{2}.</value>
</data>
<data name="ProgramFailedToComplete" xml:space="preserve">
<value>Program '{0}' failed: {1}{2}.</value>
</data>
<data name="CantInvokeInBinaryModule" xml:space="preserve">
<value>Cannot use '&amp;' to invoke in the context of binary module '{0}'. Specify a non-binary module after the '&amp;' and try the operation again.</value>
</data>
Expand Down
6 changes: 6 additions & 0 deletions src/System.Management.Automation/resources/RunspaceInit.resx
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@
<data name="ErrorActionPreferenceDescription" xml:space="preserve">
<value>Dictates the action taken when an error message is delivered</value>
</data>
<data name="NativeCommandExceptionPreferenceDescription" xml:space="preserve">
<value>Dictates whether the exception thrown for a native command failure is non-terminating or terminating</value>
</data>
<data name="NativeCommandPipeFailPreferenceDescription" xml:space="preserve">
<value>Dictates if an exception is thrown when an native command returns a non-zero exit code</value>
</data>
<data name="ProgressPreferenceDescription" xml:space="preserve">
<value>Dictates the action taken when progress records are delivered</value>
</data>
Expand Down
Loading