mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822
1541 lines
60 KiB
C#
1541 lines
60 KiB
C#
// 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
|
|
|
|
internal enum TransportMethodEnum
|
|
{
|
|
CreateShellEx = 0,
|
|
RunShellCommandEx = 1,
|
|
SendShellInputEx = 2,
|
|
ReceiveShellOutputEx = 3,
|
|
CloseShellOperationEx = 4,
|
|
CommandInputEx = 5,
|
|
ReceiveCommandOutputEx = 6,
|
|
DisconnectShellEx = 7,
|
|
ReconnectShellEx = 8,
|
|
ConnectShellEx = 9,
|
|
ReconnectShellCommandEx = 10,
|
|
ConnectShellCommandEx = 11,
|
|
Unknown = 12,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event arguments passed to TransportErrorOccured handlers.
|
|
/// </summary>
|
|
internal class TransportErrorOccuredEventArgs : EventArgs
|
|
{
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="e">
|
|
/// Error occurred.
|
|
/// </param>
|
|
/// <param name="m">
|
|
/// The transport method that raised the error
|
|
/// </param>
|
|
internal 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>
|
|
internal abstract class BaseTransportManager : IDisposable
|
|
{
|
|
#region tracer
|
|
|
|
[TraceSourceAttribute("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 = UInt32.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
|
|
|
|
protected 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(),
|
|
(UInt32)(remoteObject.Destination),
|
|
(UInt32)(remoteObject.DataType),
|
|
(UInt32)(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>
|
|
internal 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);
|
|
}
|
|
|
|
internal virtual void Dispose(bool isDisposing)
|
|
{
|
|
if (isDisposing)
|
|
{
|
|
ReceivedDataCollection.Dispose();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
|
|
namespace System.Management.Automation.Remoting.Client
|
|
{
|
|
internal abstract class BaseClientTransportManager : BaseTransportManager, IDisposable
|
|
{
|
|
#region Tracer
|
|
[TraceSourceAttribute("ClientTransport", "Traces ClientTransportManager")]
|
|
protected static PSTraceSource tracer = PSTraceSource.GetTracer("ClientTransport", "Traces ClientTransportManager");
|
|
#endregion
|
|
|
|
#region Data
|
|
|
|
protected bool isClosed;
|
|
protected object syncObject = new object();
|
|
protected 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
|
|
protected bool receiveDataInitiated;
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
protected 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
|
|
|
|
internal abstract void CreateAsync();
|
|
|
|
internal abstract void ConnectAsync();
|
|
|
|
/// <summary>
|
|
/// The caller should make sure the call is synchronized.
|
|
/// </summary>
|
|
internal 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>
|
|
/// Finalizer.
|
|
/// </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
|
|
}
|
|
}
|
|
}
|
|
|
|
internal 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
|
|
}
|
|
|
|
internal abstract class BaseClientSessionTransportManager : BaseClientTransportManager, IDisposable
|
|
{
|
|
#region Constructors
|
|
|
|
protected 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
|
|
|
|
internal 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)
|
|
{
|
|
if (_dataToBeSentQueue == null)
|
|
{
|
|
_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),
|
|
(UInt32)_dataType,
|
|
(UInt32)_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
|
|
}
|
|
}
|