Merged
Show file tree
Hide file tree
Changes from 3 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@@ -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<IEnumerable<BreakpointDetails>> SetLineBreakpointsAsync(
ScriptFile scriptFile,
BreakpointDetails[] breakpoints,
IEnumerable<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 All@@ -146,15 +146,15 @@ public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
if (!_remoteFileManager.IsUnderRemoteTempPath(scriptPath))
{
_logger.LogTrace($"Could not set breakpoints for local path '{scriptPath}' in a remote session.");
return Array.Empty<BreakpointDetails>();
return Enumerable.Empty<BreakpointDetails>();
}

scriptPath = _remoteFileManager.GetMappedPath(scriptPath, _psesHost.CurrentRunspace);
}
else if (temporaryScriptListingPath?.Equals(scriptPath, StringComparison.CurrentCultureIgnoreCase) == true)
{
_logger.LogTrace($"Could not set breakpoint on temporary script listing path '{scriptPath}'.");
return Array.Empty<BreakpointDetails>();
return Enumerable.Empty<BreakpointDetails>();
}

// Fix for issue #123 - file paths that contain wildcard chars [ and ] need to
Expand All@@ -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 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
IEnumerable<BreakpointDetails> breakpointDetails = request.Breakpoints
.Select((srcBreakpoint) => BreakpointDetails.Create(
scriptFile.FilePath,
srcBreakpoint.Line,
srcBreakpoint.Column,
srcBreakpoint.Condition,
srcBreakpoint.HitCondition,
srcBreakpoint.LogMessage))
.ToArray();
srcBreakpoint.LogMessage));

// If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints.
BreakpointDetails[] updatedBreakpointDetails = breakpointDetails;
IEnumerable<BreakpointDetails> updatedBreakpointDetails = breakpointDetails;
if (!_debugStateService.NoDebug)
{
await _debugStateService.WaitForSetBreakpointHandleAsync().ConfigureAwait(false);
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,37 @@
// 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<IEnumerable<BreakpointDetails>> SetLineBreakpointsAsync(
IInternalPowerShellExecutionService executionService,
string scriptPath,
BreakpointDetails[] breakpoints)
IEnumerable<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)
if (breakpoints.Any())
{
// Set the breakpoints for this scriptPath
breakpointsPerFile[scriptPath] =
breakpoints.Select(b => b.LineNumber).ToArray();
breakpointsPerFile[scriptPath] = breakpoints.Select(b => b.LineNumber).ToArray();
}
else
{
Expand DownExpand Up@@ -72,7 +64,7 @@ await executionService.ExecutePSCommandAsync(
breakpoint.Verified = true;
}

return breakpoints.ToArray();
return breakpoints;
}

public bool IsDscResourcePath(string scriptPath)
Expand All@@ -84,88 +76,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();
}
}
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;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context;
Expand DownExpand Up@@ -81,10 +80,10 @@ public PowerShellDebugContext(
public event Action<object, DebuggerResumingEventArgs> DebuggerResuming;
public event Action<object, BreakpointUpdatedEventArgs> BreakpointUpdated;

public Task<DscBreakpointCapability> GetDscBreakpointCapabilityAsync(CancellationToken cancellationToken)
public Task<DscBreakpointCapability> GetDscBreakpointCapabilityAsync()
{
_psesHost.Runspace.ThrowCancelledIfUnusable();
return _psesHost.CurrentRunspace.GetDscBreakpointCapabilityAsync(_logger, _psesHost, cancellationToken);
return _psesHost.CurrentRunspace.GetDscBreakpointCapabilityAsync(_logger, _psesHost);
}

// This is required by the PowerShell API so that remote debugging works. Without it, a
Expand Down
Loading