Skip to content

Fix repeated failure to load DSC module #2020

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

Merged
merged 4 commits into from
May 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff 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 Down Expand 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 Down Expand 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
25 changes: 10 additions & 15 deletions src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs
Original file line number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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 Down Expand Up @@ -34,6 +33,6 @@ internal interface IPowerShellDebugContext

void Abort();

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