/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs
1 608 строк
61 KB
xtqqczze
Fix `IDE0049` in `System.Management.Automation` [Part 4] (#27380)
07 май 2026, 02:44
Не верифицирован
07 май 2026, 02:44
1919ba8
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. /* * Common file that contains interface definitions for generic server and client * transport managers. * */ using System.Management.Automation.Tracing; using System.Text; using System.IO; using System.Xml; using System.Collections.Generic; using System.Globalization; using System.Threading; using System.Management.Automation.Internal; #if !UNIX using System.Security.Principal; #endif // Don't expose the System.Management.Automation namespace here. This is transport layer // and it shouldn't know anything about the engine. using System.Management.Automation.Remoting.Client; // TODO: this seems ugly...Remoting datatypes should be in remoting namespace using System.Management.Automation.Runspaces.Internal; using PSRemotingCryptoHelper = System.Management.Automation.Internal.PSRemotingCryptoHelper; using RunspaceConnectionInfo = System.Management.Automation.Runspaces.RunspaceConnectionInfo; using TypeTable = System.Management.Automation.Runspaces.TypeTable; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting { #region TransportErrorOccuredEventArgs /// <summary> /// Transport method for error reporting. /// </summary> public enum TransportMethodEnum { /// <summary> /// CreateShellEx /// </summary> CreateShellEx = 0, /// <summary> /// RunShellCommandEx /// </summary> RunShellCommandEx = 1, /// <summary> /// SendShellInputEx /// </summary> SendShellInputEx = 2, /// <summary> /// ReceiveShellOutputEx /// </summary> ReceiveShellOutputEx = 3, /// <summary> /// CloseShellOperationEx /// </summary> CloseShellOperationEx = 4, /// <summary> /// CommandInputEx /// </summary> CommandInputEx = 5, /// <summary> /// ReceiveCommandOutputEx /// </summary> ReceiveCommandOutputEx = 6, /// <summary> /// DisconnectShellEx /// </summary> DisconnectShellEx = 7, /// <summary> /// ReconnectShellEx /// </summary> ReconnectShellEx = 8, /// <summary> /// ConnectShellEx /// </summary> ConnectShellEx = 9, /// <summary> /// ReconnectShellCommandEx /// </summary> ReconnectShellCommandEx = 10, /// <summary> /// ConnectShellCommandEx /// </summary> ConnectShellCommandEx = 11, /// <summary> /// Unknown /// </summary> Unknown = 12, } /// <summary> /// Event arguments passed to TransportErrorOccurred handlers. /// </summary> public sealed class TransportErrorOccuredEventArgs : EventArgs { /// <summary> /// Constructor. /// </summary> /// <param name="e"> /// Error occurred. /// </param> /// <param name="m"> /// The transport method that raised the error. /// </param> public TransportErrorOccuredEventArgs( PSRemotingTransportException e, TransportMethodEnum m) { Exception = e; ReportingTransportMethod = m; } /// <summary> /// Gets the error occurred. /// </summary> internal PSRemotingTransportException Exception { get; set; } /// <summary> /// Transport method that is reporting this error. /// </summary> internal TransportMethodEnum ReportingTransportMethod { get; } } #endregion #region ConnectionStatusEventArgs /// <summary> /// Robust Connection notifications. /// </summary> internal enum ConnectionStatus { NetworkFailureDetected = 1, ConnectionRetryAttempt = 2, ConnectionRetrySucceeded = 3, AutoDisconnectStarting = 4, AutoDisconnectSucceeded = 5, InternalErrorAbort = 6 } /// <summary> /// ConnectionStatusEventArgs. /// </summary> internal class ConnectionStatusEventArgs : EventArgs { internal ConnectionStatusEventArgs(ConnectionStatus notification) { Notification = notification; } internal ConnectionStatus Notification { get; } } #endregion #region CreateCompleteEventArgs /// <summary> /// CreateCompleteEventArgs. /// </summary> internal class CreateCompleteEventArgs : EventArgs { internal RunspaceConnectionInfo ConnectionInfo { get; } internal CreateCompleteEventArgs( RunspaceConnectionInfo connectionInfo) { ConnectionInfo = connectionInfo; } } #endregion /// <summary> /// Contains implementation that is common to both client and server /// transport managers. /// </summary> public abstract class BaseTransportManager : IDisposable { #region tracer [TraceSource("Transport", "Traces BaseWSManTransportManager")] private static readonly PSTraceSource s_baseTracer = PSTraceSource.GetTracer("Transport", "Traces BaseWSManTransportManager"); #endregion #region Global Constants // KeepAlive: Server 4 minutes, Client 3 minutes // The server timeout value has to be bigger than the client timeout value. // This is due to the WinRM implementation on the Listener. // So We added a 1 minute network delay to count for this. internal const int ServerDefaultKeepAliveTimeoutMs = 4 * 60 * 1000; // milliseconds = 4 minutes internal const int ClientDefaultOperationTimeoutMs = 3 * 60 * 1000; // milliseconds = 3 minutes // Close timeout: to prevent unbounded close operation, we set a 1 minute bound. internal const int ClientCloseTimeoutMs = 60 * 1000; // This value instructs the server to use whatever setting it has for idle timeout. internal const int UseServerDefaultIdleTimeout = -1; internal const uint UseServerDefaultIdleTimeoutUInt = uint.MaxValue; // Minimum allowed idle timeout time is 60 seconds. internal const int MinimumIdleTimeout = 60 * 1000; internal const int DefaultFragmentSize = 32 << 10; // 32KB // Quota related consts and session variables. internal const int MaximumReceivedDataSize = 50 << 20; // 50MB internal const int MaximumReceivedObjectSize = 10 << 20; // 10MB // Session variables supporting powershell quotas. internal const string MAX_RECEIVED_DATA_PER_COMMAND_MB = "PSMaximumReceivedDataSizePerCommandMB"; internal const string MAX_RECEIVED_OBJECT_SIZE_MB = "PSMaximumReceivedObjectSizeMB"; #endregion #region Private Data // fragmentor used to fragment & defragment objects added to this collection. private readonly ReceiveDataCollection.OnDataAvailableCallback _onDataAvailableCallback; // crypto helper used for encrypting/decrypting // secure string #endregion #region EventHandlers internal event EventHandler<TransportErrorOccuredEventArgs> WSManTransportErrorOccured; /// <summary> /// Event that is raised when a remote object is available. The event is raised /// from a WSMan transport thread. Since this thread can hold on to a HTTP /// connection, the event handler should complete processing as fast as possible. /// Importantly the event handler should not generate any call that results in a /// user request like host.ReadLine(). /// </summary> internal event EventHandler<RemoteDataEventArgs> DataReceived; /// <summary> /// Listen to this event to observe the PowerShell guid of the processed object. /// </summary> public event EventHandler PowerShellGuidObserver; #endregion #region Constructor internal BaseTransportManager(PSRemotingCryptoHelper cryptoHelper) { CryptoHelper = cryptoHelper; // create a common fragmentor used by this transport manager to send and receive data. // so type information is serialized only the first time an object of a particular type // is sent. only data is serialized for the rest of the objects of the same type. Fragmentor = new Fragmentor(DefaultFragmentSize, cryptoHelper); ReceivedDataCollection = new PriorityReceiveDataCollection(Fragmentor, (this is BaseClientTransportManager)); _onDataAvailableCallback = new ReceiveDataCollection.OnDataAvailableCallback(OnDataAvailableCallback); } #endregion #region Helper Methods internal Fragmentor Fragmentor { get; set; } /// <summary> /// This is needed to deserialize objects coming from the network. /// This may be null..in which case type rehydration does not happen. /// At construction time we may not have typetable (server runspace /// is created only when a request from the client)..so this is /// a property on the base transport manager to allow for setting at /// a later time. /// </summary> internal TypeTable TypeTable { get { return Fragmentor.TypeTable; } set { Fragmentor.TypeTable = value; } } /// <summary> /// Uses the "OnDataAvailableCallback" to handle Deserialized objects. /// </summary> /// <param name="data"> /// data to process /// </param> /// <param name="stream"> /// priority stream this data belongs to /// </param> internal virtual void ProcessRawData(byte[] data, string stream) { try { ProcessRawData(data, stream, _onDataAvailableCallback); } catch (Exception exception) { // This will get executed on a thread pool thread.. // so we need to protect that thread, hence catching // all exceptions s_baseTracer.WriteLine("Exception processing data. {0}", exception.Message); PSRemotingTransportException e = new PSRemotingTransportException(exception.Message, exception); TransportErrorOccuredEventArgs eventargs = new TransportErrorOccuredEventArgs(e, TransportMethodEnum.ReceiveShellOutputEx); RaiseErrorHandler(eventargs); return; } } /// <summary> /// </summary> /// <param name="data"> /// data to process /// </param> /// <param name="stream"> /// priority stream this data belongs to /// </param> /// <param name="dataAvailableCallback"> /// used by the caller to supply a callback to handle deserialized object. /// </param> /// <exception cref="Exception"> /// Since dataAvailableCallback is called in this method, and the handler /// may be handled by 3rd party code (eventually),this may throw any exception. /// </exception> internal void ProcessRawData(byte[] data, string stream, ReceiveDataCollection.OnDataAvailableCallback dataAvailableCallback) { Dbg.Assert(data != null, "Cannot process null data"); s_baseTracer.WriteLine("Processing incoming data for stream {0}.", stream); bool shouldProcess = false; DataPriorityType dataPriority = DataPriorityType.Default; if (stream.Equals(WSManNativeApi.WSMAN_STREAM_ID_STDIN, StringComparison.OrdinalIgnoreCase) || stream.Equals(WSManNativeApi.WSMAN_STREAM_ID_STDOUT, StringComparison.OrdinalIgnoreCase)) { shouldProcess = true; } else if (stream.Equals(WSManNativeApi.WSMAN_STREAM_ID_PROMPTRESPONSE, StringComparison.OrdinalIgnoreCase)) { dataPriority = DataPriorityType.PromptResponse; shouldProcess = true; } if (!shouldProcess) { // we dont support this stream..so ignore the data Dbg.Assert(false, string.Format( CultureInfo.InvariantCulture, "Data should be from one of the streams : {0} or {1} or {2}", WSManNativeApi.WSMAN_STREAM_ID_STDIN, WSManNativeApi.WSMAN_STREAM_ID_STDOUT, WSManNativeApi.WSMAN_STREAM_ID_PROMPTRESPONSE)); s_baseTracer.WriteLine("{0} is not a valid stream", stream); } // process data ReceivedDataCollection.ProcessRawData(data, dataPriority, dataAvailableCallback); } /// <summary> /// </summary> /// <param name="remoteObject"></param> /// <exception cref="Exception"> /// The handler may be handled by 3rd party code (eventually), /// this may throw any exception. /// </exception> internal void OnDataAvailableCallback(RemoteDataObject<PSObject> remoteObject) { // log the data to crimson logs PSEtwLog.LogAnalyticInformational(PSEventId.TransportReceivedObject, PSOpcode.Open, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, remoteObject.RunspacePoolId.ToString(), remoteObject.PowerShellId.ToString(), (uint)(remoteObject.Destination), (uint)(remoteObject.DataType), (uint)(remoteObject.TargetInterface)); // This might throw exceptions which the caller handles. PowerShellGuidObserver.SafeInvoke(remoteObject.PowerShellId, EventArgs.Empty); RemoteDataEventArgs eventArgs = new RemoteDataEventArgs(remoteObject); DataReceived.SafeInvoke(this, eventArgs); } /// <summary> /// Copy the DataReceived event handlers to the supplied transport Manager. /// </summary> /// <param name="transportManager"></param> public void MigrateDataReadyEventHandlers(BaseTransportManager transportManager) { foreach (Delegate handler in transportManager.DataReceived.GetInvocationList()) { DataReceived += (EventHandler<RemoteDataEventArgs>)handler; } } /// <summary> /// Raise the error handlers. /// </summary> /// <param name="eventArgs"></param> public virtual void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArgs) { WSManTransportErrorOccured.SafeInvoke(this, eventArgs); } /// <summary> /// Crypto handler to be used for encrypting/decrypting /// secure strings. /// </summary> internal PSRemotingCryptoHelper CryptoHelper { get; set; } /// <summary> /// A data buffer used to store data received from remote machine. /// </summary> internal PriorityReceiveDataCollection ReceivedDataCollection { get; } #endregion #region IDisposable implementation /// <summary> /// Dispose the transport and release resources. /// </summary> public void Dispose() { Dispose(true); // if already disposing..no need to let finalizer thread // put resources to clean this object. System.GC.SuppressFinalize(this); } /// <summary> /// Dispose resources. /// </summary> protected virtual void Dispose(bool isDisposing) { if (isDisposing) { ReceivedDataCollection.Dispose(); } } #endregion } } namespace System.Management.Automation.Remoting.Client { /// <summary> /// Remoting base client transport manager. /// </summary> public abstract class BaseClientTransportManager : BaseTransportManager, IDisposable { #region Tracer [TraceSource("ClientTransport", "Traces ClientTransportManager")] internal static PSTraceSource tracer = PSTraceSource.GetTracer("ClientTransport", "Traces ClientTransportManager"); #endregion #region Data internal bool isClosed; internal object syncObject = new object(); internal PrioritySendDataCollection dataToBeSent; // used to handle callbacks from the server..these are used to synchronize received callbacks private readonly Queue<CallbackNotificationInformation> _callbackNotificationQueue; private readonly ReceiveDataCollection.OnDataAvailableCallback _onDataAvailableCallback; private bool _isServicingCallbacks; private bool _suspendQueueServicing; private bool _isDebuggerSuspend; // this is used log crimson messages. // keeps track of whether a receive request has been placed on transport internal bool receiveDataInitiated; #endregion #region Constructors internal BaseClientTransportManager(Guid runspaceId, PSRemotingCryptoHelper cryptoHelper) : base(cryptoHelper) { RunspacePoolInstanceId = runspaceId; dataToBeSent = new PrioritySendDataCollection(); _onDataAvailableCallback = new ReceiveDataCollection.OnDataAvailableCallback(OnDataAvailableHandler); _callbackNotificationQueue = new Queue<CallbackNotificationInformation>(); } #endregion #region Events /// <summary> /// Event that is raised when a create operation on transport has been successfully completed /// The event is raised /// from a WSMan transport thread. Since this thread can hold on to a HTTP /// connection, the event handler should complete processing as fast as possible. /// Importantly the event handler should not generate any call that results in a /// user request like host.ReadLine(). /// /// Errors (occurred during connection attempt) are reported through WSManTransportErrorOccured /// event. /// </summary> internal event EventHandler<CreateCompleteEventArgs> CreateCompleted; /// <summary> /// Event that is raised when a remote connection is successfully closed. The event is raised /// from a WSMan transport thread. Since this thread can hold on to a HTTP /// connection, the event handler should complete processing as fast as possible. /// Importantly the event handler should not generate any call that results in a /// user request like host.ReadLine(). /// /// Errors (occurred during connection attempt) are reported through WSManTransportErrorOccured /// event. /// </summary> /// <remarks> /// The eventhandler should make sure not to throw any exceptions. /// </remarks> internal event EventHandler<EventArgs> CloseCompleted; /// <summary> /// Indicated successful completion of a connect operation on transport /// /// Errors are reported through WSManTransportErrorOccured /// event. /// </summary> internal event EventHandler<EventArgs> ConnectCompleted; /// <summary> /// Indicated successful completion of a disconnect operation on transport /// /// Errors are reported through WSManTransportErrorOccured /// event. /// </summary> internal event EventHandler<EventArgs> DisconnectCompleted; /// <summary> /// Indicated successful completion of a reconnect operation on transport /// /// Errors are reported through WSManTransportErrorOccured /// event. /// </summary> internal event EventHandler<EventArgs> ReconnectCompleted; /// <summary> /// Indicates that the transport/command is ready for a disconnect operation. /// /// Errors are reported through WSManTransportErrorOccured event. /// </summary> internal event EventHandler<EventArgs> ReadyForDisconnect; /// <summary> /// Event to pass Robust Connection notifications to client. /// </summary> internal event EventHandler<ConnectionStatusEventArgs> RobustConnectionNotification; /// <summary> /// Indicates successful processing of a delay stream request on a receive operation /// /// this event is useful when PS wants to invoke a pipeline in disconnected mode. /// </summary> internal event EventHandler<EventArgs> DelayStreamRequestProcessed; #endregion #region Properties /// <summary> /// Gets the data collection which is used by this transport manager to send /// data to the server. /// </summary> internal PrioritySendDataCollection DataToBeSentCollection { get { return dataToBeSent; } } /// <summary> /// Used to log crimson messages. /// </summary> internal Guid RunspacePoolInstanceId { get; } /// <summary> /// Raise the Connect completed handler. /// </summary> internal void RaiseCreateCompleted(CreateCompleteEventArgs eventArgs) { CreateCompleted.SafeInvoke(this, eventArgs); } internal void RaiseConnectCompleted() { ConnectCompleted.SafeInvoke(this, EventArgs.Empty); } internal void RaiseDisconnectCompleted() { DisconnectCompleted.SafeInvoke(this, EventArgs.Empty); } internal void RaiseReconnectCompleted() { ReconnectCompleted.SafeInvoke(this, EventArgs.Empty); } /// <summary> /// Raise the close completed handler. /// </summary> internal void RaiseCloseCompleted() { CloseCompleted.SafeInvoke(this, EventArgs.Empty); } /// <summary> /// Raise the ReadyForDisconnect event. /// </summary> internal void RaiseReadyForDisconnect() { ReadyForDisconnect.SafeInvoke(this, EventArgs.Empty); } /// <summary> /// Queue the robust connection notification event. /// </summary> /// <param name="flags">Determines what kind of notification.</param> internal void QueueRobustConnectionNotification(int flags) { ConnectionStatusEventArgs args = null; switch (flags) { case (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_NETWORK_FAILURE_DETECTED: args = new ConnectionStatusEventArgs(ConnectionStatus.NetworkFailureDetected); break; case (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_RETRYING_AFTER_NETWORK_FAILURE: args = new ConnectionStatusEventArgs(ConnectionStatus.ConnectionRetryAttempt); break; case (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_RECONNECTED_AFTER_NETWORK_FAILURE: args = new ConnectionStatusEventArgs(ConnectionStatus.ConnectionRetrySucceeded); break; case (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_SHELL_AUTODISCONNECTING: args = new ConnectionStatusEventArgs(ConnectionStatus.AutoDisconnectStarting); break; case (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_SHELL_AUTODISCONNECTED: args = new ConnectionStatusEventArgs(ConnectionStatus.AutoDisconnectSucceeded); break; case (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_RETRY_ABORTED_DUE_TO_INTERNAL_ERROR: args = new ConnectionStatusEventArgs(ConnectionStatus.InternalErrorAbort); break; } // Queue worker item to raise the event so that all robust connection // events are raised in the same order as received. EnqueueAndStartProcessingThread(null, null, args); } /// <summary> /// Raise the Robust Connection notification event. /// </summary> /// <param name="args">ConnectionStatusEventArgs.</param> internal void RaiseRobustConnectionNotification(ConnectionStatusEventArgs args) { RobustConnectionNotification.SafeInvoke(this, args); } internal void RaiseDelayStreamProcessedEvent() { DelayStreamRequestProcessed.SafeInvoke(this, EventArgs.Empty); } #endregion #region Received Data Processing Thread internal override void ProcessRawData(byte[] data, string stream) { if (isClosed) { return; } try { base.ProcessRawData(data, stream, _onDataAvailableCallback); } catch (PSRemotingTransportException pte) { // PSRemotingTransportException need not be wrapped in another PSRemotingTransportException. tracer.WriteLine("Exception processing data. {0}", pte.Message); TransportErrorOccuredEventArgs eventargs = new TransportErrorOccuredEventArgs(pte, TransportMethodEnum.ReceiveShellOutputEx); EnqueueAndStartProcessingThread(null, eventargs, null); return; } catch (Exception exception) { // Enqueue an Exception to process in a thread-pool thread. Processing // Exception in a thread pool thread is important as calling // WSManCloseShell/Command from a Receive callback results in a deadlock. tracer.WriteLine("Exception processing data. {0}", exception.Message); PSRemotingTransportException e = new PSRemotingTransportException(exception.Message); TransportErrorOccuredEventArgs eventargs = new TransportErrorOccuredEventArgs(e, TransportMethodEnum.ReceiveShellOutputEx); EnqueueAndStartProcessingThread(null, eventargs, null); return; } } private void OnDataAvailableHandler(RemoteDataObject<PSObject> remoteObject) { EnqueueAndStartProcessingThread(remoteObject, null, null); } /// <summary> /// Enqueue a deserialized object or an Exception to process in a thread pool /// thread. Processing Exception in a thread pool thread is important as calling /// WSManCloseShell/Command from a Receive callback results in a deadlock. /// </summary> /// <param name="remoteObject"> /// Deserialized Object to process in a thread-pool thread. This should be null /// when <paramref name="transportException"/> is specified. /// </param> /// <param name="privateData"> /// Data that is neither RemoteObject or Exception. This is used by Client Command /// Transport manager to raise SignalCompleted callback. /// </param> /// <param name="transportErrorArgs"> /// Error containing transport exception. /// </param> internal void EnqueueAndStartProcessingThread(RemoteDataObject<PSObject> remoteObject, TransportErrorOccuredEventArgs transportErrorArgs, object privateData) { if (isClosed) { return; } lock (_callbackNotificationQueue) { if ((remoteObject != null) || (transportErrorArgs != null) || (privateData != null)) { CallbackNotificationInformation rcvdDataInfo = new CallbackNotificationInformation(); rcvdDataInfo.remoteObject = remoteObject; rcvdDataInfo.transportError = transportErrorArgs; rcvdDataInfo.privateData = privateData; if (remoteObject != null && (remoteObject.DataType == RemotingDataType.PublicKey || remoteObject.DataType == RemotingDataType.EncryptedSessionKey || remoteObject.DataType == RemotingDataType.PublicKeyRequest)) { CryptoHelper.Session.BaseSessionDataStructureHandler.RaiseKeyExchangeMessageReceived(remoteObject); } else { _callbackNotificationQueue.Enqueue(rcvdDataInfo); } } if (_suspendQueueServicing && _isDebuggerSuspend) { // Remove debugger queue suspension if remoteObject requires user response. _suspendQueueServicing = !CheckForInteractiveHostCall(remoteObject); } if (_isServicingCallbacks || _suspendQueueServicing) { // a thread pool thread is already processing callbacks or // the queue processing is suspended. return; } if (_callbackNotificationQueue.Count > 0) { _isServicingCallbacks = true; // Start a thread pool thread to process callbacks. #if !UNIX WindowsIdentity identityToImpersonate; Utils.TryGetWindowsImpersonatedIdentity(out identityToImpersonate); Utils.QueueWorkItemWithImpersonation( identityToImpersonate, new WaitCallback(ServicePendingCallbacks), null); #else ThreadPool.QueueUserWorkItem(new WaitCallback(ServicePendingCallbacks)); #endif } } } /// <summary> /// Helper method to check RemoteDataObject for a host call requiring user /// interaction. /// </summary> /// <param name="remoteObject">Remote data object.</param> /// <returns>True if remote data object requires a user response.</returns> private static bool CheckForInteractiveHostCall(RemoteDataObject<PSObject> remoteObject) { bool interactiveHostCall = false; if ((remoteObject != null) && (remoteObject.DataType == RemotingDataType.RemoteHostCallUsingPowerShellHost)) { RemoteHostMethodId methodId = 0; try { methodId = RemotingDecoder.GetPropertyValue<RemoteHostMethodId>(remoteObject.Data, RemoteDataNameStrings.MethodId); } catch (PSArgumentNullException) { } catch (PSRemotingDataStructureException) { } // If new remote host call methods are added then we need to evaluate if they are interactive. Dbg.Assert(methodId <= RemoteHostMethodId.PromptForChoiceMultipleSelection, "A new remote host method Id was added. Update switch statement as needed."); switch (methodId) { case RemoteHostMethodId.Prompt: case RemoteHostMethodId.PromptForChoice: case RemoteHostMethodId.PromptForChoiceMultipleSelection: case RemoteHostMethodId.PromptForCredential1: case RemoteHostMethodId.PromptForCredential2: case RemoteHostMethodId.ReadKey: case RemoteHostMethodId.ReadLine: case RemoteHostMethodId.ReadLineAsSecureString: interactiveHostCall = true; break; } } return interactiveHostCall; } internal void ServicePendingCallbacks(object objectToProcess) { tracer.WriteLine("ServicePendingCallbacks thread is starting"); PSEtwLog.ReplaceActivityIdForCurrentThread(RunspacePoolInstanceId, PSEventId.OperationalTransferEventRunspacePool, PSEventId.AnalyticTransferEventRunspacePool, PSKeyword.Transport, PSTask.None); try { while (true) { // if the transport manager is closed return. if (isClosed) { return; } CallbackNotificationInformation rcvdDataInfo = null; lock (_callbackNotificationQueue) { // If queue is empty or if queue servicing is suspended // then break out of loop. if (_callbackNotificationQueue.Count == 0 || _suspendQueueServicing) { break; } rcvdDataInfo = _callbackNotificationQueue.Dequeue(); } // Handle callback. if (rcvdDataInfo != null) { // Handling transport exception in thread-pool thread if (rcvdDataInfo.transportError != null) { RaiseErrorHandler(rcvdDataInfo.transportError); break; } else if (rcvdDataInfo.privateData != null) { ProcessPrivateData(rcvdDataInfo.privateData); } else { base.OnDataAvailableCallback(rcvdDataInfo.remoteObject); } } } } catch (Exception exception) { // This will get executed on a thread pool thread.. // so we need to protect that thread, hence catching // all exceptions tracer.WriteLine("Exception processing data. {0}", exception.Message); PSRemotingTransportException e = new PSRemotingTransportException(exception.Message, exception); TransportErrorOccuredEventArgs eventargs = new TransportErrorOccuredEventArgs(e, TransportMethodEnum.ReceiveShellOutputEx); RaiseErrorHandler(eventargs); return; } finally { lock (_callbackNotificationQueue) { tracer.WriteLine("ServicePendingCallbacks thread is exiting"); _isServicingCallbacks = false; // check if any new runspace request has arrived.. EnqueueAndStartProcessingThread(null, null, null); } } } internal bool IsServicing { get { lock (_callbackNotificationQueue) { return _isServicingCallbacks; } } } internal void SuspendQueue(bool debuggerSuspend = false) { lock (_callbackNotificationQueue) { _isDebuggerSuspend = debuggerSuspend; _suspendQueueServicing = true; } } internal void ResumeQueue() { lock (_callbackNotificationQueue) { _isDebuggerSuspend = false; if (_suspendQueueServicing) { _suspendQueueServicing = false; // Process any items in queue. EnqueueAndStartProcessingThread(null, null, null); } } } /// <summary> /// Used by ServicePendingCallbacks to give the control to derived classes for /// processing data that the base class does not understand. /// </summary> /// <param name="privateData"> /// Derived class specific data to process. For command transport manager this /// should be a boolean. /// </param> internal virtual void ProcessPrivateData(object privateData) { } internal class CallbackNotificationInformation { // only one of the following 2 should be present.. // anyway transportException takes precedence over remoteObject. internal RemoteDataObject<PSObject> remoteObject; internal TransportErrorOccuredEventArgs transportError; // Used by ServicePendingCallbacks to give the control to derived classes for // processing data that the base class does not understand. internal object privateData; } #endregion #region Abstract / Virtual methods /// <summary> /// Create the transport manager and initiate connection. /// </summary> public abstract void CreateAsync(); internal abstract void ConnectAsync(); /// <summary> /// The caller should make sure the call is synchronized. /// </summary> public virtual void CloseAsync() { // Clear the send collection dataToBeSent.Clear(); } internal virtual void StartReceivingData() { throw new NotImplementedException(); } /// <summary> /// Method to have transport prepare for a disconnect operation. /// </summary> internal virtual void PrepareForDisconnect() { throw new NotImplementedException(); } /// <summary> /// Method to resume post disconnect operations. /// </summary> internal virtual void PrepareForConnect() { throw new NotImplementedException(); } #endregion #region Clean up /// <summary> /// Finalizes an instance of the <see cref="BaseClientTransportManager"/> class. /// </summary> ~BaseClientTransportManager() { if (isClosed) { Dispose(false); } else { // wait for the close to be completed and then release the resources. this.CloseCompleted += (object source, EventArgs args) => Dispose(false); try { // looks like Dispose is not called for this transport manager // try closing the transport manager. CloseAsync(); } catch (ObjectDisposedException) { // intentionally blank } } } /// <summary> /// Dispose resources. /// </summary> protected override void Dispose(bool isDisposing) { // clear event handlers this.CreateCompleted = null; this.CloseCompleted = null; this.ConnectCompleted = null; this.DisconnectCompleted = null; this.ReconnectCompleted = null; // let base dispose its resources. base.Dispose(isDisposing); } #endregion } /// <summary> /// Remoting base client session transport manager. /// </summary> public abstract class BaseClientSessionTransportManager : BaseClientTransportManager, IDisposable { #region Constructors internal BaseClientSessionTransportManager(Guid runspaceId, PSRemotingCryptoHelper cryptoHelper) : base(runspaceId, cryptoHelper) { } #endregion #region Abstract / Virtual Methods /// <summary> /// Creates a command transport manager. This will create a new PrioritySendDataCollection which should be used to /// send data to the server. /// </summary> /// <param name="connectionInfo"> /// Connection info to be used for creating the command. /// </param> /// <param name="cmd"> /// Command for which transport manager is created. /// </param> /// <param name="noInput"> /// true if the command has input. /// </param> /// <returns></returns> internal virtual BaseClientCommandTransportManager CreateClientCommandTransportManager(RunspaceConnectionInfo connectionInfo, ClientRemotePowerShell cmd, bool noInput) { throw new NotImplementedException(); } /// <summary> /// RunspacePool data structure handler uses this method to remove association of a command transport manager /// from a session transport manager. /// </summary> /// <param name="powerShellCmdId"></param> internal virtual void RemoveCommandTransportManager(Guid powerShellCmdId) { } /// <summary> /// Temporarily disconnect an active session. /// </summary> internal virtual void DisconnectAsync() { throw new NotImplementedException(); } /// <summary> /// Reconnect back a temporarily disconnected session. /// </summary> internal virtual void ReconnectAsync() { throw new NotImplementedException(); } /// <summary> /// Redirect the transport manager to point to a new URI. /// </summary> /// <param name="newUri"> /// Redirect Uri to connect to. /// </param> /// <param name="connectionInfo"> /// Connection info object used for retrieving credential, auth. mechanism etc. /// </param> internal virtual void Redirect(Uri newUri, RunspaceConnectionInfo connectionInfo) { throw new NotImplementedException(); } /// <summary> /// Used by callers to prepare the session transportmanager for a URI redirection. /// This must be called only after Create callback (or Error form create) is received. /// Callers must catch the close completed event and call Redirect to perform the redirection. /// </summary> internal virtual void PrepareForRedirection() { throw new NotImplementedException(); } #endregion } internal abstract class BaseClientCommandTransportManager : BaseClientTransportManager, IDisposable { #region Private / Protected Data // pipeline in the form cmd1 | cmd2.. this is used by authz module for early validation. protected StringBuilder cmdText; protected SerializedDataStream serializedPipeline; protected Guid powershellInstanceId; protected Guid PowershellInstanceId { get { return powershellInstanceId; } } #endregion internal bool startInDisconnectedMode = false; #region Constructors protected BaseClientCommandTransportManager(ClientRemotePowerShell shell, PSRemotingCryptoHelper cryptoHelper, BaseClientSessionTransportManager sessnTM) : base(sessnTM.RunspacePoolInstanceId, cryptoHelper) { Fragmentor.FragmentSize = sessnTM.Fragmentor.FragmentSize; Fragmentor.TypeTable = sessnTM.Fragmentor.TypeTable; dataToBeSent.Fragmentor = base.Fragmentor; // used for Crimson logging. powershellInstanceId = shell.PowerShell.InstanceId; cmdText = new StringBuilder(); foreach (System.Management.Automation.Runspaces.Command cmd in shell.PowerShell.Commands.Commands) { cmdText.Append(cmd.CommandText); cmdText.Append(" | "); } cmdText.Remove(cmdText.Length - 3, 3); // remove ending " | " RemoteDataObject message; if (shell.PowerShell.IsGetCommandMetadataSpecialPipeline) { message = RemotingEncoder.GenerateGetCommandMetadata(shell); } else { message = RemotingEncoder.GenerateCreatePowerShell(shell); } serializedPipeline = new SerializedDataStream(base.Fragmentor.FragmentSize); Fragmentor.Fragment<object>(message, serializedPipeline); } #endregion #region Events internal event EventHandler<EventArgs> SignalCompleted; internal void RaiseSignalCompleted() { SignalCompleted.SafeInvoke(this, EventArgs.Empty); } #endregion #region Overrides protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (isDisposing) { // dispose serialized pipeline serializedPipeline.Dispose(); } } #endregion #region Abstract / Virtual Methods /// <summary> /// Reconnects a previously disconnected commandTM. Implemented by WSMan transport /// Note that there is not explicit disconnect on commandTM. It is implicity disconnected /// when disconnect is called on sessionTM . The TM's also dont maintain specific connection state /// This is done by DSHandlers. /// </summary> internal virtual void ReconnectAsync() { throw new NotImplementedException(); } /// <summary> /// Used by powershell/pipeline to send a stop message to the server command. /// </summary> internal virtual void SendStopSignal() { throw new NotImplementedException(); } #endregion } } namespace System.Management.Automation.Remoting.Server { /// <summary> /// This represents an abstraction for server transport manager. /// </summary> internal abstract class AbstractServerTransportManager : BaseTransportManager { #region Private Data private readonly object _syncObject = new object(); // used to listen to data available events from serialized datastream. private readonly SerializedDataStream.OnDataAvailableCallback _onDataAvailable; // the following variable are used by onDataAvailableCallback. private bool _shouldFlushData; private bool _reportAsPending; private Guid _runspacePoolInstanceId; private Guid _powerShellInstanceId; private RemotingDataType _dataType; private RemotingTargetInterface _targetInterface; // End: the following variable are used by onDataAvailableCallback. private Queue<Tuple<RemoteDataObject, bool, bool>> _dataToBeSentQueue; private bool _isSerializing; #endregion #region Constructor protected AbstractServerTransportManager(int fragmentSize, PSRemotingCryptoHelper cryptoHelper) : base(cryptoHelper) { base.Fragmentor.FragmentSize = fragmentSize; _onDataAvailable = new SerializedDataStream.OnDataAvailableCallback(OnDataAvailable); } #endregion #region Helper Methods /// <summary> /// Sends an object from the server end. The object is fragmented and each fragment is sent /// separately. The call blocks until all the fragments are sent to the client. If there /// is a failure sending any of the fragments WSManTransportErrorOccured event is raised. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> /// <param name="flush"> /// true to immediately send data to client. /// </param> /// <param name="reportPending"> /// reported as true when host message requests are sent to client /// </param> internal void SendDataToClient<T>(RemoteDataObject<T> data, bool flush, bool reportPending = false) { // make sure only one data packet can be sent in its entirety at any // given point of time using this transport manager. lock (_syncObject) { // Win8: 491001 Icm -computername $env:COMPUTERNAME {Get-NetIpInterface} fails with Unexpected ObjectId // This is because the output object has some extended script properties. Getter of one of the // script properties is calling write-progress. Write-Progress in turn results in a progress record // being sent to client. This breaks the fragmentation rule when the original object (without progress) // does not fit in fragmented packet. // ******************** repro using powershell ********************************* // icm . { // $a = new-object psobject // $a.pstypenames.Insert(0, "Microsoft.PowerShell.Test.Bug491001") // Update-TypeData -TypeName Microsoft.PowerShell.Test.Bug491001 -MemberType ScriptProperty -MemberName name -Value {( 1..50kb | % { get-random -min 97 -max 122 | % { [char]$psitem } }) -join ""} // Update-TypeData -TypeName Microsoft.PowerShell.Test.Bug491001 -MemberType ScriptProperty -MemberName Verbose -Value {write-progress "blah" -Completed; "Some verbose data"} // Update-TypeData -TypeName Microsoft.PowerShell.Test.Bug491001 -MemberType ScriptProperty -MemberName zname -Value {( 1..10kb | % { get-random -min 97 -max 122 | % { [char]$psitem } }) -join ""} // $a // } // 1. The value of "name" property is huge 50kb and cannot fit in one fragment (with fragment size 32kb) // 2. The value of "Verbose" is actually writing a progress record // 3. The value of "zname" property is also huge // 4. Notice the ascending order of property names. This is because serializer serializes properties in sort order // ******************** End of repro ****************************************** // To fix the issue, I am creating a Queue and enqueuing the data objects if we are already serializing another data object // Notice this is in lock() above. An object is serialized in its entirety in one thread. So, in my example above "name", // "verbose","zname" properties are serialized in one thread. So lock() essentially protects from serializing other objects // and not this (parent)object. RemoteDataObject dataToBeSent = RemoteDataObject.CreateFrom(data.Destination, data.DataType, data.RunspacePoolId, data.PowerShellId, data.Data); if (_isSerializing) { _dataToBeSentQueue ??= new Queue<Tuple<RemoteDataObject, bool, bool>>(); _dataToBeSentQueue.Enqueue(new Tuple<RemoteDataObject, bool, bool>(dataToBeSent, flush, reportPending)); return; } _isSerializing = true; try { do { // tell stream to notify us whenever a fragment is available instead of writing data // into internal buffers. This will save write + read + dispose.) using (SerializedDataStream serializedData = new SerializedDataStream(Fragmentor.FragmentSize, _onDataAvailable)) { _shouldFlushData = flush; _reportAsPending = reportPending; _runspacePoolInstanceId = dataToBeSent.RunspacePoolId; _powerShellInstanceId = dataToBeSent.PowerShellId; _dataType = dataToBeSent.DataType; _targetInterface = dataToBeSent.TargetInterface; Fragmentor.Fragment<object>(dataToBeSent, serializedData); } if ((_dataToBeSentQueue != null) && (_dataToBeSentQueue.Count > 0)) { Tuple<RemoteDataObject, bool, bool> dataToBeSentQueueItem = _dataToBeSentQueue.Dequeue(); dataToBeSent = dataToBeSentQueueItem.Item1; flush = dataToBeSentQueueItem.Item2; reportPending = dataToBeSentQueueItem.Item3; } else { dataToBeSent = null; } } while (dataToBeSent != null); } finally { _isSerializing = false; } } } private void OnDataAvailable(byte[] dataToSend, bool isEndFragment) { Dbg.Assert(dataToSend != null, "ServerTransportManager cannot send null fragment"); // log to crimson log. PSEtwLog.LogAnalyticInformational(PSEventId.ServerSendData, PSOpcode.Send, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, _runspacePoolInstanceId.ToString(), _powerShellInstanceId.ToString(), dataToSend.Length.ToString(CultureInfo.InvariantCulture), (uint)_dataType, (uint)_targetInterface); SendDataToClient(dataToSend, isEndFragment && _shouldFlushData, _reportAsPending, isEndFragment); } /// <summary> /// Sends an object to the server end. The object is fragmented and each fragment is sent /// separately. The call blocks until all the fragments are sent to the client. If there /// is a failure sending any of the fragments WSManTransportErrorOccured event is raised. /// </summary> /// <param name="psObjectData"></param> /// <param name="flush"> /// true to immediately send data to client. /// </param> /// <param name="reportAsPending"> /// reported as true when sending host message requests are reported true /// </param> internal void SendDataToClient(RemoteDataObject psObjectData, bool flush, bool reportAsPending = false) { SendDataToClient<object>((RemoteDataObject<object>)(psObjectData), flush, reportAsPending); } /// <summary> /// Reports error from a thread pool thread. Thread Pool is used in order to /// not block Pipeline closing. This method is generally called when the /// TransportManager fails to Send data (SendDataToClient). Pipeline Execution /// Thread directly calls SendDataToClient method from its execution thread, /// so we cannot call Stop from the same thread (as it will result in a deadlock) /// </summary> internal void ReportError(int errorCode, string methodName) { string messageResource = RemotingErrorIdStrings.GeneralError; string errorMessage = string.Format(CultureInfo.InvariantCulture, messageResource, new object[] { errorCode, methodName }); PSRemotingTransportException e = new PSRemotingTransportException(errorMessage); e.ErrorCode = errorCode; // Use thread-pool thread to raise the error handler..see explanation // in the method summary ThreadPool.QueueUserWorkItem(new WaitCallback( (object state) => { TransportErrorOccuredEventArgs eventArgs = new TransportErrorOccuredEventArgs(e, TransportMethodEnum.Unknown); RaiseErrorHandler(eventArgs); })); } /// <summary> /// Raises the closing event. /// </summary> internal void RaiseClosingEvent() { Closing.SafeInvoke(this, EventArgs.Empty); } #endregion #region Event Handlers /// <summary> /// Event that is raised when this transport manager is closing. /// </summary> internal event EventHandler Closing; #endregion #region Abstract interfaces /// <summary> /// </summary> /// <param name="data"></param> /// <param name="flush"> /// flush data by sending data immediately to the client. /// </param> /// <param name="reportAsPending"> /// reported as true when sending host message requests to client /// </param> /// <param name="reportAsDataBoundary"> /// reported as true when data being reported is as object boundary, i.e the corresponding fragment is an end fragment /// </param> protected abstract void SendDataToClient(byte[] data, bool flush, bool reportAsPending, bool reportAsDataBoundary); /// <summary> /// </summary> internal abstract void ReportExecutionStatusAsRunning(); /// <summary> /// </summary> /// <param name="reasonForClose"> /// message describing why the transport manager must be closed /// </param> internal abstract void Close(Exception reasonForClose); /// <summary> /// Prepare the transport manager to send data (because a command /// is about to start). This is used by underlying infrastructure /// to send ACK to client..so client can start sending input and /// other data to server. /// </summary> internal virtual void Prepare() { // command may hijack the calling thread to run the command // so this method call notifies the ReceivedData buffer to // allow another thread to ProcessRawData. ReceivedDataCollection.AllowTwoThreadsToProcessRawData(); } #endregion } /// <summary> /// This represents an abstraction for server session transport manager. /// </summary> internal abstract class AbstractServerSessionTransportManager : AbstractServerTransportManager { #region Constructors protected AbstractServerSessionTransportManager(int fragmentSize, PSRemotingCryptoHelper cryptoHelper) : base(fragmentSize, cryptoHelper) { } #endregion #region Abstract interfaces /// <summary> /// Server RunspacePool driver uses this method to attach to a server transport manager. /// </summary> /// <param name="powerShellCmdId"></param> /// <returns></returns> internal abstract AbstractServerTransportManager GetCommandTransportManager(Guid powerShellCmdId); /// <summary> /// Server RunspacePool driver uses this method to remove association of a command transport manager /// from a session transport manager. /// </summary> /// <param name="powerShellCmdId"></param> internal abstract void RemoveCommandTransportManager(Guid powerShellCmdId); #endregion } /// <summary> /// A container for helper functions that accomplish common client and server tasks. /// </summary> internal static class ServerOperationHelpers { #region Public Helper Methods /// <summary> /// A helper method to extract a base-64 encoded XML element from a specified input /// buffer. The calls required are not compatible with the Managed C++ CoreCLR /// mscorlib, but this operation is supported as managed C# code. /// </summary> /// <param name="xmlBuffer">The input buffer to search. It must be base-64 encoded XML.</param> /// <param name="xmlTag">The XML tag used to identify the value to extract.</param> /// <returns>The extracted tag converted from a base-64 string.</returns> internal static byte[] ExtractEncodedXmlElement(string xmlBuffer, string xmlTag) { if (xmlBuffer == null || xmlTag == null) return new byte[1]; // the inboundShellInformation is in Xml format as per the SOAP WSMan spec. // Retrieve the string (Base64 encoded) we are interested in. XmlReaderSettings readerSettings = new XmlReaderSettings(); readerSettings.CheckCharacters = false; readerSettings.IgnoreComments = true; readerSettings.IgnoreProcessingInstructions = true; readerSettings.XmlResolver = null; readerSettings.ConformanceLevel = ConformanceLevel.Fragment; readerSettings.MaxCharactersFromEntities = 1024; readerSettings.DtdProcessing = System.Xml.DtdProcessing.Prohibit; XmlReader reader = XmlReader.Create(new StringReader(xmlBuffer), readerSettings); string additionalData; if (reader.MoveToContent() == XmlNodeType.Element) { additionalData = reader.ReadElementContentAsString(xmlTag, reader.NamespaceURI); } else // No element found, so return a default value { return new byte[1]; } return Convert.FromBase64String(additionalData); } #endregion } }