PowerShell/src/System.Management.Automation/engine/debugger/debugger.cs
xtqqczze 883ca98dd7
Seal private classes (#15725)
* Seal private classes

* Fix CS0509

* Fix CS0628
2021-07-19 14:09:12 +05:00

5919 lines
221 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Internal.Host;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.PowerShell.Commands.Internal.Format;
namespace System.Management.Automation
{
#region Event Args
/// <summary>
/// Possible actions for the debugger after hitting a breakpoint/step.
/// </summary>
public enum DebuggerResumeAction
{
/// <summary>
/// Continue running until the next breakpoint, or the end of the script.
/// </summary>
Continue = 0,
/// <summary>
/// Step to next statement, going into functions, scripts, etc.
/// </summary>
StepInto = 1,
/// <summary>
/// Step to next statement, going over functions, scripts, etc.
/// </summary>
StepOut = 2,
/// <summary>
/// Step to next statement after the current function, script, etc.
/// </summary>
StepOver = 3,
/// <summary>
/// Stop executing the script.
/// </summary>
Stop = 4,
}
/// <summary>
/// Arguments for the DebuggerStop event.
/// </summary>
public class DebuggerStopEventArgs : EventArgs
{
/// <summary>
/// Initializes the DebuggerStopEventArgs.
/// </summary>
internal DebuggerStopEventArgs(InvocationInfo invocationInfo, List<Breakpoint> breakpoints)
{
this.InvocationInfo = invocationInfo;
this.Breakpoints = new ReadOnlyCollection<Breakpoint>(breakpoints);
this.ResumeAction = DebuggerResumeAction.Continue;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="invocationInfo"></param>
/// <param name="breakpoints"></param>
/// <param name="resumeAction"></param>
public DebuggerStopEventArgs(
InvocationInfo invocationInfo,
Collection<Breakpoint> breakpoints,
DebuggerResumeAction resumeAction)
{
this.InvocationInfo = invocationInfo;
this.Breakpoints = new ReadOnlyCollection<Breakpoint>(breakpoints);
this.ResumeAction = resumeAction;
}
/// <summary>
/// Invocation info of the code being executed.
/// </summary>
public InvocationInfo InvocationInfo { get; internal set; }
/// <summary>
/// The breakpoint(s) hit.
/// </summary>
/// <remarks>
/// Note there may be more than one breakpoint on the same object (line, variable, command). A single event is
/// raised for all these breakpoints.
/// </remarks>
public ReadOnlyCollection<Breakpoint> Breakpoints { get; }
/// <summary>
/// This property must be set in the event handler to indicate the debugger what it should do next.
/// </summary>
/// <remarks>
/// The default action is DebuggerAction.Continue.
/// DebuggerAction.StepToLine is only valid when debugging an script.
/// </remarks>
public DebuggerResumeAction ResumeAction { get; set; }
/// <summary>
/// This property is used internally for remote debug stops only. It is used to signal the remote debugger proxy
/// that it should *not* send a resume action to the remote debugger. This is used by runspace debug processing to
/// leave pending runspace debug sessions suspended until a debugger is attached.
/// </summary>
internal bool SuspendRemote { get; set; }
}
/// <summary>
/// Kinds of breakpoint updates.
/// </summary>
public enum BreakpointUpdateType
{
/// <summary>
/// A breakpoint was set.
/// </summary>
Set = 0,
/// <summary>
/// A breakpoint was removed.
/// </summary>
Removed = 1,
/// <summary>
/// A breakpoint was enabled.
/// </summary>
Enabled = 2,
/// <summary>
/// A breakpoint was disabled.
/// </summary>
Disabled = 3
}
/// <summary>
/// Arguments for the BreakpointUpdated event.
/// </summary>
public class BreakpointUpdatedEventArgs : EventArgs
{
/// <summary>
/// Initializes the BreakpointUpdatedEventArgs.
/// </summary>
internal BreakpointUpdatedEventArgs(Breakpoint breakpoint, BreakpointUpdateType updateType, int breakpointCount)
{
this.Breakpoint = breakpoint;
this.UpdateType = updateType;
this.BreakpointCount = breakpointCount;
}
/// <summary>
/// Gets the breakpoint that was updated.
/// </summary>
public Breakpoint Breakpoint { get; }
/// <summary>
/// Gets the type of update.
/// </summary>
public BreakpointUpdateType UpdateType { get; }
/// <summary>
/// Gets the current breakpoint count.
/// </summary>
public int BreakpointCount { get; }
}
#region PSJobStartEventArgs
/// <summary>
/// Arguments for the script job start callback event.
/// </summary>
public sealed class PSJobStartEventArgs : EventArgs
{
/// <summary>
/// Job to be started.
/// </summary>
public Job Job { get; }
/// <summary>
/// Job debugger.
/// </summary>
public Debugger Debugger { get; }
/// <summary>
/// Job is run asynchronously.
/// </summary>
public bool IsAsync { get; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="job">Started job.</param>
/// <param name="debugger">Debugger.</param>
/// <param name="isAsync">Job started asynchronously.</param>
public PSJobStartEventArgs(Job job, Debugger debugger, bool isAsync)
{
this.Job = job;
this.Debugger = debugger;
this.IsAsync = isAsync;
}
}
#endregion
#region Runspace Debug Processing
/// <summary>
/// StartRunspaceDebugProcessing event arguments.
/// </summary>
public sealed class StartRunspaceDebugProcessingEventArgs : EventArgs
{
/// <summary> The runspace to process </summary>
public Runspace Runspace { get; }
/// <summary>
/// When set to true this will cause PowerShell to process this runspace debug session through its
/// script debugger. To use the default processing return from this event call after setting
/// this property to true.
/// </summary>
public bool UseDefaultProcessing
{
get;
set;
}
/// <summary>
/// Constructor.
/// </summary>
public StartRunspaceDebugProcessingEventArgs(Runspace runspace)
{
if (runspace == null) { throw new PSArgumentNullException(nameof(runspace)); }
Runspace = runspace;
}
}
/// <summary>
/// ProcessRunspaceDebugEnd event arguments.
/// </summary>
public sealed class ProcessRunspaceDebugEndEventArgs : EventArgs
{
/// <summary>
/// The runspace where internal debug processing has ended.
/// </summary>
public Runspace Runspace { get; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="runspace"></param>
public ProcessRunspaceDebugEndEventArgs(Runspace runspace)
{
if (runspace == null) { throw new PSArgumentNullException(nameof(runspace)); }
Runspace = runspace;
}
}
#endregion
#endregion
#region Enums
/// <summary>
/// Defines debugging mode.
/// </summary>
[Flags]
public enum DebugModes
{
/// <summary>
/// PowerShell script debugging is disabled.
/// </summary>
None = 0x0,
/// <summary>
/// Default setting for original PowerShell script debugging.
/// Compatible with PowerShell Versions 2 and 3.
/// </summary>
Default = 0x1,
/// <summary>
/// PowerShell script debugging.
/// </summary>
LocalScript = 0x2,
/// <summary>
/// PowerShell remote script debugging.
/// </summary>
RemoteScript = 0x4
}
/// <summary>
/// Defines unhandled breakpoint processing behavior.
/// </summary>
internal enum UnhandledBreakpointProcessingMode
{
/// <summary>
/// Ignore unhandled breakpoint events.
/// </summary>
Ignore = 1,
/// <summary>
/// Wait on unhandled breakpoint events until a handler is available.
/// </summary>
Wait
}
#endregion
#region Debugger base class
/// <summary>
/// Base class for all PowerShell debuggers.
/// </summary>
public abstract class Debugger
{
#region Events
/// <summary>
/// Event raised when the debugger hits a breakpoint or a step.
/// </summary>
public event EventHandler<DebuggerStopEventArgs> DebuggerStop;
/// <summary>
/// Event raised when a breakpoint is updated.
/// </summary>
public event EventHandler<BreakpointUpdatedEventArgs> BreakpointUpdated;
/// <summary>
/// Event raised when nested debugging is cancelled.
/// </summary>
internal event EventHandler<EventArgs> NestedDebuggingCancelledEvent;
#region Runspace Debug Processing Events
/// <summary>
/// Event raised when a runspace debugger needs breakpoint processing.
/// </summary>
public event EventHandler<StartRunspaceDebugProcessingEventArgs> StartRunspaceDebugProcessing;
/// <summary>
/// Event raised when a runspace debugger is finished being processed.
/// </summary>
public event EventHandler<ProcessRunspaceDebugEndEventArgs> RunspaceDebugProcessingCompleted;
/// <summary>
/// Event raised to indicate that the debugging session is over and runspace debuggers queued for
/// processing should be released.
/// </summary>
public event EventHandler<EventArgs> CancelRunspaceDebugProcessing;
#endregion
#endregion
#region Properties
/// <summary>
/// True when the debugger is stopped.
/// </summary>
protected bool DebuggerStopped
{
get;
private set;
}
/// <summary>
/// IsPushed.
/// </summary>
internal virtual bool IsPushed
{
get { return false; }
}
/// <summary>
/// IsRemote.
/// </summary>
internal virtual bool IsRemote
{
get { return false; }
}
/// <summary>
/// Returns true if the debugger is preserving a DebuggerStopEvent
/// event. Use ReleaseSavedDebugStop() to allow event to process.
/// </summary>
internal virtual bool IsPendingDebugStopEvent
{
get { throw new PSNotImplementedException(); }
}
/// <summary>
/// Returns true if debugger has been set to stepInto mode.
/// </summary>
internal virtual bool IsDebuggerSteppingEnabled
{
get { throw new PSNotImplementedException(); }
}
/// <summary>
/// Returns true if there is a handler for debugger stops.
/// </summary>
internal bool IsDebugHandlerSubscribed
{
get { return (DebuggerStop != null); }
}
/// <summary>
/// UnhandledBreakpointMode.
/// </summary>
internal virtual UnhandledBreakpointProcessingMode UnhandledBreakpointMode
{
get { throw new PSNotImplementedException(); }
set { throw new PSNotImplementedException(); }
}
/// <summary>
/// DebuggerMode.
/// </summary>
public DebugModes DebugMode { get; protected set; } = DebugModes.Default;
/// <summary>
/// Returns true if debugger has breakpoints set and
/// is currently active.
/// </summary>
public virtual bool IsActive
{
get { return false; }
}
/// <summary>
/// InstanceId.
/// </summary>
public virtual Guid InstanceId
{
get { return s_instanceId; }
}
/// <summary>
/// True when debugger is stopped at a breakpoint.
/// </summary>
public virtual bool InBreakpoint
{
get { return DebuggerStopped; }
}
#endregion
#region Protected Methods
/// <summary>
/// RaiseDebuggerStopEvent.
/// </summary>
/// <param name="args">DebuggerStopEventArgs.</param>
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
protected void RaiseDebuggerStopEvent(DebuggerStopEventArgs args)
{
try
{
DebuggerStopped = true;
DebuggerStop.SafeInvoke<DebuggerStopEventArgs>(this, args);
}
finally
{
DebuggerStopped = false;
}
}
/// <summary>
/// IsDebuggerStopEventSubscribed.
/// </summary>
/// <returns>True if event subscription exists.</returns>
protected bool IsDebuggerStopEventSubscribed()
{
return (DebuggerStop != null);
}
/// <summary>
/// RaiseBreakpointUpdatedEvent.
/// </summary>
/// <param name="args">BreakpointUpdatedEventArgs.</param>
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
protected void RaiseBreakpointUpdatedEvent(BreakpointUpdatedEventArgs args)
{
BreakpointUpdated.SafeInvoke<BreakpointUpdatedEventArgs>(this, args);
}
/// <summary>
/// IsDebuggerBreakpointUpdatedEventSubscribed.
/// </summary>
/// <returns>True if event subscription exists.</returns>
protected bool IsDebuggerBreakpointUpdatedEventSubscribed()
{
return (BreakpointUpdated != null);
}
#region Runspace Debug Processing
/// <summary/>
protected void RaiseStartRunspaceDebugProcessingEvent(StartRunspaceDebugProcessingEventArgs args)
{
if (args == null) { throw new PSArgumentNullException(nameof(args)); }
StartRunspaceDebugProcessing.SafeInvoke<StartRunspaceDebugProcessingEventArgs>(this, args);
}
/// <summary/>
protected void RaiseRunspaceProcessingCompletedEvent(ProcessRunspaceDebugEndEventArgs args)
{
if (args == null) { throw new PSArgumentNullException(nameof(args)); }
RunspaceDebugProcessingCompleted.SafeInvoke<ProcessRunspaceDebugEndEventArgs>(this, args);
}
/// <summary/>
protected bool IsStartRunspaceDebugProcessingEventSubscribed()
{
return (StartRunspaceDebugProcessing != null);
}
/// <summary/>
protected void RaiseCancelRunspaceDebugProcessingEvent()
{
CancelRunspaceDebugProcessing.SafeInvoke<EventArgs>(this, null);
}
#endregion
#endregion
#region Public Methods
/// <summary>
/// Evaluates provided command either as a debugger specific command
/// or a PowerShell command.
/// </summary>
/// <param name="command">PowerShell command.</param>
/// <param name="output">Output.</param>
/// <returns>DebuggerCommandResults.</returns>
public abstract DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection<PSObject> output);
/// <summary>
/// Sets the debugger resume action.
/// </summary>
/// <param name="resumeAction">DebuggerResumeAction.</param>
public abstract void SetDebuggerAction(DebuggerResumeAction resumeAction);
/// <summary>
/// Stops a running command.
/// </summary>
public abstract void StopProcessCommand();
/// <summary>
/// Returns current debugger stop event arguments if debugger is in
/// debug stop state. Otherwise returns null.
/// </summary>
/// <returns>DebuggerStopEventArgs.</returns>
public abstract DebuggerStopEventArgs GetDebuggerStopArgs();
/// <summary>
/// Sets the parent debugger, breakpoints and other debugging context information.
/// </summary>
/// <param name="parent">Parent debugger.</param>
/// <param name="breakPoints">List of breakpoints.</param>
/// <param name="startAction">Debugger mode.</param>
/// <param name="host">Host.</param>
/// <param name="path">Current path.</param>
public virtual void SetParent(
Debugger parent,
IEnumerable<Breakpoint> breakPoints,
DebuggerResumeAction? startAction,
PSHost host,
PathInfo path)
{
throw new PSNotImplementedException();
}
/// <summary>
/// Sets the debugger mode.
/// </summary>
public virtual void SetDebugMode(DebugModes mode)
{
this.DebugMode = mode;
}
/// <summary>
/// Returns IEnumerable of CallStackFrame objects.
/// </summary>
/// <returns></returns>
public virtual IEnumerable<CallStackFrame> GetCallStack()
{
return new Collection<CallStackFrame>();
}
/// <summary>
/// Get a breakpoint by id in the current runspace, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets.
/// </summary>
/// <param name="id">Id of the breakpoint you want.</param>
public Breakpoint GetBreakpoint(int id) =>
GetBreakpoint(id, runspaceId: null);
/// <summary>
/// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets.
/// </summary>
/// <param name="id">Id of the breakpoint you want.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
public virtual Breakpoint GetBreakpoint(int id, int? runspaceId) =>
throw new PSNotImplementedException();
/// <summary>
/// Adds the provided set of breakpoints to the debugger, in the current runspace.
/// </summary>
/// <param name="breakpoints">Breakpoints.</param>
public void SetBreakpoints(IEnumerable<Breakpoint> breakpoints) =>
SetBreakpoints(breakpoints, runspaceId: null);
/// <summary>
/// Adds the provided set of breakpoints to the debugger.
/// </summary>
/// <param name="breakpoints">Breakpoints.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with, null being the current runspace.</param>
public virtual void SetBreakpoints(IEnumerable<Breakpoint> breakpoints, int? runspaceId) =>
throw new PSNotImplementedException();
/// <summary>
/// Returns breakpoints in the current runspace, primarily for the Get-PSBreakpoint cmdlet.
/// </summary>
public List<Breakpoint> GetBreakpoints() =>
GetBreakpoints(runspaceId: null);
/// <summary>
/// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet.
/// </summary>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
public virtual List<Breakpoint> GetBreakpoints(int? runspaceId) =>
throw new PSNotImplementedException();
/// <summary>
/// Sets a command breakpoint in the current runspace in the debugger.
/// </summary>
/// <param name="command">The name of the command that will trigger the breakpoint. This value may not be null.</param>
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
/// <param name="path">The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked.</param>
/// <returns>The command breakpoint that was set.</returns>
public CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path) =>
SetCommandBreakpoint(command, action, path, runspaceId: null);
/// <summary>
/// Sets a command breakpoint in the debugger.
/// </summary>
/// <param name="command">The name of the command that will trigger the breakpoint. This value may not be null.</param>
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
/// <param name="path">The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A value of null will use the current runspace.</param>
/// <returns>The command breakpoint that was set.</returns>
public virtual CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path, int? runspaceId) =>
throw new PSNotImplementedException();
/// <summary>
/// Sets a line breakpoint in the current runspace in the debugger.
/// </summary>
/// <param name="path">The path to the script file where the breakpoint may be hit. This value may not be null.</param>
/// <param name="line">The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1.</param>
/// <param name="column">The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line.</param>
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
/// <returns>The line breakpoint that was set.</returns>
public LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action) =>
SetLineBreakpoint(path, line, column, action, runspaceId: null);
/// <summary>
/// Sets a line breakpoint in the debugger.
/// </summary>
/// <param name="path">The path to the script file where the breakpoint may be hit. This value may not be null.</param>
/// <param name="line">The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1.</param>
/// <param name="column">The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line.</param>
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>The line breakpoint that was set.</returns>
public virtual LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action, int? runspaceId) =>
throw new PSNotImplementedException();
/// <summary>
/// Sets a variable breakpoint in the current runspace in the debugger.
/// </summary>
/// <param name="variableName">The name of the variable that will trigger the breakpoint. This value may not be null.</param>
/// <param name="accessMode">The variable access mode that will trigger the breakpoint.</param>
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
/// <param name="path">The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode.</param>
/// <returns>The variable breakpoint that was set.</returns>
public VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path) =>
SetVariableBreakpoint(variableName, accessMode, action, path, runspaceId: null);
/// <summary>
/// Sets a variable breakpoint in the debugger.
/// </summary>
/// <param name="variableName">The name of the variable that will trigger the breakpoint. This value may not be null.</param>
/// <param name="accessMode">The variable access mode that will trigger the breakpoint.</param>
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
/// <param name="path">The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>The variable breakpoint that was set.</returns>
public virtual VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path, int? runspaceId) =>
throw new PSNotImplementedException();
/// <summary>
/// Removes a breakpoint from the debugger in the current runspace.
/// </summary>
/// <param name="breakpoint">The breakpoint to remove from the debugger. This value may not be null.</param>
/// <returns>True if the breakpoint was removed from the debugger; false otherwise.</returns>
public bool RemoveBreakpoint(Breakpoint breakpoint) =>
RemoveBreakpoint(breakpoint, runspaceId: null);
/// <summary>
/// Removes a breakpoint from the debugger.
/// </summary>
/// <param name="breakpoint">The breakpoint to remove from the debugger. This value may not be null.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>True if the breakpoint was removed from the debugger; false otherwise.</returns>
public virtual bool RemoveBreakpoint(Breakpoint breakpoint, int? runspaceId) =>
throw new PSNotImplementedException();
/// <summary>
/// Enables a breakpoint in the debugger in the current runspace.
/// </summary>
/// <param name="breakpoint">The breakpoint to enable in the debugger. This value may not be null.</param>
/// <returns>The updated breakpoint if it was found; null if the breakpoint was not found in the debugger.</returns>
public Breakpoint EnableBreakpoint(Breakpoint breakpoint) =>
EnableBreakpoint(breakpoint, runspaceId: null);
/// <summary>
/// Enables a breakpoint in the debugger.
/// </summary>
/// <param name="breakpoint">The breakpoint to enable in the debugger. This value may not be null.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>The updated breakpoint if it was found; null if the breakpoint was not found in the debugger.</returns>
public virtual Breakpoint EnableBreakpoint(Breakpoint breakpoint, int? runspaceId) =>
throw new PSNotImplementedException();
/// <summary>
/// Disables a breakpoint in the debugger in the current runspace.
/// </summary>
/// <param name="breakpoint">The breakpoint to enable in the debugger. This value may not be null.</param>
/// <returns>The updated breakpoint if it was found; null if the breakpoint was not found in the debugger.</returns>
public Breakpoint DisableBreakpoint(Breakpoint breakpoint) =>
DisableBreakpoint(breakpoint, runspaceId: null);
/// <summary>
/// Disables a breakpoint in the debugger.
/// </summary>
/// <param name="breakpoint">The breakpoint to enable in the debugger. This value may not be null.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>The updated breakpoint if it was found; null if the breakpoint was not found in the debugger.</returns>
public virtual Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspaceId) =>
throw new PSNotImplementedException();
/// <summary>
/// Resets the command processor source information so that it is
/// updated with latest information on the next debug stop.
/// </summary>
public virtual void ResetCommandProcessorSource()
{
throw new PSNotImplementedException();
}
/// <summary>
/// Sets debugger stepping mode.
/// </summary>
/// <param name="enabled">True if stepping is to be enabled.</param>
public virtual void SetDebuggerStepMode(bool enabled)
{
throw new PSNotImplementedException();
}
#endregion
#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.
/// </summary>
/// <param name="command">Command string.</param>
/// <param name="output">Output collection.</param>
/// <returns>DebuggerCommand containing information on whether and how the command was processed.</returns>
internal virtual DebuggerCommand InternalProcessCommand(string command, IList<PSObject> output)
{
throw new PSNotImplementedException();
}
/// <summary>
/// Creates a source list based on root script debugger source information if available, with
/// the current source line highlighted. This is used internally for nested runspace debugging
/// where the runspace command is run in context of a parent script.
/// </summary>
/// <param name="lineNum">Current source line.</param>
/// <param name="output">Output collection.</param>
/// <returns>True if source listed successfully.</returns>
internal virtual bool InternalProcessListCommand(int lineNum, IList<PSObject> output)
{
throw new PSNotImplementedException();
}
/// <summary>
/// Sets up debugger to debug provided job or its child jobs.
/// </summary>
/// <param name="job">
/// Job object that is either a debuggable job or a container of
/// debuggable child jobs.
/// </param>
/// <param name="breakAll">
/// If true, the debugger automatically invokes a break all when it
/// attaches to the job.
/// </param>
internal virtual void DebugJob(Job job, bool breakAll) =>
throw new PSNotImplementedException();
/// <summary>
/// Removes job from debugger job list and pops the its
/// debugger from the active debugger stack.
/// </summary>
/// <param name="job">Job.</param>
internal virtual void StopDebugJob(Job job)
{
throw new PSNotImplementedException();
}
/// <summary>
/// GetActiveDebuggerCallStack.
/// </summary>
/// <returns>Array of stack frame objects of active debugger.</returns>
internal virtual CallStackFrame[] GetActiveDebuggerCallStack()
{
throw new PSNotImplementedException();
}
/// <summary>
/// Method to add the provided runspace information to the debugger
/// for monitoring of debugger events. This is used to implement nested
/// debugging of runspaces.
/// </summary>
/// <param name="args">PSEntityCreatedRunspaceEventArgs.</param>
internal virtual void StartMonitoringRunspace(PSMonitorRunspaceInfo args)
{
throw new PSNotImplementedException();
}
/// <summary>
/// Method to end the monitoring of a runspace for debugging events.
/// </summary>
/// <param name="args">PSEntityCreatedRunspaceEventArgs.</param>
internal virtual void EndMonitoringRunspace(PSMonitorRunspaceInfo args)
{
throw new PSNotImplementedException();
}
/// <summary>
/// If a debug stop event is currently pending then this method will release
/// the event to continue processing.
/// </summary>
internal virtual void ReleaseSavedDebugStop()
{
throw new PSNotImplementedException();
}
/// <summary>
/// Sets up debugger to debug provided Runspace in a nested debug session.
/// </summary>
/// <param name="runspace">
/// The runspace to debug.
/// </param>
/// <param name="breakAll">
/// If true, the debugger automatically invokes a break all when it
/// attaches to the runspace.
/// </param>
internal virtual void DebugRunspace(Runspace runspace, bool breakAll) =>
throw new PSNotImplementedException();
/// <summary>
/// Removes the provided Runspace from the nested "active" debugger state.
/// </summary>
/// <param name="runspace">Runspace.</param>
internal virtual void StopDebugRunspace(Runspace runspace)
{
throw new PSNotImplementedException();
}
/// <summary>
/// Raises the NestedDebuggingCancelledEvent event.
/// </summary>
internal void RaiseNestedDebuggingCancelEvent()
{
// Raise event on worker thread.
Threading.ThreadPool.QueueUserWorkItem(
(state) =>
{
try
{
NestedDebuggingCancelledEvent.SafeInvoke<EventArgs>(this, null);
}
catch (Exception)
{
}
});
}
#endregion
#region Runspace Debug Processing Methods
/// <summary>
/// Adds the provided Runspace object to the runspace debugger processing queue.
/// The queue will then raise the StartRunspaceDebugProcessing events for each runspace to allow
/// a host script debugger implementation to provide an active debugging session.
/// </summary>
/// <param name="runspace">Runspace to debug.</param>
internal virtual void QueueRunspaceForDebug(Runspace runspace)
{
throw new PSNotImplementedException();
}
/// <summary>
/// Causes the CancelRunspaceDebugProcessing event to be raised which notifies subscribers that current debugging
/// sessions should be cancelled.
/// </summary>
public virtual void CancelDebuggerProcessing()
{
throw new PSNotImplementedException();
}
#endregion
#region Members
internal const string CannotProcessCommandNotStopped = "Debugger:CannotProcessCommandNotStopped";
internal const string CannotEnableDebuggerSteppingInvalidMode = "Debugger:CannotEnableDebuggerSteppingInvalidMode";
private static readonly Guid s_instanceId = new Guid();
#endregion
}
#endregion
#region ScriptDebugger class
/// <summary>
/// Holds the debugging information for a Monad Shell session.
/// </summary>
internal sealed class ScriptDebugger : Debugger, IDisposable
{
#region constructors
internal ScriptDebugger(ExecutionContext context)
{
_context = context;
_inBreakpoint = false;
_idToBreakpoint = new ConcurrentDictionary<int, Breakpoint>();
_pendingBreakpoints = new ConcurrentDictionary<int, LineBreakpoint>();
_boundBreakpoints = new ConcurrentDictionary<string, Tuple<WeakReference, ConcurrentDictionary<int, LineBreakpoint>>>(StringComparer.OrdinalIgnoreCase);
_commandBreakpoints = new ConcurrentDictionary<int, CommandBreakpoint>();
_variableBreakpoints = new ConcurrentDictionary<string, ConcurrentDictionary<int, VariableBreakpoint>>(StringComparer.OrdinalIgnoreCase);
_steppingMode = SteppingMode.None;
_callStack = new CallStackList { _callStackList = new List<CallStackInfo>() };
_runningJobs = new Dictionary<Guid, PSJobStartEventArgs>();
_activeDebuggers = new ConcurrentStack<Debugger>();
_debuggerStopEventArgs = new ConcurrentStack<DebuggerStopEventArgs>();
_syncObject = new object();
_syncActiveDebuggerStopObject = new object();
_runningRunspaces = new Dictionary<Guid, PSMonitorRunspaceInfo>();
}
/// <summary>
/// Static constructor.
/// </summary>
static ScriptDebugger()
{
s_processDebugPromptMatch = StringUtil.Format(@"""[{0}:", DebuggerStrings.NestedRunspaceDebuggerPromptProcessName);
}
#endregion constructors
#region properties
/// <summary>
/// True when debugger is stopped at a breakpoint.
/// </summary>
public override bool InBreakpoint
{
get
{
if (_inBreakpoint)
{
return _inBreakpoint;
}
Debugger activeDebugger;
if (_activeDebuggers.TryPeek(out activeDebugger))
{
return activeDebugger.InBreakpoint;
}
return false;
}
}
internal override bool IsPushed
{
get { return (!_activeDebuggers.IsEmpty); }
}
/// <summary>
/// Returns true if the debugger is preserving a DebuggerStopEvent
/// event. Use ReleaseSavedDebugStop() to allow event to process.
/// </summary>
internal override bool IsPendingDebugStopEvent
{
get
{
return ((_preserveDebugStopEvent != null) && !_preserveDebugStopEvent.IsSet);
}
}
/// <summary>
/// Returns true if debugger has been set to stepInto mode.
/// </summary>
internal override bool IsDebuggerSteppingEnabled
{
get
{
return ((_context._debuggingMode == (int)InternalDebugMode.Enabled) &&
(_currentDebuggerAction == DebuggerResumeAction.StepInto) &&
(_steppingMode != SteppingMode.None));
}
}
private bool? _isLocalSession;
private bool IsLocalSession
{
get
{
if (_isLocalSession == null)
{
// Remote debug sessions always have a ServerRemoteHost. Otherwise it is a local session.
_isLocalSession = !(((_context.InternalHost.ExternalHost != null) &&
(_context.InternalHost.ExternalHost is System.Management.Automation.Remoting.ServerRemoteHost)));
}
return _isLocalSession.Value;
}
}
/// <summary>
/// Gets or sets the object that triggered the current breakpoint.
/// </summary>
private object TriggerObject { get; set; }
#endregion properties
#region internal methods
#region Reset Debugger
/// <summary>
/// Resets debugger to initial state.
/// </summary>
internal void ResetDebugger()
{
SetDebugMode(DebugModes.None);
SetInternalDebugMode(InternalDebugMode.Disabled);
_steppingMode = SteppingMode.None;
_inBreakpoint = false;
_idToBreakpoint.Clear();
_pendingBreakpoints.Clear();
_boundBreakpoints.Clear();
_commandBreakpoints.Clear();
_variableBreakpoints.Clear();
s_emptyBreakpointList.Clear();
_callStack.Clear();
_overOrOutFrame = null;
_commandProcessor = new DebuggerCommandProcessor();
_currentInvocationInfo = null;
_inBreakpoint = false;
_psDebuggerCommand = null;
_savedIgnoreScriptDebug = false;
_isLocalSession = null;
_nestedDebuggerStop = false;
_debuggerStopEventArgs.Clear();
_lastActiveDebuggerAction = DebuggerResumeAction.Continue;
_currentDebuggerAction = DebuggerResumeAction.Continue;
_previousDebuggerAction = DebuggerResumeAction.Continue;
_nestedRunningFrame = null;
_nestedDebuggerStop = false;
_processingOutputCount = 0;
_preserveUnhandledDebugStopEvent = false;
ClearRunningJobList();
ClearRunningRunspaceList();
_activeDebuggers.Clear();
ReleaseSavedDebugStop();
SetDebugMode(DebugModes.Default);
}
#endregion
#region Call stack management
// Called from generated code on entering the script function, called once for each dynamicparam, begin, or end
// block, and once for each object written to the pipeline. Also called when entering a trap.
internal void EnterScriptFunction(FunctionContext functionContext)
{
Diagnostics.Assert(functionContext._executionContext == _context, "Wrong debugger is being used.");
var invocationInfo = (InvocationInfo)functionContext._localsTuple.GetAutomaticVariable(AutomaticVariable.MyInvocation);
var newCallStackInfo = new CallStackInfo
{
InvocationInfo = invocationInfo,
File = functionContext._file,
DebuggerStepThrough = functionContext._debuggerStepThrough,
FunctionContext = functionContext,
IsFrameHidden = functionContext._debuggerHidden,
};
_callStack.Add(newCallStackInfo);
if (_context._debuggingMode > 0)
{
var scriptCommandInfo = invocationInfo.MyCommand as ExternalScriptInfo;
if (scriptCommandInfo != null)
{
RegisterScriptFile(scriptCommandInfo);
}
bool checkLineBp = CheckCommand(invocationInfo);
SetupBreakpoints(functionContext);
if (functionContext._debuggerStepThrough && _overOrOutFrame == null && _steppingMode == SteppingMode.StepIn)
{
// Treat like step out, but only if we're not already stepping out
ResumeExecution(DebuggerResumeAction.StepOut);
}
if (checkLineBp)
{
OnSequencePointHit(functionContext);
}
if (_context.PSDebugTraceLevel > 1 && !functionContext._debuggerStepThrough && !functionContext._debuggerHidden)
{
TraceScriptFunctionEntry(functionContext);
}
}
}
private void SetupBreakpoints(FunctionContext functionContext)
{
var scriptDebugData = _mapScriptToBreakpoints.GetValue(functionContext._sequencePoints,
_ => Tuple.Create(new List<LineBreakpoint>(),
new BitArray(functionContext._sequencePoints.Length)));
functionContext._boundBreakpoints = scriptDebugData.Item1;
functionContext._breakPoints = scriptDebugData.Item2;
SetPendingBreakpoints(functionContext);
}
// Called after exiting the script function, called once for each dynamicparam, begin, or end
// block, and once for each object written to the pipeline. Also called when leaving a trap.
internal void ExitScriptFunction()
{
// If it's stepping over to exit the current frame, we need to clear the _overOrOutFrame,
// so that we will stop at the next statement in the outer frame.
if (_callStack.Last() == _overOrOutFrame)
{
_overOrOutFrame = null;
}
_callStack.RemoveAt(_callStack.Count - 1);
// Don't disable step mode if the user enabled runspace debugging (UnhandledBreakpointMode == Wait)
if ((_callStack.Count == 0) && (UnhandledBreakpointMode != UnhandledBreakpointProcessingMode.Wait))
{
// If we've popped the last entry, don't step into anything else (like prompt, suggestions, etc.)
_steppingMode = SteppingMode.None;
_currentDebuggerAction = DebuggerResumeAction.Continue;
_previousDebuggerAction = DebuggerResumeAction.Continue;
}
}
internal void RegisterScriptFile(ExternalScriptInfo scriptCommandInfo)
{
RegisterScriptFile(scriptCommandInfo.Path, scriptCommandInfo.ScriptContents);
}
internal void RegisterScriptFile(string path, string scriptContents)
{
Tuple<WeakReference, ConcurrentDictionary<int, LineBreakpoint>> boundBreakpoints;
if (!_boundBreakpoints.TryGetValue(path, out boundBreakpoints))
{
_boundBreakpoints[path] = Tuple.Create(new WeakReference(scriptContents), new ConcurrentDictionary<int, LineBreakpoint>());
}
else
{
// If script contents have changed, or if the file got collected, we must rebind the breakpoints.
string oldScriptContents;
boundBreakpoints.Item1.TryGetTarget(out oldScriptContents);
if (oldScriptContents == null || !oldScriptContents.Equals(scriptContents, StringComparison.Ordinal))
{
UnbindBoundBreakpoints(boundBreakpoints.Item2.Values.ToList());
_boundBreakpoints[path] = Tuple.Create(new WeakReference(scriptContents), new ConcurrentDictionary<int, LineBreakpoint>());
}
}
}
#endregion Call stack management
#region setting breakpoints
internal void AddBreakpointCommon(Breakpoint breakpoint)
{
if (_context._debuggingMode == 0)
{
SetInternalDebugMode(InternalDebugMode.Enabled);
}
_idToBreakpoint[breakpoint.Id] = breakpoint;
OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Set, _idToBreakpoint.Count));
}
private CommandBreakpoint AddCommandBreakpoint(CommandBreakpoint breakpoint)
{
AddBreakpointCommon(breakpoint);
_commandBreakpoints[breakpoint.Id] = breakpoint;
return breakpoint;
}
private LineBreakpoint AddLineBreakpoint(LineBreakpoint breakpoint)
{
AddBreakpointCommon(breakpoint);
_pendingBreakpoints[breakpoint.Id] = breakpoint;
return breakpoint;
}
private void AddNewBreakpoint(Breakpoint breakpoint)
{
LineBreakpoint lineBreakpoint = breakpoint as LineBreakpoint;
if (lineBreakpoint != null)
{
AddLineBreakpoint(lineBreakpoint);
return;
}
CommandBreakpoint cmdBreakpoint = breakpoint as CommandBreakpoint;
if (cmdBreakpoint != null)
{
AddCommandBreakpoint(cmdBreakpoint);
return;
}
VariableBreakpoint varBreakpoint = breakpoint as VariableBreakpoint;
if (varBreakpoint != null)
{
AddVariableBreakpoint(varBreakpoint);
}
}
internal VariableBreakpoint AddVariableBreakpoint(VariableBreakpoint breakpoint)
{
AddBreakpointCommon(breakpoint);
if (!_variableBreakpoints.TryGetValue(breakpoint.Variable, out ConcurrentDictionary<int, VariableBreakpoint> breakpoints))
{
breakpoints = new ConcurrentDictionary<int, VariableBreakpoint>();
_variableBreakpoints[breakpoint.Variable] = breakpoints;
}
breakpoints[breakpoint.Id] = breakpoint;
return breakpoint;
}
private void UpdateBreakpoints(FunctionContext functionContext)
{
if (functionContext._breakPoints == null)
{
// This should be rare - setting a breakpoint inside a script, but debugger hadn't started.
SetupBreakpoints(functionContext);
}
else
{
// Check pending breakpoints to see if any apply to this script.
if (string.IsNullOrEmpty(functionContext._file))
{
return;
}
foreach ((int breakpointId, LineBreakpoint item) in _pendingBreakpoints)
{
if (item.IsScriptBreakpoint && item.Script.Equals(functionContext._file, StringComparison.OrdinalIgnoreCase))
{
SetPendingBreakpoints(functionContext);
break;
}
}
}
}
/// <summary>
/// Raises the BreakpointUpdated event.
/// </summary>
/// <param name="e"></param>
private void OnBreakpointUpdated(BreakpointUpdatedEventArgs e)
{
RaiseBreakpointUpdatedEvent(e);
}
#endregion setting breakpoints
#region removing breakpoints
internal bool RemoveVariableBreakpoint(VariableBreakpoint breakpoint) =>
_variableBreakpoints[breakpoint.Variable].Remove(breakpoint.Id, out _);
internal bool RemoveCommandBreakpoint(CommandBreakpoint breakpoint) =>
_commandBreakpoints.Remove(breakpoint.Id, out _);
internal bool RemoveLineBreakpoint(LineBreakpoint breakpoint)
{
bool removed = _pendingBreakpoints.Remove(breakpoint.Id, out _);
Tuple<WeakReference, ConcurrentDictionary<int, LineBreakpoint>> value;
if (_boundBreakpoints.TryGetValue(breakpoint.Script, out value))
{
removed = value.Item2.Remove(breakpoint.Id, out _);
}
return removed;
}
#endregion removing breakpoints
#region finding breakpoints
// The bit array is used to detect if a breakpoint is set or not for a given scriptblock. This bit array
// is checked when hitting sequence points. Enabling/disabling a line breakpoint is as simple as flipping
// the bit.
private readonly ConditionalWeakTable<IScriptExtent[], Tuple<List<LineBreakpoint>, BitArray>> _mapScriptToBreakpoints =
new ConditionalWeakTable<IScriptExtent[], Tuple<List<LineBreakpoint>, BitArray>>();
/// <summary>
/// Checks for command breakpoints.
/// </summary>
internal bool CheckCommand(InvocationInfo invocationInfo)
{
var functionContext = _callStack.LastFunctionContext();
if (functionContext != null && functionContext._debuggerHidden)
{
// Never stop in DebuggerHidden scripts, don't even call the actions on breakpoints.
return false;
}
List<Breakpoint> breakpoints =
_commandBreakpoints.Values.Where(bp => bp.Enabled && bp.Trigger(invocationInfo)).ToList<Breakpoint>();
bool checkLineBp = true;
if (breakpoints.Count > 0)
{
breakpoints = TriggerBreakpoints(breakpoints);
if (breakpoints.Count > 0)
{
var breakInvocationInfo =
functionContext != null
? new InvocationInfo(invocationInfo.MyCommand, functionContext.CurrentPosition)
: null;
OnDebuggerStop(breakInvocationInfo, breakpoints);
checkLineBp = false;
}
}
return checkLineBp;
}
internal void CheckVariableRead(string variableName)
{
var breakpointsToTrigger = GetVariableBreakpointsToTrigger(variableName, read: true);
if (breakpointsToTrigger != null && breakpointsToTrigger.Count > 0)
{
TriggerVariableBreakpoints(breakpointsToTrigger);
}
}
internal void CheckVariableWrite(string variableName)
{
var breakpointsToTrigger = GetVariableBreakpointsToTrigger(variableName, read: false);
if (breakpointsToTrigger != null && breakpointsToTrigger.Count > 0)
{
TriggerVariableBreakpoints(breakpointsToTrigger);
}
}
private List<VariableBreakpoint> GetVariableBreakpointsToTrigger(string variableName, bool read)
{
Diagnostics.Assert(_context._debuggingMode == 1, "breakpoints only hit when debugging mode is 1");
var functionContext = _callStack.LastFunctionContext();
if (functionContext != null && functionContext._debuggerHidden)
{
// Never stop in DebuggerHidden scripts, don't even call the action on any breakpoint.
return null;
}
try
{
SetInternalDebugMode(InternalDebugMode.Disabled);
ConcurrentDictionary<int, VariableBreakpoint> breakpoints;
if (!_variableBreakpoints.TryGetValue(variableName, out breakpoints))
{
// $PSItem is an alias for $_. We don't use PSItem internally, but a user might
// have set a bp on $PSItem, so look for that if appropriate.
if (SpecialVariables.IsUnderbar(variableName))
{
_variableBreakpoints.TryGetValue(SpecialVariables.PSItem, out breakpoints);
}
}
if (breakpoints == null)
return null;
var callStackInfo = _callStack.Last();
var currentScriptFile = callStackInfo?.File;
return breakpoints.Values.Where(bp => bp.Trigger(currentScriptFile, read: read)).ToList();
}
finally
{
SetInternalDebugMode(InternalDebugMode.Enabled);
}
}
internal void TriggerVariableBreakpoints(List<VariableBreakpoint> breakpoints)
{
var functionContext = _callStack.LastFunctionContext();
var invocationInfo = functionContext != null ? new InvocationInfo(null, functionContext.CurrentPosition, _context) : null;
OnDebuggerStop(invocationInfo, breakpoints.ToList<Breakpoint>());
}
// Return the line breakpoints bound in a specific script block (used when a sequence point
// is hit, to find which breakpoints are set on that sequence point.)
internal List<LineBreakpoint> GetBoundBreakpoints(IScriptExtent[] sequencePoints)
{
Tuple<List<LineBreakpoint>, BitArray> tuple;
if (_mapScriptToBreakpoints.TryGetValue(sequencePoints, out tuple))
{
return tuple.Item1;
}
return null;
}
#endregion finding breakpoints
#region triggering breakpoints
private List<Breakpoint> TriggerBreakpoints(List<Breakpoint> breakpoints)
{
Diagnostics.Assert(_context._debuggingMode == 1, "breakpoints only hit when debugging mode == 1");
List<Breakpoint> breaks = new List<Breakpoint>();
try
{
SetInternalDebugMode(InternalDebugMode.InScriptStop);
foreach (Breakpoint breakpoint in breakpoints)
{
if (breakpoint.Enabled)
{
try
{
// Ensure that code being processed during breakpoint triggers
// act like they are broken into the debugger.
_inBreakpoint = true;
if (breakpoint.Trigger() == Breakpoint.BreakpointAction.Break)
{
breaks.Add(breakpoint);
}
}
finally
{
_inBreakpoint = false;
}
}
}
}
finally
{
SetInternalDebugMode(InternalDebugMode.Enabled);
}
return breaks;
}
internal void OnSequencePointHit(FunctionContext functionContext)
{
if (_context.ShouldTraceStatement && !_callStack.Last().IsFrameHidden && !functionContext._debuggerStepThrough)
{
TraceLine(functionContext.CurrentPosition);
}
// If a nested debugger received a stop debug command then all debugging
// should stop.
if (_nestedDebuggerStop)
{
_nestedDebuggerStop = false;
_currentDebuggerAction = DebuggerResumeAction.Continue;
ResumeExecution(DebuggerResumeAction.Stop);
}
UpdateBreakpoints(functionContext);
if (_steppingMode == SteppingMode.StepIn &&
(_overOrOutFrame == null || _callStack.Last() == _overOrOutFrame))
{
if (!_callStack.Last().IsFrameHidden)
{
_overOrOutFrame = null;
StopOnSequencePoint(functionContext, s_emptyBreakpointList);
}
else if (_overOrOutFrame == null)
{
// Treat like step out, but only if we're not already stepping out
ResumeExecution(DebuggerResumeAction.StepOut);
}
}
else
{
if (functionContext._breakPoints[functionContext._currentSequencePointIndex])
{
var breakpoints = (from breakpoint in functionContext._boundBreakpoints
where
breakpoint.SequencePointIndex == functionContext._currentSequencePointIndex &&
breakpoint.Enabled
select breakpoint).ToList<Breakpoint>();
breakpoints = TriggerBreakpoints(breakpoints);
if (breakpoints.Count > 0)
{
StopOnSequencePoint(functionContext, breakpoints);
}
}
}
}
#endregion triggering breakpoints
#endregion internal methods
#region private members
[DebuggerDisplay("{FunctionContext.CurrentPosition}")]
private sealed class CallStackInfo
{
internal InvocationInfo InvocationInfo { get; set; }
internal string File { get; set; }
internal bool DebuggerStepThrough { get; set; }
internal FunctionContext FunctionContext { get; set; }
/// <summary>
/// The frame is hidden due to the <see cref="DebuggerHiddenAttribute"/> attribute.
/// No breakpoints will be set and no stepping in/through.
/// </summary>
internal bool IsFrameHidden { get; set; }
internal bool TopFrameAtBreakpoint { get; set; }
}
private struct CallStackList
{
internal List<CallStackInfo> _callStackList;
internal void Add(CallStackInfo item)
{
lock (_callStackList)
{
_callStackList.Add(item);
}
}
internal void RemoveAt(int index)
{
lock (_callStackList)
{
_callStackList.RemoveAt(index);
}
}
internal CallStackInfo this[int index]
{
get
{
lock (_callStackList)
{
return ((index > -1) && (index < _callStackList.Count)) ? _callStackList[index] : null;
}
}
}
internal CallStackInfo Last()
{
lock (_callStackList)
{
return (_callStackList.Count > 0) ? _callStackList[_callStackList.Count - 1] : null;
}
}
internal FunctionContext LastFunctionContext()
{
var last = Last();
return last?.FunctionContext;
}
internal bool Any()
{
lock (_callStackList)
{
return _callStackList.Count > 0;
}
}
internal int Count
{
get
{
lock (_callStackList)
{
return _callStackList.Count;
}
}
}
internal CallStackInfo[] ToArray()
{
lock (_callStackList)
{
return _callStackList.ToArray();
}
}
internal void Clear()
{
lock (_callStackList)
{
_callStackList.Clear();
}
}
}
private readonly ExecutionContext _context;
private ConcurrentDictionary<int, LineBreakpoint> _pendingBreakpoints;
private readonly ConcurrentDictionary<string, Tuple<WeakReference, ConcurrentDictionary<int, LineBreakpoint>>> _boundBreakpoints;
private readonly ConcurrentDictionary<int, CommandBreakpoint> _commandBreakpoints;
private readonly ConcurrentDictionary<string, ConcurrentDictionary<int, VariableBreakpoint>> _variableBreakpoints;
private readonly ConcurrentDictionary<int, Breakpoint> _idToBreakpoint;
private SteppingMode _steppingMode;
private CallStackInfo _overOrOutFrame;
private CallStackList _callStack;
private static readonly List<Breakpoint> s_emptyBreakpointList = new List<Breakpoint>();
private DebuggerCommandProcessor _commandProcessor = new DebuggerCommandProcessor();
private InvocationInfo _currentInvocationInfo;
private bool _inBreakpoint;
private PowerShell _psDebuggerCommand;
// Job debugger integration.
private bool _nestedDebuggerStop;
private readonly Dictionary<Guid, PSJobStartEventArgs> _runningJobs;
private readonly ConcurrentStack<Debugger> _activeDebuggers;
private readonly ConcurrentStack<DebuggerStopEventArgs> _debuggerStopEventArgs;
private DebuggerResumeAction _lastActiveDebuggerAction;
private DebuggerResumeAction _currentDebuggerAction;
private DebuggerResumeAction _previousDebuggerAction;
private CallStackInfo _nestedRunningFrame;
private readonly object _syncObject;
private readonly object _syncActiveDebuggerStopObject;
private int _processingOutputCount;
private ManualResetEventSlim _processingOutputCompleteEvent = new ManualResetEventSlim(true);
// Runspace debugger integration.
private readonly Dictionary<Guid, PSMonitorRunspaceInfo> _runningRunspaces;
private const int _jobCallStackOffset = 2;
private const int _runspaceCallStackOffset = 1;
private bool _preserveUnhandledDebugStopEvent;
private ManualResetEventSlim _preserveDebugStopEvent;
// Process runspace debugger
private readonly Lazy<ConcurrentQueue<StartRunspaceDebugProcessingEventArgs>> _runspaceDebugQueue = new Lazy<ConcurrentQueue<StartRunspaceDebugProcessingEventArgs>>();
private volatile int _processingRunspaceDebugQueue;
private ManualResetEventSlim _runspaceDebugCompleteEvent;
// System is locked down when true. Used to disable debugger on lock down.
private bool? _isSystemLockedDown;
private static readonly string s_processDebugPromptMatch;
#endregion private members
#region private methods
/// <summary>
/// Raises the DebuggerStop event.
/// </summary>
private void OnDebuggerStop(InvocationInfo invocationInfo, List<Breakpoint> breakpoints)
{
Diagnostics.Assert(breakpoints != null, "The list of breakpoints should not be null");
LocalRunspace localRunspace = _context.CurrentRunspace as LocalRunspace;
Diagnostics.Assert(localRunspace != null, "Debugging is only supported on local runspaces");
if (localRunspace.PulsePipeline != null && localRunspace.PulsePipeline == localRunspace.GetCurrentlyRunningPipeline())
{
_context.EngineHostInterface.UI.WriteWarningLine(
breakpoints.Count > 0
? string.Format(CultureInfo.CurrentCulture, DebuggerStrings.WarningBreakpointWillNotBeHit,
breakpoints[0])
: new InvalidOperationException().Message);
return;
}
_currentInvocationInfo = invocationInfo;
_steppingMode = SteppingMode.None;
// Optionally wait for a debug stop event subscriber if requested.
_inBreakpoint = true;
if (!WaitForDebugStopSubscriber())
{
// No subscriber. Ignore this debug stop event.
_inBreakpoint = false;
return;
}
bool oldQuestionMarkVariableValue = _context.QuestionMarkVariableValue;
_context.SetVariable(SpecialVariables.PSDebugContextVarPath, new PSDebugContext(invocationInfo, breakpoints, TriggerObject));
FunctionInfo defaultPromptInfo = null;
string originalPromptString = null;
bool hadDefaultPrompt = false;
try
{
Collection<PSObject> items = _context.SessionState.InvokeProvider.Item.Get("function:\\prompt");
if ((items != null) && (items.Count > 0))
{
defaultPromptInfo = items[0].BaseObject as FunctionInfo;
originalPromptString = defaultPromptInfo.Definition as string;
if (originalPromptString.Equals(InitialSessionState.DefaultPromptFunctionText, StringComparison.OrdinalIgnoreCase) ||
originalPromptString.Trim().StartsWith(s_processDebugPromptMatch, StringComparison.OrdinalIgnoreCase))
{
hadDefaultPrompt = true;
}
}
}
catch (ItemNotFoundException)
{
// Ignore, it means they don't have the default prompt
}
// Change the context language mode before updating the prompt script.
// This way the new prompt scriptblock will pick up the current context language mode.
PSLanguageMode? originalLanguageMode = null;
if (_context.UseFullLanguageModeInDebugger &&
(_context.LanguageMode != PSLanguageMode.FullLanguage))
{
originalLanguageMode = _context.LanguageMode;
_context.LanguageMode = PSLanguageMode.FullLanguage;
}
// Update the prompt to the debug prompt
if (hadDefaultPrompt)
{
int index = originalPromptString.IndexOf('"', StringComparison.OrdinalIgnoreCase);
if (index > -1)
{
// Fix up prompt.
++index;
string debugPrompt = string.Concat("\"[DBG]: ", originalPromptString.AsSpan(index, originalPromptString.Length - index));
defaultPromptInfo.Update(
ScriptBlock.Create(debugPrompt), true, ScopedItemOptions.Unspecified);
}
else
{
hadDefaultPrompt = false;
}
}
RunspaceAvailability previousAvailability = _context.CurrentRunspace.RunspaceAvailability;
_context.CurrentRunspace.UpdateRunspaceAvailability(
_context.CurrentRunspace.GetCurrentlyRunningPipeline() != null
? RunspaceAvailability.AvailableForNestedCommand
: RunspaceAvailability.Available,
true);
Diagnostics.Assert(_context._debuggingMode == 1, "Should only be stopping when debugger is on.");
try
{
SetInternalDebugMode(InternalDebugMode.InScriptStop);
if (_callStack.Any())
{
// Get-PSCallStack shouldn't report any frames above this frame, so mark it. One alternative
// to marking the frame would be to not push new frames while debugging, but that limits our
// ability to give a full callstack if there are errors during eval.
_callStack.Last().TopFrameAtBreakpoint = true;
}
// Reset list lines.
_commandProcessor.Reset();
// Save a copy of the stop arguments.
DebuggerStopEventArgs copyArgs = new DebuggerStopEventArgs(invocationInfo, breakpoints);
_debuggerStopEventArgs.Push(copyArgs);
// Blocking call to raise debugger stop event.
DebuggerStopEventArgs e = new DebuggerStopEventArgs(invocationInfo, breakpoints);
RaiseDebuggerStopEvent(e);
ResumeExecution(e.ResumeAction);
}
finally
{
SetInternalDebugMode(InternalDebugMode.Enabled);
if (_callStack.Any())
{
_callStack.Last().TopFrameAtBreakpoint = false;
}
_context.CurrentRunspace.UpdateRunspaceAvailability(previousAvailability, true);
if (originalLanguageMode.HasValue)
{
_context.LanguageMode = originalLanguageMode.Value;
}
_context.EngineSessionState.RemoveVariable(SpecialVariables.PSDebugContext);
if (hadDefaultPrompt)
{
// Restore the prompt if they had our default
defaultPromptInfo.Update(
ScriptBlock.Create(originalPromptString), true, ScopedItemOptions.Unspecified);
}
DebuggerStopEventArgs oldArgs;
_debuggerStopEventArgs.TryPop(out oldArgs);
_context.QuestionMarkVariableValue = oldQuestionMarkVariableValue;
_inBreakpoint = false;
}
}
/// <summary>
/// Resumes execution after a breakpoint/step event has been handled.
/// </summary>
private void ResumeExecution(DebuggerResumeAction action)
{
_previousDebuggerAction = _currentDebuggerAction;
_currentDebuggerAction = action;
switch (action)
{
case DebuggerResumeAction.StepInto:
_steppingMode = SteppingMode.StepIn;
_overOrOutFrame = null;
break;
case DebuggerResumeAction.StepOut:
if (_callStack.Count > 1)
{
// When we pop to the parent frame, we'll clear _overOrOutFrame (so OnSequencePointHit
// will stop) and continue with a step.
_steppingMode = SteppingMode.StepIn;
_overOrOutFrame = _callStack[_callStack.Count - 2];
}
else
{
// Stepping out of the top frame is just like continue (allow hitting
// breakpoints in the current frame, but otherwise just go.)
goto case DebuggerResumeAction.Continue;
}
break;
case DebuggerResumeAction.StepOver:
_steppingMode = SteppingMode.StepIn;
_overOrOutFrame = _callStack.Last();
break;
case DebuggerResumeAction.Continue:
// nothing to do, just continue
_steppingMode = SteppingMode.None;
_overOrOutFrame = null;
break;
case DebuggerResumeAction.Stop:
_steppingMode = SteppingMode.None;
_overOrOutFrame = null;
throw new TerminateException();
default:
Debug.Fail("Received an unknown action: " + action);
break;
}
}
/// <summary>
/// Blocking call that blocks until a release occurs via ReleaseSavedDebugStop().
/// </summary>
/// <returns>True if there is a DebuggerStop event subscriber.</returns>
private bool WaitForDebugStopSubscriber()
{
if (!IsDebuggerStopEventSubscribed())
{
if (_preserveUnhandledDebugStopEvent)
{
// Lazily create the event object.
if (_preserveDebugStopEvent == null)
{
_preserveDebugStopEvent = new ManualResetEventSlim(true);
}
// Set the event handle to non-signaled.
if (!_preserveDebugStopEvent.IsSet)
{
Diagnostics.Assert(false, "The _preserveDebugStop event handle should always be in the signaled state at this point.");
return false;
}
_preserveDebugStopEvent.Reset();
// Wait indefinitely for a signal event.
_preserveDebugStopEvent.Wait();
return IsDebuggerStopEventSubscribed();
}
return false;
}
return true;
}
private enum SteppingMode
{
StepIn,
None
}
// When a script file changes, we need to rebind all breakpoints in that script.
private void UnbindBoundBreakpoints(List<LineBreakpoint> boundBreakpoints)
{
foreach (var breakpoint in boundBreakpoints)
{
// Also remove unbound breakpoints from the script to breakpoint map.
Tuple<List<LineBreakpoint>, BitArray> lineBreakTuple;
if (_mapScriptToBreakpoints.TryGetValue(breakpoint.SequencePoints, out lineBreakTuple))
{
lineBreakTuple.Item1.Remove(breakpoint);
}
breakpoint.SequencePoints = null;
breakpoint.SequencePointIndex = -1;
breakpoint.BreakpointBitArray = null;
_pendingBreakpoints[breakpoint.Id] = breakpoint;
}
boundBreakpoints.Clear();
}
private void SetPendingBreakpoints(FunctionContext functionContext)
{
if (_pendingBreakpoints.IsEmpty)
return;
var newPendingBreakpoints = new Dictionary<int, LineBreakpoint>();
var currentScriptFile = functionContext._file;
// If we're not in a file, we can't have any line breakpoints.
if (currentScriptFile == null)
return;
// Normally we register a script file when the script is run or the module is imported,
// but if there weren't any breakpoints when the script was run and the script was dotted,
// we will end up here with pending breakpoints, but we won't have cached the list of
// breakpoints in the script.
RegisterScriptFile(currentScriptFile, functionContext.CurrentPosition.StartScriptPosition.GetFullScript());
Tuple<List<LineBreakpoint>, BitArray> tuple;
if (!_mapScriptToBreakpoints.TryGetValue(functionContext._sequencePoints, out tuple))
{
Diagnostics.Assert(false, "If the script block is still alive, the entry should not be collected.");
}
Diagnostics.Assert(tuple.Item1 == functionContext._boundBreakpoints, "What's up?");
foreach ((int breakpointId, LineBreakpoint breakpoint) in _pendingBreakpoints)
{
bool bound = false;
if (breakpoint.TrySetBreakpoint(currentScriptFile, functionContext))
{
if (_context._debuggingMode == 0)
{
SetInternalDebugMode(InternalDebugMode.Enabled);
}
bound = true;
tuple.Item1.Add(breakpoint);
// We need to keep track of any breakpoints that are bound in each script because they may
// need to be rebound if the script changes.
var boundBreakpoints = _boundBreakpoints[currentScriptFile].Item2;
boundBreakpoints[breakpoint.Id] = breakpoint;
}
if (!bound)
{
newPendingBreakpoints.Add(breakpoint.Id, breakpoint);
}
}
_pendingBreakpoints = new ConcurrentDictionary<int, LineBreakpoint>(newPendingBreakpoints);
}
private void StopOnSequencePoint(FunctionContext functionContext, List<Breakpoint> breakpoints)
{
if (functionContext._debuggerHidden)
{
// Never stop in a DebuggerHidden scriptblock.
return;
}
if (functionContext._sequencePoints.Length == 1 &&
functionContext._scriptBlock != null &&
object.ReferenceEquals(functionContext._sequencePoints[0], functionContext._scriptBlock.Ast.Extent))
{
// If the script is empty or only defines functions, we used the script block extent as a sequence point, but that
// was only intended for error reporting, it was not meant to be hit as a breakpoint.
return;
}
var invocationInfo = new InvocationInfo(null, functionContext.CurrentPosition, _context);
OnDebuggerStop(invocationInfo, breakpoints);
}
private enum InternalDebugMode
{
InPushedStop = -2,
InScriptStop = -1,
Disabled = 0,
Enabled = 1
}
/// <summary>
/// Sets the internal Execution context debug mode given the
/// current DebugMode setting.
/// </summary>
/// <param name="mode">Internal debug mode.</param>
private void SetInternalDebugMode(InternalDebugMode mode)
{
lock (_syncObject)
{
// Disable script debugger when in system lock down mode
if (IsSystemLockedDown)
{
if (_context._debuggingMode != (int)InternalDebugMode.Disabled)
{
_context._debuggingMode = (int)InternalDebugMode.Disabled;
}
return;
}
switch (mode)
{
case InternalDebugMode.InPushedStop:
case InternalDebugMode.InScriptStop:
case InternalDebugMode.Disabled:
_context._debuggingMode = (int)mode;
break;
case InternalDebugMode.Enabled:
_context._debuggingMode = CanEnableDebugger ?
(int)InternalDebugMode.Enabled : (int)InternalDebugMode.Disabled;
break;
}
}
}
private bool CanEnableDebugger
{
get
{
// The debugger can be enabled if we are not in DebugMode.None and if we are
// not in a local session set only to RemoteScript.
return !((DebugMode == DebugModes.RemoteScript) && IsLocalSession) && (DebugMode != DebugModes.None);
}
}
private bool CanDisableDebugger
{
get
{
// The debugger can be disbled if there are no breakpoints
// left and if we are not currently stepping in the debugger.
return _idToBreakpoint.IsEmpty &&
_currentDebuggerAction != DebuggerResumeAction.StepInto &&
_currentDebuggerAction != DebuggerResumeAction.StepOver &&
_currentDebuggerAction != DebuggerResumeAction.StepOut;
}
}
private bool IsSystemLockedDown
{
get
{
if (_isSystemLockedDown == null)
{
lock (_syncObject)
{
if (_isSystemLockedDown == null)
{
_isSystemLockedDown = (System.Management.Automation.Security.SystemPolicy.GetSystemLockdownPolicy() ==
System.Management.Automation.Security.SystemEnforcementMode.Enforce);
}
}
}
return _isSystemLockedDown.Value;
}
}
private void CheckForBreakpointSupport()
{
if (IsSystemLockedDown)
{
// Local script debugging is not supported in locked down mode
throw new PSNotSupportedException();
}
}
#region Enable debug stepping
[Flags]
private enum EnableNestedType
{
None = 0x0,
NestedJob = 0x1,
NestedRunspace = 0x2
}
private void EnableDebuggerStepping(EnableNestedType nestedType)
{
if (DebugMode == DebugModes.None)
{
throw new PSInvalidOperationException(
DebuggerStrings.CannotEnableDebuggerSteppingInvalidMode,
null,
Debugger.CannotEnableDebuggerSteppingInvalidMode,
ErrorCategory.InvalidOperation,
null);
}
lock (_syncObject)
{
if (_context._debuggingMode == 0)
{
SetInternalDebugMode(InternalDebugMode.Enabled);
}
Debugger activeDebugger;
if (_activeDebuggers.TryPeek(out activeDebugger))
{
// Set active debugger to StepInto mode.
activeDebugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript);
activeDebugger.SetDebuggerStepMode(true);
}
else
{
// Set script debugger to StepInto mode.
ResumeExecution(DebuggerResumeAction.StepInto);
}
}
// Look for any runspaces with debuggers and set to setp mode.
if ((nestedType & EnableNestedType.NestedRunspace) == EnableNestedType.NestedRunspace)
{
SetRunspaceListToStep(true);
}
}
/// <summary>
/// Restores debugger back to non-step mode.
/// </summary>
private void DisableDebuggerStepping()
{
if (!IsDebuggerSteppingEnabled) { return; }
lock (_syncObject)
{
ResumeExecution(DebuggerResumeAction.Continue);
RestoreInternalDebugMode();
Debugger activeDebugger;
if (_activeDebuggers.TryPeek(out activeDebugger))
{
activeDebugger.SetDebuggerStepMode(false);
}
}
SetRunningJobListToStep(false);
SetRunspaceListToStep(false);
}
private void RestoreInternalDebugMode()
{
InternalDebugMode restoreMode = ((DebugMode != DebugModes.None) && (!_idToBreakpoint.IsEmpty)) ? InternalDebugMode.Enabled : InternalDebugMode.Disabled;
SetInternalDebugMode(restoreMode);
}
#endregion
#endregion
#region Debugger Overrides
/// <summary>
/// Set ScriptDebugger action.
/// </summary>
/// <param name="resumeAction">DebuggerResumeAction.</param>
public override void SetDebuggerAction(DebuggerResumeAction resumeAction)
{
throw new PSNotSupportedException(
StringUtil.Format(DebuggerStrings.CannotSetDebuggerAction));
}
/// <summary>
/// GetDebuggerStopped.
/// </summary>
/// <returns>DebuggerStopEventArgs.</returns>
public override DebuggerStopEventArgs GetDebuggerStopArgs()
{
DebuggerStopEventArgs rtnArgs;
if (_debuggerStopEventArgs.TryPeek(out rtnArgs))
{
return rtnArgs;
}
return null;
}
/// <summary>
/// ProcessCommand.
/// </summary>
/// <param name="command">PowerShell command.</param>
/// <param name="output">Output.</param>
/// <returns>DebuggerCommandResults.</returns>
public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection<PSObject> output)
{
if (command == null)
{
throw new PSArgumentNullException(nameof(command));
}
if (output == null)
{
throw new PSArgumentNullException(nameof(output));
}
if (!DebuggerStopped)
{
throw new PSInvalidOperationException(
DebuggerStrings.CannotProcessDebuggerCommandNotStopped,
null,
Debugger.CannotProcessCommandNotStopped,
ErrorCategory.InvalidOperation,
null);
}
//
// Allow an active pushed debugger to process commands
//
DebuggerCommandResults results = ProcessCommandForActiveDebugger(command, output);
if (results != null)
{
return results;
}
//
// Otherwise let root script debugger handle it.
//
if (!(_context.CurrentRunspace is LocalRunspace localRunspace))
{
throw new PSInvalidOperationException(
DebuggerStrings.CannotProcessDebuggerCommandNotStopped,
null,
Debugger.CannotProcessCommandNotStopped,
ErrorCategory.InvalidOperation,
null);
}
try
{
using (_psDebuggerCommand = PowerShell.Create())
{
if (localRunspace.GetCurrentlyRunningPipeline() != null)
{
_psDebuggerCommand.SetIsNested(true);
}
_psDebuggerCommand.Runspace = localRunspace;
_psDebuggerCommand.Commands = command;
foreach (var cmd in _psDebuggerCommand.Commands.Commands)
{
cmd.MergeMyResults(PipelineResultTypes.All, PipelineResultTypes.Output);
}
PSDataCollection<PSObject> internalOutput = new PSDataCollection<PSObject>();
internalOutput.DataAdded += (sender, args) =>
{
foreach (var item in internalOutput.ReadAll())
{
if (item == null) { continue; }
DebuggerCommand dbgCmd = item.BaseObject as DebuggerCommand;
if (dbgCmd != null)
{
bool executedByDebugger = (dbgCmd.ResumeAction != null || dbgCmd.ExecutedByDebugger);
results = new DebuggerCommandResults(dbgCmd.ResumeAction, executedByDebugger);
}
else
{
output.Add(item);
}
}
};
// Allow any exceptions to propagate.
_psDebuggerCommand.InvokeWithDebugger(null, internalOutput, null, false);
}
}
finally
{
_psDebuggerCommand = null;
}
return results ?? new DebuggerCommandResults(null, false);
}
/// <summary>
/// StopProcessCommand.
/// </summary>
public override void StopProcessCommand()
{
//
// If we have a pushed debugger then stop that command.
//
if (StopCommandForActiveDebugger())
{
return;
}
PowerShell ps = _psDebuggerCommand;
if (ps != null)
{
ps.BeginStop(null, null);
}
}
/// <summary>
/// Set debug mode.
/// </summary>
/// <param name="mode"></param>
public override void SetDebugMode(DebugModes mode)
{
lock (_syncObject)
{
// Restrict local script debugger mode when in system lock down.
// DebugModes enum flags provide a combination of values. To disable local script debugging
// we have to disallow 'LocalScript' and 'Default' flags and only allow 'None' or 'RemoteScript'
// flags exclusively. This allows only no debugging 'None' or remote debugging 'RemoteScript'.
if (IsSystemLockedDown && (mode != DebugModes.None) && (mode != DebugModes.RemoteScript))
{
mode = DebugModes.RemoteScript;
}
base.SetDebugMode(mode);
if (!CanEnableDebugger)
{
SetInternalDebugMode(InternalDebugMode.Disabled);
}
else if ((!_idToBreakpoint.IsEmpty) && (_context._debuggingMode == 0))
{
// Set internal debugger to active.
SetInternalDebugMode(InternalDebugMode.Enabled);
}
}
}
/// <summary>
/// Returns current call stack.
/// </summary>
/// <returns>IEnumerable of CallStackFrame objects.</returns>
public override IEnumerable<CallStackFrame> GetCallStack()
{
CallStackInfo[] callStack = _callStack.ToArray();
if (callStack.Length > 0)
{
int startingIndex = callStack.Length - 1;
for (int i = startingIndex; i >= 0; i--)
{
if (callStack[i].TopFrameAtBreakpoint)
{
startingIndex = i;
break;
}
}
for (int i = startingIndex; i >= 0; i--)
{
var funcContext = callStack[i].FunctionContext;
yield return new CallStackFrame(funcContext, callStack[i].InvocationInfo);
}
}
}
/// <summary>
/// True when debugger is active with breakpoints.
/// </summary>
public override bool IsActive
{
get
{
int debuggerState = _context._debuggingMode;
return (debuggerState != 0);
}
}
/// <summary>
/// Resets the command processor source information so that it is
/// updated with latest information on the next debug stop.
/// </summary>
public override void ResetCommandProcessorSource()
{
_commandProcessor.Reset();
}
/// <summary>
/// Sets debugger stepping mode.
/// </summary>
/// <param name="enabled">True if stepping is to be enabled.</param>
public override void SetDebuggerStepMode(bool enabled)
{
if (enabled)
{
EnableDebuggerStepping(EnableNestedType.NestedJob | EnableNestedType.NestedRunspace);
}
else
{
DisableDebuggerStepping();
}
}
/// <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.
/// </summary>
/// <param name="command">Command string.</param>
/// <param name="output">Output.</param>
/// <returns>DebuggerCommand containing information on whether and how the command was processed.</returns>
internal override DebuggerCommand InternalProcessCommand(string command, IList<PSObject> output)
{
if (!DebuggerStopped)
{
return new DebuggerCommand(command, null, false, false);
}
// "Exit" command should always result with "Continue" behavior for legacy compatibility.
if (command.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
return new DebuggerCommand(command, DebuggerResumeAction.Continue, false, true);
}
return _commandProcessor.ProcessCommand(null, command, _currentInvocationInfo, output);
}
/// <summary>
/// Creates a source list based on root script debugger source information if available, with
/// the current source line highlighted. This is used internally for nested runspace debugging
/// where the runspace command is run in context of a parent script.
/// </summary>
/// <param name="lineNum">Current source line.</param>
/// <param name="output">Output collection.</param>
/// <returns>True if source listed successfully.</returns>
internal override bool InternalProcessListCommand(int lineNum, IList<PSObject> output)
{
if (!DebuggerStopped || (_currentInvocationInfo == null)) { return false; }
// Create an Invocation object that has full source script from script debugger plus
// line information provided from caller.
string fullScript = _currentInvocationInfo.GetFullScript();
ScriptPosition startScriptPosition = new ScriptPosition(
_currentInvocationInfo.ScriptName,
lineNum,
_currentInvocationInfo.ScriptPosition.StartScriptPosition.Offset,
_currentInvocationInfo.Line,
fullScript);
ScriptPosition endScriptPosition = new ScriptPosition(
_currentInvocationInfo.ScriptName,
lineNum,
_currentInvocationInfo.ScriptPosition.StartScriptPosition.Offset,
_currentInvocationInfo.Line,
fullScript);
InvocationInfo tempInvocationInfo = InvocationInfo.Create(
_currentInvocationInfo.MyCommand,
new ScriptExtent(
startScriptPosition,
endScriptPosition)
);
_commandProcessor.ProcessListCommand(tempInvocationInfo, output);
return true;
}
/// <summary>
/// IsRemote.
/// </summary>
internal override bool IsRemote
{
get
{
Debugger activeDebugger;
if (_activeDebuggers.TryPeek(out activeDebugger))
{
return (activeDebugger is RemotingJobDebugger);
}
return false;
}
}
/// <summary>
/// Array of stack frame objects of active debugger if any,
/// otherwise null.
/// </summary>
/// <returns>CallStackFrame[].</returns>
internal override CallStackFrame[] GetActiveDebuggerCallStack()
{
Debugger activeDebugger;
if (_activeDebuggers.TryPeek(out activeDebugger))
{
return activeDebugger.GetCallStack().ToArray();
}
return null;
}
/// <summary>
/// Sets how the debugger deals with breakpoint events that are not handled.
/// Ignore - This is the default behavior and ignores any breakpoint event
/// if there is no handler. Releases any preserved event.
/// Wait - This mode preserves a breakpoint event until a handler is
/// subscribed.
/// </summary>
internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode
{
get
{
return (_preserveUnhandledDebugStopEvent) ? UnhandledBreakpointProcessingMode.Wait : UnhandledBreakpointProcessingMode.Ignore;
}
set
{
switch (value)
{
case UnhandledBreakpointProcessingMode.Wait:
_preserveUnhandledDebugStopEvent = true;
break;
case UnhandledBreakpointProcessingMode.Ignore:
_preserveUnhandledDebugStopEvent = false;
ReleaseSavedDebugStop();
break;
}
}
}
#region Breakpoints
/// <summary>
/// Adds the provided set of breakpoints to the debugger.
/// </summary>
/// <param name="breakpoints">The breakpoints to set.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
public override void SetBreakpoints(IEnumerable<Breakpoint> breakpoints, int? runspaceId)
{
if (runspaceId.HasValue)
{
GetRunspaceDebugger(runspaceId.Value).SetBreakpoints(breakpoints);
return;
}
foreach (Breakpoint bp in breakpoints)
{
switch (bp)
{
case CommandBreakpoint commandBreakpoint:
AddCommandBreakpoint(commandBreakpoint);
continue;
case LineBreakpoint lineBreakpoint:
AddLineBreakpoint(lineBreakpoint);
continue;
case VariableBreakpoint variableBreakpoint:
AddVariableBreakpoint(variableBreakpoint);
continue;
}
}
}
/// <summary>
/// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets.
/// </summary>
/// <param name="id">Id of the breakpoint you want.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
public override Breakpoint GetBreakpoint(int id, int? runspaceId)
{
if (runspaceId.HasValue)
{
return GetRunspaceDebugger(runspaceId.Value).GetBreakpoint(id);
}
_idToBreakpoint.TryGetValue(id, out Breakpoint breakpoint);
return breakpoint;
}
/// <summary>
/// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet.
/// </summary>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
public override List<Breakpoint> GetBreakpoints(int? runspaceId)
{
if (runspaceId.HasValue)
{
return GetRunspaceDebugger(runspaceId.Value).GetBreakpoints();
}
return (from bp in _idToBreakpoint.Values orderby bp.Id select bp).ToList();
}
/// <summary>
/// Sets a command breakpoint in the debugger.
/// </summary>
/// <param name="command">The name of the command that will trigger the breakpoint. This value may not be null.</param>
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
/// <param name="path">The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns></returns>
public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path, int? runspaceId)
{
if (runspaceId.HasValue)
{
return GetRunspaceDebugger(runspaceId.Value).SetCommandBreakpoint(command, action, path);
}
Diagnostics.Assert(!string.IsNullOrEmpty(command), "Caller to verify command is not null or empty.");
WildcardPattern pattern = WildcardPattern.Get(command, WildcardOptions.Compiled | WildcardOptions.IgnoreCase);
CheckForBreakpointSupport();
return AddCommandBreakpoint(new CommandBreakpoint(path, pattern, command, action));
}
/// <summary>
/// Sets a line breakpoint in the debugger.
/// </summary>
/// <param name="path">The path to the script file where the breakpoint may be hit. This value may not be null.</param>
/// <param name="line">The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1.</param>
/// <param name="column">The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line.</param>
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>A LineBreakpoint</returns>
public override LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action, int? runspaceId)
{
if (runspaceId.HasValue)
{
return GetRunspaceDebugger(runspaceId.Value).SetLineBreakpoint(path, line, column, action);
}
Diagnostics.Assert(path != null, "Caller to verify path is not null.");
Diagnostics.Assert(line > 0, "Caller to verify line is greater than 0.");
CheckForBreakpointSupport();
return AddLineBreakpoint(new LineBreakpoint(path, line, column, action));
}
/// <summary>
/// Sets a variable breakpoint in the debugger.
/// </summary>
/// <param name="variableName">The name of the variable that will trigger the breakpoint. This value may not be null.</param>
/// <param name="accessMode">The variable access mode that will trigger the breakpoint.</param>
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
/// <param name="path">The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>A VariableBreakpoint that was set.</returns>
public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path, int? runspaceId)
{
if (runspaceId.HasValue)
{
return GetRunspaceDebugger(runspaceId.Value).SetVariableBreakpoint(variableName, accessMode, action, path);
}
Diagnostics.Assert(!string.IsNullOrEmpty(variableName), "Caller to verify variableName is not null or empty.");
CheckForBreakpointSupport();
return AddVariableBreakpoint(new VariableBreakpoint(path, variableName, accessMode, action));
}
/// <summary>
/// This is the implementation of the Remove-PSBreakpoint cmdlet.
/// </summary>
/// <param name="breakpoint">Id of the breakpoint you want.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
public override bool RemoveBreakpoint(Breakpoint breakpoint, int? runspaceId)
{
if (runspaceId.HasValue)
{
return GetRunspaceDebugger(runspaceId.Value).RemoveBreakpoint(breakpoint);
}
Diagnostics.Assert(breakpoint != null, "Caller to verify the breakpoint is not null.");
if (_idToBreakpoint.Remove(breakpoint.Id, out _))
{
breakpoint.RemoveSelf(this);
if (CanDisableDebugger)
{
SetInternalDebugMode(InternalDebugMode.Disabled);
}
OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Removed, _idToBreakpoint.Count));
return true;
}
return false;
}
/// <summary>
/// This is the implementation of the Enable-PSBreakpoint cmdlet.
/// </summary>
/// <param name="breakpoint">Id of the breakpoint you want.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
public override Breakpoint EnableBreakpoint(Breakpoint breakpoint, int? runspaceId)
{
if (runspaceId.HasValue)
{
return GetRunspaceDebugger(runspaceId.Value).EnableBreakpoint(breakpoint);
}
Diagnostics.Assert(breakpoint != null, "Caller to verify the breakpoint is not null.");
if (_idToBreakpoint.TryGetValue(breakpoint.Id, out _))
{
breakpoint.SetEnabled(true);
OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Enabled, _idToBreakpoint.Count));
return breakpoint;
}
return null;
}
/// <summary>
/// This is the implementation of the Disable-PSBreakpoint cmdlet.
/// </summary>
/// <param name="breakpoint">Id of the breakpoint you want.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
public override Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspaceId)
{
if (runspaceId.HasValue)
{
return GetRunspaceDebugger(runspaceId.Value).DisableBreakpoint(breakpoint);
}
Diagnostics.Assert(breakpoint != null, "Caller to verify the breakpoint is not null.");
if (_idToBreakpoint.TryGetValue(breakpoint.Id, out _))
{
breakpoint.SetEnabled(false);
OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Disabled, _idToBreakpoint.Count));
return breakpoint;
}
return null;
}
private static Debugger GetRunspaceDebugger(int runspaceId)
{
if (!Runspace.RunspaceDictionary.TryGetValue(runspaceId, out WeakReference<Runspace> wr))
{
throw new PSArgumentException(string.Format(DebuggerStrings.InvalidRunspaceId, runspaceId));
}
if (!wr.TryGetTarget(out Runspace rs))
{
throw new PSArgumentException(DebuggerStrings.UnableToGetRunspace);
}
return rs.Debugger;
}
#endregion Breakpoints
#region Job Debugging
/// <summary>
/// Sets up debugger to debug provided job or its child jobs.
/// </summary>
/// <param name="job">
/// Job object that is either a debuggable job or a container of
/// debuggable child jobs.
/// </param>
/// <param name="breakAll">
/// If true, the debugger automatically invokes a break all when it
/// attaches to the job.
/// </param>
internal override void DebugJob(Job job, bool breakAll)
{
if (job == null) { throw new PSArgumentNullException(nameof(job)); }
lock (_syncObject)
{
if (_context._debuggingMode < 0)
{
throw new RuntimeException(DebuggerStrings.CannotStartJobDebuggingDebuggerBusy);
}
}
// If a debuggable job was passed in then add it to the
// job running list.
bool jobsAdded = TryAddDebugJob(job, breakAll);
if (!jobsAdded)
{
// Otherwise treat as parent Job and iterate over child jobs.
foreach (Job childJob in job.ChildJobs)
{
if (TryAddDebugJob(childJob, breakAll) && !jobsAdded)
{
jobsAdded = true;
}
}
}
if (!jobsAdded)
{
throw new PSInvalidOperationException(DebuggerStrings.NoDebuggableJobsFound);
}
}
private bool TryAddDebugJob(Job job, bool breakAll)
{
IJobDebugger debuggableJob = job as IJobDebugger;
if ((debuggableJob != null) && (debuggableJob.Debugger != null) &&
((job.JobStateInfo.State == JobState.Running) || (job.JobStateInfo.State == JobState.AtBreakpoint)))
{
// Check to see if job is already stopped in debugger.
bool jobDebugAlreadyStopped = debuggableJob.Debugger.InBreakpoint;
// Add to running job list with debugger set to step into.
SetDebugJobAsync(debuggableJob, false);
AddToJobRunningList(
new PSJobStartEventArgs(job, debuggableJob.Debugger, false),
breakAll ? DebuggerResumeAction.StepInto : DebuggerResumeAction.Continue);
// Raise debug stop event if job is already in stopped state.
if (jobDebugAlreadyStopped)
{
RemotingJobDebugger remoteJobDebugger = debuggableJob.Debugger as RemotingJobDebugger;
if (remoteJobDebugger != null)
{
remoteJobDebugger.CheckStateAndRaiseStopEvent();
}
else
{
Diagnostics.Assert(false, "Should never get debugger stopped job that is not of RemotingJobDebugger type.");
}
}
return true;
}
return false;
}
/// <summary>
/// Removes job from debugger job list and pops its
/// debugger from the active debugger stack.
/// </summary>
/// <param name="job">Job.</param>
internal override void StopDebugJob(Job job)
{
// Parameter validation.
if (job == null) { throw new PSArgumentNullException(nameof(job)); }
SetInternalDebugMode(InternalDebugMode.Disabled);
RemoveFromRunningJobList(job);
SetDebugJobAsync(job as IJobDebugger, true);
foreach (var cJob in job.ChildJobs)
{
RemoveFromRunningJobList(cJob);
SetDebugJobAsync(cJob as IJobDebugger, true);
}
RestoreInternalDebugMode();
}
/// <summary>
/// Helper method to set a IJobDebugger job CanDebug property.
/// </summary>
/// <param name="debuggableJob">IJobDebugger.</param>
/// <param name="isAsync">Boolean.</param>
internal static void SetDebugJobAsync(IJobDebugger debuggableJob, bool isAsync)
{
if (debuggableJob != null)
{
debuggableJob.IsAsync = isAsync;
}
}
#endregion
#region Runspace Debugging
/// <summary>
/// Sets up debugger to debug provided Runspace in a nested debug session.
/// </summary>
/// <param name="runspace">
/// Runspace to debug.
/// </param>
/// <param name="breakAll">
/// When true, this command will invoke a BreakAll when the debugger is
/// first attached.
/// </param>
internal override void DebugRunspace(Runspace runspace, bool breakAll)
{
if (runspace == null)
{
throw new PSArgumentNullException(nameof(runspace));
}
if (runspace.RunspaceStateInfo.State != RunspaceState.Opened)
{
throw new PSInvalidOperationException(
string.Format(CultureInfo.InvariantCulture, DebuggerStrings.RunspaceDebuggingInvalidRunspaceState, runspace.RunspaceStateInfo.State)
);
}
lock (_syncObject)
{
if (_context._debuggingMode < 0)
{
throw new PSInvalidOperationException(DebuggerStrings.RunspaceDebuggingDebuggerBusy);
}
}
if (runspace.Debugger == null)
{
throw new PSInvalidOperationException(
string.Format(CultureInfo.InvariantCulture, DebuggerStrings.RunspaceDebuggingNoRunspaceDebugger, runspace.Name));
}
if (runspace.Debugger.DebugMode == DebugModes.None)
{
throw new PSInvalidOperationException(DebuggerStrings.RunspaceDebuggingDebuggerIsOff);
}
AddToRunningRunspaceList(new PSStandaloneMonitorRunspaceInfo(runspace));
if (!runspace.Debugger.InBreakpoint && breakAll)
{
EnableDebuggerStepping(EnableNestedType.NestedRunspace);
}
}
/// <summary>
/// Removes the provided Runspace from the nested "active" debugger state.
/// </summary>
/// <param name="runspace">Runspace.</param>
internal override void StopDebugRunspace(Runspace runspace)
{
if (runspace == null) { throw new PSArgumentNullException(nameof(runspace)); }
SetInternalDebugMode(InternalDebugMode.Disabled);
RemoveFromRunningRunspaceList(runspace);
RestoreInternalDebugMode();
}
#endregion
#region Runspace Debug Processing
/// <summary>
/// Adds the provided Runspace object to the runspace debugger processing queue.
/// The queue will then raise the StartRunspaceDebugProcessing events for each runspace to allow
/// a host script debugger implementation to provide an active debugging session.
/// </summary>
/// <param name="runspace">Runspace to debug.</param>
internal override void QueueRunspaceForDebug(Runspace runspace)
{
runspace.StateChanged += RunspaceStateChangedHandler;
runspace.AvailabilityChanged += RunspaceAvailabilityChangedHandler;
_runspaceDebugQueue.Value.Enqueue(new StartRunspaceDebugProcessingEventArgs(runspace));
StartRunspaceForDebugQueueProcessing();
}
/// <summary>
/// Causes the CancelRunspaceDebugProcessing event to be raised which notifies subscribers that these debugging
/// sessions should be cancelled.
/// </summary>
public override void CancelDebuggerProcessing()
{
// Empty runspace debugger processing queue and then notify any subscribers.
ReleaseInternalRunspaceDebugProcessing(null, true);
try
{
RaiseCancelRunspaceDebugProcessingEvent();
}
catch (Exception)
{ }
}
private void ReleaseInternalRunspaceDebugProcessing(object sender, bool emptyQueue = false)
{
Runspace runspace = sender as Runspace;
if (runspace != null)
{
runspace.StateChanged -= RunspaceStateChangedHandler;
runspace.AvailabilityChanged -= RunspaceAvailabilityChangedHandler;
}
if (emptyQueue && _runspaceDebugQueue.IsValueCreated)
{
StartRunspaceDebugProcessingEventArgs args;
while (_runspaceDebugQueue.Value.TryDequeue(out args))
{
args.Runspace.StateChanged -= RunspaceStateChangedHandler;
args.Runspace.AvailabilityChanged -= RunspaceAvailabilityChangedHandler;
try
{
args.Runspace.Debugger.UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Ignore;
}
catch (Exception) { }
}
}
if (_runspaceDebugCompleteEvent != null)
{
try
{
_runspaceDebugCompleteEvent.Set();
}
catch (ObjectDisposedException) { }
}
}
private void RunspaceStateChangedHandler(object sender, RunspaceStateEventArgs args)
{
switch (args.RunspaceStateInfo.State)
{
case RunspaceState.Closed:
case RunspaceState.Broken:
case RunspaceState.Disconnected:
ReleaseInternalRunspaceDebugProcessing(sender);
break;
}
}
private void RunspaceAvailabilityChangedHandler(object sender, RunspaceAvailabilityEventArgs args)
{
if (args.RunspaceAvailability == RunspaceAvailability.Available)
{
ReleaseInternalRunspaceDebugProcessing(sender);
}
}
#endregion
#endregion
#region Job debugger integration
private void AddToJobRunningList(PSJobStartEventArgs jobArgs, DebuggerResumeAction startAction)
{
bool newJob = false;
lock (_syncObject)
{
jobArgs.Job.StateChanged += HandleJobStateChanged;
if (jobArgs.Job.IsPersistentState(jobArgs.Job.JobStateInfo.State))
{
jobArgs.Job.StateChanged -= HandleJobStateChanged;
return;
}
if (!_runningJobs.ContainsKey(jobArgs.Job.InstanceId))
{
// For now ignore WF jobs started asynchronously from script.
if (jobArgs.IsAsync) { return; }
// Turn on output processing monitoring on workflow job so that
// the debug stop events can coordinate with end of output processing.
jobArgs.Job.OutputProcessingStateChanged += HandleOutputProcessingStateChanged;
jobArgs.Job.MonitorOutputProcessing = true;
_runningJobs.Add(jobArgs.Job.InstanceId, jobArgs);
jobArgs.Debugger.DebuggerStop += HandleMonitorRunningJobsDebuggerStop;
newJob = true;
}
}
if (newJob)
{
jobArgs.Debugger.SetParent(
this,
_idToBreakpoint.Values.ToArray<Breakpoint>(),
startAction,
_context.EngineHostInterface.ExternalHost,
_context.SessionState.Path.CurrentLocation);
}
else
{
// If job already in collection then make sure start action is set.
// Note that this covers the case where Debug-Job was performed on
// an async job, which then becomes sync, the user continues execution
// and then wants to break (step mode) into the debugger *again*.
jobArgs.Debugger.SetDebuggerStepMode(true);
}
}
private void SetRunningJobListToStep(bool enableStepping)
{
PSJobStartEventArgs[] runningJobs;
lock (_syncObject)
{
runningJobs = _runningJobs.Values.ToArray();
}
foreach (var item in runningJobs)
{
try
{
item.Debugger.SetDebuggerStepMode(enableStepping);
}
catch (PSNotImplementedException) { }
}
}
private void SetRunspaceListToStep(bool enableStepping)
{
PSMonitorRunspaceInfo[] runspaceList;
lock (_syncObject)
{
runspaceList = _runningRunspaces.Values.ToArray();
}
foreach (var item in runspaceList)
{
try
{
Debugger nestedDebugger = item.NestedDebugger;
if (nestedDebugger != null)
{
nestedDebugger.SetDebuggerStepMode(enableStepping);
}
}
catch (PSNotImplementedException) { }
}
}
private void RemoveFromRunningJobList(Job job)
{
job.StateChanged -= HandleJobStateChanged;
job.OutputProcessingStateChanged -= HandleOutputProcessingStateChanged;
PSJobStartEventArgs jobArgs = null;
lock (_syncObject)
{
if (_runningJobs.TryGetValue(job.InstanceId, out jobArgs))
{
jobArgs.Debugger.DebuggerStop -= HandleMonitorRunningJobsDebuggerStop;
_runningJobs.Remove(job.InstanceId);
}
}
if (jobArgs != null)
{
// Pop from active debugger stack.
lock (_syncActiveDebuggerStopObject)
{
Debugger activeDebugger;
if (_activeDebuggers.TryPeek(out activeDebugger))
{
if (activeDebugger.Equals(jobArgs.Debugger))
{
PopActiveDebugger();
}
}
}
}
}
private void ClearRunningJobList()
{
PSJobStartEventArgs[] runningJobs = null;
lock (_syncObject)
{
if (_runningJobs.Count > 0)
{
runningJobs = new PSJobStartEventArgs[_runningJobs.Values.Count];
_runningJobs.Values.CopyTo(runningJobs, 0);
}
}
if (runningJobs != null)
{
foreach (var item in runningJobs)
{
RemoveFromRunningJobList(item.Job);
}
}
}
private bool PushActiveDebugger(Debugger debugger, int callstackOffset)
{
// Don't push active debugger if script debugger disabled debugging.
if (_context._debuggingMode == -1) { return false; }
// Disable script debugging while another debugger is running.
// -1 - Indicates script debugging is disabled from script debugger.
// -2 - Indicates script debugging is disabled from pushed active debugger.
SetInternalDebugMode(InternalDebugMode.InPushedStop);
// Save running calling frame.
_nestedRunningFrame = _callStack[_callStack.Count - callstackOffset];
_commandProcessor.Reset();
// Make active debugger.
_activeDebuggers.Push(debugger);
return true;
}
private Debugger PopActiveDebugger()
{
Debugger poppedDebugger = null;
if (_activeDebuggers.TryPop(out poppedDebugger))
{
int runningJobCount;
lock (_syncObject)
{
runningJobCount = _runningJobs.Count;
}
if (runningJobCount == 0)
{
// If we are back to the root debugger and are in step mode, ensure
// that the root debugger is in step mode to continue stepping.
switch (_lastActiveDebuggerAction)
{
case DebuggerResumeAction.StepInto:
case DebuggerResumeAction.StepOver:
case DebuggerResumeAction.StepOut:
// Set script debugger to step mode after the WF running
// script completes.
_steppingMode = SteppingMode.StepIn;
_overOrOutFrame = _nestedRunningFrame;
_nestedRunningFrame = null;
break;
case DebuggerResumeAction.Stop:
_nestedDebuggerStop = true;
break;
default:
ResumeExecution(DebuggerResumeAction.Continue);
break;
}
// Allow script debugger to continue in debugging mode.
_processingOutputCount = 0;
SetInternalDebugMode(InternalDebugMode.Enabled);
_currentDebuggerAction = _lastActiveDebuggerAction;
_lastActiveDebuggerAction = DebuggerResumeAction.Continue;
}
}
return poppedDebugger;
}
private void HandleActiveJobDebuggerStop(object sender, DebuggerStopEventArgs args)
{
// If we are debugging nested runspaces then ignore job debugger stops
if (_runningRunspaces.Count > 0) { return; }
// Forward active debugger event.
if (args != null)
{
// Save copy of arguments.
DebuggerStopEventArgs copyArgs = new DebuggerStopEventArgs(
args.InvocationInfo,
new Collection<Breakpoint>(args.Breakpoints),
args.ResumeAction);
_debuggerStopEventArgs.Push(copyArgs);
CallStackInfo savedCallStackItem = null;
try
{
// Wait for up to 5 seconds for output processing to complete.
_processingOutputCompleteEvent.Wait(5000);
// Fix up call stack representing this WF call.
savedCallStackItem = FixUpCallStack();
// Blocking call that raises stop event.
RaiseDebuggerStopEvent(args);
_lastActiveDebuggerAction = args.ResumeAction;
}
finally
{
RestoreCallStack(savedCallStackItem);
_debuggerStopEventArgs.TryPop(out copyArgs);
}
}
}
private CallStackInfo FixUpCallStack()
{
// Remove the top level call stack item, which is
// the PS script that starts the workflow. The workflow
// debugger will add its call stack in its GetCallStack()
// override.
int count = _callStack.Count;
CallStackInfo item = null;
if (count > 1)
{
item = _callStack.Last();
_callStack.RemoveAt(count - 1);
}
return item;
}
private void RestoreCallStack(CallStackInfo item)
{
if (item != null)
{
_callStack.Add(item);
}
}
private void HandleMonitorRunningJobsDebuggerStop(object sender, DebuggerStopEventArgs args)
{
if (!IsJobDebuggingMode())
{
// Ignore job debugger stop.
args.ResumeAction = DebuggerResumeAction.Continue;
return;
}
Debugger senderDebugger = sender as Debugger;
bool pushSucceeded = false;
lock (_syncActiveDebuggerStopObject)
{
Debugger activeDebugger = null;
if (_activeDebuggers.TryPeek(out activeDebugger))
{
if (activeDebugger.Equals(senderDebugger))
{
HandleActiveJobDebuggerStop(sender, args);
return;
}
if (IsRunningWFJobsDebugger(activeDebugger))
{
// Replace current job active debugger by first popping.
PopActiveDebugger();
}
}
pushSucceeded = PushActiveDebugger(senderDebugger, _jobCallStackOffset);
}
// Handle debugger stop outside lock.
if (pushSucceeded)
{
// Forward the debug stop event.
HandleActiveJobDebuggerStop(sender, args);
}
}
private bool IsJobDebuggingMode()
{
return ((((DebugMode & DebugModes.LocalScript) == DebugModes.LocalScript) && IsLocalSession) ||
(((DebugMode & DebugModes.RemoteScript) == DebugModes.RemoteScript) && !IsLocalSession));
}
private bool IsRunningWFJobsDebugger(Debugger debugger)
{
lock (_syncObject)
{
foreach (var item in _runningJobs.Values)
{
if (item.Debugger.Equals(debugger))
{
return true;
}
}
}
return false;
}
private void HandleJobStateChanged(object sender, JobStateEventArgs args)
{
Job job = sender as Job;
if (job.IsPersistentState(args.JobStateInfo.State))
{
RemoveFromRunningJobList(job);
}
}
private void HandleOutputProcessingStateChanged(object sender, OutputProcessingStateEventArgs e)
{
lock (_syncObject)
{
if (e.ProcessingOutput)
{
if (++_processingOutputCount == 1)
{
_processingOutputCompleteEvent.Reset();
}
}
else if (_processingOutputCount > 0)
{
if (--_processingOutputCount == 0)
{
_processingOutputCompleteEvent.Set();
}
}
}
}
private DebuggerCommandResults ProcessCommandForActiveDebugger(PSCommand command, PSDataCollection<PSObject> output)
{
// Check for debugger "detach" command which is only applicable to nested debugging.
bool detachCommand = ((command.Commands.Count > 0) &&
((command.Commands[0].CommandText.Equals("Detach", StringComparison.OrdinalIgnoreCase)) ||
(command.Commands[0].CommandText.Equals("d", StringComparison.OrdinalIgnoreCase))));
Debugger activeDebugger;
if (_activeDebuggers.TryPeek(out activeDebugger))
{
if (detachCommand)
{
// Exit command means to cancel the nested debugger session. This needs to be done by the
// owner of the session so we raise an event and release the debugger stop.
UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Ignore;
RaiseNestedDebuggingCancelEvent();
return new DebuggerCommandResults(DebuggerResumeAction.Continue, true);
}
else if ((command.Commands.Count > 0) &&
(command.Commands[0].CommandText.IndexOf(".EnterNestedPrompt()", StringComparison.OrdinalIgnoreCase) > 0))
{
// Prevent a host EnterNestedPrompt() call from occuring in an active debugger.
// Host nested prompt makes no sense in this case and can cause host to stop responding depending on host implementation.
throw new PSNotSupportedException();
}
// Get current debugger stop breakpoint info.
DebuggerStopEventArgs stopArgs;
if (_debuggerStopEventArgs.TryPeek(out stopArgs))
{
string commandText = command.Commands[0].CommandText;
// Check to see if this is a resume command that we handle here.
DebuggerCommand dbgCommand = _commandProcessor.ProcessBasicCommand(commandText);
if (dbgCommand != null &&
dbgCommand.ResumeAction != null)
{
_lastActiveDebuggerAction = dbgCommand.ResumeAction.Value;
return new DebuggerCommandResults(dbgCommand.ResumeAction, true);
}
}
return activeDebugger.ProcessCommand(command, output);
}
if (detachCommand)
{
// Detach command only applies to nested debugging. So if there isn't any active debugger then emit error.
throw new PSInvalidOperationException(DebuggerStrings.InvalidDetachCommand);
}
return null;
}
private bool StopCommandForActiveDebugger()
{
Debugger activeDebugger;
if (_activeDebuggers.TryPeek(out activeDebugger))
{
activeDebugger.StopProcessCommand();
return true;
}
return false;
}
#endregion
#region Runspace debugger integration
internal override void StartMonitoringRunspace(PSMonitorRunspaceInfo runspaceInfo)
{
if (runspaceInfo == null || runspaceInfo.Runspace == null) { return; }
if ((runspaceInfo.Runspace.Debugger != null) &&
runspaceInfo.Runspace.Debugger.Equals(this))
{
Debug.Fail("Nested debugger cannot be the root debugger.");
return;
}
DebuggerResumeAction startAction = (_currentDebuggerAction == DebuggerResumeAction.StepInto) ?
DebuggerResumeAction.StepInto : DebuggerResumeAction.Continue;
AddToRunningRunspaceList(runspaceInfo.Copy());
}
internal override void EndMonitoringRunspace(PSMonitorRunspaceInfo runspaceInfo)
{
if (runspaceInfo == null || runspaceInfo.Runspace == null) { return; }
RemoveFromRunningRunspaceList(runspaceInfo.Runspace);
}
/// <summary>
/// If a debug stop event is currently pending then this method will release
/// the event to continue processing.
/// </summary>
internal override void ReleaseSavedDebugStop()
{
if (IsPendingDebugStopEvent)
{
_preserveDebugStopEvent.Set();
}
}
private void AddToRunningRunspaceList(PSMonitorRunspaceInfo args)
{
Runspace runspace = args.Runspace;
runspace.StateChanged += HandleRunspaceStateChanged;
RunspaceState rsState = runspace.RunspaceStateInfo.State;
if (rsState == RunspaceState.Broken ||
rsState == RunspaceState.Closed ||
rsState == RunspaceState.Disconnected)
{
runspace.StateChanged -= HandleRunspaceStateChanged;
return;
}
lock (_syncObject)
{
if (!_runningRunspaces.ContainsKey(runspace.InstanceId))
{
_runningRunspaces.Add(runspace.InstanceId, args);
}
}
// It is possible for the debugger to be non-null at this point if a runspace
// is being reused.
SetUpDebuggerOnRunspace(runspace);
}
private void RemoveFromRunningRunspaceList(Runspace runspace)
{
runspace.StateChanged -= HandleRunspaceStateChanged;
// Remove from running list.
PSMonitorRunspaceInfo runspaceInfo = null;
lock (_syncObject)
{
if (_runningRunspaces.TryGetValue(runspace.InstanceId, out runspaceInfo))
{
_runningRunspaces.Remove(runspace.InstanceId);
}
}
// Clean up nested debugger.
NestedRunspaceDebugger nestedDebugger = runspaceInfo?.NestedDebugger;
if (nestedDebugger != null)
{
nestedDebugger.DebuggerStop -= HandleMonitorRunningRSDebuggerStop;
nestedDebugger.Dispose();
// If current active debugger, then pop.
lock (_syncActiveDebuggerStopObject)
{
Debugger activeDebugger;
if (_activeDebuggers.TryPeek(out activeDebugger))
{
if (activeDebugger.Equals(nestedDebugger))
{
PopActiveDebugger();
}
}
}
}
}
private void ClearRunningRunspaceList()
{
PSMonitorRunspaceInfo[] runningRunspaces = null;
lock (_syncObject)
{
if (_runningRunspaces.Count > 0)
{
runningRunspaces = new PSMonitorRunspaceInfo[_runningRunspaces.Count];
_runningRunspaces.Values.CopyTo(runningRunspaces, 0);
}
}
if (runningRunspaces != null)
{
foreach (var item in runningRunspaces)
{
RemoveFromRunningRunspaceList(item.Runspace);
}
}
}
private void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs e)
{
Runspace runspace = sender as Runspace;
bool remove = false;
switch (e.RunspaceStateInfo.State)
{
// Detect transition to Opened state.
case RunspaceState.Opened:
remove = !SetUpDebuggerOnRunspace(runspace);
break;
// Detect any transition to a finished runspace.
case RunspaceState.Broken:
case RunspaceState.Closed:
case RunspaceState.Disconnected:
remove = true;
break;
}
if (remove)
{
RemoveFromRunningRunspaceList(runspace);
}
}
private void HandleMonitorRunningRSDebuggerStop(object sender, DebuggerStopEventArgs args)
{
if (sender == null || args == null) { return; }
Debugger senderDebugger = sender as Debugger;
bool pushSucceeded = false;
lock (_syncActiveDebuggerStopObject)
{
Debugger activeDebugger;
if (_activeDebuggers.TryPeek(out activeDebugger))
{
// Replace current runspace debugger by first popping the old debugger.
if (IsRunningRSDebugger(activeDebugger))
{
PopActiveDebugger();
}
}
// Get nested debugger runspace info.
if (!(senderDebugger is NestedRunspaceDebugger nestedDebugger)) { return; }
PSMonitorRunspaceType runspaceType = nestedDebugger.RunspaceType;
// Fix up invocation info script extents for embedded nested debuggers where the script source is
// from the parent.
args.InvocationInfo = nestedDebugger.FixupInvocationInfo(args.InvocationInfo);
// Finally push the runspace debugger.
pushSucceeded = PushActiveDebugger(senderDebugger, _runspaceCallStackOffset);
}
// Handle debugger stop outside lock.
if (pushSucceeded)
{
// Forward the debug stop event.
// This method will always pop the debugger after debugger stop completes.
HandleActiveRunspaceDebuggerStop(sender, args);
}
}
private void HandleActiveRunspaceDebuggerStop(object sender, DebuggerStopEventArgs args)
{
// Save copy of arguments.
DebuggerStopEventArgs copyArgs = new DebuggerStopEventArgs(
args.InvocationInfo,
new Collection<Breakpoint>(args.Breakpoints),
args.ResumeAction);
_debuggerStopEventArgs.Push(copyArgs);
// Forward active debugger event.
try
{
// Blocking call that raises the stop event.
RaiseDebuggerStopEvent(args);
_lastActiveDebuggerAction = args.ResumeAction;
}
catch (Exception)
{
// Catch all external user generated exceptions thrown on event thread.
}
finally
{
_debuggerStopEventArgs.TryPop(out copyArgs);
PopActiveDebugger();
}
}
private bool IsRunningRSDebugger(Debugger debugger)
{
lock (_syncObject)
{
foreach (var item in _runningRunspaces.Values)
{
if (item.Runspace.Debugger.Equals(debugger))
{
return true;
}
}
}
return false;
}
private bool SetUpDebuggerOnRunspace(Runspace runspace)
{
PSMonitorRunspaceInfo runspaceInfo = null;
lock (_syncObject)
{
_runningRunspaces.TryGetValue(runspace.InstanceId, out runspaceInfo);
}
// Create nested debugger wrapper if it is not already created and if
// the runspace debugger is available.
if ((runspace.Debugger != null) &&
(runspaceInfo != null) &&
(runspaceInfo.NestedDebugger == null))
{
try
{
NestedRunspaceDebugger nestedDebugger = runspaceInfo.CreateDebugger(this);
runspaceInfo.NestedDebugger = nestedDebugger;
nestedDebugger.DebuggerStop += HandleMonitorRunningRSDebuggerStop;
if (((_lastActiveDebuggerAction == DebuggerResumeAction.StepInto) || (_currentDebuggerAction == DebuggerResumeAction.StepInto)) &&
!nestedDebugger.IsActive)
{
nestedDebugger.SetDebuggerStepMode(true);
}
// If the nested debugger has a pending (saved) debug stop then
// release it here now that we have the debug stop handler added.
// Note that the DebuggerStop event is raised on the original execution
// thread in the debugger (not this thread).
nestedDebugger.CheckStateAndRaiseStopEvent();
return true;
}
catch (InvalidRunspaceStateException) { }
}
return false;
}
#endregion
#region Runspace Debug Processing
private void StartRunspaceForDebugQueueProcessing()
{
int startThread = Interlocked.CompareExchange(ref _processingRunspaceDebugQueue, 1, 0);
if (startThread == 0)
{
var thread = new System.Threading.Thread(
new ThreadStart(DebuggerQueueThreadProc));
thread.Start();
}
}
private void DebuggerQueueThreadProc()
{
StartRunspaceDebugProcessingEventArgs runspaceDebugProcessArgs;
while (_runspaceDebugQueue.Value.TryDequeue(out runspaceDebugProcessArgs))
{
if (IsStartRunspaceDebugProcessingEventSubscribed())
{
try
{
RaiseStartRunspaceDebugProcessingEvent(runspaceDebugProcessArgs);
}
catch (Exception) { }
}
else
{
// If there are no ProcessDebugger event subscribers then default to handling internally.
runspaceDebugProcessArgs.UseDefaultProcessing = true;
}
// Check for internal handling request.
if (runspaceDebugProcessArgs.UseDefaultProcessing)
{
try
{
ProcessRunspaceDebugInternally(runspaceDebugProcessArgs.Runspace);
}
catch (Exception) { }
}
}
Interlocked.CompareExchange(ref _processingRunspaceDebugQueue, 0, 1);
if (!_runspaceDebugQueue.Value.IsEmpty)
{
StartRunspaceForDebugQueueProcessing();
}
}
private void ProcessRunspaceDebugInternally(Runspace runspace)
{
WaitForReadyDebug();
DebugRunspace(runspace, breakAll: true);
// Block this event thread until debugging has ended.
WaitForDebugComplete();
// Ensure runspace debugger is not stopped in break mode.
if (runspace.Debugger.InBreakpoint)
{
try
{
runspace.Debugger.UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Ignore;
}
catch (Exception) { }
}
StopDebugRunspace(runspace);
// If we return to local script execution in step mode then ensure the debugger is enabled.
_nestedDebuggerStop = false;
if ((_steppingMode == SteppingMode.StepIn) && (_currentDebuggerAction != DebuggerResumeAction.Stop) && (_context._debuggingMode == 0))
{
SetInternalDebugMode(InternalDebugMode.Enabled);
}
RaiseRunspaceProcessingCompletedEvent(
new ProcessRunspaceDebugEndEventArgs(runspace));
}
private void WaitForReadyDebug()
{
// Wait up to ten seconds
System.Threading.Thread.Sleep(500);
int count = 0;
bool debugReady = false;
do
{
System.Threading.Thread.Sleep(250);
debugReady = IsDebuggerReady();
} while (!debugReady && (count++ < 40));
if (!debugReady) { throw new PSInvalidOperationException(); }
}
private bool IsDebuggerReady()
{
return (!this.IsPushed && !this.InBreakpoint && (this._context._debuggingMode > -1) && (this._context.InternalHost.NestedPromptCount == 0));
}
private void WaitForDebugComplete()
{
if (_runspaceDebugCompleteEvent == null)
{
_runspaceDebugCompleteEvent = new ManualResetEventSlim(false);
}
else
{
_runspaceDebugCompleteEvent.Reset();
}
_runspaceDebugCompleteEvent.Wait();
}
#endregion
#region IDisposable
/// <summary>
/// Dispose.
/// </summary>
public void Dispose()
{
// Ensure all job event handlers are removed.
PSJobStartEventArgs[] runningJobs;
lock (_syncObject)
{
runningJobs = _runningJobs.Values.ToArray();
}
foreach (var item in runningJobs)
{
Job job = item.Job;
if (job != null)
{
job.StateChanged -= HandleJobStateChanged;
job.OutputProcessingStateChanged -= HandleOutputProcessingStateChanged;
}
}
_processingOutputCompleteEvent.Dispose();
_processingOutputCompleteEvent = null;
if (_preserveDebugStopEvent != null)
{
_preserveDebugStopEvent.Dispose();
_preserveDebugStopEvent = null;
}
if (_runspaceDebugCompleteEvent != null)
{
_runspaceDebugCompleteEvent.Dispose();
_runspaceDebugCompleteEvent = null;
}
}
#endregion
#region Tracing
internal void EnableTracing(int traceLevel, bool? step)
{
// Enable might actually be disabling depending on the arguments.
if (traceLevel < 1 && (step == null || !(bool)step))
{
DisableTracing();
return;
}
_savedIgnoreScriptDebug = _context.IgnoreScriptDebug;
_context.IgnoreScriptDebug = false;
_context.PSDebugTraceLevel = traceLevel;
if (step != null)
{
_context.PSDebugTraceStep = (bool)step;
}
SetInternalDebugMode(InternalDebugMode.Enabled);
}
internal void DisableTracing()
{
_context.IgnoreScriptDebug = _savedIgnoreScriptDebug;
_context.PSDebugTraceLevel = 0;
_context.PSDebugTraceStep = false;
if (CanDisableDebugger)
{
SetInternalDebugMode(InternalDebugMode.Disabled);
}
}
private bool _savedIgnoreScriptDebug = false;
internal void Trace(string messageId, string resourceString, params object[] args)
{
ActionPreference pref = ActionPreference.Continue;
string message;
if (args == null || args.Length == 0)
{
// Don't format in case the string contains literal curly braces
message = resourceString;
}
else
{
message = StringUtil.Format(resourceString, args);
}
if (string.IsNullOrEmpty(message))
{
message = "Could not load text for msh script tracing message id '" + messageId + "'";
Diagnostics.Assert(false, message);
}
((InternalHostUserInterface)_context.EngineHostInterface.UI).WriteDebugLine(message, ref pref);
}
internal void TraceLine(IScriptExtent extent)
{
string msg = PositionUtilities.BriefMessage(extent.StartScriptPosition);
InternalHostUserInterface ui = (InternalHostUserInterface)_context.EngineHostInterface.UI;
ActionPreference pref = _context.PSDebugTraceStep ?
ActionPreference.Inquire : ActionPreference.Continue;
ui.WriteDebugLine(msg, ref pref);
if (pref == ActionPreference.Continue)
_context.PSDebugTraceStep = false;
}
internal void TraceScriptFunctionEntry(FunctionContext functionContext)
{
var methodName = functionContext._functionName;
if (string.IsNullOrEmpty(functionContext._file))
{
Trace("TraceEnteringFunction", ParserStrings.TraceEnteringFunction, methodName);
}
else
{
Trace("TraceEnteringFunctionDefinedInFile", ParserStrings.TraceEnteringFunctionDefinedInFile, methodName, functionContext._file);
}
}
internal void TraceVariableSet(string varName, object value)
{
// Don't trace into debugger hidden or debugger step through unless the trace level > 2.
if (_callStack.Any() && _context.PSDebugTraceLevel <= 2)
{
// Skip trace messages in hidden/step through frames.
var frame = _callStack.Last();
if (frame.IsFrameHidden || frame.DebuggerStepThrough)
{
return;
}
}
// If the value is an IEnumerator, we don't attempt to get its string format via 'ToStringParser' method,
// because 'ToStringParser' would iterate through the enumerator to get the individual elements, which will
// make irreversible changes to the enumerator.
bool isValueAnIEnumerator = PSObject.Base(value) is IEnumerator;
string valAsString = isValueAnIEnumerator ? nameof(IEnumerator) : PSObject.ToStringParser(_context, value);
int msgLength = 60 - varName.Length;
if (valAsString.Length > msgLength)
{
valAsString = valAsString.Substring(0, msgLength) + PSObjectHelper.Ellipsis;
}
Trace("TraceVariableAssignment", ParserStrings.TraceVariableAssignment, varName, valAsString);
}
#endregion Tracing
}
#endregion
#region NestedRunspaceDebugger
/// <summary>
/// Base class for nested runspace debugger wrapper.
/// </summary>
internal abstract class NestedRunspaceDebugger : Debugger, IDisposable
{
#region Members
private bool _isDisposed;
protected Runspace _runspace;
protected Debugger _wrappedDebugger;
#endregion
#region Properties
/// <summary>
/// Type of runspace being monitored for debugging.
/// </summary>
public PSMonitorRunspaceType RunspaceType { get; }
/// <summary>
/// Unique parent debugger identifier for monitored runspace.
/// </summary>
public Guid ParentDebuggerId
{
get;
private set;
}
#endregion
#region Constructors
/// <summary>
/// Creates an instance of NestedRunspaceDebugger.
/// </summary>
/// <param name="runspace">Runspace.</param>
/// <param name="runspaceType">Runspace type.</param>
/// <param name="parentDebuggerId">Debugger Id of parent.</param>
protected NestedRunspaceDebugger(
Runspace runspace,
PSMonitorRunspaceType runspaceType,
Guid parentDebuggerId)
{
if (runspace == null || runspace.Debugger == null)
{
throw new PSArgumentNullException(nameof(runspace));
}
_runspace = runspace;
_wrappedDebugger = runspace.Debugger;
base.SetDebugMode(_wrappedDebugger.DebugMode);
RunspaceType = runspaceType;
ParentDebuggerId = parentDebuggerId;
// Handlers for wrapped debugger events.
_wrappedDebugger.BreakpointUpdated += HandleBreakpointUpdated;
_wrappedDebugger.DebuggerStop += HandleDebuggerStop;
}
#endregion
#region Overrides
/// <summary>
/// Adds the provided set of breakpoints to the debugger.
/// </summary>
/// <param name="breakpoints">Breakpoints.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
public override void SetBreakpoints(IEnumerable<Breakpoint> breakpoints, int? runspaceId) =>
_wrappedDebugger.SetBreakpoints(breakpoints, runspaceId);
/// <summary>
/// Process debugger or PowerShell command/script.
/// </summary>
/// <param name="command">PowerShell command.</param>
/// <param name="output">Output collection.</param>
/// <returns>DebuggerCommandResults.</returns>
public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection<PSObject> output)
{
if (_isDisposed) { return new DebuggerCommandResults(null, false); }
// Preprocess debugger commands.
string cmd = command.Commands[0].CommandText.Trim();
if (cmd.Equals("prompt", StringComparison.OrdinalIgnoreCase))
{
return HandlePromptCommand(output);
}
if (cmd.Equals("k", StringComparison.OrdinalIgnoreCase) ||
cmd.StartsWith("Get-PSCallStack", StringComparison.OrdinalIgnoreCase))
{
return HandleCallStack(output);
}
if (cmd.Equals("l", StringComparison.OrdinalIgnoreCase) ||
cmd.Equals("list", StringComparison.OrdinalIgnoreCase))
{
if (HandleListCommand(output))
{
return new DebuggerCommandResults(null, true);
}
}
return _wrappedDebugger.ProcessCommand(command, output);
}
/// <summary>
/// Get a breakpoint by id.
/// </summary>
/// <param name="id">Id of the breakpoint you want.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
public override Breakpoint GetBreakpoint(int id, int? runspaceId) =>
_wrappedDebugger.GetBreakpoint(id, runspaceId);
/// <summary>
/// Returns breakpoints on a runspace.
/// </summary>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>A list of breakpoints in a runspace.</returns>
public override List<Breakpoint> GetBreakpoints(int? runspaceId) =>
_wrappedDebugger.GetBreakpoints(runspaceId);
/// <summary>
/// Sets a command breakpoint in the debugger.
/// </summary>
/// <param name="command">The name of the command that will trigger the breakpoint. This value may not be null.</param>
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
/// <param name="path">The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>The command breakpoint that was set.</returns>
public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path, int? runspaceId) =>
_wrappedDebugger.SetCommandBreakpoint(command, action, path, runspaceId);
/// <summary>
/// Sets a line breakpoint in the debugger.
/// </summary>
/// <param name="path">The path to the script file where the breakpoint may be hit. This value may not be null.</param>
/// <param name="line">The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1.</param>
/// <param name="column">The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line.</param>
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>The line breakpoint that was set.</returns>
public override LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action, int? runspaceId) =>
_wrappedDebugger.SetLineBreakpoint(path, line, column, action, runspaceId);
/// <summary>
/// Sets a variable breakpoint in the debugger.
/// </summary>
/// <param name="variableName">The name of the variable that will trigger the breakpoint. This value may not be null.</param>
/// <param name="accessMode">The variable access mode that will trigger the breakpoint.</param>
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
/// <param name="path">The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>The variable breakpoint that was set.</returns>
public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path, int? runspaceId) =>
_wrappedDebugger.SetVariableBreakpoint(variableName, accessMode, action, path, runspaceId);
/// <summary>
/// Removes a breakpoint from the debugger.
/// </summary>
/// <param name="breakpoint">The breakpoint to remove from the debugger. This value may not be null.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>True if the breakpoint was removed from the debugger; false otherwise.</returns>
public override bool RemoveBreakpoint(Breakpoint breakpoint, int? runspaceId) =>
_wrappedDebugger.RemoveBreakpoint(breakpoint, runspaceId);
/// <summary>
/// Enables a breakpoint in the debugger.
/// </summary>
/// <param name="breakpoint">The breakpoint to enable in the debugger. This value may not be null.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>The updated breakpoint if it was found; null if the breakpoint was not found in the debugger.</returns>
public override Breakpoint EnableBreakpoint(Breakpoint breakpoint, int? runspaceId) =>
_wrappedDebugger.EnableBreakpoint(breakpoint, runspaceId);
/// <summary>
/// Disables a breakpoint in the debugger.
/// </summary>
/// <param name="breakpoint">The breakpoint to enable in the debugger. This value may not be null.</param>
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
/// <returns>The updated breakpoint if it was found; null if the breakpoint was not found in the debugger.</returns>
public override Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspaceId) =>
_wrappedDebugger.DisableBreakpoint(breakpoint, runspaceId);
/// <summary>
/// SetDebuggerAction.
/// </summary>
/// <param name="resumeAction">Debugger resume action.</param>
public override void SetDebuggerAction(DebuggerResumeAction resumeAction)
{
_wrappedDebugger.SetDebuggerAction(resumeAction);
}
/// <summary>
/// Stops running command.
/// </summary>
public override void StopProcessCommand()
{
_wrappedDebugger.StopProcessCommand();
}
/// <summary>
/// Returns current debugger stop event arguments if debugger is in
/// debug stop state. Otherwise returns null.
/// </summary>
/// <returns>DebuggerStopEventArgs.</returns>
public override DebuggerStopEventArgs GetDebuggerStopArgs()
{
return _wrappedDebugger.GetDebuggerStopArgs();
}
/// <summary>
/// Sets the debugger mode.
/// </summary>
/// <param name="mode">Debug mode.</param>
public override void SetDebugMode(DebugModes mode)
{
_wrappedDebugger.SetDebugMode(mode);
}
/// <summary>
/// Sets debugger stepping mode.
/// </summary>
/// <param name="enabled">True if stepping is to be enabled.</param>
public override void SetDebuggerStepMode(bool enabled)
{
_wrappedDebugger.SetDebuggerStepMode(enabled);
}
/// <summary>
/// Returns true if debugger is active.
/// </summary>
public override bool IsActive
{
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
/// <summary>
/// Dispose.
/// </summary>
public virtual void Dispose()
{
_isDisposed = true;
if (_wrappedDebugger != null)
{
_wrappedDebugger.BreakpointUpdated -= HandleBreakpointUpdated;
_wrappedDebugger.DebuggerStop -= HandleDebuggerStop;
}
_wrappedDebugger = null;
_runspace = null;
// Call GC.SuppressFinalize since this is an unsealed type, in case derived types
// have finalizers.
GC.SuppressFinalize(this);
}
#endregion
#region Protected Methods
protected virtual void HandleDebuggerStop(object sender, DebuggerStopEventArgs e)
{
this.RaiseDebuggerStopEvent(e);
}
protected virtual void HandleBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e)
{
this.RaiseBreakpointUpdatedEvent(e);
}
protected virtual DebuggerCommandResults HandlePromptCommand(PSDataCollection<PSObject> output)
{
// Nested debugged runspace prompt should look like:
// [ComputerName]: [DBG]: [Process:<id>]: [RunspaceName]: PS C:\>
string computerName = _runspace.ConnectionInfo?.ComputerName;
const string processPartPattern = "{0}[{1}:{2}]:{3}";
string processPart = StringUtil.Format(processPartPattern,
@"""",
DebuggerStrings.NestedRunspaceDebuggerPromptProcessName,
@"$($PID)",
@"""");
const string locationPart = @"""PS $($executionContext.SessionState.Path.CurrentLocation)> """;
string promptScript = "'[DBG]: '" + " + " + processPart + " + " + "' [" + CodeGeneration.EscapeSingleQuotedStringContent(_runspace.Name) + "]: '" + " + " + locationPart;
// Get the command prompt from the wrapped debugger.
PSCommand promptCommand = new PSCommand();
promptCommand.AddScript(promptScript);
PSDataCollection<PSObject> promptOutput = new PSDataCollection<PSObject>();
_wrappedDebugger.ProcessCommand(promptCommand, promptOutput);
string promptString = (promptOutput.Count == 1) ? promptOutput[0].BaseObject as string : string.Empty;
var nestedPromptString = new System.Text.StringBuilder();
// For remote runspaces display computer name in prompt.
if (!string.IsNullOrEmpty(computerName))
{
nestedPromptString.Append("[" + computerName + "]:");
}
nestedPromptString.Append(promptString);
// Fix up for non-remote runspaces since the runspace is not in a nested prompt
// but the root runspace is.
if (string.IsNullOrEmpty(computerName))
{
nestedPromptString.Insert(nestedPromptString.Length - 1, ">");
}
output.Add(nestedPromptString.ToString());
return new DebuggerCommandResults(null, true);
}
protected virtual DebuggerCommandResults HandleCallStack(PSDataCollection<PSObject> output)
{
throw new PSNotImplementedException();
}
protected virtual bool HandleListCommand(PSDataCollection<PSObject> output)
{
return false;
}
#endregion
#region Internal Methods
/// <summary>
/// Attempts to fix up the debugger stop invocation information so that
/// the correct stack and source can be displayed in the debugger, for
/// cases where the debugged runspace is called inside a parent sccript,
/// such as with script Invoke-Command cases.
/// </summary>
/// <param name="debugStopInvocationInfo"></param>
/// <returns>InvocationInfo.</returns>
internal virtual InvocationInfo FixupInvocationInfo(InvocationInfo debugStopInvocationInfo)
{
// Default is no fix up.
return debugStopInvocationInfo;
}
internal bool IsSameDebugger(Debugger testDebugger)
{
return _wrappedDebugger.Equals(testDebugger);
}
/// <summary>
/// Checks to see if the runspace debugger is in a preserved debug
/// stop state, and if so then allows the debugger stop event to
/// continue processing and raise the event.
/// </summary>
internal void CheckStateAndRaiseStopEvent()
{
RemoteDebugger remoteDebugger = _wrappedDebugger as RemoteDebugger;
if (remoteDebugger != null)
{
// Have remote debugger raise existing debugger stop event.
remoteDebugger.CheckStateAndRaiseStopEvent();
}
else if (this._wrappedDebugger.IsPendingDebugStopEvent)
{
// Release local debugger preserved debugger stop event.
this._wrappedDebugger.ReleaseSavedDebugStop();
}
else
{
// If this is a remote server debugger then we want to convert the pending remote
// debugger stop to a local debugger stop event for this Debug-Runspace to handle.
ServerRemoteDebugger serverRemoteDebugger = this._wrappedDebugger as ServerRemoteDebugger;
if (serverRemoteDebugger != null)
{
serverRemoteDebugger.ReleaseAndRaiseDebugStopLocal();
}
}
}
/// <summary>
/// Gets the callstack of the nested runspace.
/// </summary>
/// <returns></returns>
internal PSDataCollection<PSObject> GetRSCallStack()
{
// Get call stack from wrapped debugger
PSCommand cmd = new PSCommand();
cmd.AddCommand("Get-PSCallStack");
PSDataCollection<PSObject> callStackOutput = new PSDataCollection<PSObject>();
_wrappedDebugger.ProcessCommand(cmd, callStackOutput);
return callStackOutput;
}
#endregion
}
/// <summary>
/// Wrapper class for runspace debugger where it is running in no known
/// embedding scenario and is assumed to be running independently of
/// any other running script.
/// </summary>
internal sealed class StandaloneRunspaceDebugger : NestedRunspaceDebugger
{
#region Constructor
/// <summary>
/// Constructor.
/// </summary>
/// <param name="runspace">Runspace.</param>
public StandaloneRunspaceDebugger(
Runspace runspace)
: base(runspace, PSMonitorRunspaceType.Standalone, Guid.Empty)
{ }
#endregion
#region Overrides
protected override DebuggerCommandResults HandleCallStack(PSDataCollection<PSObject> output)
{
PSDataCollection<PSObject> callStackOutput = GetRSCallStack();
// Display call stack info as formatted.
using (PowerShell ps = PowerShell.Create())
{
ps.AddCommand("Out-String").AddParameter("Stream", true);
ps.Invoke(callStackOutput, output);
}
return new DebuggerCommandResults(null, true);
}
protected override void HandleDebuggerStop(object sender, DebuggerStopEventArgs e)
{
object runningCmd = null;
try
{
runningCmd = DrainAndBlockRemoteOutput();
this.RaiseDebuggerStopEvent(e);
}
finally
{
RestoreRemoteOutput(runningCmd);
}
}
#endregion
#region Private Methods
private object DrainAndBlockRemoteOutput()
{
// We do this only for remote runspaces.
if (!(_runspace is RemoteRunspace remoteRunspace)) { return null; }
var runningPowerShell = remoteRunspace.GetCurrentBasePowerShell();
if (runningPowerShell != null)
{
runningPowerShell.WaitForServicingComplete();
runningPowerShell.SuspendIncomingData();
return runningPowerShell;
}
else
{
var runningPipe = remoteRunspace.GetCurrentlyRunningPipeline();
if (runningPipe != null)
{
runningPipe.DrainIncomingData();
runningPipe.SuspendIncomingData();
return runningPipe;
}
}
return null;
}
private static void RestoreRemoteOutput(object runningCmd)
{
if (runningCmd == null) { return; }
var runningPowerShell = runningCmd as PowerShell;
if (runningPowerShell != null)
{
runningPowerShell.ResumeIncomingData();
}
else
{
var runningPipe = runningCmd as Pipeline;
runningPipe?.ResumeIncomingData();
}
}
#endregion
}
/// <summary>
/// Wrapper class for runspace debugger where the runspace is being used in an
/// embedded scenario such as Invoke-Command command inside script.
/// </summary>
internal sealed class EmbeddedRunspaceDebugger : NestedRunspaceDebugger
{
#region Members
private PowerShell _command;
private Debugger _rootDebugger;
private ScriptBlockAst _parentScriptBlockAst;
private DebuggerStopEventArgs _sendDebuggerArgs;
#endregion
#region Constructors
/// <summary>
/// Constructor for runspaces executing from script.
/// </summary>
/// <param name="runspace">Runspace to debug.</param>
/// <param name="command">PowerShell command.</param>
/// <param name="rootDebugger">Root debugger.</param>
/// <param name="runspaceType">Runspace to monitor type.</param>
/// <param name="parentDebuggerId">Parent debugger Id.</param>
public EmbeddedRunspaceDebugger(
Runspace runspace,
PowerShell command,
Debugger rootDebugger,
PSMonitorRunspaceType runspaceType,
Guid parentDebuggerId)
: base(runspace, runspaceType, parentDebuggerId)
{
if (rootDebugger == null)
{
throw new PSArgumentNullException(nameof(rootDebugger));
}
_command = command;
_rootDebugger = rootDebugger;
}
#endregion
#region Overrides
protected override void HandleDebuggerStop(object sender, DebuggerStopEventArgs e)
{
_sendDebuggerArgs = new DebuggerStopEventArgs(
e.InvocationInfo,
new Collection<Breakpoint>(e.Breakpoints),
e.ResumeAction);
object remoteRunningCmd = null;
try
{
// For remote debugging drain/block output channel.
remoteRunningCmd = DrainAndBlockRemoteOutput();
this.RaiseDebuggerStopEvent(_sendDebuggerArgs);
}
finally
{
RestoreRemoteOutput(remoteRunningCmd);
// Return user determined resume action.
e.ResumeAction = _sendDebuggerArgs.ResumeAction;
}
}
protected override DebuggerCommandResults HandleCallStack(PSDataCollection<PSObject> output)
{
// First get call stack from wrapped debugger
PSCommand cmd = new PSCommand();
cmd.AddCommand("Get-PSCallStack");
PSDataCollection<PSObject> callStackOutput = new PSDataCollection<PSObject>();
_wrappedDebugger.ProcessCommand(cmd, callStackOutput);
// Next get call stack from parent debugger.
PSDataCollection<CallStackFrame> callStack = _rootDebugger.GetCallStack().ToArray();
// Combine call stack info.
foreach (var item in callStack)
{
callStackOutput.Add(new PSObject(item));
}
// Display call stack info as formatted.
using (PowerShell ps = PowerShell.Create())
{
ps.AddCommand("Out-String").AddParameter("Stream", true);
ps.Invoke(callStackOutput, output);
}
return new DebuggerCommandResults(null, true);
}
protected override bool HandleListCommand(PSDataCollection<PSObject> output)
{
if ((_sendDebuggerArgs != null) && (_sendDebuggerArgs.InvocationInfo != null))
{
return _rootDebugger.InternalProcessListCommand(_sendDebuggerArgs.InvocationInfo.ScriptLineNumber, output);
}
return false;
}
/// <summary>
/// Attempts to fix up the debugger stop invocation information so that
/// the correct stack and source can be displayed in the debugger, for
/// cases where the debugged runspace is called inside a parent sccript,
/// such as with script Invoke-Command cases.
/// </summary>
/// <param name="debugStopInvocationInfo">Invocation information from debugger stop.</param>
/// <returns>InvocationInfo.</returns>
internal override InvocationInfo FixupInvocationInfo(InvocationInfo debugStopInvocationInfo)
{
if (debugStopInvocationInfo == null) { return null; }
// Check to see if this nested debug stop is called from within
// a known parent source.
int dbStopLineNumber = debugStopInvocationInfo.ScriptLineNumber;
CallStackFrame topItem = null;
var parentActiveStack = _rootDebugger.GetActiveDebuggerCallStack();
if ((parentActiveStack != null) && (parentActiveStack.Length > 0))
{
topItem = parentActiveStack[0];
}
else
{
var parentStack = _rootDebugger.GetCallStack().ToArray();
if ((parentStack != null) && (parentStack.Length > 0))
{
topItem = parentStack[0];
dbStopLineNumber--;
}
}
InvocationInfo debugInvocationInfo = CreateInvocationInfoFromParent(
topItem,
dbStopLineNumber,
debugStopInvocationInfo.ScriptPosition.StartColumnNumber,
debugStopInvocationInfo.ScriptPosition.EndColumnNumber);
return debugInvocationInfo ?? debugStopInvocationInfo;
}
#endregion
#region IDisposable
/// <summary>
/// Dispose.
/// </summary>
public override void Dispose()
{
base.Dispose();
_rootDebugger = null;
_parentScriptBlockAst = null;
_command = null;
_sendDebuggerArgs = null;
}
#endregion
#region Private Methods
private InvocationInfo CreateInvocationInfoFromParent(
CallStackFrame parentStackFrame,
int debugLineNumber,
int debugStartColNumber,
int debugEndColNumber)
{
if (parentStackFrame == null) { return null; }
// Attempt to find parent script file create script block with Ast to
// find correct line and offset adjustments.
if ((_parentScriptBlockAst == null) &&
!string.IsNullOrEmpty(parentStackFrame.ScriptName) &&
System.IO.File.Exists(parentStackFrame.ScriptName))
{
ParseError[] errors;
Token[] tokens;
_parentScriptBlockAst = Parser.ParseInput(
System.IO.File.ReadAllText(parentStackFrame.ScriptName),
out tokens, out errors);
}
if (_parentScriptBlockAst != null)
{
int callingLineNumber = parentStackFrame.ScriptLineNumber;
StatementAst debugStatement = null;
StatementAst callingStatement = _parentScriptBlockAst.Find(
ast => ast is StatementAst && (ast.Extent.StartLineNumber == callingLineNumber), true) as StatementAst;
if (callingStatement != null)
{
// Find first statement in calling statement.
StatementAst firstStatement = callingStatement.Find(
ast => ast is StatementAst && ast.Extent.StartLineNumber > callingLineNumber, true) as StatementAst;
if (firstStatement != null)
{
int adjustedLineNumber = firstStatement.Extent.StartLineNumber + debugLineNumber - 1;
debugStatement = callingStatement.Find(
ast => ast is StatementAst && ast.Extent.StartLineNumber == adjustedLineNumber, true) as StatementAst;
}
}
if (debugStatement != null)
{
int endColNum = debugStartColNumber + (debugEndColNumber - debugStartColNumber) - 2;
string statementExtentText = FixUpStatementExtent(debugStatement.Extent.StartColumnNumber - 1, debugStatement.Extent.Text);
ScriptPosition scriptStartPosition = new ScriptPosition(
parentStackFrame.ScriptName,
debugStatement.Extent.StartLineNumber,
debugStartColNumber,
statementExtentText);
ScriptPosition scriptEndPosition = new ScriptPosition(
parentStackFrame.ScriptName,
debugStatement.Extent.EndLineNumber,
endColNum,
statementExtentText);
return InvocationInfo.Create(
parentStackFrame.InvocationInfo.MyCommand,
new ScriptExtent(
scriptStartPosition,
scriptEndPosition)
);
}
}
return null;
}
private static string FixUpStatementExtent(int startColNum, string stateExtentText)
{
Text.StringBuilder sb = new Text.StringBuilder();
sb.Append(' ', startColNum);
sb.Append(stateExtentText);
return sb.ToString();
}
private object DrainAndBlockRemoteOutput()
{
// We only do this for remote runspaces.
if (_runspace is not RemoteRunspace) { return null; }
try
{
if (_command != null)
{
_command.WaitForServicingComplete();
_command.SuspendIncomingData();
return _command;
}
Pipeline runningCmd = _runspace.GetCurrentlyRunningPipeline();
if (runningCmd != null)
{
runningCmd.DrainIncomingData();
runningCmd.SuspendIncomingData();
return runningCmd;
}
}
catch (PSNotSupportedException)
{ }
return null;
}
private static void RestoreRemoteOutput(object runningCmd)
{
if (runningCmd == null) { return; }
PowerShell command = runningCmd as PowerShell;
if (command != null)
{
command.ResumeIncomingData();
}
else
{
Pipeline pipelineCommand = runningCmd as Pipeline;
if (pipelineCommand != null)
{
pipelineCommand.ResumeIncomingData();
}
}
}
#endregion
}
#endregion
#region DebuggerCommandResults
/// <summary>
/// Command results returned from Debugger.ProcessCommand.
/// </summary>
public sealed class DebuggerCommandResults
{
#region Properties
/// <summary>
/// Resume action.
/// </summary>
public DebuggerResumeAction? ResumeAction
{
get;
private set;
}
/// <summary>
/// True if debugger evaluated command. Otherwise evaluation was
/// performed by PowerShell.
/// </summary>
public bool EvaluatedByDebugger { get; }
#endregion
#region Constructors
private DebuggerCommandResults()
{ }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="resumeAction">Resume action.</param>
/// <param name="evaluatedByDebugger">True if evaluated by debugger.</param>
public DebuggerCommandResults(
DebuggerResumeAction? resumeAction,
bool evaluatedByDebugger)
{
ResumeAction = resumeAction;
EvaluatedByDebugger = evaluatedByDebugger;
}
#endregion
}
#endregion
#region DebuggerCommandProcessor
/// <summary>
/// This class is used to pre-process the command read by the host when it is in debug mode; its
/// main intention is to implement the debugger commands ("s", "c", "o", etc)
/// </summary>
internal class DebuggerCommandProcessor
{
// debugger commands
private const string ContinueCommand = "continue";
private const string ContinueShortcut = "c";
private const string GetStackTraceShortcut = "k";
private const string HelpCommand = "h";
private const string HelpShortcut = "?";
private const string ListCommand = "list";
private const string ListShortcut = "l";
private const string StepCommand = "stepInto";
private const string StepShortcut = "s";
private const string StepOutCommand = "stepOut";
private const string StepOutShortcut = "o";
private const string StepOverCommand = "stepOver";
private const string StepOverShortcut = "v";
private const string StopCommand = "quit";
private const string StopShortcut = "q";
private const string DetachCommand = "detach";
private const string DetachShortcut = "d";
// default line count for the list command
private const int DefaultListLineCount = 16;
// table of debugger commands
private readonly Dictionary<string, DebuggerCommand> _commandTable;
// the Help command
private readonly DebuggerCommand _helpCommand;
// the List command
private readonly DebuggerCommand _listCommand;
// last command processed
private DebuggerCommand _lastCommand;
// the source script split into lines
private string[] _lines;
// last line displayed by the list command
private int _lastLineDisplayed;
private const string Crlf = "\x000D\x000A";
/// <summary>
/// Creates the table of debugger commands.
/// </summary>
public DebuggerCommandProcessor()
{
_commandTable = new Dictionary<string, DebuggerCommand>(StringComparer.OrdinalIgnoreCase);
_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>
/// Resets any state in the command processor.
/// </summary>
public void Reset()
{
_lines = null;
}
/// <summary>
/// Process the command read by the host and returns the DebuggerResumeAction or the command
/// that the host should execute (see comments in the DebuggerCommand class above).
/// </summary>
public DebuggerCommand ProcessCommand(PSHost host, string command, InvocationInfo invocationInfo)
{
return _lastCommand = DoProcessCommand(host, command, invocationInfo, null);
}
/// <summary>
/// ProcessCommand.
/// </summary>
/// <param name="host"></param>
/// <param name="command"></param>
/// <param name="invocationInfo"></param>
/// <param name="output"></param>
/// <returns></returns>
public DebuggerCommand ProcessCommand(PSHost host, string command, InvocationInfo invocationInfo, IList<PSObject> output)
{
DebuggerCommand dbgCommand = DoProcessCommand(host, command, invocationInfo, output);
if (dbgCommand.ExecutedByDebugger || (dbgCommand.ResumeAction != null)) { _lastCommand = dbgCommand; }
return dbgCommand;
}
/// <summary>
/// Process list command with provided line number.
/// </summary>
/// <param name="invocationInfo">Current InvocationInfo.</param>
/// <param name="output">Output.</param>
public void ProcessListCommand(InvocationInfo invocationInfo, IList<PSObject> output)
{
DoProcessCommand(null, "list", invocationInfo, output);
}
/// <summary>
/// Looks up string command and if it is a debugger command returns the
/// corresponding DebuggerCommand object.
/// </summary>
/// <param name="command">String command.</param>
/// <returns>DebuggerCommand or null.</returns>
public DebuggerCommand ProcessBasicCommand(string command)
{
if (command.Length == 0 && _lastCommand != null && _lastCommand.RepeatOnEnter)
{
return _lastCommand;
}
DebuggerCommand debuggerCommand;
if (_commandTable.TryGetValue(command, out debuggerCommand))
{
if (debuggerCommand.ExecutedByDebugger || (debuggerCommand.ResumeAction != null)) { _lastCommand = debuggerCommand; }
return debuggerCommand;
}
return null;
}
/// <summary>
/// Helper for ProcessCommand.
/// </summary>
private DebuggerCommand DoProcessCommand(PSHost host, string command, InvocationInfo invocationInfo, IList<PSObject> output)
{
// check for <enter>
if (command.Length == 0 && _lastCommand != null && _lastCommand.RepeatOnEnter)
{
if (_lastCommand == _listCommand)
{
if (_lines != null && _lastLineDisplayed < _lines.Length)
{
DisplayScript(host, output, invocationInfo, _lastLineDisplayed + 1, DefaultListLineCount);
}
return _listCommand;
}
command = _lastCommand.Command;
}
// Check for the list command using a regular expression
Regex listCommandRegex = new Regex(@"^l(ist)?(\s+(?<start>\S+))?(\s+(?<count>\S+))?$", RegexOptions.IgnoreCase);
Match match = listCommandRegex.Match(command);
if (match.Success)
{
DisplayScript(host, output, invocationInfo, match);
return _listCommand;
}
// Check for the rest of the debugger commands
DebuggerCommand debuggerCommand = null;
if (_commandTable.TryGetValue(command, out debuggerCommand))
{
// Check for the help command
if (debuggerCommand == _helpCommand)
{
DisplayHelp(host, output);
}
return debuggerCommand;
}
// Else return the same command
return new DebuggerCommand(command, null, false, false);
}
/// <summary>
/// Displays the help text for the debugger commands.
/// </summary>
private static void DisplayHelp(PSHost host, IList<PSObject> output)
{
WriteLine(string.Empty, host, output);
WriteLine(StringUtil.Format(DebuggerStrings.StepHelp, StepShortcut, StepCommand), host, output);
WriteLine(StringUtil.Format(DebuggerStrings.StepOverHelp, StepOverShortcut, StepOverCommand), host, output);
WriteLine(StringUtil.Format(DebuggerStrings.StepOutHelp, StepOutShortcut, StepOutCommand), host, output);
WriteLine(string.Empty, host, output);
WriteLine(StringUtil.Format(DebuggerStrings.ContinueHelp, ContinueShortcut, ContinueCommand), host, output);
WriteLine(StringUtil.Format(DebuggerStrings.StopHelp, StopShortcut, StopCommand), host, output);
WriteLine(StringUtil.Format(DebuggerStrings.DetachHelp, DetachShortcut, DetachCommand), host, output);
WriteLine(string.Empty, host, output);
WriteLine(StringUtil.Format(DebuggerStrings.GetStackTraceHelp, GetStackTraceShortcut), host, output);
WriteLine(string.Empty, host, output);
WriteLine(StringUtil.Format(DebuggerStrings.ListHelp, ListShortcut, ListCommand), host, output);
WriteLine(StringUtil.Format(DebuggerStrings.AdditionalListHelp1), host, output);
WriteLine(StringUtil.Format(DebuggerStrings.AdditionalListHelp2), host, output);
WriteLine(StringUtil.Format(DebuggerStrings.AdditionalListHelp3), host, output);
WriteLine(string.Empty, host, output);
WriteLine(StringUtil.Format(DebuggerStrings.EnterHelp, StepCommand, StepOverCommand, ListCommand), host, output);
WriteLine(string.Empty, host, output);
WriteLine(StringUtil.Format(DebuggerStrings.HelpCommandHelp, HelpShortcut, HelpCommand), host, output);
WriteLine("\n", host, output);
WriteLine(StringUtil.Format(DebuggerStrings.PromptHelp), host, output);
WriteLine(string.Empty, host, output);
}
/// <summary>
/// Executes the list command.
/// </summary>
private void DisplayScript(PSHost host, IList<PSObject> output, InvocationInfo invocationInfo, Match match)
{
if (invocationInfo == null) { return; }
//
// Get the source code for the script
//
if (_lines == null)
{
string scriptText = invocationInfo.GetFullScript();
if (string.IsNullOrEmpty(scriptText))
{
WriteErrorLine(StringUtil.Format(DebuggerStrings.NoSourceCode), host, output);
return;
}
_lines = scriptText.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
}
//
// Get the starting line
//
int start = Math.Max(invocationInfo.ScriptLineNumber - 5, 1);
if (match.Groups["start"].Value.Length > 0)
{
try
{
start = int.Parse(match.Groups["start"].Value, CultureInfo.CurrentCulture.NumberFormat);
}
catch
{
WriteErrorLine(StringUtil.Format(DebuggerStrings.BadStartFormat, _lines.Length), host, output);
return;
}
if (start <= 0 || start > _lines.Length)
{
WriteErrorLine(StringUtil.Format(DebuggerStrings.BadStartFormat, _lines.Length), host, output);
return;
}
}
//
// Get the line count
//
int count = DefaultListLineCount;
if (match.Groups["count"].Value.Length > 0)
{
try
{
count = int.Parse(match.Groups["count"].Value, CultureInfo.CurrentCulture.NumberFormat);
}
catch
{
WriteErrorLine(StringUtil.Format(DebuggerStrings.BadCountFormat, _lines.Length), host, output);
return;
}
// Limit requested line count to maximum number of existing lines
count = (count > _lines.Length) ? _lines.Length : count;
if (count <= 0)
{
WriteErrorLine(DebuggerStrings.BadCountFormat, host, output);
return;
}
}
//
// Execute the command
//
DisplayScript(host, output, invocationInfo, start, count);
}
/// <summary>
/// Executes the list command.
/// </summary>
private void DisplayScript(PSHost host, IList<PSObject> output, InvocationInfo invocationInfo, int start, int count)
{
WriteCR(host, output);
for (int lineNumber = start; lineNumber <= _lines.Length && lineNumber < start + count; lineNumber++)
{
WriteLine(
lineNumber == invocationInfo.ScriptLineNumber ?
string.Format(CultureInfo.CurrentCulture, "{0,5}:* {1}", lineNumber, _lines[lineNumber - 1])
:
string.Format(CultureInfo.CurrentCulture, "{0,5}: {1}", lineNumber, _lines[lineNumber - 1]),
host,
output);
_lastLineDisplayed = lineNumber;
}
WriteCR(host, output);
}
private static void WriteLine(string line, PSHost host, IList<PSObject> output)
{
if (host != null)
{
host.UI.WriteLine(line);
}
if (output != null)
{
output.Add(new PSObject(line));
}
}
private static void WriteCR(PSHost host, IList<PSObject> output)
{
if (host != null)
{
host.UI.WriteLine();
}
if (output != null)
{
output.Add(new PSObject(Crlf));
}
}
private static void WriteErrorLine(string error, PSHost host, IList<PSObject> output)
{
if (host != null)
{
host.UI.WriteErrorLine(error);
}
if (output != null)
{
output.Add(
new PSObject(
new ErrorRecord(
new RuntimeException(error),
"DebuggerError",
ErrorCategory.InvalidOperation,
null)));
}
}
}
/// <summary>
/// Class used to hold the output of the DebuggerCommandProcessor.
/// </summary>
internal class DebuggerCommand
{
public DebuggerCommand(string command, DebuggerResumeAction? action, bool repeatOnEnter, bool executedByDebugger)
{
ResumeAction = action;
Command = command;
RepeatOnEnter = repeatOnEnter;
ExecutedByDebugger = executedByDebugger;
}
/// <summary>
/// If ResumeAction is not null it indicates that the host must exit the debugger
/// and resume execution of the suspended pipeline; the debugger will use the
/// value of this property to decide how to resume the pipeline (i.e. step into,
/// step-over, continue, etc)
/// </summary>
public DebuggerResumeAction? ResumeAction { get; }
/// <summary>
/// When ResumeAction is null, this property indicates the command that the
/// host should pass to the PowerShell engine.
/// </summary>
public string Command { get; }
/// <summary>
/// If true, the host should repeat this command if the next command in an empty line (enter)
/// </summary>
public bool RepeatOnEnter { get; }
/// <summary>
/// If true, the command was executed by the debugger and the host should ignore the command.
/// </summary>
public bool ExecutedByDebugger { get; }
}
#endregion
#region PSDebugContext class
/// <summary>
/// This class exposes the information about the debugger that is available via $PSDebugContext.
/// </summary>
public class PSDebugContext
{
/// <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>
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)
{
throw new PSArgumentNullException(nameof(breakpoints));
}
this.InvocationInfo = invocationInfo;
this.Breakpoints = breakpoints.ToArray();
this.Trigger = triggerObject;
}
/// <summary>
/// InvocationInfo of the command currently being executed.
/// </summary>
public InvocationInfo InvocationInfo { get; }
/// <summary>
/// If not empty, indicates that the execution was suspended because one or more breakpoints
/// were hit. Otherwise, the execution was suspended as part of a step operation.
/// </summary>
public Breakpoint[] Breakpoints { get; }
/// <summary>
/// Gets the object that triggered the current dynamic breakpoint.
/// </summary>
public object Trigger { get; }
}
#endregion
#region CallStackFrame class
/// <summary>
/// A call stack item returned by the Get-PSCallStack cmdlet.
/// </summary>
public sealed class CallStackFrame
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="invocationInfo">Invocation Info.</param>
public CallStackFrame(InvocationInfo invocationInfo)
: this(null, invocationInfo)
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="functionContext">Function context.</param>
/// <param name="invocationInfo">Invocation Info.</param>
internal CallStackFrame(FunctionContext functionContext, InvocationInfo invocationInfo)
{
if (invocationInfo == null)
{
throw new PSArgumentNullException(nameof(invocationInfo));
}
if (functionContext != null)
{
this.InvocationInfo = invocationInfo;
FunctionContext = functionContext;
this.Position = functionContext.CurrentPosition;
}
else
{
// WF functions do not have functionContext. Use InvocationInfo.
this.InvocationInfo = invocationInfo;
this.Position = invocationInfo.ScriptPosition;
FunctionContext = new FunctionContext();
FunctionContext._functionName = invocationInfo.ScriptName;
}
}
/// <summary>
/// File name of the current location, or null if the frame is not associated to a script.
/// </summary>
public string ScriptName
{
get { return Position.File; }
}
/// <summary>
/// Line number of the current location, or 0 if the frame is not associated to a script.
/// </summary>
public int ScriptLineNumber
{
get { return Position.StartLineNumber; }
}
/// <summary>
/// The InvocationInfo of the command.
/// </summary>
public InvocationInfo InvocationInfo { get; }
/// <summary>
/// The position information for the current position in the frame. Null if the frame is not
/// associated with a script.
/// </summary>
public IScriptExtent Position { get; }
/// <summary>
/// The name of the function associated with this frame.
/// </summary>
public string FunctionName { get { return FunctionContext._functionName; } }
internal FunctionContext FunctionContext { get; }
/// <summary>
/// Returns a formatted string containing the ScriptName and ScriptLineNumber.
/// </summary>
public string GetScriptLocation()
{
if (string.IsNullOrEmpty(this.ScriptName))
{
return DebuggerStrings.NoFile;
}
return StringUtil.Format(DebuggerStrings.LocationFormat, Path.GetFileName(this.ScriptName), this.ScriptLineNumber);
}
/// <summary>
/// Return a dictionary with the names and values of variables that are "local"
/// to the frame.
/// </summary>
/// <returns></returns>
public Dictionary<string, PSVariable> GetFrameVariables()
{
var result = new Dictionary<string, PSVariable>(StringComparer.OrdinalIgnoreCase);
if (FunctionContext._executionContext == null) { return result; }
var scope = FunctionContext._executionContext.EngineSessionState.CurrentScope;
while (scope != null)
{
if (scope.LocalsTuple == FunctionContext._localsTuple)
{
// We can ignore any dotted scopes.
break;
}
if (scope.DottedScopes != null && scope.DottedScopes.Any(s => s == FunctionContext._localsTuple))
{
var dottedScopes = scope.DottedScopes.ToArray();
int i;
// Skip dotted scopes above the current scope
for (i = 0; i < dottedScopes.Length; ++i)
{
if (dottedScopes[i] == FunctionContext._localsTuple)
break;
}
for (; i < dottedScopes.Length; ++i)
{
dottedScopes[i].GetVariableTable(result, true);
}
break;
}
scope = scope.Parent;
}
FunctionContext._localsTuple.GetVariableTable(result, true);
return result;
}
/// <summary>
/// ToString override.
/// </summary>
public override string ToString()
{
return StringUtil.Format(DebuggerStrings.StackTraceFormat, FunctionName,
ScriptName ?? DebuggerStrings.NoFile, ScriptLineNumber);
}
}
#endregion
}
namespace System.Management.Automation.Internal
{
#region DebuggerUtils
/// <summary>
/// Debugger Utilities class.
/// </summary>
[SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")]
public static class DebuggerUtils
{
private static readonly SortedSet<string> s_noHistoryCommandNames = new SortedSet<string>(StringComparer.OrdinalIgnoreCase)
{
"prompt",
"Set-PSDebuggerAction",
"Get-PSDebuggerStopArgs",
"Set-PSDebugMode",
"TabExpansion2"
};
/// <summary>
/// Helper method to determine if command should be added to debugger
/// history.
/// </summary>
/// <param name="command">Command string.</param>
/// <returns>True if command can be added to history.</returns>
public static bool ShouldAddCommandToHistory(string command)
{
if (command == null)
{
throw new PSArgumentNullException(nameof(command));
}
lock (s_noHistoryCommandNames)
{
return !(s_noHistoryCommandNames.Contains(command, StringComparer.OrdinalIgnoreCase));
}
}
/// <summary>
/// Start monitoring a runspace on the target debugger.
/// </summary>
/// <param name="debugger">Target debugger.</param>
/// <param name="runspaceInfo">PSMonitorRunspaceInfo.</param>
public static void StartMonitoringRunspace(Debugger debugger, PSMonitorRunspaceInfo runspaceInfo)
{
if (debugger == null)
{
throw new PSArgumentNullException(nameof(debugger));
}
if (runspaceInfo == null)
{
throw new PSArgumentNullException(nameof(runspaceInfo));
}
debugger.StartMonitoringRunspace(runspaceInfo);
}
/// <summary>
/// End monitoring a runspace on the target degbugger.
/// </summary>
/// <param name="debugger">Target debugger.</param>
/// <param name="runspaceInfo">PSMonitorRunspaceInfo.</param>
public static void EndMonitoringRunspace(Debugger debugger, PSMonitorRunspaceInfo runspaceInfo)
{
if (debugger == null)
{
throw new PSArgumentNullException(nameof(debugger));
}
if (runspaceInfo == null)
{
throw new PSArgumentNullException(nameof(runspaceInfo));
}
debugger.EndMonitoringRunspace(runspaceInfo);
}
}
#region PSMonitorRunspaceEvent
/// <summary>
/// PSMonitorRunspaceEvent.
/// </summary>
[SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")]
public enum PSMonitorRunspaceType
{
/// <summary>
/// Standalone runspace.
/// </summary>
Standalone = 0,
/// <summary>
/// Runspace from remote Invoke-Command script.
/// </summary>
InvokeCommand,
}
/// <summary>
/// Runspace information for monitoring runspaces for debugging.
/// </summary>
[SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")]
public abstract class PSMonitorRunspaceInfo
{
#region Properties
/// <summary>
/// Created Runspace.
/// </summary>
public Runspace Runspace { get; }
/// <summary>
/// Type of runspace for monitoring.
/// </summary>
public PSMonitorRunspaceType RunspaceType { get; }
/// <summary>
/// Nested debugger wrapper for runspace debugger.
/// </summary>
internal NestedRunspaceDebugger NestedDebugger { get; set; }
#endregion
#region Constructors
private PSMonitorRunspaceInfo() { }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="runspace">Runspace.</param>
/// <param name="runspaceType">Runspace type.</param>
protected PSMonitorRunspaceInfo(
Runspace runspace,
PSMonitorRunspaceType runspaceType)
{
if (runspace == null)
{
throw new PSArgumentNullException(nameof(runspace));
}
Runspace = runspace;
RunspaceType = runspaceType;
}
#endregion
#region Methods
/// <summary>
/// Returns a copy of this object.
/// </summary>
/// <returns></returns>
internal abstract PSMonitorRunspaceInfo Copy();
/// <summary>
/// Creates an instance of a NestedRunspaceDebugger.
/// </summary>
/// <param name="rootDebugger">Root debugger or null.</param>
/// <returns>NestedRunspaceDebugger.</returns>
internal abstract NestedRunspaceDebugger CreateDebugger(Debugger rootDebugger);
#endregion
}
/// <summary>
/// Standalone runspace information for monitoring runspaces for debugging.
/// </summary>
[SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")]
public sealed class PSStandaloneMonitorRunspaceInfo : PSMonitorRunspaceInfo
{
#region Constructor
/// <summary>
/// Creates instance of PSStandaloneMonitorRunspaceInfo.
/// </summary>
/// <param name="runspace">Runspace to monitor.</param>
public PSStandaloneMonitorRunspaceInfo(
Runspace runspace)
: base(runspace, PSMonitorRunspaceType.Standalone)
{ }
#endregion
#region Overrides
/// <summary>
/// Returns a copy of this object.
/// </summary>
/// <returns></returns>
internal override PSMonitorRunspaceInfo Copy()
{
return new PSStandaloneMonitorRunspaceInfo(Runspace);
}
/// <summary>
/// Creates an instance of a NestedRunspaceDebugger.
/// </summary>
/// <param name="rootDebugger">Root debugger or null.</param>
/// <returns>NestedRunspaceDebugger wrapper.</returns>
internal override NestedRunspaceDebugger CreateDebugger(Debugger rootDebugger)
{
return new StandaloneRunspaceDebugger(Runspace);
}
#endregion
}
/// <summary>
/// Embedded runspaces running in context of a parent script, used for monitoring
/// runspace debugging.
/// </summary>
[SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")]
public sealed class PSEmbeddedMonitorRunspaceInfo : PSMonitorRunspaceInfo
{
#region Properties
/// <summary>
/// PowerShell command to run. Can be null.
/// </summary>
public PowerShell Command { get; }
/// <summary>
/// Unique parent debugger identifier.
/// </summary>
public Guid ParentDebuggerId { get; private set; }
#endregion
#region Constructor
/// <summary>
/// Creates instance of PSEmbeddedMonitorRunspaceInfo.
/// </summary>
/// <param name="runspace">Runspace to monitor.</param>
/// <param name="runspaceType">Type of runspace.</param>
/// <param name="command">Running command.</param>
/// <param name="parentDebuggerId">Unique parent debugger id or null.</param>
public PSEmbeddedMonitorRunspaceInfo(
Runspace runspace,
PSMonitorRunspaceType runspaceType,
PowerShell command,
Guid parentDebuggerId)
: base(runspace, runspaceType)
{
Command = command;
ParentDebuggerId = parentDebuggerId;
}
#endregion
#region Overrides
/// <summary>
/// Returns a copy of this object.
/// </summary>
/// <returns></returns>
internal override PSMonitorRunspaceInfo Copy()
{
return new PSEmbeddedMonitorRunspaceInfo(
Runspace,
RunspaceType,
Command,
ParentDebuggerId);
}
/// <summary>
/// Creates an instance of a NestedRunspaceDebugger.
/// </summary>
/// <param name="rootDebugger">Root debugger or null.</param>
/// <returns>NestedRunspaceDebugger wrapper.</returns>
internal override NestedRunspaceDebugger CreateDebugger(Debugger rootDebugger)
{
return new EmbeddedRunspaceDebugger(
Runspace,
Command,
rootDebugger,
RunspaceType,
ParentDebuggerId);
}
#endregion
}
#endregion
#endregion
}