Skip to content

Commit 3401a2d

Browse files
Merge pull request #2020 from PowerShell/andschwa/write-output
Fix repeated failure to load DSC module
2 parents c6c902d + aa73a21 commit 3401a2d

File tree

10 files changed

+98
-160
lines changed

10 files changed

+98
-160
lines changed

src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs

+5-7
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public BreakpointService(
4040
_debugStateService = debugStateService;
4141
}
4242

43-
public async Task<List<Breakpoint>> GetBreakpointsAsync()
43+
public async Task<IReadOnlyList<Breakpoint>> GetBreakpointsAsync()
4444
{
4545
if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
4646
{
@@ -52,14 +52,12 @@ public async Task<List<Breakpoint>> GetBreakpointsAsync()
5252

5353
// Legacy behavior
5454
PSCommand psCommand = new PSCommand().AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint");
55-
IEnumerable<Breakpoint> breakpoints = await _executionService
55+
return await _executionService
5656
.ExecutePSCommandAsync<Breakpoint>(psCommand, CancellationToken.None)
5757
.ConfigureAwait(false);
58-
59-
return breakpoints.ToList();
6058
}
6159

62-
public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IEnumerable<BreakpointDetails> breakpoints)
60+
public async Task<IReadOnlyList<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IReadOnlyList<BreakpointDetails> breakpoints)
6361
{
6462
if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
6563
{
@@ -147,7 +145,7 @@ public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string esc
147145
return configuredBreakpoints;
148146
}
149147

150-
public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpointsAsync(IEnumerable<CommandBreakpointDetails> breakpoints)
148+
public async Task<IReadOnlyList<CommandBreakpointDetails>> SetCommandBreakpointsAsync(IReadOnlyList<CommandBreakpointDetails> breakpoints)
151149
{
152150
if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
153151
{
@@ -216,7 +214,7 @@ public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpointsAs
216214
// If no PSCommand was created then there are no breakpoints to set.
217215
if (psCommand is not null)
218216
{
219-
IEnumerable<Breakpoint> setBreakpoints = await _executionService
217+
IReadOnlyList<Breakpoint> setBreakpoints = await _executionService
220218
.ExecutePSCommandAsync<Breakpoint>(psCommand, CancellationToken.None)
221219
.ConfigureAwait(false);
222220
configuredBreakpoints.AddRange(setBreakpoints.Select(CommandBreakpointDetails.Create));

src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs

+10-15
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,12 @@ public DebugService(
130130
/// <param name="breakpoints">BreakpointDetails for each breakpoint that will be set.</param>
131131
/// <param name="clearExisting">If true, causes all existing breakpoints to be cleared before setting new ones.</param>
132132
/// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns>
133-
public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
133+
public async Task<IReadOnlyList<BreakpointDetails>> SetLineBreakpointsAsync(
134134
ScriptFile scriptFile,
135-
BreakpointDetails[] breakpoints,
135+
IReadOnlyList<BreakpointDetails> breakpoints,
136136
bool clearExisting = true)
137137
{
138-
DscBreakpointCapability dscBreakpoints = await _debugContext.GetDscBreakpointCapabilityAsync(CancellationToken.None).ConfigureAwait(false);
138+
DscBreakpointCapability dscBreakpoints = await _debugContext.GetDscBreakpointCapabilityAsync().ConfigureAwait(false);
139139

140140
string scriptPath = scriptFile.FilePath;
141141

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

171-
return (await _breakpointService.SetBreakpointsAsync(escapedScriptPath, breakpoints).ConfigureAwait(false)).ToArray();
171+
return await _breakpointService.SetBreakpointsAsync(escapedScriptPath, breakpoints).ConfigureAwait(false);
172172
}
173173

174174
return await dscBreakpoints
@@ -182,25 +182,20 @@ public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
182182
/// <param name="breakpoints">CommandBreakpointDetails for each command breakpoint that will be set.</param>
183183
/// <param name="clearExisting">If true, causes all existing function breakpoints to be cleared before setting new ones.</param>
184184
/// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns>
185-
public async Task<CommandBreakpointDetails[]> SetCommandBreakpointsAsync(
186-
CommandBreakpointDetails[] breakpoints,
185+
public async Task<IReadOnlyList<CommandBreakpointDetails>> SetCommandBreakpointsAsync(
186+
IReadOnlyList<CommandBreakpointDetails> breakpoints,
187187
bool clearExisting = true)
188188
{
189-
CommandBreakpointDetails[] resultBreakpointDetails = null;
190-
191189
if (clearExisting)
192190
{
193191
// Flatten dictionary values into one list and remove them all.
194-
IEnumerable<Breakpoint> existingBreakpoints = await _breakpointService.GetBreakpointsAsync().ConfigureAwait(false);
192+
IReadOnlyList<Breakpoint> existingBreakpoints = await _breakpointService.GetBreakpointsAsync().ConfigureAwait(false);
195193
await _breakpointService.RemoveBreakpointsAsync(existingBreakpoints.OfType<CommandBreakpoint>()).ConfigureAwait(false);
196194
}
197195

198-
if (breakpoints.Length > 0)
199-
{
200-
resultBreakpointDetails = (await _breakpointService.SetCommandBreakpointsAsync(breakpoints).ConfigureAwait(false)).ToArray();
201-
}
202-
203-
return resultBreakpointDetails ?? Array.Empty<CommandBreakpointDetails>();
196+
return breakpoints.Count > 0
197+
? await _breakpointService.SetCommandBreakpointsAsync(breakpoints).ConfigureAwait(false)
198+
: Array.Empty<CommandBreakpointDetails>();
204199
}
205200

206201
/// <summary>

src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs

+11-16
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.IO;
67
using System.Linq;
78
using System.Threading;
@@ -51,7 +52,7 @@ public async Task<SetBreakpointsResponse> Handle(SetBreakpointsArguments request
5152
if (!_workspaceService.TryGetFile(request.Source.Path, out ScriptFile scriptFile))
5253
{
5354
string message = _debugStateService.NoDebug ? string.Empty : "Source file could not be accessed, breakpoint not set.";
54-
System.Collections.Generic.IEnumerable<Breakpoint> srcBreakpoints = request.Breakpoints
55+
IEnumerable<Breakpoint> srcBreakpoints = request.Breakpoints
5556
.Select(srcBkpt => LspDebugUtils.CreateBreakpoint(
5657
srcBkpt, request.Source.Path, message, verified: _debugStateService.NoDebug));
5758

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

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

73-
System.Collections.Generic.IEnumerable<Breakpoint> srcBreakpoints = request.Breakpoints
74+
IEnumerable<Breakpoint> srcBreakpoints = request.Breakpoints
7475
.Select(srcBkpt => LspDebugUtils.CreateBreakpoint(
7576
srcBkpt, request.Source.Path, message, verified: _debugStateService.NoDebug));
7677

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

8485
// At this point, the source file has been verified as a PowerShell script.
85-
BreakpointDetails[] breakpointDetails = request.Breakpoints
86+
IReadOnlyList<BreakpointDetails> breakpointDetails = request.Breakpoints
8687
.Select((srcBreakpoint) => BreakpointDetails.Create(
8788
scriptFile.FilePath,
8889
srcBreakpoint.Line,
8990
srcBreakpoint.Column,
9091
srcBreakpoint.Condition,
9192
srcBreakpoint.HitCondition,
92-
srcBreakpoint.LogMessage))
93-
.ToArray();
93+
srcBreakpoint.LogMessage)).ToList();
9494

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

126126
public async Task<SetFunctionBreakpointsResponse> Handle(SetFunctionBreakpointsArguments request, CancellationToken cancellationToken)
127127
{
128-
CommandBreakpointDetails[] breakpointDetails = request.Breakpoints
128+
IReadOnlyList<CommandBreakpointDetails> breakpointDetails = request.Breakpoints
129129
.Select((funcBreakpoint) => CommandBreakpointDetails.Create(
130130
funcBreakpoint.Name,
131-
funcBreakpoint.Condition))
132-
.ToArray();
131+
funcBreakpoint.Condition)).ToList();
133132

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

140139
try
141140
{
142-
updatedBreakpointDetails =
143-
await _debugService.SetCommandBreakpointsAsync(
144-
breakpointDetails).ConfigureAwait(false);
141+
updatedBreakpointDetails = await _debugService.SetCommandBreakpointsAsync(breakpointDetails).ConfigureAwait(false);
145142
}
146143
catch (Exception e)
147144
{
@@ -156,9 +153,7 @@ await _debugService.SetCommandBreakpointsAsync(
156153

157154
return new SetFunctionBreakpointsResponse
158155
{
159-
Breakpoints = updatedBreakpointDetails
160-
.Select(LspDebugUtils.CreateBreakpoint)
161-
.ToArray()
156+
Breakpoints = updatedBreakpointDetails.Select(LspDebugUtils.CreateBreakpoint).ToList()
162157
};
163158
}
164159

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

4-
using System.Linq;
5-
using System.Threading.Tasks;
6-
using Microsoft.Extensions.Logging;
7-
using Microsoft.PowerShell.EditorServices.Logging;
8-
using Microsoft.PowerShell.EditorServices.Services.DebugAdapter;
94
using System;
105
using System.Collections.Generic;
11-
using System.Collections.ObjectModel;
6+
using System.Linq;
127
using System.Management.Automation;
138
using System.Threading;
14-
using SMA = System.Management.Automation;
15-
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility;
16-
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace;
9+
using System.Threading.Tasks;
10+
using Microsoft.Extensions.Logging;
11+
using Microsoft.PowerShell.EditorServices.Services.DebugAdapter;
12+
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution;
1713
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
14+
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace;
1815

1916
namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging
2017
{
2118
internal class DscBreakpointCapability
2219
{
20+
private static bool? isDscInstalled;
2321
private string[] dscResourceRootPaths = Array.Empty<string>();
22+
private readonly Dictionary<string, int[]> breakpointsPerFile = new();
2423

25-
private readonly Dictionary<string, int[]> breakpointsPerFile =
26-
new();
27-
28-
public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
24+
public async Task<IReadOnlyList<BreakpointDetails>> SetLineBreakpointsAsync(
2925
IInternalPowerShellExecutionService executionService,
3026
string scriptPath,
31-
BreakpointDetails[] breakpoints)
27+
IReadOnlyList<BreakpointDetails> breakpoints)
3228
{
33-
List<BreakpointDetails> resultBreakpointDetails =
34-
new();
35-
3629
// We always get the latest array of breakpoint line numbers
3730
// so store that for future use
38-
if (breakpoints.Length > 0)
31+
int[] lineNumbers = breakpoints.Select(b => b.LineNumber).ToArray();
32+
if (lineNumbers.Length > 0)
3933
{
4034
// Set the breakpoints for this scriptPath
41-
breakpointsPerFile[scriptPath] =
42-
breakpoints.Select(b => b.LineNumber).ToArray();
35+
breakpointsPerFile[scriptPath] = lineNumbers;
4336
}
4437
else
4538
{
@@ -72,7 +65,7 @@ await executionService.ExecutePSCommandAsync(
7265
breakpoint.Verified = true;
7366
}
7467

75-
return breakpoints.ToArray();
68+
return breakpoints;
7669
}
7770

7871
public bool IsDscResourcePath(string scriptPath)
@@ -84,88 +77,57 @@ public bool IsDscResourcePath(string scriptPath)
8477
StringComparison.CurrentCultureIgnoreCase));
8578
}
8679

87-
public static Task<DscBreakpointCapability> GetDscCapabilityAsync(
80+
public static async Task<DscBreakpointCapability> GetDscCapabilityAsync(
8881
ILogger logger,
8982
IRunspaceInfo currentRunspace,
90-
PsesInternalHost psesHost,
91-
CancellationToken cancellationToken)
83+
PsesInternalHost psesHost)
9284
{
9385
// DSC support is enabled only for Windows PowerShell.
9486
if ((currentRunspace.PowerShellVersionDetails.Version.Major >= 6) &&
9587
(currentRunspace.RunspaceOrigin != RunspaceOrigin.DebuggedRunspace))
9688
{
97-
return Task.FromResult<DscBreakpointCapability>(null);
89+
return null;
9890
}
9991

100-
DscBreakpointCapability getDscBreakpointCapabilityFunc(SMA.PowerShell pwsh, CancellationToken _)
92+
if (!isDscInstalled.HasValue)
10193
{
102-
PSInvocationSettings invocationSettings = new()
103-
{
104-
AddToHistory = false,
105-
ErrorActionPreference = ActionPreference.Stop
106-
};
107-
108-
PSModuleInfo dscModule = null;
109-
try
110-
{
111-
dscModule = pwsh.AddCommand("Import-Module")
112-
.AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1")
113-
.AddParameter("PassThru")
114-
.InvokeAndClear<PSModuleInfo>(invocationSettings)
115-
.FirstOrDefault();
116-
}
117-
catch (RuntimeException e)
118-
{
119-
logger.LogException("Could not load the DSC module!", e);
120-
}
121-
122-
if (dscModule == null)
123-
{
124-
logger.LogTrace("Side-by-side DSC module was not found.");
125-
return null;
126-
}
127-
128-
logger.LogTrace("Side-by-side DSC module found, gathering DSC resource paths...");
129-
130-
// The module was loaded, add the breakpoint capability
131-
DscBreakpointCapability capability = new();
132-
133-
pwsh.AddCommand("Microsoft.PowerShell.Utility\\Write-Host")
134-
.AddArgument("Gathering DSC resource paths, this may take a while...")
135-
.InvokeAndClear(invocationSettings);
136-
137-
Collection<string> resourcePaths = null;
138-
try
139-
{
140-
// Get the list of DSC resource paths
141-
resourcePaths = pwsh.AddCommand("Get-DscResource")
142-
.AddCommand("Select-Object")
143-
.AddParameter("ExpandProperty", "ParentPath")
144-
.InvokeAndClear<string>(invocationSettings);
145-
}
146-
catch (CmdletInvocationException e)
147-
{
148-
logger.LogException("Get-DscResource failed!", e);
149-
}
150-
151-
if (resourcePaths == null)
152-
{
153-
logger.LogTrace("No DSC resources found.");
154-
return null;
155-
}
94+
PSCommand psCommand = new PSCommand()
95+
.AddCommand("Import-Module")
96+
.AddArgument(@"C:\Program Files\DesiredStateConfiguration\1.0.0.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psd1")
97+
.AddParameter("PassThru");
98+
99+
IReadOnlyList<PSModuleInfo> dscModule =
100+
await psesHost.ExecutePSCommandAsync<PSModuleInfo>(
101+
psCommand,
102+
CancellationToken.None,
103+
new PowerShellExecutionOptions { ThrowOnError = false }).ConfigureAwait(false);
104+
105+
isDscInstalled = dscModule.Count > 0;
106+
logger.LogTrace("Side-by-side DSC module found: " + isDscInstalled.Value);
107+
}
156108

157-
capability.dscResourceRootPaths = resourcePaths.ToArray();
109+
if (isDscInstalled.Value)
110+
{
111+
PSCommand psCommand = new PSCommand()
112+
.AddCommand("Get-DscResource")
113+
.AddCommand("Select-Object")
114+
.AddParameter("ExpandProperty", "ParentPath");
115+
116+
IReadOnlyList<string> resourcePaths =
117+
await psesHost.ExecutePSCommandAsync<string>(
118+
psCommand,
119+
CancellationToken.None,
120+
new PowerShellExecutionOptions { ThrowOnError = false }
121+
).ConfigureAwait(false);
158122

159123
logger.LogTrace($"DSC resources found: {resourcePaths.Count}");
160-
161-
return capability;
124+
return new DscBreakpointCapability
125+
{
126+
dscResourceRootPaths = resourcePaths.ToArray()
127+
};
162128
}
163129

164-
return psesHost.ExecuteDelegateAsync(
165-
nameof(getDscBreakpointCapabilityFunc),
166-
executionOptions: null,
167-
getDscBreakpointCapabilityFunc,
168-
cancellationToken);
130+
return null;
169131
}
170132
}
171133
}

src/PowerShellEditorServices/Services/PowerShell/Debugging/IPowerShellDebugContext.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System;
55
using System.Management.Automation;
6-
using System.Threading;
76
using System.Threading.Tasks;
87

98
namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging
@@ -34,6 +33,6 @@ internal interface IPowerShellDebugContext
3433

3534
void Abort();
3635

37-
Task<DscBreakpointCapability> GetDscBreakpointCapabilityAsync(CancellationToken cancellationToken);
36+
Task<DscBreakpointCapability> GetDscBreakpointCapabilityAsync();
3837
}
3938
}

0 commit comments

Comments
 (0)