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