Add support to ActionPreference.Break
to break into debugger (#8205)
This commit is contained in:
parent
27fcb35913
commit
8b9f4124ce
|
@ -351,7 +351,7 @@ namespace Microsoft.PowerShell.Commands
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The optional breakpoint objects to use for debugging.
|
||||
/// Gets or sets the optional breakpoint objects to use for debugging.
|
||||
/// </summary>
|
||||
[Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)]
|
||||
[Parameter(Position = 1,
|
||||
|
@ -555,19 +555,11 @@ namespace Microsoft.PowerShell.Commands
|
|||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -277,18 +277,26 @@ namespace System.Management.Automation
|
|||
public enum ActionPreference
|
||||
{
|
||||
/// <summary>Ignore this event and continue</summary>
|
||||
SilentlyContinue,
|
||||
SilentlyContinue = 0,
|
||||
|
||||
/// <summary>Stop the command</summary>
|
||||
Stop,
|
||||
Stop = 1,
|
||||
|
||||
/// <summary>Handle this event as normal and continue</summary>
|
||||
Continue,
|
||||
Continue = 2,
|
||||
|
||||
/// <summary>Ask whether to stop or continue</summary>
|
||||
Inquire,
|
||||
Inquire = 3,
|
||||
|
||||
/// <summary>Ignore the event completely (not even logging it to the target stream)</summary>
|
||||
Ignore,
|
||||
Ignore = 4,
|
||||
|
||||
/// <summary>Suspend the command for further diagnosis. Supported only for workflows.</summary>
|
||||
Suspend,
|
||||
}
|
||||
Suspend = 5,
|
||||
|
||||
/// <summary>Enter the debugger.</summary>
|
||||
Break = 6,
|
||||
} // enum ActionPreference
|
||||
#endregion ActionPreference
|
||||
|
||||
#region ConfirmImpact
|
||||
|
|
|
@ -570,9 +570,7 @@ namespace System.Management.Automation
|
|||
|
||||
internal T GetEnumPreference<T>(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 @@ namespace System.Management.Automation
|
|||
get
|
||||
{
|
||||
bool defaultUsed = false;
|
||||
return this.GetEnumPreference<ActionPreference>(
|
||||
return this.GetEnumPreference(
|
||||
SpecialVariables.DebugPreferenceVarPath,
|
||||
InitialSessionState.defaultDebugPreference,
|
||||
out defaultUsed);
|
||||
|
@ -1069,7 +1067,7 @@ namespace System.Management.Automation
|
|||
get
|
||||
{
|
||||
bool defaultUsed = false;
|
||||
return this.GetEnumPreference<ActionPreference>(
|
||||
return this.GetEnumPreference(
|
||||
SpecialVariables.VerbosePreferenceVarPath,
|
||||
InitialSessionState.defaultVerbosePreference,
|
||||
out defaultUsed);
|
||||
|
@ -1090,7 +1088,7 @@ namespace System.Management.Automation
|
|||
get
|
||||
{
|
||||
bool defaultUsed = false;
|
||||
return this.GetEnumPreference<ActionPreference>(
|
||||
return this.GetEnumPreference(
|
||||
SpecialVariables.ErrorActionPreferenceVarPath,
|
||||
InitialSessionState.defaultErrorActionPreference,
|
||||
out defaultUsed);
|
||||
|
@ -1111,7 +1109,7 @@ namespace System.Management.Automation
|
|||
get
|
||||
{
|
||||
bool defaultUsed = false;
|
||||
return this.GetEnumPreference<ActionPreference>(
|
||||
return this.GetEnumPreference(
|
||||
SpecialVariables.WarningPreferenceVarPath,
|
||||
InitialSessionState.defaultWarningPreference,
|
||||
out defaultUsed);
|
||||
|
@ -1132,7 +1130,7 @@ namespace System.Management.Automation
|
|||
get
|
||||
{
|
||||
bool defaultUsed = false;
|
||||
return this.GetEnumPreference<ActionPreference>(
|
||||
return this.GetEnumPreference(
|
||||
SpecialVariables.InformationPreferenceVarPath,
|
||||
InitialSessionState.defaultInformationPreference,
|
||||
out defaultUsed);
|
||||
|
@ -1178,7 +1176,7 @@ namespace System.Management.Automation
|
|||
get
|
||||
{
|
||||
bool defaultUsed = false;
|
||||
return this.GetEnumPreference<ConfirmImpact>(
|
||||
return this.GetEnumPreference(
|
||||
SpecialVariables.ConfirmPreferenceVarPath,
|
||||
InitialSessionState.defaultConfirmPreference,
|
||||
out defaultUsed);
|
||||
|
|
|
@ -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 @@ namespace System.Management.Automation
|
|||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an object enumerated from a collection to the output pipe.
|
||||
/// </summary>
|
||||
/// <param name="sendToPipeline">
|
||||
/// The enumerated object that needs to be written to the pipeline.
|
||||
/// </param>
|
||||
/// <exception cref="System.Management.Automation.PipelineStoppedException">
|
||||
/// The pipeline has already been terminated, or was terminated
|
||||
/// during the execution of this method.
|
||||
|
@ -276,7 +281,7 @@ namespace System.Management.Automation
|
|||
/// to percolate up to the caller of ProcessRecord etc.
|
||||
/// </exception>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// Not permitted at this time or from this thread
|
||||
/// Not permitted at this time or from this thread.
|
||||
/// </exception>
|
||||
private void DoWriteEnumeratedObject(object sendToPipeline)
|
||||
{
|
||||
|
@ -411,6 +416,12 @@ namespace System.Management.Automation
|
|||
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 @@ namespace System.Management.Automation
|
|||
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 @@ namespace System.Management.Automation
|
|||
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 @@ namespace System.Management.Automation
|
|||
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 @@ namespace System.Management.Automation
|
|||
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 @@ namespace System.Management.Automation
|
|||
|
||||
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 @@ namespace System.Management.Automation
|
|||
#region Write
|
||||
internal bool UseSecurityContextRun = true;
|
||||
|
||||
// NOTICE-2004/06/08-JonN 959638
|
||||
// Use this variant to skip the ThrowIfWriteNotPermitted check
|
||||
/// <summary>
|
||||
/// Writes an object to the output pipe, skipping the ThrowIfWriteNotPermitted check.
|
||||
/// </summary>
|
||||
/// <param name="sendToPipeline">
|
||||
/// The object to write to the output pipe.
|
||||
/// </param>
|
||||
/// <exception cref="System.Management.Automation.PipelineStoppedException">
|
||||
/// The pipeline has already been terminated, or was terminated
|
||||
/// during the execution of this method.
|
||||
|
@ -2571,8 +2618,12 @@ namespace System.Management.Automation
|
|||
this.OutputPipe.Add(sendToPipeline);
|
||||
}
|
||||
|
||||
// NOTICE-2004/06/08-JonN 959638
|
||||
// Use this variant to skip the ThrowIfWriteNotPermitted check
|
||||
/// <summary>
|
||||
/// Enumerates and writes an object to the output pipe, skipping the ThrowIfWriteNotPermitted check.
|
||||
/// </summary>
|
||||
/// <param name="sendToPipeline">
|
||||
/// The object to enumerate and write to the output pipe.
|
||||
/// </param>
|
||||
/// <exception cref="System.Management.Automation.PipelineStoppedException">
|
||||
/// The pipeline has already been terminated, or was terminated
|
||||
/// during the execution of this method.
|
||||
|
@ -2594,7 +2645,9 @@ namespace System.Management.Automation
|
|||
foreach (object toConvert in enumerable)
|
||||
{
|
||||
if (AutomationNull.Value == toConvert)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
object converted = LanguagePrimitives.AsPSObjectOrNull(toConvert);
|
||||
convertedList.Add(converted);
|
||||
|
@ -2660,6 +2713,12 @@ namespace System.Management.Automation
|
|||
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, ActionPreference>(errorRecord, preference));
|
||||
|
@ -2898,7 +2957,7 @@ namespace System.Management.Automation
|
|||
if (!_isConfirmPreferenceCached)
|
||||
{
|
||||
bool defaultUsed = false;
|
||||
_confirmPreference = Context.GetEnumPreference<ConfirmImpact>(SpecialVariables.ConfirmPreferenceVarPath, _confirmPreference, out defaultUsed);
|
||||
_confirmPreference = Context.GetEnumPreference(SpecialVariables.ConfirmPreferenceVarPath, _confirmPreference, out defaultUsed);
|
||||
_isConfirmPreferenceCached = true;
|
||||
}
|
||||
|
||||
|
@ -2933,7 +2992,7 @@ namespace System.Management.Automation
|
|||
{
|
||||
bool defaultUsed = false;
|
||||
|
||||
_debugPreference = Context.GetEnumPreference<ActionPreference>(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 @@ namespace System.Management.Automation
|
|||
if (!_isVerbosePreferenceCached)
|
||||
{
|
||||
bool defaultUsed = false;
|
||||
_verbosePreference = Context.GetEnumPreference<ActionPreference>(
|
||||
_verbosePreference = Context.GetEnumPreference(
|
||||
SpecialVariables.VerbosePreferenceVarPath,
|
||||
_verbosePreference,
|
||||
out defaultUsed);
|
||||
|
@ -3029,7 +3088,7 @@ namespace System.Management.Automation
|
|||
if (!_isWarningPreferenceCached)
|
||||
{
|
||||
bool defaultUsed = false;
|
||||
_warningPreference = Context.GetEnumPreference<ActionPreference>(SpecialVariables.WarningPreferenceVarPath, _warningPreference, out defaultUsed);
|
||||
_warningPreference = Context.GetEnumPreference(SpecialVariables.WarningPreferenceVarPath, _warningPreference, out defaultUsed);
|
||||
}
|
||||
|
||||
return _warningPreference;
|
||||
|
@ -3198,7 +3257,7 @@ namespace System.Management.Automation
|
|||
if (!_isErrorActionPreferenceCached)
|
||||
{
|
||||
bool defaultUsed = false;
|
||||
_errorAction = Context.GetEnumPreference<ActionPreference>(SpecialVariables.ErrorActionPreferenceVarPath, _errorAction, out defaultUsed);
|
||||
_errorAction = Context.GetEnumPreference(SpecialVariables.ErrorActionPreferenceVarPath, _errorAction, out defaultUsed);
|
||||
_isErrorActionPreferenceCached = true;
|
||||
}
|
||||
|
||||
|
@ -3233,7 +3292,7 @@ namespace System.Management.Automation
|
|||
if (!_isProgressPreferenceCached)
|
||||
{
|
||||
bool defaultUsed = false;
|
||||
_progressPreference = Context.GetEnumPreference<ActionPreference>(SpecialVariables.ProgressPreferenceVarPath, _progressPreference, out defaultUsed);
|
||||
_progressPreference = Context.GetEnumPreference(SpecialVariables.ProgressPreferenceVarPath, _progressPreference, out defaultUsed);
|
||||
_isProgressPreferenceCached = true;
|
||||
}
|
||||
|
||||
|
@ -3265,7 +3324,7 @@ namespace System.Management.Automation
|
|||
if (!_isInformationPreferenceCached)
|
||||
{
|
||||
bool defaultUsed = false;
|
||||
_informationPreference = Context.GetEnumPreference<ActionPreference>(SpecialVariables.InformationPreferenceVarPath, _informationPreference, out defaultUsed);
|
||||
_informationPreference = Context.GetEnumPreference(SpecialVariables.InformationPreferenceVarPath, _informationPreference, out defaultUsed);
|
||||
_isInformationPreferenceCached = true;
|
||||
}
|
||||
|
||||
|
@ -3364,6 +3423,7 @@ namespace System.Management.Automation
|
|||
case ActionPreference.Continue:
|
||||
case ActionPreference.Stop:
|
||||
case ActionPreference.Inquire:
|
||||
case ActionPreference.Break:
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
@ -3416,6 +3476,7 @@ namespace System.Management.Automation
|
|||
case ActionPreference.Ignore: // YesToAll
|
||||
case ActionPreference.SilentlyContinue:
|
||||
case ActionPreference.Continue:
|
||||
case ActionPreference.Break:
|
||||
return ContinueStatus.Yes;
|
||||
|
||||
case ActionPreference.Stop:
|
||||
|
|
|
@ -666,6 +666,15 @@ namespace System.Management.Automation
|
|||
|
||||
#region Internal Methods
|
||||
|
||||
/// <summary>
|
||||
/// Breaks into the debugger.
|
||||
/// </summary>
|
||||
/// <param name="triggerObject">The object that triggered the breakpoint, if there is one.</param>
|
||||
internal virtual void Break(object triggerObject = null)
|
||||
{
|
||||
throw new PSNotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 @@ namespace System.Management.Automation
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the object that triggered the current breakpoint.
|
||||
/// </summary>
|
||||
private object TriggerObject { get; set; }
|
||||
|
||||
#endregion properties
|
||||
|
||||
#region internal methods
|
||||
|
@ -1734,7 +1748,9 @@ namespace System.Management.Automation
|
|||
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 @@ namespace System.Management.Automation
|
|||
DebuggerStopEventArgs oldArgs;
|
||||
_debuggerStopEventArgs.TryPop(out oldArgs);
|
||||
|
||||
_context.QuestionMarkVariableValue = oldQuestionMarkVariableValue;
|
||||
|
||||
_inBreakpoint = false;
|
||||
}
|
||||
}
|
||||
|
@ -2500,6 +2518,39 @@ namespace System.Management.Automation
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Breaks into the debugger.
|
||||
/// </summary>
|
||||
/// <param name="triggerObject">The object that triggered the breakpoint, if there is one.</param>
|
||||
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<CallStackFrame> enumerator = GetCallStack().GetEnumerator())
|
||||
{
|
||||
if (enumerator.MoveNext())
|
||||
{
|
||||
OnSequencePointHit(enumerator.Current.FunctionContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 @@ namespace System.Management.Automation
|
|||
get { return _wrappedDebugger.IsActive; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Breaks into the debugger.
|
||||
/// </summary>
|
||||
/// <param name="triggerObject">The object that triggered the breakpoint, if there is one.</param>
|
||||
internal override void Break(object triggerObject = null)
|
||||
{
|
||||
_wrappedDebugger.Break(triggerObject);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
@ -4776,15 +4836,15 @@ namespace System.Management.Automation
|
|||
public DebuggerCommandProcessor()
|
||||
{
|
||||
_commandTable = new Dictionary<string, DebuggerCommand>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -5129,11 +5189,22 @@ namespace System.Management.Automation
|
|||
public class PSDebugContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// Initializes a new instance of the <see cref="PSDebugContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="invocationInfo">InvocationInfo.</param>
|
||||
/// <param name="breakpoints">Breakpoints.</param>
|
||||
/// <param name="invocationInfo">The invocation information for the current command.</param>
|
||||
/// <param name="breakpoints">The breakpoint(s) that caused the script to break in the debugger.</param>
|
||||
public PSDebugContext(InvocationInfo invocationInfo, List<Breakpoint> breakpoints)
|
||||
: this(invocationInfo, breakpoints, triggerObject: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PSDebugContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="invocationInfo">The invocation information for the current command.</param>
|
||||
/// <param name="breakpoints">The breakpoint(s) that caused the script to break in the debugger.</param>
|
||||
/// <param name="triggerObject">The object that caused the script to break in the debugger.</param>
|
||||
public PSDebugContext(InvocationInfo invocationInfo, List<Breakpoint> breakpoints, object triggerObject)
|
||||
{
|
||||
if (breakpoints == null)
|
||||
{
|
||||
|
@ -5142,6 +5213,7 @@ namespace System.Management.Automation
|
|||
|
||||
this.InvocationInfo = invocationInfo;
|
||||
this.Breakpoints = breakpoints.ToArray();
|
||||
this.Trigger = triggerObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -5154,6 +5226,11 @@ namespace System.Management.Automation
|
|||
/// were hit. Otherwise, the execution was suspended as part of a step operation.
|
||||
/// </summary>
|
||||
public Breakpoint[] Breakpoints { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object that triggered the current dynamic breakpoint.
|
||||
/// </summary>
|
||||
public object Trigger { get; private set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -379,6 +379,7 @@ namespace System.Management.Automation.Internal.Host
|
|||
switch (preference)
|
||||
{
|
||||
case ActionPreference.Continue:
|
||||
case ActionPreference.Break:
|
||||
WriteDebugLineHelper(message);
|
||||
break;
|
||||
case ActionPreference.SilentlyContinue:
|
||||
|
|
|
@ -4954,7 +4954,8 @@ namespace System.Management.Automation.Language
|
|||
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
|
||||
|
|
|
@ -145,9 +145,9 @@ namespace System.Management.Automation.Language
|
|||
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)
|
||||
|
|
|
@ -1981,6 +1981,11 @@ namespace System.Management.Automation
|
|||
return _wrappedDebugger.Value.GetCallStack();
|
||||
}
|
||||
|
||||
internal override void Break(object triggerObject = null)
|
||||
{
|
||||
_wrappedDebugger.Value.Break(triggerObject);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
|
|
@ -108,10 +108,13 @@ namespace System.Management.Automation
|
|||
|
||||
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 @@ namespace System.Management.Automation
|
|||
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 @@ namespace System.Management.Automation
|
|||
{
|
||||
foreach (object obj in enumerableValue)
|
||||
{
|
||||
if (markUntrustedData) { ExecutionContext.MarkObjectAsUntrusted(obj); }
|
||||
if (markUntrustedData)
|
||||
{
|
||||
ExecutionContext.MarkObjectAsUntrusted(obj);
|
||||
}
|
||||
|
||||
yield return SplatEnumerableElement(obj, splatAst);
|
||||
}
|
||||
|
@ -1335,15 +1344,15 @@ namespace System.Management.Automation
|
|||
}
|
||||
|
||||
// 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 @@ namespace System.Management.Automation
|
|||
// 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)
|
||||
{
|
||||
context.Debugger?.Break(rte);
|
||||
}
|
||||
|
||||
// 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 if (ExceptionCannotBeStoppedContinuedOrIgnored(rte, context))
|
||||
{
|
||||
throw rte;
|
||||
}
|
||||
|
||||
ActionPreference preference;
|
||||
if (anyTrapHandlers)
|
||||
else if (preference == ActionPreference.Inquire && !rte.SuppressPromptInInterpreter)
|
||||
{
|
||||
preference = ProcessTraps(funcContext, rte);
|
||||
preference = InquireForActionPreference(rte.Message, context);
|
||||
}
|
||||
else
|
||||
{
|
||||
preference = ExceptionHandlingOps.QueryForAction(rte, 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 @@ namespace System.Management.Automation
|
|||
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 @@ namespace System.Management.Automation
|
|||
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 @@ namespace System.Management.Automation
|
|||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if we should continue or not after and error or exception....
|
||||
/// Gets the current error action preference value.
|
||||
/// </summary>
|
||||
/// <param name="context">The execution context.</param>
|
||||
/// <returns>The preference the user selected.</returns>
|
||||
/// <remarks>
|
||||
/// Error action is decided by error action preference. If preference is inquire, we will
|
||||
/// prompt user for their preference.
|
||||
/// </remarks>
|
||||
internal static ActionPreference GetErrorActionPreference(ExecutionContext context)
|
||||
{
|
||||
return context.GetEnumPreference(
|
||||
SpecialVariables.ErrorActionPreferenceVarPath,
|
||||
ActionPreference.Continue,
|
||||
out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if we should continue or not after an error or exception.
|
||||
/// </summary>
|
||||
/// <param name="rte">The RuntimeException which was reported.</param>
|
||||
/// <param name="message">The message to display.</param>
|
||||
|
@ -1766,10 +1809,11 @@ namespace System.Management.Automation
|
|||
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 @@ namespace System.Management.Automation
|
|||
|
||||
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 @@ namespace System.Management.Automation
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1901,10 +1949,13 @@ namespace System.Management.Automation
|
|||
|
||||
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 @@ namespace System.Management.Automation
|
|||
{
|
||||
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 @@ namespace System.Management.Automation
|
|||
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 @@ namespace System.Management.Automation
|
|||
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);
|
||||
|
||||
|
|
|
@ -284,6 +284,8 @@ namespace System.Management.Automation
|
|||
|
||||
private bool _thrownByThrowStatement;
|
||||
|
||||
internal bool WasRethrown { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fix for BUG: Windows Out Of Band Releases: 906263 and 906264
|
||||
/// The interpreter prompt CommandBaseStrings:InquireHalt
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue