From cc241624f419bd81d86cac6b903b778a700b8ac2 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 10 Apr 2020 09:05:22 -0700 Subject: [PATCH 1/5] try this --- .../PowerShellEditorServices.psd1 | 6 +--- .../InvokeReadLineConstructorCommand.cs | 1 - .../InvokeReadLineForEditorServicesCommand.cs | 1 - .../Server/PsesDebugServer.cs | 8 ++++- .../Session/PSReadLinePromptContext.cs | 15 ++++++---- .../Utility/PSCommandExtensions.cs | 29 +++++++++++++++++++ 6 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 src/PowerShellEditorServices/Utility/PSCommandExtensions.cs diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 77772c481..4c5887f4f 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -76,11 +76,7 @@ Copyright = '(c) 2017 Microsoft. All rights reserved.' FunctionsToExport = @() # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. -CmdletsToExport = @( - 'Start-EditorServices', - '__Invoke-ReadLineForEditorServices', - '__Invoke-ReadLineConstructor' -) +CmdletsToExport = @('Start-EditorServices') # Variables to export from this module VariablesToExport = @() diff --git a/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineConstructorCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineConstructorCommand.cs index 48d28d16d..d27708c39 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineConstructorCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineConstructorCommand.cs @@ -12,7 +12,6 @@ namespace Microsoft.PowerShell.EditorServices.Commands /// /// The Start-EditorServices command, the conventional entrypoint for PowerShell Editor Services. /// - [Cmdlet("__Invoke", "ReadLineConstructor")] public sealed class InvokeReadLineConstructorCommand : PSCmdlet { protected override void EndProcessing() diff --git a/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineForEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineForEditorServicesCommand.cs index 7590f769d..20aa66d44 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineForEditorServicesCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineForEditorServicesCommand.cs @@ -14,7 +14,6 @@ namespace Microsoft.PowerShell.EditorServices.Commands /// /// The Start-EditorServices command, the conventional entrypoint for PowerShell Editor Services. /// - [Cmdlet("__Invoke", "ReadLineForEditorServices")] public sealed class InvokeReadLineForEditorServicesCommand : PSCmdlet { private delegate string ReadLineInvoker( diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 73b20546b..4391e7b07 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Utility; using OmniSharp.Extensions.DebugAdapter.Protocol.Serialization; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Server; @@ -28,6 +29,9 @@ internal class PsesDebugServer : IDisposable /// private static int s_hasRunPsrlStaticCtor = 0; + private static readonly Lazy s_lazyReadLineCmdletType = new Lazy(() => + Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineConstructorCommand, Microsoft.PowerShell.EditorServices.Hosting")); + private readonly Stream _inputStream; private readonly Stream _outputStream; private readonly bool _useTempSession; @@ -80,8 +84,10 @@ public async Task StartAsync() // This is only needed for Temp sessions who only have a debug server. if (_usePSReadLine && _useTempSession && Interlocked.Exchange(ref s_hasRunPsrlStaticCtor, 1) == 0) { + var command = new PSCommand() + .AddCommand(new CmdletInfo("__Invoke-ReadLineConstructor", s_lazyReadLineCmdletType.Value)); + // This must be run synchronously to ensure debugging works - var command = new PSCommand().AddCommand("__Invoke-ReadLineConstructor"); _powerShellContextService .ExecuteCommandAsync(command, sendOutputToHost: true, sendErrorToHost: true) .GetAwaiter() diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs index 3812a3250..11f0ef2ab 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs @@ -3,18 +3,18 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; +using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; +using System.Management.Automation.Runspaces; using System.Threading; using System.Threading.Tasks; -using System; -using System.Management.Automation.Runspaces; +using Microsoft.Extensions.Logging; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext { - using System.Collections.Generic; using System.Management.Automation; - using Microsoft.Extensions.Logging; internal class PSReadLinePromptContext : IPromptContext { @@ -44,6 +44,9 @@ internal class PSReadLinePromptContext : IPromptContext IsReadLine = true, }; + private static readonly Lazy s_lazyReadLineCmdletType = new Lazy(() => + Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineForEditorServicesCommand, Microsoft.PowerShell.EditorServices.Hosting")); + private readonly PowerShellContextService _powerShellContext; private readonly PromptNest _promptNest; @@ -129,7 +132,7 @@ public async Task InvokeReadLineAsync(bool isCommandLine, CancellationTo } var readLineCommand = new PSCommand() - .AddCommand("__Invoke-ReadLineForEditorServices") + .AddCommand(new CmdletInfo("__Invoke-ReadLineForEditorServices", s_lazyReadLineCmdletType.Value)) .AddParameter("CancellationToken", _readLineCancellationSource.Token); IEnumerable readLineResults = await _powerShellContext.ExecuteCommandAsync( diff --git a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs new file mode 100644 index 000000000..4c5d8e005 --- /dev/null +++ b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Reflection; + +namespace Microsoft.PowerShell.EditorServices.Utility +{ + internal static class PSCommandExtensions + { + private static ConstructorInfo s_commandCtor = + typeof(Command).GetConstructor( + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, + binder: null, + new[] { typeof(CommandInfo) }, + modifiers: null); + + internal static PSCommand AddCommand(this PSCommand command, CommandInfo commandInfo) + { + var rsCommand = (Command) s_commandCtor + .Invoke(new object[] { commandInfo }); + + return command.AddCommand(rsCommand); + } + } +} From 17587fa6514b274305a7ec16d156216409cdabd0 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 10 Apr 2020 13:59:51 -0700 Subject: [PATCH 2/5] Manually add commands --- .../PowerShellContext/PowerShellContextService.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs index 3af5094c4..4f9639209 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs @@ -740,7 +740,15 @@ public async Task> ExecuteCommandAsync( PowerShell shell = this.PromptNest.GetPowerShell(executionOptions.IsReadLine); - shell.Commands = psCommand; + + // Due to the following PowerShell bug, we can't just assign shell.Commands to psCommand + // because PowerShell strips out CommandInfo: + // https://github.com/PowerShell/PowerShell/issues/12297 + shell.Commands.Clear(); + foreach (Command command in psCommand.Commands) + { + shell.Commands.AddCommand(command); + } // Don't change our SessionState for ReadLine. if (!executionOptions.IsReadLine) From 74e386667b34690ddf44490d1523e8ef0873da5d Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 10 Apr 2020 14:05:44 -0700 Subject: [PATCH 3/5] add comment about extension --- src/PowerShellEditorServices/Utility/PSCommandExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs index 4c5d8e005..8ed565a4a 100644 --- a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs +++ b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs @@ -18,6 +18,9 @@ internal static class PSCommandExtensions new[] { typeof(CommandInfo) }, modifiers: null); + // PowerShell's missing an API for us to AddCommand using a CommandInfo. + // An issue was filed here: https://github.com/PowerShell/PowerShell/issues/12295 + // This works around this by creating a `Command` and passing it into PSCommand.AddCommand(Command command) internal static PSCommand AddCommand(this PSCommand command, CommandInfo commandInfo) { var rsCommand = (Command) s_commandCtor From 17badd75e8dc8773a22d881ec2d15aa505821c2f Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 10 Apr 2020 14:31:37 -0700 Subject: [PATCH 4/5] patrick's feedback --- .../Server/PsesDebugServer.cs | 9 +++++--- .../Session/PSReadLinePromptContext.cs | 9 +++++--- .../Utility/PSCommandExtensions.cs | 22 ++++++++++++++----- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 4391e7b07..3e74221a5 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -29,8 +29,11 @@ internal class PsesDebugServer : IDisposable /// private static int s_hasRunPsrlStaticCtor = 0; - private static readonly Lazy s_lazyReadLineCmdletType = new Lazy(() => - Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineConstructorCommand, Microsoft.PowerShell.EditorServices.Hosting")); + private static readonly Lazy s_lazyInvokeReadLineConstructorCmdletInfo = new Lazy(() => + { + var type = Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineConstructorCommand, Microsoft.PowerShell.EditorServices.Hosting"); + return new CmdletInfo("__Invoke-ReadLineConstructor", type); + }); private readonly Stream _inputStream; private readonly Stream _outputStream; @@ -85,7 +88,7 @@ public async Task StartAsync() if (_usePSReadLine && _useTempSession && Interlocked.Exchange(ref s_hasRunPsrlStaticCtor, 1) == 0) { var command = new PSCommand() - .AddCommand(new CmdletInfo("__Invoke-ReadLineConstructor", s_lazyReadLineCmdletType.Value)); + .AddCommand(s_lazyInvokeReadLineConstructorCmdletInfo.Value); // This must be run synchronously to ensure debugging works _powerShellContextService diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs index 11f0ef2ab..66ebca309 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs @@ -44,8 +44,11 @@ internal class PSReadLinePromptContext : IPromptContext IsReadLine = true, }; - private static readonly Lazy s_lazyReadLineCmdletType = new Lazy(() => - Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineForEditorServicesCommand, Microsoft.PowerShell.EditorServices.Hosting")); + private static readonly Lazy s_lazyInvokeReadLineForEditorServicesCmdletInfo = new Lazy(() => + { + var type = Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineForEditorServicesCommand, Microsoft.PowerShell.EditorServices.Hosting"); + return new CmdletInfo("__Invoke-ReadLineForEditorServices", type); + }); private readonly PowerShellContextService _powerShellContext; @@ -132,7 +135,7 @@ public async Task InvokeReadLineAsync(bool isCommandLine, CancellationTo } var readLineCommand = new PSCommand() - .AddCommand(new CmdletInfo("__Invoke-ReadLineForEditorServices", s_lazyReadLineCmdletType.Value)) + .AddCommand(s_lazyInvokeReadLineForEditorServicesCmdletInfo.Value) .AddParameter("CancellationToken", _readLineCancellationSource.Token); IEnumerable readLineResults = await _powerShellContext.ExecuteCommandAsync( diff --git a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs index 8ed565a4a..a6de3dafc 100644 --- a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs +++ b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs @@ -3,6 +3,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; +using System.Linq.Expressions; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Reflection; @@ -11,21 +13,31 @@ namespace Microsoft.PowerShell.EditorServices.Utility { internal static class PSCommandExtensions { - private static ConstructorInfo s_commandCtor = - typeof(Command).GetConstructor( + + private static readonly Func s_commandCtor; + + static PSCommandExtensions() + { + var ctor = typeof(Command).GetConstructor( BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, binder: null, new[] { typeof(CommandInfo) }, modifiers: null); + ParameterExpression commandInfo = Expression.Parameter(typeof(CommandInfo), nameof(commandInfo)); + + s_commandCtor = Expression.Lambda>( + Expression.New(ctor, commandInfo), + new[] { commandInfo }) + .Compile(); + } + // PowerShell's missing an API for us to AddCommand using a CommandInfo. // An issue was filed here: https://github.com/PowerShell/PowerShell/issues/12295 // This works around this by creating a `Command` and passing it into PSCommand.AddCommand(Command command) internal static PSCommand AddCommand(this PSCommand command, CommandInfo commandInfo) { - var rsCommand = (Command) s_commandCtor - .Invoke(new object[] { commandInfo }); - + var rsCommand = s_commandCtor(commandInfo); return command.AddCommand(rsCommand); } } From 804caec7352e7d41fd7dbee44113a80b5269693a Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Fri, 10 Apr 2020 14:39:31 -0700 Subject: [PATCH 5/5] misc cosmetic changes --- .../Session/PSReadLinePromptContext.cs | 12 ++++++------ .../Utility/PSCommandExtensions.cs | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs index 66ebca309..93a098f2e 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs @@ -35,6 +35,12 @@ internal class PSReadLinePromptContext : IPromptContext return [Microsoft.PowerShell.PSConsoleReadLine] }"; + private static readonly Lazy s_lazyInvokeReadLineForEditorServicesCmdletInfo = new Lazy(() => + { + var type = Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineForEditorServicesCommand, Microsoft.PowerShell.EditorServices.Hosting"); + return new CmdletInfo("__Invoke-ReadLineForEditorServices", type); + }); + private static ExecutionOptions s_psrlExecutionOptions = new ExecutionOptions { WriteErrorsToHost = false, @@ -44,12 +50,6 @@ internal class PSReadLinePromptContext : IPromptContext IsReadLine = true, }; - private static readonly Lazy s_lazyInvokeReadLineForEditorServicesCmdletInfo = new Lazy(() => - { - var type = Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineForEditorServicesCommand, Microsoft.PowerShell.EditorServices.Hosting"); - return new CmdletInfo("__Invoke-ReadLineForEditorServices", type); - }); - private readonly PowerShellContextService _powerShellContext; private readonly PromptNest _promptNest; diff --git a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs index a6de3dafc..4f0140f56 100644 --- a/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs +++ b/src/PowerShellEditorServices/Utility/PSCommandExtensions.cs @@ -13,7 +13,6 @@ namespace Microsoft.PowerShell.EditorServices.Utility { internal static class PSCommandExtensions { - private static readonly Func s_commandCtor; static PSCommandExtensions()