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 3 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 @@ -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 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
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 number Diff line number Diff 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 Down Expand 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 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();
}
}
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;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context;
Expand Down Expand 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using System.Threading;
using System;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;

Expand Down Expand Up @@ -86,14 +85,12 @@ public RunspaceInfo(

public async Task<DscBreakpointCapability> GetDscBreakpointCapabilityAsync(
ILogger logger,
PsesInternalHost psesHost,
CancellationToken cancellationToken)
PsesInternalHost psesHost)
{
return _dscBreakpointCapability ??= await DscBreakpointCapability.GetDscCapabilityAsync(
logger,
this,
psesHost,
cancellationToken)
psesHost)
.ConfigureAwait(false);
}
}
Expand Down
20 changes: 7 additions & 13 deletions src/PowerShellEditorServices/Services/Template/TemplateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,12 @@ public async Task<bool> ImportPlasterIfInstalledAsync()

_logger.LogTrace("Checking if Plaster is installed...");

PSObject moduleObject = (await _executionService.ExecutePSCommandAsync<PSObject>(psCommand, CancellationToken.None).ConfigureAwait(false))[0];
IReadOnlyList<PSObject> moduleObject =
await _executionService.ExecutePSCommandAsync<PSObject>(
psCommand, CancellationToken.None).ConfigureAwait(false);

isPlasterInstalled = moduleObject != null;
string installedQualifier =
isPlasterInstalled.Value
? string.Empty : "not ";

_logger.LogTrace($"Plaster is {installedQualifier}installed!");
isPlasterInstalled = moduleObject.Count > 0;
_logger.LogTrace("Plaster installed: " + isPlasterInstalled.Value);

// Attempt to load plaster
if (isPlasterInstalled.Value && !isPlasterLoaded)
Expand All @@ -89,17 +87,13 @@ public async Task<bool> ImportPlasterIfInstalledAsync()
psCommand = new PSCommand();
psCommand
.AddCommand("Import-Module")
.AddParameter("ModuleInfo", (PSModuleInfo)moduleObject.ImmediateBaseObject)
.AddParameter("ModuleInfo", (PSModuleInfo)moduleObject[0].ImmediateBaseObject)
.AddParameter("PassThru");

IReadOnlyList<PSModuleInfo> importResult = await _executionService.ExecutePSCommandAsync<PSModuleInfo>(psCommand, CancellationToken.None).ConfigureAwait(false);

isPlasterLoaded = importResult.Count > 0;
string loadedQualifier =
isPlasterInstalled.Value
? "was" : "could not be";

_logger.LogTrace($"Plaster {loadedQualifier} loaded successfully!");
_logger.LogTrace("Plaster loaded: " + isPlasterLoaded);
}
}

Expand Down
Loading