diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs index 4765b7f90..df79c806a 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs @@ -20,19 +20,20 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel public class NamedPipeServerListener : ServerListenerBase { // This int will be casted to a PipeOptions enum that only exists in .NET Core 2.1 and up which is why it's not available to us in .NET Standard. - private const int CurrentUserOnly = 536870912; + private const int CurrentUserOnly = 0x20000000; // In .NET Framework, NamedPipeServerStream has a constructor that takes in a PipeSecurity object. We will use reflection to call the constructor, // since .NET Framework doesn't have the `CurrentUserOnly` PipeOption. // doc: https://docs.microsoft.com/en-us/dotnet/api/system.io.pipes.namedpipeserverstream.-ctor?view=netframework-4.7.2#System_IO_Pipes_NamedPipeServerStream__ctor_System_String_System_IO_Pipes_PipeDirection_System_Int32_System_IO_Pipes_PipeTransmissionMode_System_IO_Pipes_PipeOptions_System_Int32_System_Int32_System_IO_Pipes_PipeSecurity_ - private static ConstructorInfo _netFrameworkPipeServerConstructor = + private static readonly ConstructorInfo s_netFrameworkPipeServerConstructor = typeof(NamedPipeServerStream).GetConstructor(new [] { typeof(string), typeof(PipeDirection), typeof(int), typeof(PipeTransmissionMode), typeof(PipeOptions), typeof(int), typeof(int), typeof(PipeSecurity) }); - private ILogger logger; - private string inOutPipeName; - private readonly string outPipeName; - private NamedPipeServerStream inOutPipeServer; - private NamedPipeServerStream outPipeServer; + private readonly ILogger _logger; + private readonly string _inOutPipeName; + private readonly string _outPipeName; + + private NamedPipeServerStream _inOutPipeServer; + private NamedPipeServerStream _outPipeServer; public NamedPipeServerListener( MessageProtocolType messageProtocolType, @@ -40,8 +41,8 @@ public NamedPipeServerListener( ILogger logger) : base(messageProtocolType) { - this.logger = logger; - this.inOutPipeName = inOutPipeName; + _logger = logger; + _inOutPipeName = inOutPipeName; } public NamedPipeServerListener( @@ -51,88 +52,21 @@ public NamedPipeServerListener( ILogger logger) : base(messageProtocolType) { - this.logger = logger; - this.inOutPipeName = inPipeName; - this.outPipeName = outPipeName; + _logger = logger; + _inOutPipeName = inPipeName; + _outPipeName = outPipeName; } public override void Start() { try { - // If we're running in Windows PowerShell, we use the constructor via Reflection - if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework")) - { - PipeSecurity pipeSecurity = new PipeSecurity(); - - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new WindowsPrincipal(identity); - - if (principal.IsInRole(WindowsBuiltInRole.Administrator)) - { - // Allow the Administrators group full access to the pipe. - pipeSecurity.AddAccessRule(new PipeAccessRule( - new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).Translate(typeof(NTAccount)), - PipeAccessRights.FullControl, AccessControlType.Allow)); - } - else - { - // Allow the current user read/write access to the pipe. - pipeSecurity.AddAccessRule(new PipeAccessRule( - WindowsIdentity.GetCurrent().User, - PipeAccessRights.ReadWrite, AccessControlType.Allow)); - } - - _netFrameworkPipeServerConstructor.Invoke(new object[] - { - inOutPipeName, - PipeDirection.InOut, - 1, // maxNumberOfServerInstances - PipeTransmissionMode.Byte, - PipeOptions.Asynchronous, - 1024, // inBufferSize - 1024, // outBufferSize - pipeSecurity - }); - - if (this.outPipeName != null) - { - _netFrameworkPipeServerConstructor.Invoke(new object[] - { - outPipeName, - PipeDirection.InOut, - 1, // maxNumberOfServerInstances - PipeTransmissionMode.Byte, - PipeOptions.Asynchronous, - 1024, // inBufferSize - 1024, // outBufferSize - pipeSecurity - }); - } - } - else - { - this.inOutPipeServer = new NamedPipeServerStream( - pipeName: inOutPipeName, - direction: PipeDirection.InOut, - maxNumberOfServerInstances: 1, - transmissionMode: PipeTransmissionMode.Byte, - options: PipeOptions.Asynchronous | (PipeOptions)CurrentUserOnly); - if (this.outPipeName != null) - { - this.outPipeServer = new NamedPipeServerStream( - pipeName: outPipeName, - direction: PipeDirection.Out, - maxNumberOfServerInstances: 1, - transmissionMode: PipeTransmissionMode.Byte, - options: (PipeOptions)CurrentUserOnly); - } - } + _inOutPipeServer = ConnectNamedPipe(_inOutPipeName, _outPipeName, out _outPipeServer); ListenForConnection(); } catch (IOException e) { - this.logger.Write( + _logger.Write( LogLevel.Verbose, "Named pipe server failed to start due to exception:\r\n\r\n" + e.Message); @@ -142,22 +76,98 @@ public override void Start() public override void Stop() { - if (this.inOutPipeServer != null) + if (_inOutPipeServer != null) { - this.logger.Write(LogLevel.Verbose, "Named pipe server shutting down..."); + _logger.Write(LogLevel.Verbose, "Named pipe server shutting down..."); - this.inOutPipeServer.Dispose(); + _inOutPipeServer.Dispose(); - this.logger.Write(LogLevel.Verbose, "Named pipe server has been disposed."); + _logger.Write(LogLevel.Verbose, "Named pipe server has been disposed."); } - if (this.outPipeServer != null) + + if (_outPipeServer != null) + { + _logger.Write(LogLevel.Verbose, $"Named out pipe server {_outPipeServer} shutting down..."); + + _outPipeServer.Dispose(); + + _logger.Write(LogLevel.Verbose, $"Named out pipe server {_outPipeServer} has been disposed."); + } + } + + private static NamedPipeServerStream ConnectNamedPipe( + string inOutPipeName, + string outPipeName, + out NamedPipeServerStream outPipe) + { + // .NET Core implementation is simplest so try that first + if (Utils.IsNetCore) { - this.logger.Write(LogLevel.Verbose, $"Named out pipe server {outPipeServer} shutting down..."); + outPipe = outPipeName == null + ? null + : new NamedPipeServerStream( + pipeName: outPipeName, + direction: PipeDirection.Out, + maxNumberOfServerInstances: 1, + transmissionMode: PipeTransmissionMode.Byte, + options: (PipeOptions)CurrentUserOnly); + + return new NamedPipeServerStream( + pipeName: inOutPipeName, + direction: PipeDirection.InOut, + maxNumberOfServerInstances: 1, + transmissionMode: PipeTransmissionMode.Byte, + options: PipeOptions.Asynchronous | (PipeOptions)CurrentUserOnly); + } + + // Now deal with Windows PowerShell + // We need to use reflection to get a nice constructor - this.outPipeServer.Dispose(); + PipeSecurity pipeSecurity = new PipeSecurity(); - this.logger.Write(LogLevel.Verbose, $"Named out pipe server {outPipeServer} has been disposed."); + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new WindowsPrincipal(identity); + + if (principal.IsInRole(WindowsBuiltInRole.Administrator)) + { + // Allow the Administrators group full access to the pipe. + pipeSecurity.AddAccessRule(new PipeAccessRule( + new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).Translate(typeof(NTAccount)), + PipeAccessRights.FullControl, AccessControlType.Allow)); + } + else + { + // Allow the current user read/write access to the pipe. + pipeSecurity.AddAccessRule(new PipeAccessRule( + WindowsIdentity.GetCurrent().User, + PipeAccessRights.ReadWrite, AccessControlType.Allow)); } + + outPipe = outPipeName == null + ? null + : (NamedPipeServerStream)s_netFrameworkPipeServerConstructor.Invoke( + new object[] { + outPipeName, + PipeDirection.InOut, + 1, // maxNumberOfServerInstances + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous, + 1024, // inBufferSize + 1024, // outBufferSize + pipeSecurity + }); + + return (NamedPipeServerStream)s_netFrameworkPipeServerConstructor.Invoke( + new object[] { + inOutPipeName, + PipeDirection.InOut, + 1, // maxNumberOfServerInstances + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous, + 1024, // inBufferSize + 1024, // outBufferSize + pipeSecurity + }); } private void ListenForConnection() @@ -166,18 +176,18 @@ private void ListenForConnection() { try { - var connectionTasks = new List {WaitForConnectionAsync(this.inOutPipeServer)}; - if (this.outPipeServer != null) + var connectionTasks = new List {WaitForConnectionAsync(_inOutPipeServer)}; + if (_outPipeServer != null) { - connectionTasks.Add(WaitForConnectionAsync(this.outPipeServer)); + connectionTasks.Add(WaitForConnectionAsync(_outPipeServer)); } await Task.WhenAll(connectionTasks); - this.OnClientConnect(new NamedPipeServerChannel(this.inOutPipeServer, this.outPipeServer, this.logger)); + OnClientConnect(new NamedPipeServerChannel(_inOutPipeServer, _outPipeServer, _logger)); } catch (Exception e) { - this.logger.WriteException( + _logger.WriteException( "An unhandled exception occurred while listening for a named pipe client connection", e); @@ -188,11 +198,7 @@ private void ListenForConnection() private static async Task WaitForConnectionAsync(NamedPipeServerStream pipeServerStream) { -#if CoreCLR await pipeServerStream.WaitForConnectionAsync(); -#else - await Task.Factory.FromAsync(pipeServerStream.BeginWaitForConnection, pipeServerStream.EndWaitForConnection, null); -#endif await pipeServerStream.FlushAsync(); } }