Merged
Show file tree
Hide file tree
Changes from all commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Failed to load files.
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,7 +40,7 @@ public BreakpointService(
_debugStateService = debugStateService;
}

public async Task<List<Breakpoint>> GetBreakpointsAsync()
public async Task<IReadOnlyList<Breakpoint>> GetBreakpointsAsync()
{
if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
{
Expand All@@ -52,14 +52,12 @@ public async Task<List<Breakpoint>> GetBreakpointsAsync()

// Legacy behavior
PSCommand psCommand = new PSCommand().AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint");
IEnumerable<Breakpoint> breakpoints = await _executionService
return await _executionService
.ExecutePSCommandAsync<Breakpoint>(psCommand, CancellationToken.None)
.ConfigureAwait(false);

return breakpoints.ToList();
}

public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IEnumerable<BreakpointDetails> breakpoints)
public async Task<IReadOnlyList<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IReadOnlyList<BreakpointDetails> breakpoints)
{
if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
{
Expand DownExpand Up@@ -147,7 +145,7 @@ public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string esc
return configuredBreakpoints;
}

public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpointsAsync(IEnumerable<CommandBreakpointDetails> breakpoints)
public async Task<IReadOnlyList<CommandBreakpointDetails>> SetCommandBreakpointsAsync(IReadOnlyList<CommandBreakpointDetails> breakpoints)
{
if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
{
Expand DownExpand Up@@ -216,7 +214,7 @@ public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpointsAs
// If no PSCommand was created then there are no breakpoints to set.
if (psCommand is not null)
{
IEnumerable<Breakpoint> setBreakpoints = await _executionService
IReadOnlyList<Breakpoint> setBreakpoints = await _executionService
.ExecutePSCommandAsync<Breakpoint>(psCommand, CancellationToken.None)
.ConfigureAwait(false);
configuredBreakpoints.AddRange(setBreakpoints.Select(CommandBreakpointDetails.Create));
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -130,12 +130,12 @@ public DebugService(
/// <param name="breakpoints">BreakpointDetails for each breakpoint that will be set.</param>
/// <param name="clearExisting">If true, causes all existing breakpoints to be cleared before setting new ones.</param>
/// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns>
public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
public async Task<IReadOnlyList<BreakpointDetails>> SetLineBreakpointsAsync(
ScriptFile scriptFile,
BreakpointDetails[] breakpoints,
IReadOnlyList<BreakpointDetails> breakpoints,
bool clearExisting = true)
{
DscBreakpointCapability dscBreakpoints = await _debugContext.GetDscBreakpointCapabilityAsync(CancellationToken.None).ConfigureAwait(false);
DscBreakpointCapability dscBreakpoints = await _debugContext.GetDscBreakpointCapabilityAsync().ConfigureAwait(false);

string scriptPath = scriptFile.FilePath;

Expand DownExpand Up@@ -168,7 +168,7 @@ public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
await _breakpointService.RemoveAllBreakpointsAsync(scriptFile.FilePath).ConfigureAwait(false);
}

return (await _breakpointService.SetBreakpointsAsync(escapedScriptPath, breakpoints).ConfigureAwait(false)).ToArray();
return await _breakpointService.SetBreakpointsAsync(escapedScriptPath, breakpoints).ConfigureAwait(false);
}

return await dscBreakpoints
Expand All@@ -182,25 +182,20 @@ public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
/// <param name="breakpoints">CommandBreakpointDetails for each command breakpoint that will be set.</param>
/// <param name="clearExisting">If true, causes all existing function breakpoints to be cleared before setting new ones.</param>
/// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns>
public async Task<CommandBreakpointDetails[]> SetCommandBreakpointsAsync(
CommandBreakpointDetails[] breakpoints,
public async Task<IReadOnlyList<CommandBreakpointDetails>> SetCommandBreakpointsAsync(
IReadOnlyList<CommandBreakpointDetails> breakpoints,
bool clearExisting = true)
{
CommandBreakpointDetails[] resultBreakpointDetails = null;

if (clearExisting)
{
// Flatten dictionary values into one list and remove them all.
IEnumerable<Breakpoint> existingBreakpoints = await _breakpointService.GetBreakpointsAsync().ConfigureAwait(false);
IReadOnlyList<Breakpoint> existingBreakpoints = await _breakpointService.GetBreakpointsAsync().ConfigureAwait(false);
await _breakpointService.RemoveBreakpointsAsync(existingBreakpoints.OfType<CommandBreakpoint>()).ConfigureAwait(false);
}

if (breakpoints.Length > 0)
{
resultBreakpointDetails = (await _breakpointService.SetCommandBreakpointsAsync(breakpoints).ConfigureAwait(false)).ToArray();
}

return resultBreakpointDetails ?? Array.Empty<CommandBreakpointDetails>();
return breakpoints.Count > 0
? await _breakpointService.SetCommandBreakpointsAsync(breakpoints).ConfigureAwait(false)
: Array.Empty<CommandBreakpointDetails>();
}

/// <summary>
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
Expand DownExpand Up@@ -51,7 +52,7 @@ public async Task<SetBreakpointsResponse> Handle(SetBreakpointsArguments request
if (!_workspaceService.TryGetFile(request.Source.Path, out ScriptFile scriptFile))
{
string message = _debugStateService.NoDebug ? string.Empty : "Source file could not be accessed, breakpoint not set.";
System.Collections.Generic.IEnumerable<Breakpoint> srcBreakpoints = request.Breakpoints
IEnumerable<Breakpoint> srcBreakpoints = request.Breakpoints
.Select(srcBkpt => LspDebugUtils.CreateBreakpoint(
srcBkpt, request.Source.Path, message, verified: _debugStateService.NoDebug));

Expand All@@ -70,7 +71,7 @@ public async Task<SetBreakpointsResponse> Handle(SetBreakpointsArguments request

string message = _debugStateService.NoDebug ? string.Empty : "Source is not a PowerShell script, breakpoint not set.";

System.Collections.Generic.IEnumerable<Breakpoint> srcBreakpoints = request.Breakpoints
IEnumerable<Breakpoint> srcBreakpoints = request.Breakpoints
.Select(srcBkpt => LspDebugUtils.CreateBreakpoint(
srcBkpt, request.Source.Path, message, verified: _debugStateService.NoDebug));

Expand All@@ -82,18 +83,17 @@ public async Task<SetBreakpointsResponse> Handle(SetBreakpointsArguments request
}

// At this point, the source file has been verified as a PowerShell script.
BreakpointDetails[] breakpointDetails = request.Breakpoints
IReadOnlyList<BreakpointDetails> breakpointDetails = request.Breakpoints
.Select((srcBreakpoint) => BreakpointDetails.Create(
scriptFile.FilePath,
srcBreakpoint.Line,
srcBreakpoint.Column,
srcBreakpoint.Condition,
srcBreakpoint.HitCondition,
srcBreakpoint.LogMessage))
.ToArray();
srcBreakpoint.LogMessage)).ToList();

// If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints.
BreakpointDetails[] updatedBreakpointDetails = breakpointDetails;
IReadOnlyList<BreakpointDetails> updatedBreakpointDetails = breakpointDetails;
if (!_debugStateService.NoDebug)
{
await _debugStateService.WaitForSetBreakpointHandleAsync().ConfigureAwait(false);
Expand DownExpand Up@@ -125,23 +125,20 @@ await _debugService.SetLineBreakpointsAsync(

public async Task<SetFunctionBreakpointsResponse> Handle(SetFunctionBreakpointsArguments request, CancellationToken cancellationToken)
{
CommandBreakpointDetails[] breakpointDetails = request.Breakpoints
IReadOnlyList<CommandBreakpointDetails> breakpointDetails = request.Breakpoints
.Select((funcBreakpoint) => CommandBreakpointDetails.Create(
funcBreakpoint.Name,
funcBreakpoint.Condition))
.ToArray();
funcBreakpoint.Condition)).ToList();

// If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints.
CommandBreakpointDetails[] updatedBreakpointDetails = breakpointDetails;
IReadOnlyList<CommandBreakpointDetails> updatedBreakpointDetails = breakpointDetails;
if (!_debugStateService.NoDebug)
{
await _debugStateService.WaitForSetBreakpointHandleAsync().ConfigureAwait(false);

try
{
updatedBreakpointDetails =
await _debugService.SetCommandBreakpointsAsync(
breakpointDetails).ConfigureAwait(false);
updatedBreakpointDetails = await _debugService.SetCommandBreakpointsAsync(breakpointDetails).ConfigureAwait(false);
}
catch (Exception e)
{
Expand All@@ -156,9 +153,7 @@ await _debugService.SetCommandBreakpointsAsync(

return new SetFunctionBreakpointsResponse
{
Breakpoints = updatedBreakpointDetails
.Select(LspDebugUtils.CreateBreakpoint)
.ToArray()
Breakpoints = updatedBreakpointDetails.Select(LspDebugUtils.CreateBreakpoint).ToList()
};
}

Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,38 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices.Logging;
using Microsoft.PowerShell.EditorServices.Services.DebugAdapter;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Threading;
using SMA = System.Management.Automation;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices.Services.DebugAdapter;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace;

namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging
{
internal class DscBreakpointCapability
{
private static bool? isDscInstalled;
private string[] dscResourceRootPaths = Array.Empty<string>();
private readonly Dictionary<string, int[]> breakpointsPerFile = new();

private readonly Dictionary<string, int[]> breakpointsPerFile =
new();

public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
public async Task<IReadOnlyList<BreakpointDetails>> SetLineBreakpointsAsync(
IInternalPowerShellExecutionService executionService,
string scriptPath,
BreakpointDetails[] breakpoints)
IReadOnlyList<BreakpointDetails> breakpoints)
{
List<BreakpointDetails> resultBreakpointDetails =
new();

// We always get the latest array of breakpoint line numbers
// so store that for future use
if (breakpoints.Length > 0)
int[] lineNumbers = breakpoints.Select(b => b.LineNumber).ToArray();
if (lineNumbers.Length > 0)
{
// Set the breakpoints for this scriptPath
breakpointsPerFile[scriptPath] =
breakpoints.Select(b => b.LineNumber).ToArray();
breakpointsPerFile[scriptPath] = lineNumbers;
}
else
{
Expand DownExpand Up@@ -72,7 +65,7 @@ await executionService.ExecutePSCommandAsync(
breakpoint.Verified = true;
}

return breakpoints.ToArray();
return breakpoints;
}

public bool IsDscResourcePath(string scriptPath)
Expand All@@ -84,88 +77,57 @@ public bool IsDscResourcePath(string scriptPath)
StringComparison.CurrentCultureIgnoreCase));
}

public static Task<DscBreakpointCapability> GetDscCapabilityAsync(
public static async Task<DscBreakpointCapability> GetDscCapabilityAsync(
ILogger logger,
IRunspaceInfo currentRunspace,
PsesInternalHost psesHost,
CancellationToken cancellationToken)
PsesInternalHost psesHost)
{
// DSC support is enabled only for Windows PowerShell.
if ((currentRunspace.PowerShellVersionDetails.Version.Major >= 6) &&
(currentRunspace.RunspaceOrigin != RunspaceOrigin.DebuggedRunspace))
{
return Task.FromResult<DscBreakpointCapability>(null);
return null;
}

DscBreakpointCapability getDscBreakpointCapabilityFunc(SMA.PowerShell pwsh, CancellationToken _)
if (!isDscInstalled.HasValue)
{
PSInvocationSettings invocationSettings = new()
{
AddToHistory = false,
ErrorActionPreference = ActionPreference.Stop
};

PSModuleInfo dscModule = null;
try
{
dscModule = pwsh.AddCommand("Import-Module")
.AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1")
.AddParameter("PassThru")
.InvokeAndClear<PSModuleInfo>(invocationSettings)
.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
DscBreakpointCapability capability = new();

pwsh.AddCommand("Microsoft.PowerShell.Utility\\Write-Host")
.AddArgument("Gathering DSC resource paths, this may take a while...")
.InvokeAndClear(invocationSettings);

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>(invocationSettings);
}
catch (CmdletInvocationException e)
{
logger.LogException("Get-DscResource failed!", e);
}

if (resourcePaths == null)
{
logger.LogTrace("No DSC resources found.");
return null;
}
PSCommand psCommand = new PSCommand()
.AddCommand("Import-Module")
.AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1")
.AddParameter("PassThru");

IReadOnlyList<PSModuleInfo> dscModule =
await psesHost.ExecutePSCommandAsync<PSModuleInfo>(
psCommand,
CancellationToken.None,
new PowerShellExecutionOptions { ThrowOnError = false }).ConfigureAwait(false);

isDscInstalled = dscModule.Count > 0;
logger.LogTrace("Side-by-side DSC module found: " + isDscInstalled.Value);
}

capability.dscResourceRootPaths = resourcePaths.ToArray();
if (isDscInstalled.Value)
{
PSCommand psCommand = new PSCommand()
.AddCommand("Get-DscResource")
.AddCommand("Select-Object")
.AddParameter("ExpandProperty", "ParentPath");

IReadOnlyList<string> resourcePaths =
await psesHost.ExecutePSCommandAsync<string>(
psCommand,
CancellationToken.None,
new PowerShellExecutionOptions { ThrowOnError = false }
).ConfigureAwait(false);

logger.LogTrace($"DSC resources found: {resourcePaths.Count}");

return capability;
return new DscBreakpointCapability
{
dscResourceRootPaths = resourcePaths.ToArray()
};
}

return psesHost.ExecuteDelegateAsync(
nameof(getDscBreakpointCapabilityFunc),
executionOptions: null,
getDscBreakpointCapabilityFunc,
cancellationToken);
return null;
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@

using System;
using System.Management.Automation;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging
Expand DownExpand Up@@ -34,6 +33,6 @@ internal interface IPowerShellDebugContext

void Abort();

Task<DscBreakpointCapability> GetDscBreakpointCapabilityAsync(CancellationToken cancellationToken);
Task<DscBreakpointCapability> GetDscBreakpointCapabilityAsync();
}
}
Loading