MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Diagnostics.CodeAnalysis;5using System.Globalization;6using System.IO;7using System.IO.Pipes;8using System.Management.Automation.Internal;9using System.Management.Automation.Remoting.Server;10using System.Management.Automation.Tracing;11using System.Runtime.InteropServices;12using System.Security.AccessControl;13using System.Security.Principal;14using System.Threading;15 16using Microsoft.Win32.SafeHandles;17 18using Dbg = System.Diagnostics.Debug;19 20namespace System.Management.Automation.Remoting21{22 /// <summary>23 /// Shared named pipe utilities.24 /// </summary>25 internal static class NamedPipeUtils26 {27 #region Strings28 29 internal const string NamedPipeNamePrefix = "PSHost.";30#if UNIX31 internal const string DefaultAppDomainName = "None";32 // This `CoreFxPipe` prefix is defined by CoreFx33 internal const string NamedPipeNamePrefixSearch = "CoreFxPipe_PSHost*";34#else35 internal const string DefaultAppDomainName = "DefaultAppDomain";36 internal const string NamedPipeNamePrefixSearch = "PSHost*";37#endif38 // On non-Windows, .NET named pipes are limited to up to 104 characters39 internal const int MaxNamedPipeNameSize = 104;40 41 #endregion42 43 #region Static Methods44 45 /// <summary>46 /// Create a pipe name based on process information.47 /// E.g., "PSHost.ProcessStartTime.ProcessId.DefaultAppDomain.ProcessName"48 /// </summary>49 /// <param name="procId">Process Id.</param>50 /// <returns>Pipe name.</returns>51 internal static string CreateProcessPipeName(52 int procId)53 {54 return CreateProcessPipeName(55 System.Diagnostics.Process.GetProcessById(procId));56 }57 58 /// <summary>59 /// Create a pipe name based on process information.60 /// E.g., "PSHost.ProcessStartTime.ProcessId.DefaultAppDomain.ProcessName"61 /// </summary>62 /// <param name="proc">Process object.</param>63 /// <returns>Pipe name.</returns>64 internal static string CreateProcessPipeName(65 System.Diagnostics.Process proc)66 {67 return CreateProcessPipeName(proc, DefaultAppDomainName);68 }69 70 /// <summary>71 /// Create a pipe name based on process Id and appdomain name information.72 /// E.g., "PSHost.ProcessStartTime.ProcessId.DefaultAppDomain.ProcessName"73 /// </summary>74 /// <param name="procId">Process Id.</param>75 /// <param name="appDomainName">Name of process app domain to connect to.</param>76 /// <returns>Pipe name.</returns>77 internal static string CreateProcessPipeName(78 int procId,79 string appDomainName)80 {81 return CreateProcessPipeName(System.Diagnostics.Process.GetProcessById(procId), appDomainName);82 }83 84 /// <summary>85 /// Create a pipe name based on process and appdomain name information.86 /// E.g., "PSHost.ProcessStartTime.ProcessId.DefaultAppDomain.ProcessName"87 /// </summary>88 /// <param name="proc">Process object.</param>89 /// <param name="appDomainName">Name of process app domain to connect to.</param>90 /// <returns>Pipe name.</returns>91 internal static string CreateProcessPipeName(92 System.Diagnostics.Process proc,93 string appDomainName)94 {95 if (proc == null)96 {97 throw new PSArgumentNullException(nameof(proc));98 }99 100 if (string.IsNullOrEmpty(appDomainName))101 {102 appDomainName = DefaultAppDomainName;103 }104 105 System.Text.StringBuilder pipeNameBuilder = new System.Text.StringBuilder(MaxNamedPipeNameSize);106 pipeNameBuilder.Append(NamedPipeNamePrefix)107 // The starttime is there to prevent another process easily guessing the pipe name108 // and squatting on it.109 // There is a limit of 104 characters in total including the temp path to the named pipe file110 // on non-Windows systems, so we'll convert the starttime to hex and just take the first 8 characters.111#if UNIX112 .Append(proc.StartTime.ToFileTime().ToString("X8").AsSpan(1, 8))113#else114 .Append(proc.StartTime.ToFileTime().ToString(CultureInfo.InvariantCulture))115#endif116 .Append('.')117 .Append(proc.Id.ToString(CultureInfo.InvariantCulture))118 .Append('.')119 .Append(CleanAppDomainNameForPipeName(appDomainName))120 .Append('.')121 .Append(proc.ProcessName);122#if UNIX123 int charsToTrim = pipeNameBuilder.Length - MaxNamedPipeNameSize;124 if (charsToTrim > 0)125 {126 // TODO: In the case the pipe name is truncated, the user cannot connect to it using the cmdlet127 // unless we add a `-Force` type switch as it attempts to validate the current process name128 // matches the process name in the pipe name129 pipeNameBuilder.Remove(MaxNamedPipeNameSize + 1, charsToTrim);130 }131#endif132 133 return pipeNameBuilder.ToString();134 }135 136 private static string CleanAppDomainNameForPipeName(string appDomainName)137 {138 // Pipe names cannot contain the ':' character. Remove unwanted characters.139 return appDomainName.Replace(":", string.Empty).Replace(" ", string.Empty);140 }141 142 /// <summary>143 /// Returns the current process AppDomain name.144 /// </summary>145 /// <returns>AppDomain Name string.</returns>146 internal static string GetCurrentAppDomainName()147 {148#if CORECLR // There is only one AppDomain per application in CoreCLR, which would be the default149 return DefaultAppDomainName;150#else // Use the AppDomain in which current powershell is running151 return AppDomain.CurrentDomain.IsDefaultAppDomain() ? DefaultAppDomainName : AppDomain.CurrentDomain.FriendlyName;152#endif153 }154 155 #endregion156 }157 158 /// <summary>159 /// Native API for Named Pipes.160 /// </summary>161 internal static class NamedPipeNative162 {163 #region Pipe constants164 165 // Pipe open modes166 internal const uint PIPE_ACCESS_DUPLEX = 0x00000003;167 internal const uint PIPE_ACCESS_OUTBOUND = 0x00000002;168 internal const uint PIPE_ACCESS_INBOUND = 0x00000001;169 170 // Pipe modes171 internal const uint PIPE_TYPE_BYTE = 0x00000000;172 internal const uint PIPE_TYPE_MESSAGE = 0x00000004;173 internal const uint FILE_FLAG_OVERLAPPED = 0x40000000;174 internal const uint FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000;175 internal const uint PIPE_WAIT = 0x00000000;176 internal const uint PIPE_NOWAIT = 0x00000001;177 internal const uint PIPE_READMODE_BYTE = 0x00000000;178 internal const uint PIPE_READMODE_MESSAGE = 0x00000002;179 internal const uint PIPE_ACCEPT_REMOTE_CLIENTS = 0x00000000;180 internal const uint PIPE_REJECT_REMOTE_CLIENTS = 0x00000008;181 182 // Pipe errors183 internal const uint ERROR_FILE_NOT_FOUND = 2;184 internal const uint ERROR_BROKEN_PIPE = 109;185 internal const uint ERROR_PIPE_BUSY = 231;186 internal const uint ERROR_NO_DATA = 232;187 internal const uint ERROR_MORE_DATA = 234;188 internal const uint ERROR_PIPE_CONNECTED = 535;189 internal const uint ERROR_IO_INCOMPLETE = 996;190 internal const uint ERROR_IO_PENDING = 997;191 192 #endregion193 194 #region Data structures195 196 [StructLayout(LayoutKind.Sequential)]197 internal class SECURITY_ATTRIBUTES198 {199 /// <summary>200 /// The size, in bytes, of this structure. Set this value to the size of the SECURITY_ATTRIBUTES structure.201 /// </summary>202 public int NLength;203 204 /// <summary>205 /// A pointer to a security descriptor for the object that controls the sharing of it.206 /// </summary>207 public IntPtr LPSecurityDescriptor = IntPtr.Zero;208 209 /// <summary>210 /// A Boolean value that specifies whether the returned handle is inherited when a new process is created.211 /// </summary>212 public bool InheritHandle;213 214 /// <summary>215 /// Initializes a new instance of the SECURITY_ATTRIBUTES class.216 /// </summary>217 public SECURITY_ATTRIBUTES()218 {219 this.NLength = 12;220 }221 }222 223 #endregion224 225 #region Pipe methods226 227 [DllImport(PinvokeDllNames.CreateNamedPipeDllName, SetLastError = true, CharSet = CharSet.Unicode)]228 internal static extern SafePipeHandle CreateNamedPipe(229 string lpName,230 uint dwOpenMode,231 uint dwPipeMode,232 uint nMaxInstances,233 uint nOutBufferSize,234 uint nInBufferSize,235 uint nDefaultTimeOut,236 SECURITY_ATTRIBUTES securityAttributes);237 238 internal static SECURITY_ATTRIBUTES GetSecurityAttributes(GCHandle securityDescriptorPinnedHandle, bool inheritHandle = false)239 {240 SECURITY_ATTRIBUTES securityAttributes = new NamedPipeNative.SECURITY_ATTRIBUTES();241 securityAttributes.InheritHandle = inheritHandle;242 securityAttributes.NLength = (int)Marshal.SizeOf(securityAttributes);243 securityAttributes.LPSecurityDescriptor = securityDescriptorPinnedHandle.AddrOfPinnedObject();244 return securityAttributes;245 }246 247 #endregion248 }249 250 /// <summary>251 /// Event arguments for listener thread end event.252 /// </summary>253 internal sealed class ListenerEndedEventArgs : EventArgs254 {255 #region Properties256 257 /// <summary>258 /// Exception reason for listener end event. Can be null259 /// which indicates listener thread end is not due to an error.260 /// </summary>261 public Exception Reason { get; }262 263 /// <summary>264 /// True if listener should be restarted after ending.265 /// </summary>266 public bool RestartListener { get; }267 268 #endregion269 270 #region Constructors271 272 private ListenerEndedEventArgs() { }273 274 /// <summary>275 /// Constructor.276 /// </summary>277 /// <param name="reason">Listener end reason.</param>278 /// <param name="restartListener">Restart listener.</param>279 public ListenerEndedEventArgs(280 Exception reason,281 bool restartListener)282 {283 Reason = reason;284 RestartListener = restartListener;285 }286 287 #endregion288 }289 290 /// <summary>291 /// Light wrapper class for BCL NamedPipeServerStream class, that292 /// creates the named pipe server with process named pipe name,293 /// having correct access restrictions, and provides a listener294 /// thread loop.295 /// </summary>296 public sealed class RemoteSessionNamedPipeServer : IDisposable297 {298 #region Members299 300 private readonly object _syncObject;301 private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();302 303 private const string _threadName = "IPC Listener Thread";304 private const int _namedPipeBufferSizeForRemoting = 32768;305 private const int _maxPipePathLengthLinux = 108;306 private const int _maxPipePathLengthMacOS = 104;307 308 // Singleton server.309 private static readonly object s_syncObject;310 internal static RemoteSessionNamedPipeServer IPCNamedPipeServer;311 internal static bool IPCNamedPipeServerEnabled;312 313 // Optional custom server.314 private static RemoteSessionNamedPipeServer _customNamedPipeServer;315 316 // Access mask constant taken from PipeSecurity access rights and is equivalent to317 // PipeAccessRights.FullControl.318 // See: https://msdn.microsoft.com/library/vstudio/bb348408(v=vs.100).aspx319 //320 private const int _pipeAccessMaskFullControl = 0x1f019f;321 322 #endregion323 324 #region Properties325 326 /// <summary>327 /// Returns the Named Pipe stream object.328 /// </summary>329 internal NamedPipeServerStream Stream { get; }330 331 /// <summary>332 /// Returns the Named Pipe name.333 /// </summary>334 internal string PipeName { get; }335 336 /// <summary>337 /// Returns true if listener is currently running.338 /// </summary>339 internal bool IsListenerRunning { get; private set; }340 341 /// <summary>342 /// Name of session configuration.343 /// </summary>344 internal string ConfigurationName { get; set; }345 346 /// <summary>347 /// Accessor for the named pipe reader.348 /// </summary>349 internal StreamReader TextReader { get; private set; }350 351 /// <summary>352 /// Accessor for the named pipe writer.353 /// </summary>354 internal StreamWriter TextWriter { get; private set; }355 356 /// <summary>357 /// Returns true if object is currently disposed.358 /// </summary>359 internal bool IsDisposed { get; private set; }360 361 /// <summary>362 /// Buffer size for PSRP fragmentor.363 /// </summary>364 internal static int NamedPipeBufferSizeForRemoting365 {366 get { return _namedPipeBufferSizeForRemoting; }367 }368 369 #endregion370 371 #region Events372 373 /// <summary>374 /// Event raised when the named pipe server listening thread375 /// ends.376 /// </summary>377 internal event EventHandler<ListenerEndedEventArgs> ListenerEnded;378 379 #endregion380 381 #region Constructors382 383 /// <summary>384 /// Creates a RemoteSessionNamedPipeServer with the current process and AppDomain information.385 /// </summary>386 /// <returns>RemoteSessionNamedPipeServer.</returns>387 internal static RemoteSessionNamedPipeServer CreateRemoteSessionNamedPipeServer()388 {389 string appDomainName = NamedPipeUtils.GetCurrentAppDomainName();390 391 return new RemoteSessionNamedPipeServer(NamedPipeUtils.CreateProcessPipeName(392 System.Diagnostics.Process.GetCurrentProcess(), appDomainName));393 }394 395 /// <summary>396 /// Constructor. Creates named pipe server with provided pipe name.397 /// </summary>398 /// <param name="pipeName">Named Pipe name.</param>399 internal RemoteSessionNamedPipeServer(400 string pipeName)401 {402 if (pipeName == null)403 {404 throw new PSArgumentNullException(nameof(pipeName));405 }406 407 _syncObject = new object();408 PipeName = pipeName;409 410 Stream = CreateNamedPipe(411 serverName: ".",412 namespaceName: "pipe",413 coreName: pipeName,414 securityDesc: GetServerPipeSecurity());415 }416 417 /// <summary>418 /// Helper method to create a PowerShell transport named pipe via native API, along419 /// with a returned .Net NamedPipeServerStream object wrapping the named pipe.420 /// </summary>421 /// <param name="serverName">Named pipe server name.</param>422 /// <param name="namespaceName">Named pipe namespace name.</param>423 /// <param name="coreName">Named pipe core name.</param>424 /// <param name="securityDesc"></param>425 /// <returns>NamedPipeServerStream.</returns>426 private static NamedPipeServerStream CreateNamedPipe(427 string serverName,428 string namespaceName,429 string coreName,430 CommonSecurityDescriptor securityDesc)431 {432 if (serverName == null) { throw new PSArgumentNullException(nameof(serverName)); }433 434 if (namespaceName == null) { throw new PSArgumentNullException(nameof(namespaceName)); }435 436 if (coreName == null) { throw new PSArgumentNullException(nameof(coreName)); }437 438#if !UNIX439 string fullPipeName = @"\\" + serverName + @"\" + namespaceName + @"\" + coreName;440 441 // Create optional security attributes based on provided PipeSecurity.442 NamedPipeNative.SECURITY_ATTRIBUTES securityAttributes = null;443 GCHandle? securityDescHandle = null;444 if (securityDesc != null)445 {446 byte[] securityDescBuffer = new byte[securityDesc.BinaryLength];447 securityDesc.GetBinaryForm(securityDescBuffer, 0);448 securityDescHandle = GCHandle.Alloc(securityDescBuffer, GCHandleType.Pinned);449 securityAttributes = NamedPipeNative.GetSecurityAttributes(securityDescHandle.Value);450 }451 452 // Create named pipe.453 SafePipeHandle pipeHandle = NamedPipeNative.CreateNamedPipe(454 fullPipeName,455 NamedPipeNative.PIPE_ACCESS_DUPLEX | NamedPipeNative.FILE_FLAG_FIRST_PIPE_INSTANCE | NamedPipeNative.FILE_FLAG_OVERLAPPED,456 NamedPipeNative.PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_READMODE_MESSAGE | NamedPipeNative.PIPE_REJECT_REMOTE_CLIENTS,457 1,458 _namedPipeBufferSizeForRemoting,459 _namedPipeBufferSizeForRemoting,460 0,461 securityAttributes);462 463 int lastError = Marshal.GetLastWin32Error();464 securityDescHandle?.Free();465 466 if (pipeHandle.IsInvalid)467 {468 throw new PSInvalidOperationException(469 StringUtil.Format(RemotingErrorIdStrings.CannotCreateNamedPipe, lastError));470 }471 472 // Create the .Net NamedPipeServerStream wrapper.473 try474 {475 return new NamedPipeServerStream(476 PipeDirection.InOut,477 true, // IsAsync478 false, // IsConnected479 pipeHandle);480 }481 catch (Exception)482 {483 pipeHandle.Dispose();484 throw;485 }486#else487 return new NamedPipeServerStream(488 pipeName: coreName,489 direction: PipeDirection.InOut,490 maxNumberOfServerInstances: 1,491 transmissionMode: PipeTransmissionMode.Byte,492 options: PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly,493 inBufferSize: _namedPipeBufferSizeForRemoting,494 outBufferSize: _namedPipeBufferSizeForRemoting);495#endif496 }497 498 static RemoteSessionNamedPipeServer()499 {500 s_syncObject = new object();501 502 // Unless opt-out, all PowerShell instances will start with the named-pipe listener created and running.503 IPCNamedPipeServerEnabled = !Utils.GetEnvironmentVariableAsBool(name: "POWERSHELL_DIAGNOSTICS_OPTOUT", defaultValue: false);504 505 if (IPCNamedPipeServerEnabled)506 {507 CreateIPCNamedPipeServerSingleton();508 CreateProcessExitHandler();509 }510 }511 512 #endregion513 514 #region IDisposable515 516 /// <summary>517 /// Dispose.518 /// </summary>519 public void Dispose()520 {521 lock (_syncObject)522 {523 if (IsDisposed) { return; }524 525 IsDisposed = true;526 }527 528 if (TextReader != null)529 {530 try { TextReader.Dispose(); }531 catch (ObjectDisposedException) { }532 533 TextReader = null;534 }535 536 if (TextWriter != null)537 {538 try { TextWriter.Dispose(); }539 catch (ObjectDisposedException) { }540 541 TextWriter = null;542 }543 544 if (Stream != null)545 {546 try { Stream.Dispose(); }547 catch (ObjectDisposedException) { }548 }549 }550 551 #endregion552 553 #region Public Methods554 555 /// <summary>556 /// Creates the custom named pipe server with the given pipename.557 /// </summary>558 /// <param name="pipeName">The name of the pipe to create.</param>559 public static void CreateCustomNamedPipeServer(string pipeName)560 {561 lock (s_syncObject)562 {563 if (_customNamedPipeServer != null && !_customNamedPipeServer.IsDisposed)564 {565 if (pipeName == _customNamedPipeServer.PipeName)566 {567 // we shouldn't recreate the server object if we're using the same pipeName568 return;569 }570 571 // Dispose of the current pipe server so we can create a new one with the new pipeName572 _customNamedPipeServer.Dispose();573 }574 575 if (!Platform.IsWindows)576 {577 int maxNameLength = (Platform.IsLinux ? _maxPipePathLengthLinux : _maxPipePathLengthMacOS) - Path.GetTempPath().Length;578 if (pipeName.Length > maxNameLength)579 {580 throw new InvalidOperationException(581 string.Format(582 RemotingErrorIdStrings.CustomPipeNameTooLong,583 maxNameLength,584 pipeName,585 pipeName.Length));586 }587 }588 589 try590 {591 try592 {593 _customNamedPipeServer = new RemoteSessionNamedPipeServer(pipeName);594 }595 catch (IOException)596 {597 // Expected when named pipe server for this process already exists.598 // This can happen if process has multiple AppDomains hosting PowerShell (SMA.dll).599 return;600 }601 602 // Listener ended callback, used to create listening new pipe server.603 _customNamedPipeServer.ListenerEnded += OnCustomNamedPipeServerEnded;604 605 // Start the pipe server listening thread, and provide client connection callback.606 _customNamedPipeServer.StartListening(ClientConnectionCallback);607 }608 catch (Exception)609 {610 _customNamedPipeServer = null;611 }612 }613 }614 615 #endregion616 617 #region Private Methods618 619 /// <summary>620 /// Starts named pipe server listening thread. When a client connects this thread621 /// makes a callback to implement the client communication. When the thread ends622 /// this object is disposed and a new RemoteSessionNamedPipeServer must be created623 /// and a new listening thread started to handle subsequent client connections.624 /// </summary>625 /// <param name="clientConnectCallback">Connection callback.</param>626 internal void StartListening(627 Action<RemoteSessionNamedPipeServer> clientConnectCallback)628 {629 if (clientConnectCallback == null)630 {631 throw new PSArgumentNullException(nameof(clientConnectCallback));632 }633 634 lock (_syncObject)635 {636 if (IsListenerRunning)637 {638 throw new InvalidOperationException(RemotingErrorIdStrings.NamedPipeAlreadyListening);639 }640 641 IsListenerRunning = true;642 643 // Create listener thread.644 Thread listenerThread = new Thread(ProcessListeningThread);645 listenerThread.Name = _threadName;646 listenerThread.IsBackground = true;647 listenerThread.Start(clientConnectCallback);648 }649 }650 651 internal static CommonSecurityDescriptor GetServerPipeSecurity()652 {653#if UNIX654 return null;655#else656 // Built-in Admin SID657 SecurityIdentifier adminSID = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);658 DiscretionaryAcl dacl = new DiscretionaryAcl(false, false, 1);659 dacl.AddAccess(660 AccessControlType.Allow,661 adminSID,662 _pipeAccessMaskFullControl,663 InheritanceFlags.None,664 PropagationFlags.None);665 666 CommonSecurityDescriptor securityDesc = new CommonSecurityDescriptor(667 false, false,668 ControlFlags.DiscretionaryAclPresent | ControlFlags.OwnerDefaulted | ControlFlags.GroupDefaulted,669 null, null, null, dacl);670 671 // Conditionally add User SID672 bool isAdminElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);673 if (!isAdminElevated)674 {675 securityDesc.DiscretionaryAcl.AddAccess(676 AccessControlType.Allow,677 WindowsIdentity.GetCurrent().User,678 _pipeAccessMaskFullControl,679 InheritanceFlags.None,680 PropagationFlags.None);681 }682 683 return securityDesc;684#endif685 }686 687 /// <summary>688 /// Wait for client connection.689 /// </summary>690 private void WaitForConnection()691 {692 Stream.WaitForConnection();693 }694 695 /// <summary>696 /// Process listening thread.697 /// </summary>698 /// <param name="state">Client callback delegate.</param>699 [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle")]700 private void ProcessListeningThread(object state)701 {702 string processId = Environment.ProcessId.ToString(CultureInfo.InvariantCulture);703 string appDomainName = NamedPipeUtils.GetCurrentAppDomainName();704 705 // Logging.706 _tracer.WriteMessage("RemoteSessionNamedPipeServer", "StartListening", Guid.Empty,707 "Listener thread started on Process {0} in AppDomainName {1}.", processId, appDomainName);708 PSEtwLog.LogOperationalInformation(709 PSEventId.NamedPipeIPC_ServerListenerStarted, PSOpcode.Open, PSTask.NamedPipe,710 PSKeyword.UseAlwaysOperational,711 processId, appDomainName);712 713 Exception ex = null;714 string userName = string.Empty;715 bool restartListenerThread = true;716 717 // Wait for connection.718 try719 {720 // Begin listening for a client connect.721 this.WaitForConnection();722 723 try724 {725#if UNIX726 userName = System.Environment.UserName;727#else728 userName = WindowsIdentity.GetCurrent().Name;729#endif730 }731 catch (System.Security.SecurityException) { }732 733 // Logging.734 _tracer.WriteMessage("RemoteSessionNamedPipeServer", "StartListening", Guid.Empty,735 "Client connection started on Process {0} in AppDomainName {1} for User {2}.", processId, appDomainName, userName);736 PSEtwLog.LogOperationalInformation(737 PSEventId.NamedPipeIPC_ServerConnect, PSOpcode.Connect, PSTask.NamedPipe,738 PSKeyword.UseAlwaysOperational,739 processId, appDomainName, userName);740 741 // Create reader/writer streams.742 TextReader = new StreamReader(Stream);743 TextWriter = new StreamWriter(Stream);744 TextWriter.AutoFlush = true;745 }746 catch (Exception e)747 {748 ex = e;749 }750 751 if (ex != null)752 {753 // Error during connection handling. Don't try to restart listening thread.754 string errorMessage = !string.IsNullOrEmpty(ex.Message) ? ex.Message : string.Empty;755 _tracer.WriteMessage("RemoteSessionNamedPipeServer", "StartListening", Guid.Empty,756 "Unexpected error in listener thread on process {0} in AppDomainName {1}. Error Message: {2}", processId, appDomainName, errorMessage);757 PSEtwLog.LogOperationalError(PSEventId.NamedPipeIPC_ServerListenerError, PSOpcode.Exception, PSTask.NamedPipe,758 PSKeyword.UseAlwaysOperational,759 processId, appDomainName, errorMessage);760 761 Dispose();762 return;763 }764 765 // Start server session on new connection.766 ex = null;767 try768 {769 Action<RemoteSessionNamedPipeServer> clientConnectCallback = state as Action<RemoteSessionNamedPipeServer>;770 Dbg.Assert(clientConnectCallback != null, "Client callback should never be null.");771 772 // Handle a new client connect by making the callback.773 // The callback must handle all exceptions except774 // for a named pipe disposed or disconnected exception775 // which propagates up to the thread listener loop.776 clientConnectCallback(this);777 }778 catch (IOException)779 {780 // Expected connection terminated.781 }782 catch (ObjectDisposedException)783 {784 // Expected from PS transport close/dispose.785 }786 catch (Exception e)787 {788 ex = e;789 restartListenerThread = false;790 }791 792 // Logging.793 _tracer.WriteMessage("RemoteSessionNamedPipeServer", "StartListening", Guid.Empty,794 "Client connection ended on process {0} in AppDomainName {1} for User {2}.", processId, appDomainName, userName);795 PSEtwLog.LogOperationalInformation(796 PSEventId.NamedPipeIPC_ServerDisconnect, PSOpcode.Close, PSTask.NamedPipe,797 PSKeyword.UseAlwaysOperational,798 processId, appDomainName, userName);799 800 if (ex == null)801 {802 // Normal listener exit.803 _tracer.WriteMessage("RemoteSessionNamedPipeServer", "StartListening", Guid.Empty,804 "Listener thread ended on process {0} in AppDomainName {1}.", processId, appDomainName);805 PSEtwLog.LogOperationalInformation(PSEventId.NamedPipeIPC_ServerListenerEnded, PSOpcode.Close, PSTask.NamedPipe,806 PSKeyword.UseAlwaysOperational,807 processId, appDomainName);808 }809 else810 {811 // Unexpected error.812 string errorMessage = !string.IsNullOrEmpty(ex.Message) ? ex.Message : string.Empty;813 _tracer.WriteMessage("RemoteSessionNamedPipeServer", "StartListening", Guid.Empty,814 "Unexpected error in listener thread on process {0} in AppDomainName {1}. Error Message: {2}", processId, appDomainName, errorMessage);815 PSEtwLog.LogOperationalError(PSEventId.NamedPipeIPC_ServerListenerError, PSOpcode.Exception, PSTask.NamedPipe,816 PSKeyword.UseAlwaysOperational,817 processId, appDomainName, errorMessage);818 }819 820 lock (_syncObject)821 {822 IsListenerRunning = false;823 }824 825 // Ensure this named pipe server object is disposed.826 Dispose();827 828 ListenerEnded.SafeInvoke(829 this,830 new ListenerEndedEventArgs(ex, restartListenerThread));831 }832 833 #endregion834 835 #region Static Methods836 837 /// <summary>838 /// Ensures the namedpipe singleton server is running and waits for a client connection.839 /// This is a blocking call that returns after the client connection ends.840 /// This method supports PowerShell running in "NamedPipeServerMode", which is used for841 /// PowerShell Direct Windows Server Container connection and management.842 /// </summary>843 /// <param name="configurationName">Name of the configuration to use.</param>844 internal static void RunServerMode(string configurationName)845 {846 IPCNamedPipeServerEnabled = true;847 CreateIPCNamedPipeServerSingleton();848 849 if (IPCNamedPipeServer == null)850 {851 throw new RuntimeException(RemotingErrorIdStrings.NamedPipeServerCannotStart);852 }853 854 IPCNamedPipeServer.ConfigurationName = configurationName;855 856 ManualResetEventSlim clientConnectionEnded = new ManualResetEventSlim(false);857 IPCNamedPipeServer.ListenerEnded -= OnIPCNamedPipeServerEnded;858 IPCNamedPipeServer.ListenerEnded += (sender, e) => clientConnectionEnded.Set();859 860 // Wait for server to service a single client connection.861 clientConnectionEnded.Wait();862 clientConnectionEnded.Dispose();863 IPCNamedPipeServerEnabled = false;864 }865 866 /// <summary>867 /// Creates the process named pipe server object singleton and868 /// starts the client listening thread.869 /// </summary>870 internal static void CreateIPCNamedPipeServerSingleton()871 {872 lock (s_syncObject)873 {874 if (!IPCNamedPipeServerEnabled) { return; }875 876 if (IPCNamedPipeServer == null || IPCNamedPipeServer.IsDisposed)877 {878 try879 {880 try881 {882 IPCNamedPipeServer = CreateRemoteSessionNamedPipeServer();883 }884 catch (IOException)885 {886 // Expected when named pipe server for this process already exists.887 // This can happen if process has multiple AppDomains hosting PowerShell (SMA.dll).888 return;889 }890 891 // Listener ended callback, used to create listening new pipe server.892 IPCNamedPipeServer.ListenerEnded += OnIPCNamedPipeServerEnded;893 894 // Start the pipe server listening thread, and provide client connection callback.895 IPCNamedPipeServer.StartListening(ClientConnectionCallback);896 }897 catch (Exception)898 {899 IPCNamedPipeServer = null;900 }901 }902 }903 }904 905 private static void CreateProcessExitHandler()906 {907 AppDomain.CurrentDomain.ProcessExit += (sender, args) =>908 {909 IPCNamedPipeServerEnabled = false;910 RemoteSessionNamedPipeServer namedPipeServer = IPCNamedPipeServer;911 if (namedPipeServer != null)912 {913 try914 {915 // Terminate the IPC thread.916 namedPipeServer.Dispose();917 }918 catch (ObjectDisposedException)919 {920 // Ignore if object already disposed.921 }922 catch (Exception)923 {924 // Don't throw an exception on the app domain unload event thread.925 }926 }927 };928 }929 930 private static void OnIPCNamedPipeServerEnded(object sender, ListenerEndedEventArgs args)931 {932 if (args.RestartListener)933 {934 CreateIPCNamedPipeServerSingleton();935 }936 }937 938 private static void OnCustomNamedPipeServerEnded(object sender, ListenerEndedEventArgs args)939 {940 if (args.RestartListener && sender is RemoteSessionNamedPipeServer server)941 {942 CreateCustomNamedPipeServer(server.PipeName);943 }944 }945 946 private static void ClientConnectionCallback(RemoteSessionNamedPipeServer pipeServer)947 {948 // Create server mediator object and begin remote session with client.949 NamedPipeProcessMediator.Run(950 string.Empty,951 pipeServer);952 }953 954 #endregion955 }956 957 /// <summary>958 /// Base class for RemoteSessionNamedPipeClient and ContainerSessionNamedPipeClient.959 /// </summary>960 internal class NamedPipeClientBase : IDisposable961 {962 #region Members963 964 private NamedPipeClientStream _clientPipeStream;965 private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();966 967 #endregion968 969 #region Properties970 971 /// <summary>972 /// Accessor for the named pipe reader.973 /// </summary>974 public StreamReader TextReader { get; private set; }975 976 /// <summary>977 /// Accessor for the named pipe writer.978 /// </summary>979 public StreamWriter TextWriter { get; private set; }980 981 /// <summary>982 /// Name of pipe.983 /// </summary>984 public string PipeName985 {986 get;987 internal set;988 }989 990 #endregion991 992 #region IDisposable993 994 /// <summary>995 /// Dispose object.996 /// </summary>997 public void Dispose()998 {999 Dispose(true);1000 GC.SuppressFinalize(this);1001 }1002 1003 private void Dispose(bool disposing)1004 {1005 if (!disposing)1006 {1007 return;1008 }1009 1010 if (TextReader != null)1011 {1012 try { TextReader.Dispose(); }1013 catch (ObjectDisposedException) { }1014 1015 TextReader = null;1016 }1017 1018 if (TextWriter != null)1019 {1020 try { TextWriter.Dispose(); }1021 catch (ObjectDisposedException) { }1022 1023 TextWriter = null;1024 }1025 1026 if (_clientPipeStream != null)1027 {1028 try { _clientPipeStream.Dispose(); }1029 catch (ObjectDisposedException) { }1030 }1031 }1032 1033 #endregion1034 1035 #region Methods1036 1037 /// <summary>1038 /// Connect to named pipe server. This is a blocking call until a1039 /// connection occurs or the timeout time has elapsed.1040 /// </summary>1041 /// <param name="timeout">Connection attempt timeout in milliseconds.</param>1042 public void Connect(1043 int timeout)1044 {1045 // Uses Native API to connect to pipe and return NamedPipeClientStream object.1046 _clientPipeStream = DoConnect(timeout);1047 1048 // Create reader/writer streams.1049 TextReader = new StreamReader(_clientPipeStream);1050 TextWriter = new StreamWriter(_clientPipeStream);1051 TextWriter.AutoFlush = true;1052 1053 _tracer.WriteMessage("NamedPipeClientBase", "Connect", Guid.Empty,1054 "Connection started on pipe: {0}", PipeName);1055 }1056 1057 /// <summary>1058 /// Closes the named pipe.1059 /// </summary>1060 public void Close() => _clientPipeStream?.Dispose();1061 1062 /// <summary>1063 /// Abort connection attempt.1064 /// </summary>1065 public virtual void AbortConnect()1066 { }1067 1068 /// <summary>1069 /// Begin connection attempt.1070 /// </summary>1071 protected virtual NamedPipeClientStream DoConnect(int timeout)1072 {1073 return null;1074 }1075 1076 #endregion1077 }1078 1079 /// <summary>1080 /// Light wrapper class for BCL NamedPipeClientStream class, that1081 /// creates the named pipe name and initiates connection to1082 /// target named pipe server.1083 /// </summary>1084 internal sealed class RemoteSessionNamedPipeClient : NamedPipeClientBase1085 {1086 #region Members1087 1088 private volatile bool _connecting;1089 1090 #endregion1091 1092 #region Constructors1093 1094 private RemoteSessionNamedPipeClient()1095 { }1096 1097 /// <summary>1098 /// Constructor. Creates Named Pipe based on process object.1099 /// </summary>1100 /// <param name="process">Target process object for pipe.</param>1101 /// <param name="appDomainName">AppDomain name or null for default AppDomain.</param>1102 public RemoteSessionNamedPipeClient(System.Diagnostics.Process process, string appDomainName)1103 : this(NamedPipeUtils.CreateProcessPipeName(process, appDomainName))1104 { }1105 1106 /// <summary>1107 /// Constructor. Creates Named Pipe based on process Id.1108 /// </summary>1109 /// <param name="procId">Target process Id for pipe.</param>1110 /// <param name="appDomainName">AppDomain name or null for default AppDomain.</param>1111 public RemoteSessionNamedPipeClient(int procId, string appDomainName)1112 : this(NamedPipeUtils.CreateProcessPipeName(procId, appDomainName))1113 { }1114 1115 /// <summary>1116 /// Constructor. Creates Named Pipe based on name argument.1117 /// </summary>1118 /// <param name="pipeName">Named Pipe name.</param>1119 internal RemoteSessionNamedPipeClient(1120 string pipeName)1121 {1122 if (pipeName == null)1123 {1124 throw new PSArgumentNullException(nameof(pipeName));1125 }1126 1127 PipeName = pipeName;1128 1129 // Defer creating the .Net NamedPipeClientStream object until we connect.1130 // _clientPipeStream == null.1131 }1132 1133 /// <summary>1134 /// Constructor.1135 /// </summary>1136 /// <param name="serverName"></param>1137 /// <param name="namespaceName"></param>1138 /// <param name="coreName"></param>1139 internal RemoteSessionNamedPipeClient(1140 string serverName,1141 string namespaceName,1142 string coreName)1143 {1144 if (serverName == null) { throw new PSArgumentNullException(nameof(serverName)); }1145 1146 if (namespaceName == null) { throw new PSArgumentNullException(nameof(namespaceName)); }1147 1148 if (coreName == null) { throw new PSArgumentNullException(nameof(coreName)); }1149 1150 PipeName = @"\\" + serverName + @"\" + namespaceName + @"\" + coreName;1151 1152 // Defer creating the .Net NamedPipeClientStream object until we connect.1153 // _clientPipeStream == null.1154 }1155 1156 #endregion1157 1158 #region Public Methods1159 1160 /// <summary>1161 /// Abort connection attempt.1162 /// </summary>1163 public override void AbortConnect()1164 {1165 _connecting = false;1166 }1167 1168 #endregion1169 1170 #region Protected Methods1171 1172 /// <summary>1173 /// Begin connection attempt.1174 /// </summary>1175 protected override NamedPipeClientStream DoConnect(int timeout)1176 {1177 // Repeatedly attempt connection to pipe until timeout expires.1178 int startTime = Environment.TickCount;1179 int elapsedTime = 0;1180 _connecting = true;1181 1182 NamedPipeClientStream namedPipeClientStream = new NamedPipeClientStream(1183 serverName: ".",1184 pipeName: PipeName,1185 direction: PipeDirection.InOut,1186 options: PipeOptions.Asynchronous);1187 1188 namedPipeClientStream.ConnectAsync(timeout);1189 1190 do1191 {1192 if (!namedPipeClientStream.IsConnected)1193 {1194 Thread.Sleep(100);1195 elapsedTime = unchecked(Environment.TickCount - startTime);1196 continue;1197 }1198 1199 _connecting = false;1200 return namedPipeClientStream;