Skip to content

[Revamp pipeline thread handling] Support debug runspace #1447

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
SydneyhSmith opened this issue Mar 29, 2021 · 1 comment
Closed

[Revamp pipeline thread handling] Support debug runspace #1447

SydneyhSmith opened this issue Mar 29, 2021 · 1 comment

Comments

@SydneyhSmith
Copy link
Collaborator

SydneyhSmith commented Mar 29, 2021

// 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
}

@ghost ghost added the Needs: Triage Maintainer attention needed! label Mar 29, 2021
@SydneyhSmith SydneyhSmith added Area-Debugging Area-Engine Issue-Bug A bug to squash. and removed Needs: Triage Maintainer attention needed! labels Mar 29, 2021
@rjmholt
Copy link
Contributor

rjmholt commented Oct 11, 2021

To solve in #1459

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants