diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs index 5c1b61f3601..a9192ae2da9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs @@ -351,7 +351,7 @@ public SwitchParameter BreakAll } /// - /// The optional breakpoint objects to use for debugging. + /// Gets or sets the optional breakpoint objects to use for debugging. /// [Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)] [Parameter(Position = 1, @@ -555,19 +555,11 @@ protected override void EndProcessing() { Runspace currentRunspace = this.Context.CurrentRunspace; - if ((currentRunspace != null) && (currentRunspace.Debugger != null)) + if (currentRunspace != null && currentRunspace.Debugger != null) { - if (!currentRunspace.Debugger.IsDebugHandlerSubscribed && - (currentRunspace.Debugger.UnhandledBreakpointMode == UnhandledBreakpointProcessingMode.Ignore)) - { - // No debugger attached and runspace debugging is not enabled. Enable runspace debugging here - // so that this command is effective. - currentRunspace.Debugger.UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Wait; - } - - // Set debugger to step mode so that a break occurs immediately. - currentRunspace.Debugger.SetDebuggerStepMode(true); WriteVerbose(string.Format(CultureInfo.InvariantCulture, Debugger.DebugBreakMessage, MyInvocation.ScriptLineNumber, MyInvocation.ScriptName)); + + currentRunspace.Debugger.Break(); } } diff --git a/src/System.Management.Automation/engine/CommandBase.cs b/src/System.Management.Automation/engine/CommandBase.cs index 08500961cfc..8848d86f3d9 100644 --- a/src/System.Management.Automation/engine/CommandBase.cs +++ b/src/System.Management.Automation/engine/CommandBase.cs @@ -277,18 +277,26 @@ namespace System.Management.Automation public enum ActionPreference { /// Ignore this event and continue - SilentlyContinue, + SilentlyContinue = 0, + /// Stop the command - Stop, + Stop = 1, + /// Handle this event as normal and continue - Continue, + Continue = 2, + /// Ask whether to stop or continue - Inquire, + Inquire = 3, + /// Ignore the event completely (not even logging it to the target stream) - Ignore, + Ignore = 4, + /// Suspend the command for further diagnosis. Supported only for workflows. - Suspend, - } + Suspend = 5, + + /// Enter the debugger. + Break = 6, + } // enum ActionPreference #endregion ActionPreference #region ConfirmImpact diff --git a/src/System.Management.Automation/engine/ExecutionContext.cs b/src/System.Management.Automation/engine/ExecutionContext.cs index ff06791f3a7..45cb3bd7fa5 100644 --- a/src/System.Management.Automation/engine/ExecutionContext.cs +++ b/src/System.Management.Automation/engine/ExecutionContext.cs @@ -570,9 +570,7 @@ internal void SetVariable(VariablePath path, object newValue) internal T GetEnumPreference(VariablePath preferenceVariablePath, T defaultPref, out bool defaultUsed) { - CmdletProviderContext context = null; - SessionStateScope scope = null; - object val = EngineSessionState.GetVariableValue(preferenceVariablePath, out context, out scope); + object val = EngineSessionState.GetVariableValue(preferenceVariablePath, out _, out _); if (val is T) { // We don't want to support "Ignore" as action preferences, as it leads to bad @@ -1048,7 +1046,7 @@ internal ActionPreference DebugPreferenceVariable get { bool defaultUsed = false; - return this.GetEnumPreference( + return this.GetEnumPreference( SpecialVariables.DebugPreferenceVarPath, InitialSessionState.defaultDebugPreference, out defaultUsed); @@ -1069,7 +1067,7 @@ internal ActionPreference VerbosePreferenceVariable get { bool defaultUsed = false; - return this.GetEnumPreference( + return this.GetEnumPreference( SpecialVariables.VerbosePreferenceVarPath, InitialSessionState.defaultVerbosePreference, out defaultUsed); @@ -1090,7 +1088,7 @@ internal ActionPreference ErrorActionPreferenceVariable get { bool defaultUsed = false; - return this.GetEnumPreference( + return this.GetEnumPreference( SpecialVariables.ErrorActionPreferenceVarPath, InitialSessionState.defaultErrorActionPreference, out defaultUsed); @@ -1111,7 +1109,7 @@ internal ActionPreference WarningActionPreferenceVariable get { bool defaultUsed = false; - return this.GetEnumPreference( + return this.GetEnumPreference( SpecialVariables.WarningPreferenceVarPath, InitialSessionState.defaultWarningPreference, out defaultUsed); @@ -1132,7 +1130,7 @@ internal ActionPreference InformationActionPreferenceVariable get { bool defaultUsed = false; - return this.GetEnumPreference( + return this.GetEnumPreference( SpecialVariables.InformationPreferenceVarPath, InitialSessionState.defaultInformationPreference, out defaultUsed); @@ -1178,7 +1176,7 @@ internal ConfirmImpact ConfirmPreferenceVariable get { bool defaultUsed = false; - return this.GetEnumPreference( + return this.GetEnumPreference( SpecialVariables.ConfirmPreferenceVarPath, InitialSessionState.defaultConfirmPreference, out defaultUsed); diff --git a/src/System.Management.Automation/engine/MshCommandRuntime.cs b/src/System.Management.Automation/engine/MshCommandRuntime.cs index ed7468cf56b..07208dc3435 100644 --- a/src/System.Management.Automation/engine/MshCommandRuntime.cs +++ b/src/System.Management.Automation/engine/MshCommandRuntime.cs @@ -5,13 +5,12 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Threading; -using System.Security; using System.Management.Automation.Host; -using System.Management.Automation.Internal.Host; using System.Management.Automation.Internal; +using System.Management.Automation.Internal.Host; using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; +using System.Threading; using Dbg = System.Management.Automation.Diagnostics; @@ -269,6 +268,12 @@ public void WriteObject(object sendToPipeline, bool enumerateCollection) #endif } + /// + /// Writes an object enumerated from a collection to the output pipe. + /// + /// + /// The enumerated object that needs to be written to the pipeline. + /// /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -276,7 +281,7 @@ public void WriteObject(object sendToPipeline, bool enumerateCollection) /// to percolate up to the caller of ProcessRecord etc. /// /// - /// Not permitted at this time or from this thread + /// Not permitted at this time or from this thread. /// private void DoWriteEnumeratedObject(object sendToPipeline) { @@ -411,6 +416,12 @@ internal void WriteProgress( if (WriteHelper_ShouldWrite( preference, lastProgressContinueStatus)) { + // Break into the debugger if requested + if (preference == ActionPreference.Break) + { + CBhost?.Runspace?.Debugger?.Break(progressRecord); + } + ui.WriteProgress(sourceId, progressRecord); } @@ -476,6 +487,12 @@ internal void WriteDebug(DebugRecord record, bool overrideInquire = false) record.SetInvocationInfo(MyInvocation); } + // Break into the debugger if requested + if (preference == ActionPreference.Break) + { + CBhost?.Runspace?.Debugger?.Break(record); + } + if (DebugOutputPipe != null) { if (CBhost != null && CBhost.InternalUI != null && @@ -564,6 +581,12 @@ internal void WriteVerbose(VerboseRecord record, bool overrideInquire = false) record.SetInvocationInfo(MyInvocation); } + // Break into the debugger if requested + if (preference == ActionPreference.Break) + { + CBhost?.Runspace?.Debugger?.Break(record); + } + if (VerboseOutputPipe != null) { if (CBhost != null && CBhost.InternalUI != null && @@ -652,6 +675,12 @@ internal void WriteWarning(WarningRecord record, bool overrideInquire = false) record.SetInvocationInfo(MyInvocation); } + // Break into the debugger if requested + if (preference == ActionPreference.Break) + { + CBhost?.Runspace?.Debugger?.Break(record); + } + if (WarningOutputPipe != null) { if (CBhost != null && CBhost.InternalUI != null && @@ -712,6 +741,12 @@ internal void WriteInformation(InformationRecord record, bool overrideInquire = if (overrideInquire && preference == ActionPreference.Inquire) preference = ActionPreference.Continue; + // Break into the debugger if requested + if (preference == ActionPreference.Break) + { + CBhost?.Runspace?.Debugger?.Break(record); + } + if (preference != ActionPreference.Ignore) { if (InformationOutputPipe != null) @@ -2051,6 +2086,14 @@ public void ThrowTerminatingError(ErrorRecord errorRecord) CmdletInvocationException e = new CmdletInvocationException(errorRecord); + + // If the error action preference is set to break, break immediately + // into the debugger + if (ErrorAction == ActionPreference.Break) + { + Context.Debugger?.Break(e.InnerException ?? e); + } + // Code sees only that execution stopped throw ManageException(e); } @@ -2551,8 +2594,12 @@ internal void AppendInformationVarList(object obj) #region Write internal bool UseSecurityContextRun = true; - // NOTICE-2004/06/08-JonN 959638 - // Use this variant to skip the ThrowIfWriteNotPermitted check + /// + /// Writes an object to the output pipe, skipping the ThrowIfWriteNotPermitted check. + /// + /// + /// The object to write to the output pipe. + /// /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -2571,8 +2618,12 @@ internal void _WriteObjectSkipAllowCheck(object sendToPipeline) this.OutputPipe.Add(sendToPipeline); } - // NOTICE-2004/06/08-JonN 959638 - // Use this variant to skip the ThrowIfWriteNotPermitted check + /// + /// Enumerates and writes an object to the output pipe, skipping the ThrowIfWriteNotPermitted check. + /// + /// + /// The object to enumerate and write to the output pipe. + /// /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -2594,7 +2645,9 @@ internal void _EnumerateAndWriteObjectSkipAllowCheck(object sendToPipeline) foreach (object toConvert in enumerable) { if (AutomationNull.Value == toConvert) + { continue; + } object converted = LanguagePrimitives.AsPSObjectOrNull(toConvert); convertedList.Add(converted); @@ -2660,6 +2713,12 @@ internal void WriteError(ErrorRecord errorRecord, bool overrideInquire) preference = ActionPreference.Continue; } + // Break into the debugger if requested + if (preference == ActionPreference.Break) + { + CBhost?.Runspace?.Debugger?.Break(errorRecord); + } + #if CORECLR // SecurityContext is not supported in CoreCLR DoWriteError(new KeyValuePair(errorRecord, preference)); @@ -2898,7 +2957,7 @@ internal ConfirmImpact ConfirmPreference if (!_isConfirmPreferenceCached) { bool defaultUsed = false; - _confirmPreference = Context.GetEnumPreference(SpecialVariables.ConfirmPreferenceVarPath, _confirmPreference, out defaultUsed); + _confirmPreference = Context.GetEnumPreference(SpecialVariables.ConfirmPreferenceVarPath, _confirmPreference, out defaultUsed); _isConfirmPreferenceCached = true; } @@ -2933,7 +2992,7 @@ internal ActionPreference DebugPreference { bool defaultUsed = false; - _debugPreference = Context.GetEnumPreference(SpecialVariables.DebugPreferenceVarPath, _debugPreference, out defaultUsed); + _debugPreference = Context.GetEnumPreference(SpecialVariables.DebugPreferenceVarPath, _debugPreference, out defaultUsed); // If the host couldn't prompt for the debug action anyways, change it to 'Continue'. // This lets hosts still see debug output without having to implement the prompting logic. @@ -2992,7 +3051,7 @@ internal ActionPreference VerbosePreference if (!_isVerbosePreferenceCached) { bool defaultUsed = false; - _verbosePreference = Context.GetEnumPreference( + _verbosePreference = Context.GetEnumPreference( SpecialVariables.VerbosePreferenceVarPath, _verbosePreference, out defaultUsed); @@ -3029,7 +3088,7 @@ internal ActionPreference WarningPreference if (!_isWarningPreferenceCached) { bool defaultUsed = false; - _warningPreference = Context.GetEnumPreference(SpecialVariables.WarningPreferenceVarPath, _warningPreference, out defaultUsed); + _warningPreference = Context.GetEnumPreference(SpecialVariables.WarningPreferenceVarPath, _warningPreference, out defaultUsed); } return _warningPreference; @@ -3198,7 +3257,7 @@ internal ActionPreference ErrorAction if (!_isErrorActionPreferenceCached) { bool defaultUsed = false; - _errorAction = Context.GetEnumPreference(SpecialVariables.ErrorActionPreferenceVarPath, _errorAction, out defaultUsed); + _errorAction = Context.GetEnumPreference(SpecialVariables.ErrorActionPreferenceVarPath, _errorAction, out defaultUsed); _isErrorActionPreferenceCached = true; } @@ -3233,7 +3292,7 @@ internal ActionPreference ProgressPreference if (!_isProgressPreferenceCached) { bool defaultUsed = false; - _progressPreference = Context.GetEnumPreference(SpecialVariables.ProgressPreferenceVarPath, _progressPreference, out defaultUsed); + _progressPreference = Context.GetEnumPreference(SpecialVariables.ProgressPreferenceVarPath, _progressPreference, out defaultUsed); _isProgressPreferenceCached = true; } @@ -3265,7 +3324,7 @@ internal ActionPreference InformationPreference if (!_isInformationPreferenceCached) { bool defaultUsed = false; - _informationPreference = Context.GetEnumPreference(SpecialVariables.InformationPreferenceVarPath, _informationPreference, out defaultUsed); + _informationPreference = Context.GetEnumPreference(SpecialVariables.InformationPreferenceVarPath, _informationPreference, out defaultUsed); _isInformationPreferenceCached = true; } @@ -3364,6 +3423,7 @@ internal bool WriteHelper_ShouldWrite( case ActionPreference.Continue: case ActionPreference.Stop: case ActionPreference.Inquire: + case ActionPreference.Break: return true; default: @@ -3416,6 +3476,7 @@ internal ContinueStatus WriteHelper( case ActionPreference.Ignore: // YesToAll case ActionPreference.SilentlyContinue: case ActionPreference.Continue: + case ActionPreference.Break: return ContinueStatus.Yes; case ActionPreference.Stop: diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index 83723d86d6d..60be22a00d0 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -666,6 +666,15 @@ public virtual void SetDebuggerStepMode(bool enabled) #region Internal Methods + /// + /// Breaks into the debugger. + /// + /// The object that triggered the breakpoint, if there is one. + internal virtual void Break(object triggerObject = null) + { + throw new PSNotImplementedException(); + } + /// /// Passes the debugger command to the internal script debugger command processor. This /// is used internally to handle debugger commands such as list, help, etc. @@ -947,6 +956,11 @@ private bool IsLocalSession } } + /// + /// Gets or sets the object that triggered the current breakpoint. + /// + private object TriggerObject { get; set; } + #endregion properties #region internal methods @@ -1734,7 +1748,9 @@ private void OnDebuggerStop(InvocationInfo invocationInfo, List brea return; } - _context.SetVariable(SpecialVariables.PSDebugContextVarPath, new PSDebugContext(invocationInfo, breakpoints)); + bool oldQuestionMarkVariableValue = _context.QuestionMarkVariableValue; + + _context.SetVariable(SpecialVariables.PSDebugContextVarPath, new PSDebugContext(invocationInfo, breakpoints, TriggerObject)); FunctionInfo defaultPromptInfo = null; string originalPromptString = null; @@ -1848,6 +1864,8 @@ private void OnDebuggerStop(InvocationInfo invocationInfo, List brea DebuggerStopEventArgs oldArgs; _debuggerStopEventArgs.TryPop(out oldArgs); + _context.QuestionMarkVariableValue = oldQuestionMarkVariableValue; + _inBreakpoint = false; } } @@ -2500,6 +2518,39 @@ public override void SetDebuggerStepMode(bool enabled) } } + /// + /// Breaks into the debugger. + /// + /// The object that triggered the breakpoint, if there is one. + internal override void Break(object triggerObject = null) + { + if (!IsDebugHandlerSubscribed && + (UnhandledBreakpointMode == UnhandledBreakpointProcessingMode.Ignore)) + { + // No debugger attached and runspace debugging is not enabled. Enable runspace debugging here + // so that this command is effective. + UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Wait; + } + + // Store the triggerObject so that we can add it to PSDebugContext + TriggerObject = triggerObject; + + // Set debugger to step mode so that a break can occur. + SetDebuggerStepMode(true); + + // If the debugger is enabled and we are not in a breakpoint, trigger an immediate break in the current location + if (_context._debuggingMode > 0) + { + using (IEnumerator enumerator = GetCallStack().GetEnumerator()) + { + if (enumerator.MoveNext()) + { + OnSequencePointHit(enumerator.Current.FunctionContext); + } + } + } + } + /// /// Passes the debugger command to the internal script debugger command processor. This /// is used internally to handle debugger commands such as list, help, etc. @@ -4101,6 +4152,15 @@ public override bool IsActive get { return _wrappedDebugger.IsActive; } } + /// + /// Breaks into the debugger. + /// + /// The object that triggered the breakpoint, if there is one. + internal override void Break(object triggerObject = null) + { + _wrappedDebugger.Break(triggerObject); + } + #endregion #region IDisposable @@ -4776,15 +4836,15 @@ internal class DebuggerCommandProcessor public DebuggerCommandProcessor() { _commandTable = new Dictionary(StringComparer.OrdinalIgnoreCase); - _commandTable[StepCommand] = _commandTable[StepShortcut] = new DebuggerCommand(StepCommand, DebuggerResumeAction.StepInto, true, false); - _commandTable[StepOutCommand] = _commandTable[StepOutShortcut] = new DebuggerCommand(StepOutCommand, DebuggerResumeAction.StepOut, false, false); - _commandTable[StepOverCommand] = _commandTable[StepOverShortcut] = new DebuggerCommand(StepOverCommand, DebuggerResumeAction.StepOver, true, false); - _commandTable[ContinueCommand] = _commandTable[ContinueShortcut] = new DebuggerCommand(ContinueCommand, DebuggerResumeAction.Continue, false, false); - _commandTable[StopCommand] = _commandTable[StopShortcut] = new DebuggerCommand(StopCommand, DebuggerResumeAction.Stop, false, false); - _commandTable[GetStackTraceShortcut] = new DebuggerCommand("get-pscallstack", null, false, false); - _commandTable[HelpCommand] = _commandTable[HelpShortcut] = _helpCommand = new DebuggerCommand(HelpCommand, null, false, true); - _commandTable[ListCommand] = _commandTable[ListShortcut] = _listCommand = new DebuggerCommand(ListCommand, null, true, true); - _commandTable[string.Empty] = new DebuggerCommand(string.Empty, null, false, true); + _commandTable[StepCommand] = _commandTable[StepShortcut] = new DebuggerCommand(StepCommand, DebuggerResumeAction.StepInto, repeatOnEnter: true, executedByDebugger: false); + _commandTable[StepOutCommand] = _commandTable[StepOutShortcut] = new DebuggerCommand(StepOutCommand, DebuggerResumeAction.StepOut, repeatOnEnter: false, executedByDebugger: false); + _commandTable[StepOverCommand] = _commandTable[StepOverShortcut] = new DebuggerCommand(StepOverCommand, DebuggerResumeAction.StepOver, repeatOnEnter: true, executedByDebugger: false); + _commandTable[ContinueCommand] = _commandTable[ContinueShortcut] = new DebuggerCommand(ContinueCommand, DebuggerResumeAction.Continue, repeatOnEnter: false, executedByDebugger: false); + _commandTable[StopCommand] = _commandTable[StopShortcut] = new DebuggerCommand(StopCommand, DebuggerResumeAction.Stop, repeatOnEnter: false, executedByDebugger: false); + _commandTable[GetStackTraceShortcut] = new DebuggerCommand("get-pscallstack", null, repeatOnEnter: false, executedByDebugger: false); + _commandTable[HelpCommand] = _commandTable[HelpShortcut] = _helpCommand = new DebuggerCommand(HelpCommand, null, repeatOnEnter: false, executedByDebugger: true); + _commandTable[ListCommand] = _commandTable[ListShortcut] = _listCommand = new DebuggerCommand(ListCommand, null, repeatOnEnter: true, executedByDebugger: true); + _commandTable[string.Empty] = new DebuggerCommand(string.Empty, null, repeatOnEnter: false, executedByDebugger: true); } /// @@ -5129,11 +5189,22 @@ public DebuggerCommand(string command, DebuggerResumeAction? action, bool repeat public class PSDebugContext { /// - /// Constructor. + /// Initializes a new instance of the class. /// - /// InvocationInfo. - /// Breakpoints. + /// The invocation information for the current command. + /// The breakpoint(s) that caused the script to break in the debugger. public PSDebugContext(InvocationInfo invocationInfo, List breakpoints) + : this(invocationInfo, breakpoints, triggerObject: null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The invocation information for the current command. + /// The breakpoint(s) that caused the script to break in the debugger. + /// The object that caused the script to break in the debugger. + public PSDebugContext(InvocationInfo invocationInfo, List breakpoints, object triggerObject) { if (breakpoints == null) { @@ -5142,6 +5213,7 @@ public PSDebugContext(InvocationInfo invocationInfo, List breakpoint this.InvocationInfo = invocationInfo; this.Breakpoints = breakpoints.ToArray(); + this.Trigger = triggerObject; } /// @@ -5154,6 +5226,11 @@ public PSDebugContext(InvocationInfo invocationInfo, List breakpoint /// were hit. Otherwise, the execution was suspended as part of a step operation. /// public Breakpoint[] Breakpoints { get; private set; } + + /// + /// Gets the object that triggered the current dynamic breakpoint. + /// + public object Trigger { get; private set; } } #endregion diff --git a/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs index e4d782da1f9..acfd0b5a1ed 100644 --- a/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs @@ -379,6 +379,7 @@ internal void WriteDebugInfoBuffers(DebugRecord record) switch (preference) { case ActionPreference.Continue: + case ActionPreference.Break: WriteDebugLineHelper(message); break; case ActionPreference.SilentlyContinue: diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index a61f36bd292..bc4d4f3d81e 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -4954,7 +4954,8 @@ public object VisitThrowStatement(ThrowStatementAst throwStatementAst) UpdatePosition(throwStatementAst), Expression.Throw(Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_ConvertToException, throwExpr.Convert(typeof(object)), - Expression.Constant(throwStatementAst.Extent)))); + Expression.Constant(throwStatementAst.Extent), + Expression.Constant(throwStatementAst.IsRethrow)))); } #endregion Statements diff --git a/src/System.Management.Automation/engine/parser/Position.cs b/src/System.Management.Automation/engine/parser/Position.cs index af5a124d14e..5d033191633 100644 --- a/src/System.Management.Automation/engine/parser/Position.cs +++ b/src/System.Management.Automation/engine/parser/Position.cs @@ -145,9 +145,9 @@ internal static string VerboseMessage(IScriptExtent position) if (!string.IsNullOrEmpty(sourceLine)) { int spacesBeforeError = position.StartColumnNumber - 1; - int errorLength = (position.StartLineNumber == position.EndLineNumber) + int errorLength = (position.StartLineNumber == position.EndLineNumber && position.EndColumnNumber <= sourceLine.Length + 1) ? position.EndColumnNumber - position.StartColumnNumber - : sourceLine.TrimEnd().Length - position.StartColumnNumber + 1; + : sourceLine.Length - position.StartColumnNumber + 1; // Expand tabs before figuring out if we need to truncate the line if (sourceLine.IndexOf('\t') != -1) diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs index 2f6972dca3d..42005deac1e 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs @@ -1981,6 +1981,11 @@ public override IEnumerable GetCallStack() return _wrappedDebugger.Value.GetCallStack(); } + internal override void Break(object triggerObject = null) + { + _wrappedDebugger.Value.Break(triggerObject); + } + #endregion #region IDisposable diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index 470c1113b1c..db135416d49 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -108,10 +108,13 @@ private static CommandProcessorBase AddCommand(PipelineProcessor pipe, if (string.IsNullOrEmpty(commandName)) { - throw InterpreterError.NewInterpreterException(command, typeof(RuntimeException), - commandExtent, "BadExpression", - ParserStrings.BadExpression, - dotSource ? "." : "&"); + throw InterpreterError.NewInterpreterException( + command, + typeof(RuntimeException), + commandExtent, + "BadExpression", + ParserStrings.BadExpression, + dotSource ? "." : "&"); } try @@ -329,7 +332,10 @@ internal static IEnumerable Splat(object splattedValue object parameterValue = de.Value; string parameterText = GetParameterText(parameterName); - if (markUntrustedData) { ExecutionContext.MarkObjectAsUntrusted(parameterValue); } + if (markUntrustedData) + { + ExecutionContext.MarkObjectAsUntrusted(parameterValue); + } yield return CommandParameterInternal.CreateParameterWithArgument( splatAst, parameterName, parameterText, @@ -343,7 +349,10 @@ internal static IEnumerable Splat(object splattedValue { foreach (object obj in enumerableValue) { - if (markUntrustedData) { ExecutionContext.MarkObjectAsUntrusted(obj); } + if (markUntrustedData) + { + ExecutionContext.MarkObjectAsUntrusted(obj); + } yield return SplatEnumerableElement(obj, splatAst); } @@ -1335,15 +1344,15 @@ internal static object Add(IDictionary lvalDict, IDictionary rvalDict) } // Add key and values from left hand side... - foreach (object myKey in lvalDict.Keys) + foreach (object key in lvalDict.Keys) { - newDictionary.Add(myKey, lvalDict[myKey]); + newDictionary.Add(key, lvalDict[key]); } // and the right-hand side - foreach (object myKey in rvalDict.Keys) + foreach (object key in rvalDict.Keys) { - newDictionary.Add(myKey, rvalDict[myKey]); + newDictionary.Add(key, rvalDict[key]); } return newDictionary; @@ -1595,25 +1604,38 @@ internal static void CheckActionPreference(FunctionContext funcContext, Exceptio // set $? to false indicating an error context.QuestionMarkVariableValue = false; - bool anyTrapHandlers = funcContext._traps.Any() && funcContext._traps.Last().Item2 != null; + ActionPreference preference = GetErrorActionPreference(context); - if (!anyTrapHandlers && !ExceptionHandlingOps.NeedToQueryForActionPreference(rte, context)) + // If the exception was not rethrown and we are not currently + // handling an exception, then the exception is new, and we + // can break on it if requested. + if (!rte.WasRethrown && + context.CurrentExceptionBeingHandled == null && + preference == ActionPreference.Break) { - throw rte; + context.Debugger?.Break(rte); } - ActionPreference preference; + // Item2 in the trap tuples is the action (script) for the trap. + // A null action script is only used to indicate when exceptions + // should be thrown up to a higher level, and doesn't count as an + // actual trap handler in the function context. + bool anyTrapHandlers = funcContext._traps.Count > 0 && funcContext._traps[funcContext._traps.Count - 1].Item2 != null; + if (anyTrapHandlers) { + // update the action preference according to how the exception is + // handled in the trap statement(s). preference = ProcessTraps(funcContext, rte); } - else + else if (ExceptionCannotBeStoppedContinuedOrIgnored(rte, context)) { - preference = ExceptionHandlingOps.QueryForAction(rte, rte.Message, context); + throw rte; + } + else if (preference == ActionPreference.Inquire && !rte.SuppressPromptInInterpreter) + { + preference = InquireForActionPreference(rte.Message, context); } - - // set the value of $? here in case it is reset in trap handling. - context.QuestionMarkVariableValue = false; if ((preference == ActionPreference.SilentlyContinue) || (preference == ActionPreference.Ignore)) @@ -1637,12 +1659,7 @@ internal static void CheckActionPreference(FunctionContext funcContext, Exceptio throw rte; } - bool b = ExceptionHandlingOps.ReportErrorRecord(extent, rte, context); - - // set the value of $? here in case it is reset in error reporting - context.QuestionMarkVariableValue = false; - - if (!b) + if (!ReportErrorRecord(extent, rte, context)) { throw rte; } @@ -1680,10 +1697,12 @@ private static ActionPreference ProcessTraps(FunctionContext funcContext, if (handler != -1) { Diagnostics.Assert(exception != null, "Exception object can't be null."); + + var context = funcContext._executionContext; + try { ErrorRecord err = rte.ErrorRecord; - var context = funcContext._executionContext; // CurrentCommandProcessor is normally not null, but it is null // when executing some unit tests through reflection. if (context.CurrentCommandProcessor != null) @@ -1747,13 +1766,37 @@ private static ActionPreference ProcessTraps(FunctionContext funcContext, // Terminate this block of statements. return ActionPreference.Stop; } + finally + { + // The questionmark variable will always be false when we process a trap, so + // set it to false to ensure it didn't change as a result of anything done + // inside the trap + context.QuestionMarkVariableValue = false; + } } return ActionPreference.Stop; } /// - /// Determine if we should continue or not after and error or exception.... + /// Gets the current error action preference value. + /// + /// The execution context. + /// The preference the user selected. + /// + /// Error action is decided by error action preference. If preference is inquire, we will + /// prompt user for their preference. + /// + internal static ActionPreference GetErrorActionPreference(ExecutionContext context) + { + return context.GetEnumPreference( + SpecialVariables.ErrorActionPreferenceVarPath, + ActionPreference.Continue, + out _); + } + + /// + /// Determine if we should continue or not after an error or exception. /// /// The RuntimeException which was reported. /// The message to display. @@ -1766,10 +1809,11 @@ private static ActionPreference ProcessTraps(FunctionContext funcContext, internal static ActionPreference QueryForAction(RuntimeException rte, string message, ExecutionContext context) { // 906264 "$ErrorActionPreference="Inquire" prevents original non-terminating error from being reported to $error" - bool defaultUsed; ActionPreference preference = - context.GetEnumPreference(SpecialVariables.ErrorActionPreferenceVarPath, - ActionPreference.Continue, out defaultUsed); + context.GetEnumPreference( + SpecialVariables.ErrorActionPreferenceVarPath, + ActionPreference.Continue, + out _); if (preference != ActionPreference.Inquire || rte.SuppressPromptInInterpreter) return preference; @@ -1808,12 +1852,16 @@ internal static ActionPreference InquireForActionPreference(string message, Exec string caption = ParserStrings.ExceptionActionPromptCaption; + bool oldQuestionMarkVariableValue = context.QuestionMarkVariableValue; + int choice; while ((choice = ui.PromptForChoice(caption, message, choices, 0)) == 3) { context.EngineHostInterface.EnterNestedPrompt(); } + context.QuestionMarkVariableValue = oldQuestionMarkVariableValue; + if (choice == 0) return ActionPreference.Continue; @@ -1863,13 +1911,13 @@ internal static void SetErrorVariables(IScriptExtent extent, RuntimeException rt } } - internal static bool NeedToQueryForActionPreference(RuntimeException rte, ExecutionContext context) + internal static bool ExceptionCannotBeStoppedContinuedOrIgnored(RuntimeException rte, ExecutionContext context) { - return !context.PropagateExceptionsToEnclosingStatementBlock - && context.ShellFunctionErrorOutputPipe != null - && !context.CurrentPipelineStopping - && !rte.SuppressPromptInInterpreter - && !(rte is PipelineStoppedException); + return context.PropagateExceptionsToEnclosingStatementBlock + || context.ShellFunctionErrorOutputPipe == null + || context.CurrentPipelineStopping + || rte.SuppressPromptInInterpreter + || rte is PipelineStoppedException; } /// @@ -1901,10 +1949,13 @@ internal static bool ReportErrorRecord(IScriptExtent extent, RuntimeException rt context.ShellFunctionErrorOutputPipe.Add(errorWrap); + // set the value of $? here in case it is reset in error reporting. + context.QuestionMarkVariableValue = false; + return true; } - internal static RuntimeException ConvertToException(object result, IScriptExtent extent) + internal static RuntimeException ConvertToException(object result, IScriptExtent extent, bool rethrow) { result = PSObject.Base(result); @@ -1913,13 +1964,14 @@ internal static RuntimeException ConvertToException(object result, IScriptExtent { InterpreterError.UpdateExceptionErrorRecordPosition(runtimeException, extent); runtimeException.WasThrownFromThrowStatement = true; + runtimeException.WasRethrown = rethrow; return runtimeException; } ErrorRecord er = result as ErrorRecord; if (er != null) { - runtimeException = new RuntimeException(er.ToString(), er.Exception, er) { WasThrownFromThrowStatement = true }; + runtimeException = new RuntimeException(er.ToString(), er.Exception, er) { WasThrownFromThrowStatement = true, WasRethrown = rethrow }; InterpreterError.UpdateExceptionErrorRecordPosition(runtimeException, extent); return runtimeException; @@ -1929,7 +1981,7 @@ internal static RuntimeException ConvertToException(object result, IScriptExtent if (exception != null) { er = new ErrorRecord(exception, exception.Message, ErrorCategory.OperationStopped, null); - runtimeException = new RuntimeException(exception.Message, exception, er) { WasThrownFromThrowStatement = true }; + runtimeException = new RuntimeException(exception.Message, exception, er) { WasThrownFromThrowStatement = true, WasRethrown = rethrow }; InterpreterError.UpdateExceptionErrorRecordPosition(runtimeException, extent); return runtimeException; } @@ -1940,7 +1992,7 @@ internal static RuntimeException ConvertToException(object result, IScriptExtent exception = new RuntimeException(message, null); er = new ErrorRecord(exception, message, ErrorCategory.OperationStopped, null); - runtimeException = new RuntimeException(message, exception, er) { WasThrownFromThrowStatement = true }; + runtimeException = new RuntimeException(message, exception, er) { WasThrownFromThrowStatement = true, WasRethrown = rethrow }; runtimeException.SetTargetObject(result); InterpreterError.UpdateExceptionErrorRecordPosition(runtimeException, extent); diff --git a/src/System.Management.Automation/utils/RuntimeException.cs b/src/System.Management.Automation/utils/RuntimeException.cs index 2035ffec168..d3fc5e95201 100644 --- a/src/System.Management.Automation/utils/RuntimeException.cs +++ b/src/System.Management.Automation/utils/RuntimeException.cs @@ -284,6 +284,8 @@ public bool WasThrownFromThrowStatement private bool _thrownByThrowStatement; + internal bool WasRethrown { get; set; } + /// /// Fix for BUG: Windows Out Of Band Releases: 906263 and 906264 /// The interpreter prompt CommandBaseStrings:InquireHalt diff --git a/test/powershell/Language/Scripting/ActionPreference.Tests.ps1 b/test/powershell/Language/Scripting/ActionPreference.Tests.ps1 index 134d4d1836d..3cc2e0337c4 100644 --- a/test/powershell/Language/Scripting/ActionPreference.Tests.ps1 +++ b/test/powershell/Language/Scripting/ActionPreference.Tests.ps1 @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. + Describe "Tests for (error, warning, etc) action preference" -Tags "CI" { BeforeAll { $orgin = $GLOBAL:errorActionPreference @@ -37,7 +38,7 @@ Describe "Tests for (error, warning, etc) action preference" -Tags "CI" { $e = { $GLOBAL:errorActionPreference = "Ignore" Get-Process -Name asdfasdfasdf - } | Should -Throw -ErrorId 'System.NotSupportedException,Microsoft.PowerShell.Commands.GetProcessCommand' -PassThru + } | Should -Throw -ErrorId 'System.NotSupportedException' -PassThru $e.CategoryInfo.Reason | Should -BeExactly 'NotSupportedException' $GLOBAL:errorActionPreference = $orgin @@ -115,3 +116,240 @@ Describe "Tests for (error, warning, etc) action preference" -Tags "CI" { Remove-Item "$testdrive\test.txt" -Force } } + +Describe 'ActionPreference.Break tests' -tag 'CI' { + + BeforeAll { + Register-DebuggerHandler + } + + AfterAll { + Unregister-DebuggerHandler + } + + Context '-ErrorAction Break should break on a non-terminating error' { + BeforeAll { + $testScript = { + function Test-Break { + [CmdletBinding()] + param() + try { + # Generate a non-terminating error + Write-Error 'This is a non-terminating error.' + # Do something afterwards + 'This should still run' + } catch { + 'Do nothing' + } finally { + 'This finally runs' + } + } + Test-Break -ErrorAction Break + } + + $results = @(Test-Debugger -ScriptBlock $testScript -CommandQueue 'v', 'v') + } + + It 'Should show 3 debugger commands were invoked' { + # There is always an implicit 'c' command that keeps the debugger automation moving + $results.Count | Should -Be 3 + } + + It 'The breakpoint should be the statement that generated the non-terminating error' { + $results[0] | ShouldHaveExtent -Line 7 -FromColumn 25 -ToColumn 71 + } + + It 'The second statement should be the statement after that which generated the non-terminating error' { + $results[1] | ShouldHaveExtent -Line 9 -FromColumn 25 -ToColumn 48 + } + + It 'The third statement should be the statement in the finally block' { + $results[2] | ShouldHaveExtent -Line 13 -FromColumn 25 -ToColumn 44 + } + } + + Context '-ErrorAction Break should break on a terminating error' { + BeforeAll { + $testScript = { + function Test-Break { + [CmdletBinding()] + param() + try { + # Generate a terminating error + Get-Process -TheAnswer 42 + # Do something afterwards + 'This should not run' + } catch { + 'Do nothing' + } finally { + 'This finally runs' + } + } + Test-Break -ErrorAction Break + } + + $results = @(Test-Debugger -ScriptBlock $testScript -CommandQueue 'v', 'v') + } + + It 'Should show 3 debugger commands were invoked' { + # There is always an implicit 'c' command that keeps the debugger automation moving + $results.Count | Should -Be 3 + } + + It 'The breakpoint should be the statement that generated the terminating error' { + $results[0] | ShouldHaveExtent -Line 7 -FromColumn 25 -ToColumn 50 + } + + It 'The second statement should be the statement in the catch block where the terminating error is caught' { + $results[1] | ShouldHaveExtent -Line 11 -FromColumn 25 -ToColumn 37 + } + + It 'The third statement should be the statement in the finally block' { + $results[2] | ShouldHaveExtent -Line 13 -FromColumn 25 -ToColumn 44 + } + } + + Context '-ErrorAction Break should not break on a naked rethrow' { + BeforeAll { + $testScript = { + function Test-Break { + [CmdletBinding()] + param() + try { + try { + # Generate a terminating error + Get-Process -TheAnswer 42 + } catch { + throw + } + } catch { + # Swallow the exception here + } + } + Test-Break -ErrorAction Break + } + + $results = @(Test-Debugger -ScriptBlock $testScript) + } + + It 'Should show 1 debugger command was invoked' { + # ErrorAction break should only trigger on the initial terminating error + $results.Count | Should -Be 1 + } + + It 'The breakpoint should be the statement that generated the terminating error' { + $results[0] | ShouldHaveExtent -Line 8 -FromColumn 29 -ToColumn 54 + } + } + + Context '-ErrorAction Break should break when throwing a specific error or object' { + BeforeAll { + $testScript = { + function Test-Break { + [CmdletBinding()] + param() + try { + try { + # Generate a terminating error + Get-Process -TheAnswer 42 + } catch { + throw $_ + } + } catch { + # Swallow the exception here + } + } + Test-Break -ErrorAction Break + } + + $results = @(Test-Debugger -ScriptBlock $testScript) + } + + It 'Should show 2 debugger commands were invoked' { + # ErrorAction break should trigger on the initial terminating error and the throw + # since it throws a "new" error (throwing anything is considered a new terminating + # error) + $results.Count | Should -Be 2 + } + + It 'The first breakpoint should be the statement that generated the terminating error' { + $results[0] | ShouldHaveExtent -Line 8 -FromColumn 29 -ToColumn 54 + } + + It 'The second breakpoint should be the statement that threw $_' { + $results[1] | ShouldHaveExtent -Line 10 -FromColumn 29 -ToColumn 37 + } + } + + Context 'Other message types should break on their corresponding messages when requested' { + BeforeAll { + $testScript = { + function Test-Break { + [CmdletBinding()] + param() + Write-Warning -Message 'This is a warning message' + Write-Verbose -Message 'This is a verbose message' + Write-Debug -Message 'This is a debug message' + Write-Information -MessageData 'This is an information message' + Write-Progress -Activity 'This shows progress' + } + Test-Break -WarningAction Break -InformationAction Break *>$null + $WarningPreference = $VerbosePreference = $DebugPreference = $InformationPreference = $ProgressPreference = [System.Management.Automation.ActionPreference]::Break + Test-Break *>$null + } + + $results = @(Test-Debugger -ScriptBlock $testScript) + } + + It 'Should show 7 debugger commands were invoked' { + # When no debugger commands are provided, 'c' is invoked every time a breakpoint is hit + $results.Count | Should -Be 7 + } + + It 'Write-Warning should trigger a breakpoint from -WarningAction Break' { + $results[0] | ShouldHaveExtent -Line 5 -FromColumn 21 -ToColumn 71 + } + + It 'Write-Information should trigger a breakpoint from -InformationAction Break' { + $results[1] | ShouldHaveExtent -Line 8 -FromColumn 21 -ToColumn 84 + } + + It 'Write-Warning should trigger a breakpoint from $WarningPreference = [System.Management.Automation.ActionPreference]::Break' { + $results[2] | ShouldHaveExtent -Line 5 -FromColumn 21 -ToColumn 71 + } + + It 'Write-Verbose should trigger a breakpoint from $VerbosePreference = [System.Management.Automation.ActionPreference]::Break' { + $results[3] | ShouldHaveExtent -Line 6 -FromColumn 21 -ToColumn 71 + } + + It 'Write-Debug should trigger a breakpoint from $DebugPreference = [System.Management.Automation.ActionPreference]::Break' { + $results[4] | ShouldHaveExtent -Line 7 -FromColumn 21 -ToColumn 67 + } + + It 'Write-Information should trigger a breakpoint from $InformationPreference = [System.Management.Automation.ActionPreference]::Break' { + $results[5] | ShouldHaveExtent -Line 8 -FromColumn 21 -ToColumn 84 + } + + It 'Write-Progress should trigger a breakpoint from $ProgressPreference = [System.Management.Automation.ActionPreference]::Break' { + $results[6] | ShouldHaveExtent -Line 9 -FromColumn 21 -ToColumn 67 + } + } + + Context 'ActionPreference.Break in jobs' { + + BeforeAll { + $job = Start-Job { + $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Break + Get-Process -TheAnswer 42 + } + } + + AfterAll { + Remove-Job -Job $job -Force + } + + It 'ActionPreference.Break should break in a running job' { + Wait-UntilTrue -sb { $job.State -eq 'AtBreakpoint' } -TimeoutInMilliseconds (10 * 1000) -IntervalInMilliseconds 100 | Should -BeTrue + } + } +} diff --git a/test/powershell/Language/Scripting/Debugging/Debugging.Tests.ps1 b/test/powershell/Language/Scripting/Debugging/Debugging.Tests.ps1 index 851e2d5282d..2259a587e82 100644 --- a/test/powershell/Language/Scripting/Debugging/Debugging.Tests.ps1 +++ b/test/powershell/Language/Scripting/Debugging/Debugging.Tests.ps1 @@ -1,6 +1,56 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +Describe 'Basic debugger tests' -tag 'CI' { + + BeforeAll { + Register-DebuggerHandler + } + + AfterAll { + Unregister-DebuggerHandler + } + + Context 'The value of $? should be preserved when exiting the debugger' { + BeforeAll { + $testScript = { + function Test-DollarQuestionMark { + [CmdletBinding()] + param() + Get-Process -id ([int]::MaxValue) + if (-not $?) { + 'The value of $? was preserved during debugging.' + } else { + 'The value of $? was changed to $true during debugging.' + } + } + $global:DollarQuestionMarkResults = Test-DollarQuestionMark -ErrorAction Break + } + + $global:results = @(Test-Debugger -ScriptBlock $testScript -CommandQueue '$?') + } + + AfterAll { + Remove-Variable -Name DollarQuestionMarkResults -Scope Global -ErrorAction Ignore + } + + It 'Should show 2 debugger commands were invoked' { + # One extra for the implicit 'c' command that keeps the debugger automation moving + $results.Count | Should -Be 2 + } + + It 'Should have $false output from the first $? command' { + $results[0].Output | Should -BeOfType bool + $results[0].Output | Should -Not -BeTrue + } + + It 'Should have string output showing that $? was preserved as $false by the debugger' { + $global:DollarQuestionMarkResults | Should -BeOfType string + $global:DollarQuestionMarkResults | Should -BeExactly 'The value of $? was preserved during debugging.' + } + } +} + Describe "Breakpoints when set should be hit" -tag "CI" { Context "Basic tests" { BeforeAll { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Wait-Debugger.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Wait-Debugger.Tests.ps1 new file mode 100644 index 00000000000..180c066016f --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Wait-Debugger.Tests.ps1 @@ -0,0 +1,37 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe 'Tests for Wait-Debugger' -Tags "CI" { + BeforeAll { + Register-DebuggerHandler + } + + AfterAll { + Unregister-DebuggerHandler + } + + Context 'Wait-Debugger should break on the statement containing the Wait-Debugger command' { + BeforeAll { + $testScript = { + function Test-Break { + [CmdletBinding()] + param() + Wait-Debugger + 'The debugger should break on the previous line, not on this line.' + } + Test-Break + } + + $results = @(Test-Debugger -ScriptBlock $testScript) + } + + It 'Should show 1 debugger command was invoked' { + # There is always an implicit 'c' command that keeps the debugger automation moving + $results.Count | Should -Be 1 + } + + It 'The breakpoint should be the statement containing Wait-Debugger' { + $results[0] | ShouldHaveExtent -Line 5 -FromColumn 21 -ToColumn 34 + } + } +} diff --git a/test/powershell/engine/Api/TaskBasedAsyncPowerShellAPI.Tests.ps1 b/test/powershell/engine/Api/TaskBasedAsyncPowerShellAPI.Tests.ps1 index be08c60fbb2..c22d994a2b9 100644 --- a/test/powershell/engine/Api/TaskBasedAsyncPowerShellAPI.Tests.ps1 +++ b/test/powershell/engine/Api/TaskBasedAsyncPowerShellAPI.Tests.ps1 @@ -101,14 +101,27 @@ try { } It 'cannot invoke a single script asynchronously in a runspace that is busy' { - $ps = [powershell]::Create($Host.Runspace) + $rs = [runspacefactory]::CreateRunspace() try { - # This test is designed to fail. You cannot invoke PowerShell asynchronously - # in a runspace that is busy, because pipelines cannot be run concurrently. - $err = { InvokeAsyncHelper -PowerShell $ps -Wait } | Should -Throw -ErrorId 'AggregateException' -PassThru - GetInnerErrorId -Exception $err.Exception | Should -Be 'InvalidOperation' + $rs.Open(); + $ps1 = [powershell]::Create($rs) + $ps2 = [powershell]::Create($rs) + try { + # Make the runspace busy by running an async command. + $ps1.AddScript('@(1..100).foreach{Start-Sleep -Milliseconds 250}').InvokeAsync() + Wait-UntilTrue { $rs.RunspaceAvailability -eq [System.Management.Automation.Runspaces.RunspaceAvailability]::Busy } | Should -BeTrue + # This test is designed to fail. You cannot invoke PowerShell asynchronously + # in a runspace that is busy, because pipelines cannot be run concurrently. + $err = { InvokeAsyncHelper -PowerShell $ps2 -Wait } | Should -Throw -ErrorId 'AggregateException' -PassThru + GetInnerErrorId -Exception $err.Exception | Should -Be 'InvalidOperation' + } finally { + $ps1.Stop() + $ps1.Dispose() + $ps2.Dispose() + } + } finally { - $ps.Dispose() + $rs.Dispose() } } diff --git a/test/tools/Modules/HelpersDebugger/HelpersDebugger.psm1 b/test/tools/Modules/HelpersDebugger/HelpersDebugger.psm1 index 79cafc91a92..aaf41b6b103 100644 --- a/test/tools/Modules/HelpersDebugger/HelpersDebugger.psm1 +++ b/test/tools/Modules/HelpersDebugger/HelpersDebugger.psm1 @@ -30,14 +30,19 @@ $debuggerStopHandler = { $stringDbgCommand = $script:dbgCmdQueue.Dequeue() } $dbgCmd = [System.Management.Automation.PSCommand]::new() - $dbgCmd.AddCommand($stringDbgCommand) + $dbgCmd.AddScript($stringDbgCommand) > $null $output = [System.Management.Automation.PSDataCollection[PSObject]]::new() $result = $Host.Runspace.Debugger.ProcessCommand($dbgCmd, $output) + if ($stringDbgCommand -eq '$?' -and $output.Count -eq 1) { + $output[0] = $PSDebugContext.Trigger -isnot [System.Management.Automation.ErrorRecord] + } $script:dbgResults += [pscustomobject]@{ - PSTypeName = 'DebuggerCommandResult' - Command = $stringDbgCommand - Context = $PSDebugContext - Output = $output + PSTypeName = 'DebuggerCommandResult' + Command = $stringDbgCommand + Context = $PSDebugContext + Output = $output + EvaluatedByDebugger = $result.EvaluatedByDebugger + ResumeAction = $result.ResumeAction } } while ($result -eq $null -or $result.ResumeAction -eq $null) $e.ResumeAction = $result.ResumeAction