Skip to content

Commit

Permalink
Ensure NamedPipeServerStream is assigned in Windows PowerShell (Power…
Browse files Browse the repository at this point in the history
…Shell#955)

* Ensure NamedPipeServerStream is reassigned properly
* Refactor code for style and to ensure compiler errors for non-assignment
  • Loading branch information
rjmholt committed May 15, 2019
1 parent 2cfdae7 commit 51909ee
Showing 1 changed file with 104 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,29 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel
public class NamedPipeServerListener : ServerListenerBase<NamedPipeServerChannel>
{
// 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,
string inOutPipeName,
ILogger logger)
: base(messageProtocolType)
{
this.logger = logger;
this.inOutPipeName = inOutPipeName;
_logger = logger;
_inOutPipeName = inOutPipeName;
}

public NamedPipeServerListener(
Expand All @@ -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);

Expand All @@ -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()
Expand All @@ -166,18 +176,18 @@ private void ListenForConnection()
{
try
{
var connectionTasks = new List<Task> {WaitForConnectionAsync(this.inOutPipeServer)};
if (this.outPipeServer != null)
var connectionTasks = new List<Task> {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);
Expand All @@ -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();
}
}
Expand Down

0 comments on commit 51909ee

Please sign in to comment.