Skip to content

Commit 916b5e4

Browse files
committed
Fix #232: Language server crashes when ErrorActionPreference is "Stop" (#234)
This change fixes an issue where the language server can sometimes crash when the user has configured their $ErrorActionPreference variable to "Stop" in the language server's session. The fix is to refactor existing code to use a common code path that handles RuntimeExceptions appropriately.
1 parent 7f0fefe commit 916b5e4

File tree

5 files changed

+60
-73
lines changed

5 files changed

+60
-73
lines changed

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -528,21 +528,16 @@ protected async Task HandleCompletionResolveRequest(
528528
{
529529
if (completionItem.Kind == CompletionItemKind.Function)
530530
{
531-
RunspaceHandle runspaceHandle =
532-
await editorSession.PowerShellContext.GetRunspaceHandle();
533-
534531
// Get the documentation for the function
535532
CommandInfo commandInfo =
536-
CommandHelpers.GetCommandInfo(
533+
await CommandHelpers.GetCommandInfo(
537534
completionItem.Label,
538-
runspaceHandle.Runspace);
535+
this.editorSession.PowerShellContext);
539536

540537
completionItem.Documentation =
541-
CommandHelpers.GetCommandSynopsis(
538+
await CommandHelpers.GetCommandSynopsis(
542539
commandInfo,
543-
runspaceHandle.Runspace);
544-
545-
runspaceHandle.Dispose();
540+
this.editorSession.PowerShellContext);
546541
}
547542

548543
// Send back the updated CompletionItem

src/PowerShellEditorServices/Language/CommandHelpers.cs

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
//
55

66
using System.Linq;
7+
using System.Management.Automation;
8+
using System.Threading.Tasks;
79

810
namespace Microsoft.PowerShell.EditorServices
911
{
10-
using System.Management.Automation;
11-
using System.Management.Automation.Runspaces;
12-
1312
/// <summary>
1413
/// Provides utility methods for working with PowerShell commands.
1514
/// </summary>
@@ -19,34 +18,29 @@ public class CommandHelpers
1918
/// Gets the CommandInfo instance for a command with a particular name.
2019
/// </summary>
2120
/// <param name="commandName">The name of the command.</param>
22-
/// <param name="runspace">The Runspace to use for running Get-Command.</param>
21+
/// <param name="powerShellContext">The PowerShellContext to use for running Get-Command.</param>
2322
/// <returns>A CommandInfo object with details about the specified command.</returns>
24-
public static CommandInfo GetCommandInfo(
23+
public static async Task<CommandInfo> GetCommandInfo(
2524
string commandName,
26-
Runspace runspace)
25+
PowerShellContext powerShellContext)
2726
{
28-
CommandInfo commandInfo = null;
29-
30-
using (PowerShell powerShell = PowerShell.Create())
31-
{
32-
powerShell.Runspace = runspace;
33-
powerShell.AddCommand("Get-Command");
34-
powerShell.AddArgument(commandName);
35-
commandInfo = powerShell.Invoke<CommandInfo>().FirstOrDefault();
36-
}
27+
PSCommand command = new PSCommand();
28+
command.AddCommand(@"Microsoft.PowerShell.Core\Get-Command");
29+
command.AddArgument(commandName);
3730

38-
return commandInfo;
31+
var results = await powerShellContext.ExecuteCommand<CommandInfo>(command, false, false);
32+
return results.FirstOrDefault();
3933
}
4034

4135
/// <summary>
4236
/// Gets the command's "Synopsis" documentation section.
4337
/// </summary>
4438
/// <param name="commandInfo">The CommandInfo instance for the command.</param>
45-
/// <param name="runspace">The Runspace to use for getting command documentation.</param>
39+
/// <param name="powerShellContext">The PowerShellContext to use for getting command documentation.</param>
4640
/// <returns></returns>
47-
public static string GetCommandSynopsis(
41+
public static async Task<string> GetCommandSynopsis(
4842
CommandInfo commandInfo,
49-
Runspace runspace)
43+
PowerShellContext powerShellContext)
5044
{
5145
string synopsisString = string.Empty;
5246

@@ -57,13 +51,12 @@ public static string GetCommandSynopsis(
5751
commandInfo.CommandType == CommandTypes.Function ||
5852
commandInfo.CommandType == CommandTypes.Filter))
5953
{
60-
using (PowerShell powerShell = PowerShell.Create())
61-
{
62-
powerShell.Runspace = runspace;
63-
powerShell.AddCommand("Get-Help");
64-
powerShell.AddArgument(commandInfo);
65-
helpObject = powerShell.Invoke<PSObject>().FirstOrDefault();
66-
}
54+
PSCommand command = new PSCommand();
55+
command.AddCommand(@"Microsoft.PowerShell.Core\Get-Help");
56+
command.AddArgument(commandInfo);
57+
58+
var results = await powerShellContext.ExecuteCommand<PSObject>(command, false, false);
59+
helpObject = results.FirstOrDefault();
6760

6861
if (helpObject != null)
6962
{

src/PowerShellEditorServices/Language/LanguageService.cs

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@
77
using System;
88
using System.Collections.Generic;
99
using System.Linq;
10+
using System.Management.Automation;
11+
using System.Threading;
1012
using System.Threading.Tasks;
1113

1214
namespace Microsoft.PowerShell.EditorServices
1315
{
14-
using System.Management.Automation;
15-
using System.Management.Automation.Runspaces;
16-
using System.Threading;
1716
/// <summary>
1817
/// Provides a high-level service for performing code completion and
1918
/// navigation operations on PowerShell scripts.
@@ -196,15 +195,11 @@ public async Task<SymbolDetails> FindSymbolDetailsAtLocation(
196195

197196
if (symbolReference != null)
198197
{
199-
// Request a runspace handle with a short timeout
200-
RunspaceHandle runspaceHandle =
201-
await this.powerShellContext.GetRunspaceHandle(
202-
new CancellationTokenSource(DefaultWaitTimeoutMilliseconds).Token);
203-
204198
symbolReference.FilePath = scriptFile.FilePath;
205-
symbolDetails = new SymbolDetails(symbolReference, runspaceHandle.Runspace);
206-
207-
runspaceHandle.Dispose();
199+
symbolDetails =
200+
await SymbolDetails.Create(
201+
symbolReference,
202+
this.powerShellContext);
208203
}
209204
else
210205
{
@@ -335,7 +330,10 @@ public async Task<GetDefinitionResult> GetDefinitionOfSymbol(
335330
// look for it in the builtin commands
336331
if (foundDefinition == null)
337332
{
338-
CommandInfo cmdInfo = await GetCommandInfo(foundSymbol.SymbolName);
333+
CommandInfo cmdInfo =
334+
await CommandHelpers.GetCommandInfo(
335+
foundSymbol.SymbolName,
336+
this.powerShellContext);
339337

340338
foundDefinition =
341339
await FindDeclarationForBuiltinCommand(
@@ -409,7 +407,11 @@ public async Task<ParameterSetSignatures> FindParameterSetsInFile(
409407

410408
if (foundSymbol != null)
411409
{
412-
CommandInfo commandInfo = await GetCommandInfo(foundSymbol.SymbolName);
410+
CommandInfo commandInfo =
411+
await CommandHelpers.GetCommandInfo(
412+
foundSymbol.SymbolName,
413+
this.powerShellContext);
414+
413415
if (commandInfo != null)
414416
{
415417
try
@@ -481,16 +483,6 @@ await this.powerShellContext.GetRunspaceHandle(
481483
}
482484
}
483485

484-
private async Task<CommandInfo> GetCommandInfo(string commandName)
485-
{
486-
PSCommand command = new PSCommand();
487-
command.AddCommand("Get-Command");
488-
command.AddArgument(commandName);
489-
490-
var results = await this.powerShellContext.ExecuteCommand<CommandInfo>(command);
491-
return results.FirstOrDefault();
492-
}
493-
494486
private ScriptFile[] GetBuiltinCommandScriptFiles(
495487
PSModuleInfo moduleInfo,
496488
Workspace workspace)
@@ -541,7 +533,10 @@ private async Task<SymbolReference> FindDeclarationForBuiltinCommand(
541533
int index = 0;
542534
ScriptFile[] nestedModuleFiles;
543535

544-
CommandInfo commandInfo = await GetCommandInfo(foundSymbol.SymbolName);
536+
CommandInfo commandInfo =
537+
await CommandHelpers.GetCommandInfo(
538+
foundSymbol.SymbolName,
539+
this.powerShellContext);
545540

546541
nestedModuleFiles =
547542
GetBuiltinCommandScriptFiles(

src/PowerShellEditorServices/Language/SymbolDetails.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Diagnostics;
77
using System.Management.Automation;
88
using System.Management.Automation.Runspaces;
9+
using System.Threading.Tasks;
910

1011
namespace Microsoft.PowerShell.EditorServices
1112
{
@@ -37,53 +38,56 @@ public class SymbolDetails
3738

3839
#region Constructors
3940

40-
internal SymbolDetails(
41+
static internal async Task<SymbolDetails> Create(
4142
SymbolReference symbolReference,
42-
Runspace runspace)
43+
PowerShellContext powerShellContext)
4344
{
44-
this.SymbolReference = symbolReference;
45+
SymbolDetails symbolDetails = new SymbolDetails();
46+
symbolDetails.SymbolReference = symbolReference;
4547

4648
// If the symbol is a command, get its documentation
4749
if (symbolReference.SymbolType == SymbolType.Function)
4850
{
4951
CommandInfo commandInfo =
50-
CommandHelpers.GetCommandInfo(
52+
await CommandHelpers.GetCommandInfo(
5153
symbolReference.SymbolName,
52-
runspace);
54+
powerShellContext);
5355

5456
if (commandInfo != null)
5557
{
56-
this.Documentation =
57-
CommandHelpers.GetCommandSynopsis(
58+
symbolDetails.Documentation =
59+
await CommandHelpers.GetCommandSynopsis(
5860
commandInfo,
59-
runspace);
61+
powerShellContext);
6062

6163
if (commandInfo.CommandType == CommandTypes.Application)
6264
{
63-
this.DisplayString = "(application) " + symbolReference.SymbolName;
65+
symbolDetails.DisplayString = "(application) " + symbolReference.SymbolName;
6466
}
6567
else
6668
{
67-
this.DisplayString = "function " + symbolReference.SymbolName;
69+
symbolDetails.DisplayString = "function " + symbolReference.SymbolName;
6870
}
6971
}
7072
else
7173
{
7274
// Command information can't be loaded. This is likely due to
7375
// the symbol being a function that is defined in a file that
7476
// hasn't been loaded in the runspace yet.
75-
this.DisplayString = "function " + symbolReference.SymbolName;
77+
symbolDetails.DisplayString = "function " + symbolReference.SymbolName;
7678
}
7779
}
7880
else if (symbolReference.SymbolType == SymbolType.Parameter)
7981
{
8082
// TODO: Get parameter help
81-
this.DisplayString = "(parameter) " + symbolReference.SymbolName;
83+
symbolDetails.DisplayString = "(parameter) " + symbolReference.SymbolName;
8284
}
8385
else if (symbolReference.SymbolType == SymbolType.Variable)
8486
{
85-
this.DisplayString = symbolReference.SymbolName;
87+
symbolDetails.DisplayString = symbolReference.SymbolName;
8688
}
89+
90+
return symbolDetails;
8791
}
8892

8993
#endregion

src/PowerShellEditorServices/Session/PowerShellContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ public async Task<IEnumerable<TResult>> ExecuteCommand<TResult>(
296296
bool sendErrorToHost = true)
297297
{
298298
RunspaceHandle runspaceHandle = null;
299-
IEnumerable<TResult> executionResult = null;
299+
IEnumerable<TResult> executionResult = Enumerable.Empty<TResult>();
300300

301301
// If the debugger is active and the caller isn't on the pipeline
302302
// thread, send the command over to that thread to be executed.

0 commit comments

Comments
 (0)