diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 4c5887f4f..77772c481 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -76,7 +76,11 @@ 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') +CmdletsToExport = @( + 'Start-EditorServices', + '__Invoke-ReadLineForEditorServices', + '__Invoke-ReadLineConstructor' +) # Variables to export from this module VariablesToExport = @() diff --git a/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineConstructorCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineConstructorCommand.cs new file mode 100644 index 000000000..48d28d16d --- /dev/null +++ b/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineConstructorCommand.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Management.Automation; +using System.Runtime.CompilerServices; + +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() + { + Type type = Type.GetType("Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2"); + RuntimeHelpers.RunClassConstructor(type.TypeHandle); + } + } +} diff --git a/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineForEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineForEditorServicesCommand.cs new file mode 100644 index 000000000..7590f769d --- /dev/null +++ b/src/PowerShellEditorServices.Hosting/Commands/InvokeReadLineForEditorServicesCommand.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Reflection; +using System.Threading; + +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( + Runspace runspace, + EngineIntrinsics engineIntrinsics, + CancellationToken cancellationToken); + + private static Lazy s_readLine = new Lazy(() => + { + Type type = Type.GetType("Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2"); + MethodInfo method = type?.GetMethod( + "ReadLine", + new[] { typeof(Runspace), typeof(EngineIntrinsics), typeof(CancellationToken) }); + + // TODO: Handle method being null here. This shouldn't ever happen. + + return (ReadLineInvoker)method.CreateDelegate(typeof(ReadLineInvoker)); + }); + + /// + /// The ID to give to the host's profile. + /// + [Parameter(Mandatory = true)] + [ValidateNotNullOrEmpty] + public CancellationToken CancellationToken { get; set; } + + protected override void EndProcessing() + { + // This returns a string. + object result = s_readLine.Value( + Runspace.DefaultRunspace, + SessionState.PSVariable.Get("ExecutionContext").Value as EngineIntrinsics, + CancellationToken + ); + + WriteObject(result); + } + } +} diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index ea570a0d3..73b20546b 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -5,6 +5,7 @@ using System; using System.IO; +using System.Management.Automation; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -80,8 +81,9 @@ public async Task StartAsync() if (_usePSReadLine && _useTempSession && Interlocked.Exchange(ref s_hasRunPsrlStaticCtor, 1) == 0) { // This must be run synchronously to ensure debugging works + var command = new PSCommand().AddCommand("__Invoke-ReadLineConstructor"); _powerShellContextService - .ExecuteScriptStringAsync("[System.Runtime.CompilerServices.RuntimeHelpers]::RunClassConstructor([Microsoft.PowerShell.PSConsoleReadLine].TypeHandle)") + .ExecuteCommandAsync(command, sendOutputToHost: true, sendErrorToHost: true) .GetAwaiter() .GetResult(); } diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs index eb286f5a4..3812a3250 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs @@ -18,15 +18,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext internal class PSReadLinePromptContext : IPromptContext { - private const string ReadLineScript = @" - [System.Diagnostics.DebuggerHidden()] - [System.Diagnostics.DebuggerStepThrough()] - param() - return [Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null]::ReadLine( - $Host.Runspace, - $ExecutionContext, - $args[0])"; - private const string ReadLineInitScript = @" [System.Diagnostics.DebuggerHidden()] [System.Diagnostics.DebuggerStepThrough()] @@ -41,7 +32,7 @@ internal class PSReadLinePromptContext : IPromptContext } Import-Module -ModuleInfo $module - return [Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null] + return [Microsoft.PowerShell.PSConsoleReadLine] }"; private static ExecutionOptions s_psrlExecutionOptions = new ExecutionOptions @@ -138,8 +129,8 @@ public async Task InvokeReadLineAsync(bool isCommandLine, CancellationTo } var readLineCommand = new PSCommand() - .AddScript(ReadLineScript) - .AddArgument(_readLineCancellationSource.Token); + .AddCommand("__Invoke-ReadLineForEditorServices") + .AddParameter("CancellationToken", _readLineCancellationSource.Token); IEnumerable readLineResults = await _powerShellContext.ExecuteCommandAsync( readLineCommand,