diff --git a/src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs b/src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs index 73da0e447..8f8792980 100644 --- a/src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs +++ b/src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs @@ -93,6 +93,7 @@ public async Task StartAsync() .WithHandler() .WithHandler() .WithHandler() + .WithHandler() .WithHandler(); logger.LogInformation("Handlers added"); diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/IGetPSHostProcessesHandler.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/IGetPSHostProcessesHandler.cs new file mode 100644 index 000000000..f5a366c1d --- /dev/null +++ b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/IGetPSHostProcessesHandler.cs @@ -0,0 +1,21 @@ +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; + +namespace PowerShellEditorServices.Engine.Services.Handlers +{ + [Serial, Method("powerShell/getPSHostProcesses")] + public interface IGetPSHostProcessesHandler : IJsonRpcRequestHandler { } + + public class GetPSHostProcesssesParams : IRequest { } + + public class PSHostProcessResponse + { + public string ProcessName { get; set; } + + public int ProcessId { get; set; } + + public string AppDomainName { get; set; } + + public string MainWindowTitle { get; set; } + } +} diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/IGetRunspaceHandler.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/IGetRunspaceHandler.cs new file mode 100644 index 000000000..dcd94c1e6 --- /dev/null +++ b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/IGetRunspaceHandler.cs @@ -0,0 +1,22 @@ +using OmniSharp.Extensions.Embedded.MediatR; +using OmniSharp.Extensions.JsonRpc; + +namespace PowerShellEditorServices.Engine.Services.Handlers +{ + [Serial, Method("powerShell/getRunspace")] + public interface IGetRunspaceHandler : IJsonRpcRequestHandler { } + + public class GetRunspaceParams : IRequest + { + public string ProcessId {get; set; } + } + + public class RunspaceResponse + { + public int Id { get; set; } + + public string Name { get; set; } + + public string Availability { get; set; } + } +} diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs new file mode 100644 index 000000000..b24e2c7aa --- /dev/null +++ b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace PowerShellEditorServices.Engine.Services.Handlers +{ + public class PSHostProcessAndRunspaceHandlers : IGetPSHostProcessesHandler, IGetRunspaceHandler + { + private readonly ILogger _logger; + + public PSHostProcessAndRunspaceHandlers(ILoggerFactory factory) + { + _logger = factory.CreateLogger(); + } + + public Task Handle(GetPSHostProcesssesParams request, CancellationToken cancellationToken) + { + var psHostProcesses = new List(); + + int processId = System.Diagnostics.Process.GetCurrentProcess().Id; + + using (var pwsh = PowerShell.Create()) + { + pwsh.AddCommand("Get-PSHostProcessInfo") + .AddCommand("Where-Object") + .AddParameter("Property", "ProcessId") + .AddParameter("NE") + .AddParameter("Value", processId.ToString()); + + var processes = pwsh.Invoke(); + + if (processes != null) + { + foreach (dynamic p in processes) + { + psHostProcesses.Add( + new PSHostProcessResponse + { + ProcessName = p.ProcessName, + ProcessId = p.ProcessId, + AppDomainName = p.AppDomainName, + MainWindowTitle = p.MainWindowTitle + }); + } + } + } + + return Task.FromResult(psHostProcesses.ToArray()); + } + + public Task Handle(GetRunspaceParams request, CancellationToken cancellationToken) + { + IEnumerable runspaces = null; + + if (request.ProcessId == null) { + request.ProcessId = "current"; + } + + // If the processId is a valid int, we need to run Get-Runspace within that process + // otherwise just use the current runspace. + if (int.TryParse(request.ProcessId, out int pid)) + { + // Create a remote runspace that we will invoke Get-Runspace in. + using(var rs = RunspaceFactory.CreateRunspace(new NamedPipeConnectionInfo(pid))) + using(var ps = PowerShell.Create()) + { + rs.Open(); + ps.Runspace = rs; + // Returns deserialized Runspaces. For simpler code, we use PSObject and rely on dynamic later. + runspaces = ps.AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace").Invoke(); + } + } + else + { + // TODO: Bring back + // var psCommand = new PSCommand().AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace"); + // var sb = new StringBuilder(); + // // returns (not deserialized) Runspaces. For simpler code, we use PSObject and rely on dynamic later. + // runspaces = await editorSession.PowerShellContext.ExecuteCommandAsync(psCommand, sb); + } + + var runspaceResponses = new List(); + + if (runspaces != null) + { + foreach (dynamic runspace in runspaces) + { + runspaceResponses.Add( + new RunspaceResponse + { + Id = runspace.Id, + Name = runspace.Name, + Availability = runspace.RunspaceAvailability.ToString() + }); + } + } + + return Task.FromResult(runspaceResponses.ToArray()); + } + } +} diff --git a/test/Pester/EditorServices.Integration.Tests.ps1 b/test/Pester/EditorServices.Integration.Tests.ps1 index 78d8de1a7..3903e3f32 100644 --- a/test/Pester/EditorServices.Integration.Tests.ps1 +++ b/test/Pester/EditorServices.Integration.Tests.ps1 @@ -331,6 +331,30 @@ Write-Host 'Goodbye' $response.Result[1].Range.End.Character | Should -BeExactly 10 } + It "Can handle a powerShell/getPSHostProcesses request" { + $request = Send-LspRequest -Client $client -Method "powerShell/getPSHostProcesses" + $response = Get-LspResponse -Client $client -Id $request.Id + $response.Result | Should -Not -BeNullOrEmpty + + $processInfos = @(Get-PSHostProcessInfo) + + # We need to subtract one because this message fiilters out the "current" process. + $processInfos.Count - 1 | Should -BeExactly $response.Result.Count + + $response.Result[0].processName | + Should -MatchExactly -RegularExpression "((pwsh)|(powershell))(.exe)*" + } + + It "Can handle a powerShell/getRunspace request" { + $processInfos = Get-PSHostProcessInfo + + $request = Send-LspGetRunspaceRequest -Client $client -ProcessId $processInfos[0].ProcessId + $response = Get-LspResponse -Client $client -Id $request.Id + + $response.Result | Should -Not -BeNullOrEmpty + $response.Result.Count | Should -BeGreaterThan 0 + } + It "Can handle a textDocument/codeLens Pester request" { $filePath = New-TestFile -FileName ("$([System.IO.Path]::GetRandomFileName()).Tests.ps1") -Script ' Describe "DescribeName" { diff --git a/tools/PsesPsClient/PsesPsClient.psd1 b/tools/PsesPsClient/PsesPsClient.psd1 index 822ef602d..5a296e017 100644 --- a/tools/PsesPsClient/PsesPsClient.psd1 +++ b/tools/PsesPsClient/PsesPsClient.psd1 @@ -81,6 +81,7 @@ FunctionsToExport = @( 'Send-LspDocumentSymbolRequest', 'Send-LspDocumentHighlightRequest', 'Send-LspReferencesRequest', + 'Send-LspGetRunspaceRequest', 'Send-LspCodeLensRequest', 'Send-LspCodeLensResolveRequest', 'Send-LspShutdownRequest', diff --git a/tools/PsesPsClient/PsesPsClient.psm1 b/tools/PsesPsClient/PsesPsClient.psm1 index 059b1ee17..d2395368a 100644 --- a/tools/PsesPsClient/PsesPsClient.psm1 +++ b/tools/PsesPsClient/PsesPsClient.psm1 @@ -487,7 +487,7 @@ function Send-LspDocumentHighlightRequest return Send-LspRequest -Client $Client -Method 'textDocument/documentHighlight' -Parameters $documentHighlightParams } -function Send-LspCodeLensRequest +function Send-LspGetRunspaceRequest { [OutputType([PsesPsClient.LspRequest])] param( @@ -496,6 +496,24 @@ function Send-LspCodeLensRequest $Client, [Parameter(Mandatory)] + [int] + $ProcessId + ) + + $params = [PowerShellEditorServices.Engine.Services.Handlers.GetRunspaceParams]@{ + ProcessId = $ProcessId + } + return Send-LspRequest -Client $Client -Method 'powerShell/getRunspace' -Parameters $params +} + +function Send-LspCodeLensRequest +{ + [OutputType([PsesPsClient.LspRequest])] + param( + [Parameter(Position = 0, Mandatory)] + [PsesPsClient.PsesLspClient] + $Client, + [string] $Uri )