Files
PowerShell/src/System.Management.Automation/engine/debugger/debugger.cs
T
PowerShell Team ae8d2ed794 Refactor - convert to auto-property
Mostly using R# to automatically refactor, with some manual
fixups where it wouldn't work automatically (e.g. xml comments
on fields) or some it seemed to miss.

Some minor code reformatting was also done on properties near
other stuff I was manually inspecting.
2016-08-03 12:14:28 -07:00

5452 lines
193 KiB
C#

/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Management.Automation.Host;
using System.Management.Automation.Internal.Host;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Internal;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Diagnostics.CodeAnalysis;
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; private set; }
/// <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>
/// 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; private set; }
/// <summary>
/// Gets the type of update
/// </summary>
public BreakpointUpdateType UpdateType { get; private set; }
/// <summary>
/// Gets the current breakpoint count
/// </summary>
public int BreakpointCount { get; private set; }
};
#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;
private set;
}
/// <summary>
/// Job debugger
/// </summary>
public Debugger Debugger
{
get;
private set;
}
/// <summary>
/// Job is run asynchronously
/// </summary>
public bool IsAsync
{
get;
private set;
}
/// <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 DebugSource
/// <summary>
/// Contains debugger script and script file information.
/// </summary>
public sealed class DebugSource
{
/// <summary>
/// Full script.
/// </summary>
public string Script { get; private set; }
/// <summary>
/// Script file for script or null.
/// </summary>
public string ScriptFile { get; private set; }
/// <summary>
/// Xaml definition for Workflow script or null.
/// </summary>
public string XamlDefinition { get; private set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="script">Script text</param>
/// <param name="scriptFile">Script file</param>
/// <param name="xamlDefinition">Xaml definition</param>
public DebugSource(
string script,
string scriptFile,
string xamlDefinition)
{
Script = script;
ScriptFile = scriptFile;
XamlDefinition = xamlDefinition;
}
private DebugSource() { }
}
#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 including workflow script.
/// </summary>
LocalScript = 0x2,
/// <summary>
/// PowerShell remote script and workflow 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 avaialable.
/// </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;
#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);
}
#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 and breakpoints.
/// </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 parent debugger, breakpoints, function source 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">PowerShell host</param>
/// <param name="path">Current path</param>
/// <param name="functionSourceMap">Function to source map</param>
public virtual void SetParent(
Debugger parent,
IEnumerable<Breakpoint> breakPoints,
DebuggerResumeAction? startAction,
PSHost host,
PathInfo path,
Dictionary<string, DebugSource> functionSourceMap)
{
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>
/// Adds the provided set of breakpoints to the debugger.
/// </summary>
/// <param name="breakpoints">Breakpoints.</param>
public virtual void SetBreakpoints(IEnumerable<Breakpoint> breakpoints)
{
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>
/// 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 procssed.</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>
internal virtual void DebugJob(Job job)
{
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">Runspace to debug</param>
internal virtual void DebugRunspace(Runspace runspace)
{
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 e)
{
CommandProcessorBase.CheckForSevereException(e);
}
});
}
#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 Dictionary<int, Breakpoint>();
_pendingBreakpoints = new List<LineBreakpoint>();
_boundBreakpoints = new Dictionary<string, Tuple<WeakReference, List<LineBreakpoint>>>(StringComparer.OrdinalIgnoreCase);
_commandBreakpoints = new List<CommandBreakpoint>();
_variableBreakpoints = new Dictionary<string, List<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.Count > 0); }
}
/// <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;
}
}
#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;
_writeWFErrorOnce = 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, List<LineBreakpoint>> boundBreakpoints;
if (!_boundBreakpoints.TryGetValue(path, out boundBreakpoints))
{
_boundBreakpoints.Add(path, Tuple.Create(new WeakReference(scriptContents), new List<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);
_boundBreakpoints[path] = Tuple.Create(new WeakReference(scriptContents), new List<LineBreakpoint>());
}
}
}
#endregion Call stack management
#region adding 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 Breakpoint AddCommandBreakpoint(CommandBreakpoint breakpoint)
{
AddBreakpointCommon(breakpoint);
_commandBreakpoints.Add(breakpoint);
return breakpoint;
}
internal Breakpoint NewCommandBreakpoint(string path, string command, ScriptBlock action)
{
WildcardPattern pattern = WildcardPattern.Get(command, WildcardOptions.Compiled | WildcardOptions.IgnoreCase);
return AddCommandBreakpoint(new CommandBreakpoint(path, pattern, command, action));
}
internal Breakpoint NewCommandBreakpoint(string command, ScriptBlock action)
{
WildcardPattern pattern = WildcardPattern.Get(command, WildcardOptions.Compiled | WildcardOptions.IgnoreCase);
return AddCommandBreakpoint(new CommandBreakpoint(null, pattern, command, action));
}
private Breakpoint AddLineBreakpoint(LineBreakpoint breakpoint)
{
AddBreakpointCommon(breakpoint);
_pendingBreakpoints.Add(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 Breakpoint NewLineBreakpoint(string path, int line, ScriptBlock action)
{
Diagnostics.Assert(path != null, "caller to verify path is not null");
return AddLineBreakpoint(new LineBreakpoint(path, line, action));
}
internal Breakpoint NewStatementBreakpoint(string path, int line, int column, ScriptBlock action)
{
Diagnostics.Assert(path != null, "caller to verify path is not null");
return AddLineBreakpoint(new LineBreakpoint(path, line, column, action));
}
internal VariableBreakpoint AddVariableBreakpoint(VariableBreakpoint breakpoint)
{
AddBreakpointCommon(breakpoint);
List<VariableBreakpoint> breakpoints;
if (!_variableBreakpoints.TryGetValue(breakpoint.Variable, out breakpoints))
{
breakpoints = new List<VariableBreakpoint>();
_variableBreakpoints.Add(breakpoint.Variable, breakpoints);
}
breakpoints.Add(breakpoint);
return breakpoint;
}
internal Breakpoint NewVariableBreakpoint(string path, string variableName, VariableAccessMode accessMode, ScriptBlock action)
{
return AddVariableBreakpoint(new VariableBreakpoint(path, variableName, accessMode, action));
}
internal Breakpoint NewVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action)
{
return AddVariableBreakpoint(new VariableBreakpoint(null, variableName, accessMode, action));
}
/// <summary>
/// Raises the BreakpointUpdated event
/// </summary>
/// <param name="e"></param>
private void OnBreakpointUpdated(BreakpointUpdatedEventArgs e)
{
RaiseBreakpointUpdatedEvent(e);
}
#endregion adding breakpoints
#region removing breakpoints
// This is the implementation of the Remove-PSBreakpoint cmdlet.
internal void RemoveBreakpoint(Breakpoint breakpoint)
{
_idToBreakpoint.Remove(breakpoint.Id);
breakpoint.RemoveSelf(this);
if (_idToBreakpoint.Count == 0)
{
// The last breakpoint was removed, turn off debugging.
SetInternalDebugMode(InternalDebugMode.Disabled);
}
OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Removed, _idToBreakpoint.Count));
}
internal void RemoveVariableBreakpoint(VariableBreakpoint breakpoint)
{
_variableBreakpoints[breakpoint.Variable].Remove(breakpoint);
}
internal void RemoveCommandBreakpoint(CommandBreakpoint breakpoint)
{
_commandBreakpoints.Remove(breakpoint);
}
internal void RemoveLineBreakpoint(LineBreakpoint breakpoint)
{
_pendingBreakpoints.Remove(breakpoint);
Tuple<WeakReference, List<LineBreakpoint>> value;
if (_boundBreakpoints.TryGetValue(breakpoint.Script, out value))
{
value.Item2.Remove(breakpoint);
}
}
#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.Where(bp => bp.Enabled && bp.Trigger(invocationInfo)).ToList<Breakpoint>();
bool checkLineBp = true;
if (breakpoints.Any())
{
breakpoints = TriggerBreakpoints(breakpoints);
if (breakpoints.Any())
{
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.Any())
{
TriggerVariableBreakpoints(breakpointsToTrigger);
}
}
internal void CheckVariableWrite(string variableName)
{
var breakpointsToTrigger = GetVariableBreakpointsToTrigger(variableName, read: false);
if (breakpointsToTrigger != null && breakpointsToTrigger.Any())
{
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);
List<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 != null) ? callStackInfo.File : null;
return breakpoints.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>());
}
/// <summary>
/// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets.
/// </summary>
internal Breakpoint GetBreakpoint(int id)
{
Breakpoint breakpoint;
_idToBreakpoint.TryGetValue(id, out breakpoint);
return breakpoint;
}
/// <summary>
/// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet.
/// </summary>
internal List<Breakpoint> GetBreakpoints()
{
return (from bp in _idToBreakpoint.Values orderby bp.Id select bp).ToList();
}
// 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;
}
#endregion triggering breakpoints
#region enabling/disabling breakpoints
/// <summary>
/// Implmentation of Enable-PSBreakpoint cmdlet.
/// </summary>
internal void EnableBreakpoint(Breakpoint bp)
{
bp.SetEnabled(true);
OnBreakpointUpdated(new BreakpointUpdatedEventArgs(bp, BreakpointUpdateType.Enabled, _idToBreakpoint.Count));
}
/// <summary>
/// Implementation of Disable-PSBreakpoint cmdlet.
/// </summary>
internal void DisableBreakpoint(Breakpoint bp)
{
bp.SetEnabled(false);
OnBreakpointUpdated(new BreakpointUpdatedEventArgs(bp, BreakpointUpdateType.Disabled, _idToBreakpoint.Count));
}
#endregion enabling/disabling breakpoints
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);
}
#if !CORECLR // Workflow Not Supported on OneCore PS
// Lazily subscribe to workflow start engine event for workflow debugging.
if (!_wfStartEventSubscribed && IsJobDebuggingMode())
{
SubscribeToEngineWFJobStartEvent();
}
#endif
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.Any())
{
StopOnSequencePoint(functionContext, breakpoints);
}
}
}
}
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; }
bool havePendingBreakpoint = false;
foreach (var item in _pendingBreakpoints)
{
if (item.IsScriptBreakpoint && item.Script.Equals(functionContext._file, StringComparison.OrdinalIgnoreCase))
{
havePendingBreakpoint = true;
break;
}
}
if (havePendingBreakpoint)
{
SetPendingBreakpoints(functionContext);
}
}
}
#endregion internal methods
#region private members
[DebuggerDisplay("{FunctionContext.CurrentPosition}")]
private 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 != null ? last.FunctionContext : null;
}
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 List<LineBreakpoint> _pendingBreakpoints;
private readonly Dictionary<string, Tuple<WeakReference, List<LineBreakpoint>>> _boundBreakpoints;
private readonly List<CommandBreakpoint> _commandBreakpoints;
private readonly Dictionary<string, List<VariableBreakpoint>> _variableBreakpoints;
private readonly Dictionary<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.
#if !CORECLR // Workflow Not Supported on OneCore PS
private bool _wfStartEventSubscribed;
#endif
private bool _nestedDebuggerStop;
private bool _writeWFErrorOnce;
private Dictionary<Guid, PSJobStartEventArgs> _runningJobs;
private ConcurrentStack<Debugger> _activeDebuggers;
private ConcurrentStack<DebuggerStopEventArgs> _debuggerStopEventArgs;
private DebuggerResumeAction _lastActiveDebuggerAction;
private DebuggerResumeAction _currentDebuggerAction;
private DebuggerResumeAction _previousDebuggerAction;
private CallStackInfo _nestedRunningFrame;
private object _syncObject;
private object _syncActiveDebuggerStopObject;
private int _processingOutputCount;
private ManualResetEventSlim _processingOutputCompleteEvent = new ManualResetEventSlim(true);
// Runspace debugger integration.
private Dictionary<Guid, PSMonitorRunspaceInfo> _runningRunspaces;
private const int _jobCallStackOffset = 2;
private const int _runspaceCallStackOffset = 1;
private bool _preserveUnhandledDebugStopEvent;
private ManualResetEventSlim _preserveDebugStopEvent;
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;
}
_context.SetVariable(SpecialVariables.PSDebugContextVarPath, new PSDebugContext(invocationInfo, breakpoints));
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
}
// Update the prompt to the debug prompt
if (hadDefaultPrompt)
{
int index = originalPromptString.IndexOf("\"", StringComparison.OrdinalIgnoreCase);
if (index > -1)
{
// Fix up prompt.
++index;
string debugPrompt = "\"[DBG]: " + originalPromptString.Substring(index, originalPromptString.Length - index);
defaultPromptInfo.Update(
ScriptBlock.Create(debugPrompt), true, ScopedItemOptions.Unspecified);
}
else
{
hadDefaultPrompt = false;
}
}
PSLanguageMode? originalLanguageMode = null;
if (_context.UseFullLanguageModeInDebugger &&
(_context.LanguageMode != PSLanguageMode.FullLanguage))
{
originalLanguageMode = _context.LanguageMode;
_context.LanguageMode = PSLanguageMode.FullLanguage;
}
else if (System.Management.Automation.Security.SystemPolicy.GetSystemLockdownPolicy() ==
System.Management.Automation.Security.SystemEnforcementMode.Enforce)
{
// If there is a system lockdown in place, enforce it
originalLanguageMode = _context.LanguageMode;
_context.LanguageMode = PSLanguageMode.ConstrainedLanguage;
}
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);
_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.Assert(false, "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.Add(breakpoint);
}
boundBreakpoints.Clear();
}
private void SetPendingBreakpoints(FunctionContext functionContext)
{
if (!_pendingBreakpoints.Any())
return;
var newPendingBreakpoints = new List<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 (var 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;
Diagnostics.Assert(boundBreakpoints.IndexOf(breakpoint) < 0, "Don't add more than once.");
boundBreakpoints.Add(breakpoint);
}
if (!bound)
{
newPendingBreakpoints.Add(breakpoint);
}
}
_pendingBreakpoints = 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)
{
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);
}
}
#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);
}
}
#if !CORECLR
// Workflow Not Supported on OneCore PS
// Look for any running workflow jobs and set to step mode.
if ((nestedType & EnableNestedType.NestedJob) == EnableNestedType.NestedJob)
{
EnableRunningWorkflowJobsForStepping();
}
#endif
// 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.Count > 0)) ? 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("command");
}
if (output == null)
{
throw new PSArgumentNullException("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.
//
LocalRunspace localRunspace = _context.CurrentRunspace as LocalRunspace;
if (localRunspace == null)
{
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)
{
base.SetDebugMode(mode);
if (!CanEnableDebugger)
{
SetInternalDebugMode(InternalDebugMode.Disabled);
}
else if ((_idToBreakpoint.Count > 0) && (_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>
/// SetBreakpoints
/// </summary>
/// <param name="breakpoints"></param>
public override void SetBreakpoints(IEnumerable<Breakpoint> breakpoints)
{
if (breakpoints == null)
{
throw new PSArgumentNullException("breakpoints");
}
foreach (var breakpoint in breakpoints)
{
if (_idToBreakpoint.ContainsKey(breakpoint.Id)) { continue; }
LineBreakpoint lineBp = breakpoint as LineBreakpoint;
if (lineBp != null)
{
AddLineBreakpoint(lineBp);
continue;
}
CommandBreakpoint cmdBp = breakpoint as CommandBreakpoint;
if (cmdBp != null)
{
AddCommandBreakpoint(cmdBp);
continue;
}
VariableBreakpoint variableBp = breakpoint as VariableBreakpoint;
if (variableBp != null)
{
AddVariableBreakpoint(variableBp);
}
}
}
/// <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>
/// 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 procssed.</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 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>
internal override void DebugJob(Job job)
{
if (job == null) { throw new PSArgumentNullException("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);
if (!jobsAdded)
{
// Otherwise treat as parent Job and iterate over child jobs.
foreach (Job childJob in job.ChildJobs)
{
if (TryAddDebugJob(childJob) && !jobsAdded)
{
jobsAdded = true;
}
}
}
if (!jobsAdded)
{
throw new PSInvalidOperationException(DebuggerStrings.NoDebuggableJobsFound);
}
}
private bool TryAddDebugJob(Job job)
{
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),
DebuggerResumeAction.StepInto);
// 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("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>
internal override void DebugRunspace(Runspace runspace)
{
if (runspace == null)
{
throw new PSArgumentNullException("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)
{
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("runspace"); }
SetInternalDebugMode(InternalDebugMode.Disabled);
RemoveFromRunningRunspaceList(runspace);
RestoreInternalDebugMode();
}
#endregion
#endregion
#region Job debugger integration
#if !CORECLR // Workflow Not Supported on OneCore PS
private void SubscribeToEngineWFJobStartEvent()
{
Diagnostics.Assert(_context.Events != null, "Event manager cannot be null.");
_context.Events.SubscribeEvent(
source: null,
eventName: null,
sourceIdentifier: PSEngineEvent.WorkflowJobStartEvent,
data: null,
handlerDelegate: HandleJobStartEvent,
supportEvent: true,
forwardEvent: false);
_wfStartEventSubscribed = true;
}
private void UnsubscribeFromEngineWFJobStartEvent()
{
PSEventManager eventManager = _context.Events;
Diagnostics.Assert(eventManager != null, "Event manager cannot be null.");
foreach (var subscriber in eventManager.GetEventSubscribers(PSEngineEvent.WorkflowJobStartEvent))
{
eventManager.UnsubscribeEvent(subscriber);
}
_wfStartEventSubscribed = false;
}
private void HandleJobStartEvent(object sender, PSEventArgs args)
{
Diagnostics.Assert(args.SourceArgs.Length == 1, "WF Job Started engine event SourceArgs should have a single element.");
PSJobStartEventArgs jobStartedArgs = args.SourceArgs[0] as PSJobStartEventArgs;
Diagnostics.Assert(jobStartedArgs != null, "WF Job Started engine event args cannot be null.");
Diagnostics.Assert(jobStartedArgs.Job != null, "WF Job to start cannot be null.");
Diagnostics.Assert(jobStartedArgs.Debugger != null, "WF Job Started Debugger object cannot be null.");
if (!(jobStartedArgs.Debugger.GetType().FullName.Equals("Microsoft.PowerShell.Workflow.PSWorkflowDebugger",
StringComparison.OrdinalIgnoreCase)))
{
// Check to ensure only PS workflow debuggers can be passed in.
throw new PSInvalidOperationException();
}
// At this point the script debugger stack frame must be the Workflow execution function which is DebuggerHidden.
// The internal debugger resume action is set to StepOut, *if* the user selected StepIn, so as to skip this frame
// since debugging the workflow execution function is turned off. We look at the previous resume action to see
// what the user intended. If it is StepIn then we start the workflow job debugger in step mode.
Diagnostics.Assert(_callStack.LastFunctionContext()._debuggerHidden, "Current stack frame must be WF function DebuggerHidden");
DebuggerResumeAction startAction = (_previousDebuggerAction == DebuggerResumeAction.StepInto) ?
DebuggerResumeAction.StepInto : DebuggerResumeAction.Continue;
AddToJobRunningList(jobStartedArgs, startAction);
}
#endif
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;
jobArgs.Debugger.BreakpointUpdated += HandleBreakpointUpdated;
newJob = true;
}
}
if (newJob)
{
jobArgs.Debugger.SetParent(
this,
_idToBreakpoint.Values.ToArray<Breakpoint>(),
startAction,
_context.EngineHostInterface.ExternalHost,
_context.SessionState.Path.CurrentLocation,
GetFunctionToSourceMap());
}
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 Dictionary<string, DebugSource> GetFunctionToSourceMap()
{
Dictionary<string, DebugSource> fnToSource = new Dictionary<string, DebugSource>();
// Get workflow function source information for workflow debugger.
Collection<PSObject> items = _context.SessionState.InvokeProvider.Item.Get("function:\\*");
foreach (var item in items)
{
var funcItem = item.BaseObject as WorkflowInfo;
if ((funcItem != null) &&
(!string.IsNullOrEmpty(funcItem.Name)))
{
if ((funcItem.Module != null) && (funcItem.Module.Path != null))
{
string scriptFile = funcItem.Module.Path;
string scriptSource = GetFunctionSource(scriptFile);
if (scriptSource != null)
{
fnToSource.Add(
funcItem.Name,
new DebugSource(
scriptSource,
scriptFile,
funcItem.XamlDefinition));
}
}
}
}
return fnToSource;
}
private string GetFunctionSource(
string scriptFile)
{
if (System.IO.File.Exists(scriptFile))
{
try
{
return System.IO.File.ReadAllText(scriptFile);
}
catch (ArgumentException) { }
catch (System.IO.IOException) { }
catch (UnauthorizedAccessException) { }
catch (NotSupportedException) { }
catch (System.Security.SecurityException) { }
}
return null;
}
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;
jobArgs.Debugger.BreakpointUpdated -= HandleBreakpointUpdated;
_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()
{
#if !CORECLR // Workflow Not Supported on OneCore PS
UnsubscribeFromEngineWFJobStartEvent();
#endif
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())
{
#if !CORECLR // Workflow Not Supported on OneCore PS
// Remove workflow job callback.
UnsubscribeFromEngineWFJobStartEvent();
// Write warning to user.
WriteWorkflowDebugNotSupportedError();
#endif
// Ignore job debugger stop.
args.ResumeAction = DebuggerResumeAction.Continue;
return;
}
Debugger senderDebugger = sender as Debugger;
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();
}
}
if (PushActiveDebugger(senderDebugger, _jobCallStackOffset))
{
// 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 void HandleBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e)
{
switch (e.UpdateType)
{
case BreakpointUpdateType.Set:
AddNewBreakpoint(e.Breakpoint);
break;
case BreakpointUpdateType.Removed:
RemoveBreakpoint(e.Breakpoint);
break;
case BreakpointUpdateType.Enabled:
EnableBreakpoint(e.Breakpoint);
break;
case BreakpointUpdateType.Disabled:
DisableBreakpoint(e.Breakpoint);
break;
}
}
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 hangs 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);
}
// If active debugger is Workflow debugger then process command here (for "list" and "help").
if (activeDebugger.GetType().FullName.Equals("Microsoft.PowerShell.Workflow.PSWorkflowDebugger", StringComparison.OrdinalIgnoreCase))
{
DebuggerCommand results = _commandProcessor.ProcessCommand(null, commandText, stopArgs.InvocationInfo, output);
if ((results != null) &&
results.ExecutedByDebugger)
{
return new DebuggerCommandResults(results.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;
}
private void WriteWorkflowDebugNotSupportedError()
{
if (!_writeWFErrorOnce)
{
var host = _context.EngineHostInterface.ExternalHost;
if (host != null && host.UI != null)
{
host.UI.WriteErrorLine(DebuggerStrings.WorkflowDebuggingNotSupported);
}
_writeWFErrorOnce = true;
}
}
#if !CORECLR // Workflow Not Supported on OneCore PS
private void EnableRunningWorkflowJobsForStepping()
{
// Make sure workflow job start callback is set to pick
// up any newly starting jobs.
if (!_wfStartEventSubscribed)
{
lock (_syncObject)
{
if (!_wfStartEventSubscribed)
{
SubscribeToEngineWFJobStartEvent();
}
}
}
// Get list of workflow jobs
Collection<Job> jobs;
using (PowerShell ps = PowerShell.Create())
{
ps.Commands.Clear();
ps.AddScript(@"Get-Job | ? {$_.PSJobTypeName -eq 'PSWorkflowJob'}");
jobs = ps.Invoke<Job>();
}
// Add debuggable workflow jobs to running Job list
// and set debugger to step mode.
foreach (var parentJob in jobs)
{
if (parentJob != null)
{
foreach (var childJob in parentJob.ChildJobs)
{
IJobDebugger debuggableJob = childJob as IJobDebugger;
if (debuggableJob != null)
{
AddToJobRunningList(
new PSJobStartEventArgs(
childJob,
debuggableJob.Debugger,
debuggableJob.IsAsync),
DebuggerResumeAction.StepInto);
}
}
}
}
// Ensure existing running jobs in list are also set to step mode.
SetRunningJobListToStep(true);
}
#endif
#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.Assert(false, "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 != null) ? runspaceInfo.NestedDebugger : null;
if (nestedDebugger != null)
{
nestedDebugger.DebuggerStop -= HandleMonitorRunningRSDebuggerStop;
nestedDebugger.BreakpointUpdated -= HandleBreakpointUpdated;
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;
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.
NestedRunspaceDebugger nestedDebugger = senderDebugger as NestedRunspaceDebugger;
if (nestedDebugger == null) { return; }
PSMonitorRunspaceType runspaceType = nestedDebugger.RunspaceType;
// If this is a workflow debugger then ensure that there is a current active
// debugger that is the associated job debugger for this inline script WF runspace.
if (runspaceType == PSMonitorRunspaceType.WorkflowInlineScript)
{
bool needToPushAssociatedWFDebugger = true;
if (_activeDebuggers.TryPeek(out activeDebugger))
{
needToPushAssociatedWFDebugger = (activeDebugger.InstanceId != nestedDebugger.ParentDebuggerId);
if (needToPushAssociatedWFDebugger)
{
// Pop incorrect active debugger.
PopActiveDebugger();
}
}
if (needToPushAssociatedWFDebugger)
{
PSJobStartEventArgs wfJobArgs = null;
lock (_syncObject)
{
_runningJobs.TryGetValue(nestedDebugger.ParentDebuggerId, out wfJobArgs);
}
if (wfJobArgs == null)
{
Diagnostics.Assert(false, "We should never get a WF job InlineScript debugger without an associated WF parent job.");
return;
}
PushActiveDebugger(wfJobArgs.Debugger, _jobCallStackOffset);
}
}
// 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.
if (PushActiveDebugger(senderDebugger, _runspaceCallStackOffset))
{
// 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 ex)
{
// Catch all external user generated exceptions thrown on event thread.
CommandProcessorBase.CheckForSevereException(ex);
}
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;
nestedDebugger.BreakpointUpdated += HandleBreakpointUpdated;
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 IDisposable
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
#if !CORECLR // Workflow Not Supported on OneCore PS
UnsubscribeFromEngineWFJobStartEvent();
#endif
// 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;
}
}
#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 (!_idToBreakpoint.Any())
{
// Only disable debug mode if there are no breakpoints.
SetInternalDebugMode(InternalDebugMode.Disabled);
}
}
private bool _savedIgnoreScriptDebug = false;
internal void Trace(string messageId, string resourceString, params object[] args)
{
ActionPreference pref = ActionPreference.Continue;
string message;
if (null == args || 0 == args.Length)
{
// 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 ? typeof(IEnumerator).Name : PSObject.ToStringParser(_context, value);
int msgLength = 60 - varName.Length;
if (valAsString.Length > msgLength)
{
valAsString = valAsString.Substring(0, msgLength) + "...";
}
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
protected Runspace _runspace;
protected Debugger _wrappedDebugger;
#endregion
#region Properties
/// <summary>
/// Type of runspace being monitored for debugging.
/// </summary>
public PSMonitorRunspaceType RunspaceType
{
get;
private set;
}
/// <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>
public NestedRunspaceDebugger(
Runspace runspace,
PSMonitorRunspaceType runspaceType,
Guid parentDebuggerId)
{
if (runspace == null || runspace.Debugger == null)
{
throw new PSArgumentNullException("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>
/// Process debugger or PowerShell command/script.
/// </summary>
/// <param name="command">PowreShell command</param>
/// <param name="output">Output collection</param>
/// <returns>DebuggerCommandResults</returns>
public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection<PSObject> output)
{
// 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>
/// 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; }
}
#endregion
#region IDisposable
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose()
{
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 != null) ? _runspace.ConnectionInfo.ComputerName : null;
string processPartPattern = "{0}[{1}:{2}]:{3}";
string processPart = StringUtil.Format(processPartPattern,
@"""",
DebuggerStrings.NestedRunspaceDebuggerPromptProcessName,
@"$($PID)",
@"""");
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 Workflow InlineScripts and 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();
}
}
}
#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)
{
// Get call stack from wrapped debugger
PSCommand cmd = new PSCommand();
cmd.AddCommand("Get-PSCallStack");
PSDataCollection<PSObject> callStackOutput = new PSDataCollection<PSObject>();
_wrappedDebugger.ProcessCommand(cmd, callStackOutput);
// 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)
{
PowerShell runningCmd = null;
try
{
runningCmd = DrainAndBlockRemoteOutput();
this.RaiseDebuggerStopEvent(e);
}
finally
{
RestoreRemoteOutput(runningCmd);
}
}
#endregion
#region Private Methods
private PowerShell DrainAndBlockRemoteOutput()
{
// We only do this for remote runspaces.
RemoteRunspace remoteRunspace = _runspace as RemoteRunspace;
if (remoteRunspace == null) { return null; }
PowerShell runningCmd = remoteRunspace.GetCurrentBasePowerShell();
if (runningCmd != null)
{
runningCmd.WaitForServicingComplete();
runningCmd.SuspendIncomingData();
return runningCmd;
}
return null;
}
private void RestoreRemoteOutput(PowerShell runningCmd)
{
if (runningCmd != null)
{
runningCmd.ResumeIncomingData();
}
}
#endregion
}
/// <summary>
/// Wrapper class for runspace debugger where the runspace is being used in an
/// embedded scenario such as Workflow InlineScript or 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("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 Workflow InlineScripts and 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 adjustements.
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 =>
{ return ((ast is StatementAst) && (ast.Extent.StartLineNumber == callingLineNumber)); }
, true) as StatementAst;
if (callingStatement != null)
{
// Find first statement in calling statement.
StatementAst firstStatement = callingStatement.Find(ast => { return ((ast is StatementAst) && ast.Extent.StartLineNumber > callingLineNumber); }, true) as StatementAst;
if (firstStatement != null)
{
int adjustedLineNumber = firstStatement.Extent.StartLineNumber + debugLineNumber - 1;
debugStatement = callingStatement.Find(ast => { return ((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 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 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 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;
private set;
}
#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 Dictionary<string, DebuggerCommand> _commandTable;
// the Help command
private DebuggerCommand _helpCommand;
// the List command
private 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, true, false);
_commandTable[StepOutCommand] = _commandTable[StepOutShortcut] = new DebuggerCommand(StepOutCommand, DebuggerResumeAction.StepOut, false, false);
_commandTable[StepOverCommand] = _commandTable[StepOverShortcut] = new DebuggerCommand(StepOverCommand, DebuggerResumeAction.StepOver, true, false);
_commandTable[ContinueCommand] = _commandTable[ContinueShortcut] = new DebuggerCommand(ContinueCommand, DebuggerResumeAction.Continue, false, false);
_commandTable[StopCommand] = _commandTable[StopShortcut] = new DebuggerCommand(StopCommand, DebuggerResumeAction.Stop, false, false);
_commandTable[GetStackTraceShortcut] = new DebuggerCommand("get-pscallstack", null, false, false);
_commandTable[HelpCommand] = _commandTable[HelpShortcut] = _helpCommand = new DebuggerCommand(HelpCommand, null, false, true);
_commandTable[ListCommand] = _commandTable[ListShortcut] = _listCommand = new DebuggerCommand(ListCommand, null, true, true);
_commandTable[string.Empty] = new DebuggerCommand(string.Empty, null, false, true);
}
/// <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 void DisplayHelp(PSHost host, IList<PSObject> output)
{
WriteLine("", 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("", 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("", host, output);
WriteLine(StringUtil.Format(DebuggerStrings.GetStackTraceHelp, GetStackTraceShortcut), host, output);
WriteLine("", 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("", host, output);
WriteLine(StringUtil.Format(DebuggerStrings.EnterHelp, StepCommand, StepOverCommand, ListCommand), host, output);
WriteLine("", host, output);
WriteLine(StringUtil.Format(DebuggerStrings.HelpCommandHelp, HelpShortcut, HelpCommand), host, output);
WriteLine("\n", host, output);
WriteLine(StringUtil.Format(DebuggerStrings.PromptHelp), host, output);
WriteLine("", 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 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 void WriteCR(PSHost host, IList<PSObject> output)
{
if (host != null)
{
host.UI.WriteLine();
}
if (output != null)
{
output.Add(new PSObject(Crlf));
}
}
private 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>
/// Constructor.
/// </summary>
/// <param name="invocationInfo">InvocationInfo</param>
/// <param name="breakpoints">Breakpoints</param>
public PSDebugContext(InvocationInfo invocationInfo, List<Breakpoint> breakpoints)
{
if (breakpoints == null)
{
throw new PSArgumentNullException("breakpoints");
}
this.InvocationInfo = invocationInfo;
this.Breakpoints = breakpoints.ToArray();
}
/// <summary>
/// InvocationInfo of the command currently being executed
/// </summary>
public InvocationInfo InvocationInfo { get; private set; }
/// <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; private set; }
}
#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("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; private set; }
/// <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; private set; }
/// <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.Where(s => s == FunctionContext._localsTuple).Any())
{
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
{
/// <summary>
/// Set-DebuggerVariable function.
/// </summary>
public const string SetVariableFunction = @"function Set-DebuggerVariable
{
[CmdletBinding()]
param(
[Parameter(Position=0)]
[HashTable]
$Variables
)
foreach($key in $Variables.Keys)
{
microsoft.powershell.utility\set-variable -Name $key -Value $Variables[$key] -Scope global
}
Set-StrictMode -Off
}";
/// <summary>
/// Remove-DebuggerVariable function.
/// </summary>
public const string RemoveVariableFunction = @"function Remove-DebuggerVariable
{
[CmdletBinding()]
param(
[Parameter(Position=0)]
[string[]]
$Name
)
foreach ($item in $Name)
{
microsoft.powershell.utility\remove-variable -name $item -scope global
}
Set-StrictMode -Off
}";
/// <summary>
/// Get-PSCallStack override function.
/// </summary>
public const string GetPSCallStackOverrideFunction = @"function Get-PSCallStack
{
[CmdletBinding()]
param()
if ($PSWorkflowDebugger -ne $null)
{
foreach ($frame in $PSWorkflowDebugger.GetCallStack())
{
Write-Output $frame
}
}
Set-StrictMode -Off
}";
internal const string SetDebugModeFunctionName = "__Set-PSDebugMode";
internal const string SetDebuggerActionFunctionName = "__Set-PSDebuggerAction";
internal const string GetDebuggerStopArgsFunctionName = "__Get-PSDebuggerStopArgs";
internal const string SetDebuggerStepMode = "__Set-PSDebuggerStepMode";
internal const string SetPSUnhandledBreakpointMode = "__Set-PSUnhandledBreakpointMode";
private static 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("command");
}
lock (s_noHistoryCommandNames)
{
return !(s_noHistoryCommandNames.Contains(command, StringComparer.OrdinalIgnoreCase));
}
}
/// <summary>
/// Helper method to return an enumeration of workflow debugger
/// functions.
/// </summary>
/// <returns></returns>
public static IEnumerable<string> GetWorkflowDebuggerFunctions()
{
return new Collection<string>()
{
SetVariableFunction,
RemoveVariableFunction,
GetPSCallStackOverrideFunction
};
}
/// <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("debugger");
}
if (runspaceInfo == null)
{
throw new PSArgumentNullException("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("debugger");
}
if (runspaceInfo == null)
{
throw new PSArgumentNullException("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 from Workflow activity inline script.
/// </summary>
WorkflowInlineScript
}
/// <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; private set; }
/// <summary>
/// Type of runspace for monitoring.
/// </summary>
public PSMonitorRunspaceType RunspaceType { get; private set; }
/// <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("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; private set; }
/// <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
}