Skip to content

[Revamp pipeline thread handling] Support debug runspace #1447

Closed
@SydneyhSmith

Description

@SydneyhSmith

// DSC support is enabled only for Windows PowerShell.
if ((runspaceDetails.PowerShellVersion.Version.Major < 6) &&
(runspaceDetails.Context != RunspaceContext.DebuggedRunspace))
{
using (PowerShell powerShell = PowerShell.Create())
{
powerShell.Runspace = runspaceDetails.Runspace;
// Attempt to import the updated DSC module
powerShell.AddCommand("Import-Module");
powerShell.AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1");
powerShell.AddParameter("PassThru");
powerShell.AddParameter("ErrorAction", "Ignore");
PSObject moduleInfo = null;
try
{
moduleInfo = powerShell.Invoke().FirstOrDefault();
}
catch (RuntimeException e)
{
logger.LogException("Could not load the DSC module!", e);
}
if (moduleInfo != null)
{
logger.LogTrace("Side-by-side DSC module found, gathering DSC resource paths...");
// The module was loaded, add the breakpoint capability
capability = new DscBreakpointCapability();
runspaceDetails.AddCapability(capability);
powerShell.Commands.Clear();
powerShell
.AddCommand("Microsoft.PowerShell.Utility\\Write-Host")
.AddArgument("Gathering DSC resource paths, this may take a while...")
.Invoke();
// Get the list of DSC resource paths
powerShell.Commands.Clear();
powerShell
.AddCommand("Get-DscResource")
.AddCommand("Select-Object")
.AddParameter("ExpandProperty", "ParentPath");
Collection<PSObject> resourcePaths = null;
try
{
resourcePaths = powerShell.Invoke();
}
catch (CmdletInvocationException e)
{
logger.LogException("Get-DscResource failed!", e);
}
if (resourcePaths != null)
{
capability.dscResourceRootPaths =
resourcePaths
.Select(o => (string)o.BaseObject)
.ToArray();
logger.LogTrace($"DSC resources found: {resourcePaths.Count}");
}
else
{
logger.LogTrace($"No DSC resources found.");
}
}
else
{
logger.LogTrace($"Side-by-side DSC module was not found.");
}
}
}

if (_debugStateService.WaitingForAttach &&
e.ChangeAction == RunspaceChangeAction.Enter &&
e.NewRunspace.Context == RunspaceContext.DebuggedRunspace)
{
// Sends the InitializedEvent so that the debugger will continue
// sending configuration requests
_debugStateService.WaitingForAttach = false;
_debugStateService.ServerStarted.SetResult(true);
}
else if (
e.ChangeAction == RunspaceChangeAction.Exit &&
_powerShellContextService.IsDebuggerStopped)
{
// Exited the session while the debugger is stopped,
// send a ContinuedEvent so that the client changes the
// UI to appear to be running again
_debugAdapterServer.SendNotification(EventNames.Continued,
new ContinuedEvent
{
ThreadId = 1,
AllThreadsContinued = true
});
}

// Pop the current RunspaceDetails if we were attached
// to a runspace and the resume action is Stop
if (this.CurrentRunspace.Context == RunspaceContext.DebuggedRunspace &&
e.ResumeAction == DebuggerResumeAction.Stop)
{
this.PopRunspace();
}
else if (e.ResumeAction != DebuggerResumeAction.Stop)
{
// Update the session state
this.OnSessionStateChanged(
this,
new SessionStateChangedEventArgs(
PowerShellContextState.Running,
PowerShellExecutionResult.NotFinished,
null));
}

case RunspaceContext.DebuggedRunspace:
// An attached runspace will be detached when the
// running pipeline is aborted
break;

if (runspaceDetails.Context == RunspaceContext.DebuggedRunspace)
{
this.powerShellContext.ExecuteCommandAsync(createCommand).Wait();
}

See also:

/// <summary>
/// Specifies the possible types of a runspace.
/// </summary>
internal enum RunspaceLocation
{
/// <summary>
/// A runspace on the local machine.
/// </summary>
Local,
/// <summary>
/// A runspace on a different machine.
/// </summary>
Remote
}
/// <summary>
/// Specifies the context in which the runspace was encountered.
/// </summary>
internal enum RunspaceContext
{
/// <summary>
/// The original runspace in a local or remote session.
/// </summary>
Original,
/// <summary>
/// A runspace in a process that was entered with Enter-PSHostProcess.
/// </summary>
EnteredProcess,
/// <summary>
/// A runspace that is being debugged with Debug-Runspace.
/// </summary>
DebuggedRunspace
}
/// <summary>
/// Provides details about a runspace being used in the current
/// editing session.
/// </summary>
internal class RunspaceDetails
{
#region Private Fields
private Dictionary<Type, IRunspaceCapability> capabilities =
new Dictionary<Type, IRunspaceCapability>();
#endregion
#region Properties
/// <summary>
/// Gets the Runspace instance for which this class contains details.
/// </summary>
internal Runspace Runspace { get; private set; }
/// <summary>
/// Gets the PowerShell version of the new runspace.
/// </summary>
public PowerShellVersionDetails PowerShellVersion { get; private set; }
/// <summary>
/// Gets the runspace location, either Local or Remote.
/// </summary>
public RunspaceLocation Location { get; private set; }
/// <summary>
/// Gets the context in which the runspace was encountered.
/// </summary>
public RunspaceContext Context { get; private set; }
/// <summary>
/// Gets the "connection string" for the runspace, generally the
/// ComputerName for a remote runspace or the ProcessId of an
/// "Attach" runspace.
/// </summary>
public string ConnectionString { get; private set; }
/// <summary>
/// Gets the details of the runspace's session at the time this
/// RunspaceDetails object was created.
/// </summary>
public SessionDetails SessionDetails { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Creates a new instance of the RunspaceDetails class.
/// </summary>
/// <param name="runspace">
/// The runspace for which this instance contains details.
/// </param>
/// <param name="sessionDetails">
/// The SessionDetails for the runspace.
/// </param>
/// <param name="powerShellVersion">
/// The PowerShellVersionDetails of the runspace.
/// </param>
/// <param name="runspaceLocation">
/// The RunspaceLocation of the runspace.
/// </param>
/// <param name="runspaceContext">
/// The RunspaceContext of the runspace.
/// </param>
/// <param name="connectionString">
/// The connection string of the runspace.
/// </param>
public RunspaceDetails(
Runspace runspace,
SessionDetails sessionDetails,
PowerShellVersionDetails powerShellVersion,
RunspaceLocation runspaceLocation,
RunspaceContext runspaceContext,
string connectionString)
{
this.Runspace = runspace;
this.SessionDetails = sessionDetails;
this.PowerShellVersion = powerShellVersion;
this.Location = runspaceLocation;
this.Context = runspaceContext;
this.ConnectionString = connectionString;
}
#endregion
#region Public Methods
internal void AddCapability<TCapability>(TCapability capability)
where TCapability : IRunspaceCapability
{
this.capabilities.Add(typeof(TCapability), capability);
}
internal TCapability GetCapability<TCapability>()
where TCapability : IRunspaceCapability
{
TCapability capability = default(TCapability);
this.TryGetCapability<TCapability>(out capability);
return capability;
}
internal bool TryGetCapability<TCapability>(out TCapability capability)
where TCapability : IRunspaceCapability
{
IRunspaceCapability capabilityAsInterface = default(TCapability);
if (this.capabilities.TryGetValue(typeof(TCapability), out capabilityAsInterface))
{
capability = (TCapability)capabilityAsInterface;
return true;
}
capability = default(TCapability);
return false;
}
internal bool HasCapability<TCapability>()
{
return this.capabilities.ContainsKey(typeof(TCapability));
}
/// <summary>
/// Creates and populates a new RunspaceDetails instance for the given runspace.
/// </summary>
/// <param name="runspace">
/// The runspace for which details will be gathered.
/// </param>
/// <param name="sessionDetails">
/// The SessionDetails for the runspace.
/// </param>
/// <param name="logger">An ILogger implementation used for writing log messages.</param>
/// <returns>A new RunspaceDetails instance.</returns>
internal static RunspaceDetails CreateFromRunspace(
Runspace runspace,
SessionDetails sessionDetails,
ILogger logger)
{
Validate.IsNotNull(nameof(runspace), runspace);
Validate.IsNotNull(nameof(sessionDetails), sessionDetails);
var runspaceLocation = RunspaceLocation.Local;
var runspaceContext = RunspaceContext.Original;
var versionDetails = PowerShellVersionDetails.GetVersionDetails(runspace, logger);
string connectionString = null;
if (runspace.ConnectionInfo != null)
{
// Use 'dynamic' to avoid missing NamedPipeRunspaceConnectionInfo
// on PS v3 and v4
try
{
dynamic connectionInfo = runspace.ConnectionInfo;
if (connectionInfo.ProcessId != null)
{
connectionString = connectionInfo.ProcessId.ToString();
runspaceContext = RunspaceContext.EnteredProcess;
}
}
catch (RuntimeBinderException)
{
// ProcessId property isn't on the object, move on.
}
// Grab the $host.name which will tell us if we're in a PSRP session or not
string hostName =
PowerShellContextService.ExecuteScriptAndGetItem<string>(
"$Host.Name",
runspace,
defaultValue: string.Empty,
useLocalScope: true);
// hostname is 'ServerRemoteHost' when the user enters a session.
// ex. Enter-PSSession
// Attaching to process currently needs to be marked as a local session
// so we skip this if block if the runspace is from Enter-PSHostProcess
if (hostName.Equals("ServerRemoteHost", StringComparison.Ordinal)
&& runspace.OriginalConnectionInfo?.GetType().ToString() != "System.Management.Automation.Runspaces.NamedPipeConnectionInfo")
{
runspaceLocation = RunspaceLocation.Remote;
connectionString =
runspace.ConnectionInfo.ComputerName +
(connectionString != null ? $"-{connectionString}" : string.Empty);
}
}
return
new RunspaceDetails(
runspace,
sessionDetails,
versionDetails,
runspaceLocation,
runspaceContext,
connectionString);
}
/// <summary>
/// Creates a clone of the given runspace through which another
/// runspace was attached. Sets the IsAttached property of the
/// resulting RunspaceDetails object to true.
/// </summary>
/// <param name="runspaceDetails">
/// The RunspaceDetails object which the new object based.
/// </param>
/// <param name="runspaceContext">
/// The RunspaceContext of the runspace.
/// </param>
/// <param name="sessionDetails">
/// The SessionDetails for the runspace.
/// </param>
/// <returns>
/// A new RunspaceDetails instance for the attached runspace.
/// </returns>
public static RunspaceDetails CreateFromContext(
RunspaceDetails runspaceDetails,
RunspaceContext runspaceContext,
SessionDetails sessionDetails)
{
return
new RunspaceDetails(
runspaceDetails.Runspace,
sessionDetails,
runspaceDetails.PowerShellVersion,
runspaceDetails.Location,
runspaceContext,
runspaceDetails.ConnectionString);
}
/// <summary>
/// Creates a new RunspaceDetails object from a remote
/// debugging session.
/// </summary>
/// <param name="runspaceDetails">
/// The RunspaceDetails object which the new object based.
/// </param>
/// <param name="runspaceLocation">
/// The RunspaceLocation of the runspace.
/// </param>
/// <param name="runspaceContext">
/// The RunspaceContext of the runspace.
/// </param>
/// <param name="sessionDetails">
/// The SessionDetails for the runspace.
/// </param>
/// <returns>
/// A new RunspaceDetails instance for the attached runspace.
/// </returns>
public static RunspaceDetails CreateFromDebugger(
RunspaceDetails runspaceDetails,
RunspaceLocation runspaceLocation,
RunspaceContext runspaceContext,
SessionDetails sessionDetails)
{
// TODO: Get the PowerShellVersion correctly!
return
new RunspaceDetails(
runspaceDetails.Runspace,
sessionDetails,
runspaceDetails.PowerShellVersion,
runspaceLocation,
runspaceContext,
runspaceDetails.ConnectionString);
}
#endregion
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions