Skip to content

Ensure NamedPipeServerStream is assigned in Windows PowerShell #955

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 15, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lol that explains the weird decimal number


// 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