Skip to content

[Revamp pipeline thread handling] DSC breakpoint capability support #1437

Closed
@SydneyhSmith

Description

@SydneyhSmith

Today the extension has support for DSC breakpoints:

internal class DscBreakpointCapability : IRunspaceCapability
{
private string[] dscResourceRootPaths = Array.Empty<string>();
private Dictionary<string, int[]> breakpointsPerFile =
new Dictionary<string, int[]>();
public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
PowerShellContextService powerShellContext,
string scriptPath,
BreakpointDetails[] breakpoints)
{
List<BreakpointDetails> resultBreakpointDetails =
new List<BreakpointDetails>();
// We always get the latest array of breakpoint line numbers
// so store that for future use
if (breakpoints.Length > 0)
{
// Set the breakpoints for this scriptPath
this.breakpointsPerFile[scriptPath] =
breakpoints.Select(b => b.LineNumber).ToArray();
}
else
{
// No more breakpoints for this scriptPath, remove it
this.breakpointsPerFile.Remove(scriptPath);
}
string hashtableString =
string.Join(
", ",
this.breakpointsPerFile
.Select(file => $"@{{Path=\"{file.Key}\";Line=@({string.Join(",", file.Value)})}}"));
// Run Enable-DscDebug as a script because running it as a PSCommand
// causes an error which states that the Breakpoint parameter has not
// been passed.
await powerShellContext.ExecuteScriptStringAsync(
hashtableString.Length > 0
? $"Enable-DscDebug -Breakpoint {hashtableString}"
: "Disable-DscDebug",
false,
false).ConfigureAwait(false);
// Verify all the breakpoints and return them
foreach (var breakpoint in breakpoints)
{
breakpoint.Verified = true;
}
return breakpoints.ToArray();
}
public bool IsDscResourcePath(string scriptPath)
{
return dscResourceRootPaths.Any(
dscResourceRootPath =>
scriptPath.StartsWith(
dscResourceRootPath,
StringComparison.CurrentCultureIgnoreCase));
}
public static DscBreakpointCapability CheckForCapability(
RunspaceDetails runspaceDetails,
PowerShellContextService powerShellContext,
ILogger logger)
{
DscBreakpointCapability capability = null;
// 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.");
}
}
}
return capability;
}
}

The new pipeline thread consumer's debugger needs to also support these.

The capability has been migrated here:

internal class DscBreakpointCapability
{
private string[] dscResourceRootPaths = Array.Empty<string>();
private Dictionary<string, int[]> breakpointsPerFile =
new Dictionary<string, int[]>();
public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
PowerShellExecutionService executionService,
string scriptPath,
BreakpointDetails[] breakpoints)
{
List<BreakpointDetails> resultBreakpointDetails =
new List<BreakpointDetails>();
// We always get the latest array of breakpoint line numbers
// so store that for future use
if (breakpoints.Length > 0)
{
// Set the breakpoints for this scriptPath
this.breakpointsPerFile[scriptPath] =
breakpoints.Select(b => b.LineNumber).ToArray();
}
else
{
// No more breakpoints for this scriptPath, remove it
this.breakpointsPerFile.Remove(scriptPath);
}
string hashtableString =
string.Join(
", ",
this.breakpointsPerFile
.Select(file => $"@{{Path=\"{file.Key}\";Line=@({string.Join(",", file.Value)})}}"));
// Run Enable-DscDebug as a script because running it as a PSCommand
// causes an error which states that the Breakpoint parameter has not
// been passed.
var dscCommand = new PSCommand().AddScript(
hashtableString.Length > 0
? $"Enable-DscDebug -Breakpoint {hashtableString}"
: "Disable-DscDebug");
await executionService.ExecutePSCommandAsync(
dscCommand,
new PowerShellExecutionOptions(),
CancellationToken.None);
// Verify all the breakpoints and return them
foreach (var breakpoint in breakpoints)
{
breakpoint.Verified = true;
}
return breakpoints.ToArray();
}
public bool IsDscResourcePath(string scriptPath)
{
return dscResourceRootPaths.Any(
dscResourceRootPath =>
scriptPath.StartsWith(
dscResourceRootPath,
StringComparison.CurrentCultureIgnoreCase));
}
public static async Task<DscBreakpointCapability> GetDscCapabilityAsync(
ILogger logger,
IRunspaceInfo currentRunspace,
PowerShellExecutionService executionService,
CancellationToken cancellationToken)
{
// DSC support is enabled only for Windows PowerShell.
if ((currentRunspace.PowerShellVersionDetails.Version.Major >= 6) &&
(currentRunspace.RunspaceOrigin != RunspaceOrigin.DebuggedRunspace))
{
return null;
}
Func<SMA.PowerShell, CancellationToken, DscBreakpointCapability> getDscBreakpointCapabilityFunc = (pwsh, cancellationToken) =>
{
PSModuleInfo dscModule = null;
try
{
dscModule = pwsh.AddCommand("Import-Module")
.AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1")
.AddParameter("PassThru")
.AddParameter("ErrorAction", "Ignore")
.InvokeAndClear<PSModuleInfo>()
.FirstOrDefault();
}
catch (RuntimeException e)
{
logger.LogException("Could not load the DSC module!", e);
}
if (dscModule == null)
{
logger.LogTrace($"Side-by-side DSC module was not found.");
return null;
}
logger.LogTrace("Side-by-side DSC module found, gathering DSC resource paths...");
// The module was loaded, add the breakpoint capability
var capability = new DscBreakpointCapability();
pwsh.AddCommand("Microsoft.PowerShell.Utility\\Write-Host")
.AddArgument("Gathering DSC resource paths, this may take a while...")
.InvokeAndClear();
Collection<string> resourcePaths = null;
try
{
// Get the list of DSC resource paths
resourcePaths = pwsh.AddCommand("Get-DscResource")
.AddCommand("Select-Object")
.AddParameter("ExpandProperty", "ParentPath")
.InvokeAndClear<string>();
}
catch (CmdletInvocationException e)
{
logger.LogException("Get-DscResource failed!", e);
}
if (resourcePaths == null)
{
logger.LogTrace($"No DSC resources found.");
return null;
}
capability.dscResourceRootPaths = resourcePaths.ToArray();
logger.LogTrace($"DSC resources found: {resourcePaths.Count}");
return capability;
};
return await executionService.ExecuteDelegateAsync<DscBreakpointCapability>(
getDscBreakpointCapabilityFunc,
nameof(getDscBreakpointCapabilityFunc),
cancellationToken);
}
}

But we need an equivalent of this still:

private void ConfigureRunspaceCapabilities(RunspaceDetails runspaceDetails)
{
DscBreakpointCapability.CheckForCapability(this.CurrentRunspace, this, this.logger);
}

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