PowerShell/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs
xtqqczze 883ca98dd7
Seal private classes (#15725)
* Seal private classes

* Fix CS0509

* Fix CS0628
2021-07-19 14:09:12 +05:00

4172 lines
182 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/*
* Common file that contains implementation for both server and client transport
* managers based on WSMan protocol.
*
*/
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Management.Automation.Internal;
using System.Management.Automation.Remoting.Server;
using System.Management.Automation.Runspaces.Internal;
using System.Management.Automation.Tracing;
using System.Runtime.InteropServices;
#if !UNIX
using System.Security.Principal;
#endif
using System.Xml;
using System.Threading;
using PSRemotingCryptoHelper = System.Management.Automation.Internal.PSRemotingCryptoHelper;
using WSManConnectionInfo = System.Management.Automation.Runspaces.WSManConnectionInfo;
using RunspaceConnectionInfo = System.Management.Automation.Runspaces.RunspaceConnectionInfo;
using AuthenticationMechanism = System.Management.Automation.Runspaces.AuthenticationMechanism;
using Dbg = System.Management.Automation.Diagnostics;
namespace System.Management.Automation.Remoting.Client
{
/// <summary>
/// WSMan TransportManager related utils.
/// </summary>
internal static class WSManTransportManagerUtils
{
#region Static Data
// Fully qualified error Id modifiers based on transport (WinRM) error codes.
private static readonly Dictionary<int, string> s_transportErrorCodeToFQEID = new Dictionary<int, string>()
{
{WSManNativeApi.ERROR_WSMAN_ACCESS_DENIED, "AccessDenied"},
{WSManNativeApi.ERROR_WSMAN_OUTOF_MEMORY, "ServerOutOfMemory"},
{WSManNativeApi.ERROR_WSMAN_NETWORKPATH_NOTFOUND, "NetworkPathNotFound"},
{WSManNativeApi.ERROR_WSMAN_COMPUTER_NOTFOUND, "ComputerNotFound"},
{WSManNativeApi.ERROR_WSMAN_AUTHENTICATION_FAILED, "AuthenticationFailed"},
{WSManNativeApi.ERROR_WSMAN_LOGON_FAILURE, "LogonFailure"},
{WSManNativeApi.ERROR_WSMAN_IMPROPER_RESPONSE, "ImproperResponse"},
{WSManNativeApi.ERROR_WSMAN_INCORRECT_PROTOCOLVERSION, "IncorrectProtocolVersion"},
{WSManNativeApi.ERROR_WSMAN_SENDDATA_CANNOT_COMPLETE, "WinRMOperationTimeout"},
{WSManNativeApi.ERROR_WSMAN_URL_NOTAVAILABLE, "URLNotAvailable"},
{WSManNativeApi.ERROR_WSMAN_SENDDATA_CANNOT_CONNECT, "CannotConnect"},
{WSManNativeApi.ERROR_WSMAN_INVALID_RESOURCE_URI, "InvalidResourceUri"},
{WSManNativeApi.ERROR_WSMAN_INUSE_CANNOT_RECONNECT, "CannotConnectAlreadyConnected"},
{WSManNativeApi.ERROR_WSMAN_INVALID_AUTHENTICATION, "InvalidAuthentication"},
{WSManNativeApi.ERROR_WSMAN_SHUTDOWN_INPROGRESS, "ShutDownInProgress"},
{WSManNativeApi.ERROR_WSMAN_CANNOT_CONNECT_INVALID, "CannotConnectInvalidOperation"},
{WSManNativeApi.ERROR_WSMAN_CANNOT_CONNECT_MISMATCH, "CannotConnectMismatchSessions"},
{WSManNativeApi.ERROR_WSMAN_CANNOT_CONNECT_RUNASFAILED, "CannotConnectRunAsFailed"},
{WSManNativeApi.ERROR_WSMAN_CREATEFAILED_INVALIDNAME, "SessionCreateFailedInvalidName"},
{WSManNativeApi.ERROR_WSMAN_TARGETSESSION_DOESNOTEXIST, "CannotConnectTargetSessionDoesNotExist"},
{WSManNativeApi.ERROR_WSMAN_REMOTESESSION_DISALLOWED, "RemoteSessionDisallowed"},
{WSManNativeApi.ERROR_WSMAN_REMOTECONNECTION_DISALLOWED, "RemoteConnectionDisallowed"},
{WSManNativeApi.ERROR_WSMAN_INVALID_RESOURCE_URI2, "InvalidResourceUri"},
{WSManNativeApi.ERROR_WSMAN_CORRUPTED_CONFIG, "CorruptedWinRMConfig"},
{WSManNativeApi.ERROR_WSMAN_OPERATION_ABORTED, "WinRMOperationAborted"},
{WSManNativeApi.ERROR_WSMAN_URI_LIMIT, "URIExceedsMaxAllowedSize"},
{WSManNativeApi.ERROR_WSMAN_CLIENT_KERBEROS_DISABLED, "ClientKerberosDisabled"},
{WSManNativeApi.ERROR_WSMAN_SERVER_NOTTRUSTED, "ServerNotTrusted"},
{WSManNativeApi.ERROR_WSMAN_WORKGROUP_NO_KERBEROS, "WorkgroupCannotUseKerberos"},
{WSManNativeApi.ERROR_WSMAN_EXPLICIT_CREDENTIALS_REQUIRED, "ExplicitCredentialsRequired"},
{WSManNativeApi.ERROR_WSMAN_REDIRECT_LOCATION_INVALID, "RedirectLocationInvalid"},
{WSManNativeApi.ERROR_WSMAN_REDIRECT_REQUESTED, "RedirectInformationRequired"},
{WSManNativeApi.ERROR_WSMAN_BAD_METHOD, "WinRMOperationNotSupportedOnServer"},
{WSManNativeApi.ERROR_WSMAN_HTTP_SERVICE_UNAVAILABLE, "CannotConnectWinRMService"},
{WSManNativeApi.ERROR_WSMAN_HTTP_SERVICE_ERROR, "WinRMHttpError"},
{WSManNativeApi.ERROR_WSMAN_TARGET_UNKNOWN, "TargetUnknown"},
{WSManNativeApi.ERROR_WSMAN_CANNOTUSE_IP, "CannotUseIPAddress"}
};
#endregion
#region Helper Methods
/// <summary>
/// Constructs a WSManTransportErrorOccuredEventArgs instance from the supplied data.
/// </summary>
/// <param name="wsmanAPIHandle">
/// WSMan API handle to use to get error messages from WSMan error id(s)
/// </param>
/// <param name="wsmanSessionTM">
/// Session Transportmanager to use to get error messages (for redirect)
/// </param>
/// <param name="errorStruct">
/// Error structure supplied by callbacks from WSMan API
/// </param>
/// <param name="transportMethodReportingError">
/// The transport method call that reported this error.
/// </param>
/// <param name="resourceString">
/// resource string that holds the message.
/// </param>
/// <param name="resourceArgs">
/// Arguments to pass to the resource
/// </param>
/// <returns>
/// An instance of WSManTransportErrorOccuredEventArgs
/// </returns>
internal static TransportErrorOccuredEventArgs ConstructTransportErrorEventArgs(IntPtr wsmanAPIHandle,
WSManClientSessionTransportManager wsmanSessionTM,
WSManNativeApi.WSManError errorStruct,
TransportMethodEnum transportMethodReportingError,
string resourceString,
params object[] resourceArgs)
{
PSRemotingTransportException e;
// For the first two special error conditions, it is remotely possible that the wsmanSessionTM is null when the failures are returned
// as part of command TM operations (could be returned because of RC retries under the hood)
// Not worth to handle these cases separately as there are very corner scenarios, but need to make sure wsmanSessionTM is not referenced
// Destination server is reporting that URI redirect is required for this user.
if ((errorStruct.errorCode == WSManNativeApi.ERROR_WSMAN_REDIRECT_REQUESTED) && (wsmanSessionTM != null))
{
IntPtr wsmanSessionHandle = wsmanSessionTM.SessionHandle;
// populate the transport message with the redirection uri..this will
// allow caller to make a new connection.
string redirectLocation = WSManNativeApi.WSManGetSessionOptionAsString(wsmanSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_REDIRECT_LOCATION);
string winrmMessage = ParseEscapeWSManErrorMessage(
WSManNativeApi.WSManGetErrorMessage(wsmanAPIHandle, errorStruct.errorCode)).Trim();
e = new PSRemotingTransportRedirectException(redirectLocation,
PSRemotingErrorId.URIEndPointNotResolved,
RemotingErrorIdStrings.URIEndPointNotResolved,
winrmMessage,
redirectLocation);
}
else if ((errorStruct.errorCode == WSManNativeApi.ERROR_WSMAN_INVALID_RESOURCE_URI) && (wsmanSessionTM != null))
{
string configurationName =
wsmanSessionTM.ConnectionInfo.ShellUri.Replace(Remoting.Client.WSManNativeApi.ResourceURIPrefix, string.Empty);
string errorMessage = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidConfigurationName,
configurationName,
wsmanSessionTM.ConnectionInfo.ComputerName);
e = new PSRemotingTransportException(PSRemotingErrorId.InvalidConfigurationName,
RemotingErrorIdStrings.ConnectExCallBackError, wsmanSessionTM.ConnectionInfo.ComputerName, errorMessage);
e.TransportMessage = ParseEscapeWSManErrorMessage(
WSManNativeApi.WSManGetErrorMessage(wsmanAPIHandle, errorStruct.errorCode));
}
else
{
// Construct specific error message and then append this message pointing to our own
// help topic. PowerShell's about help topic "about_Remote_Troubleshooting" should
// contain all the trouble shooting information.
string wsManErrorMessage = PSRemotingErrorInvariants.FormatResourceString(resourceString, resourceArgs);
e = new PSRemotingTransportException(PSRemotingErrorId.TroubleShootingHelpTopic,
RemotingErrorIdStrings.TroubleShootingHelpTopic,
wsManErrorMessage);
e.TransportMessage = ParseEscapeWSManErrorMessage(
WSManNativeApi.WSManGetErrorMessage(wsmanAPIHandle, errorStruct.errorCode));
}
e.ErrorCode = errorStruct.errorCode;
TransportErrorOccuredEventArgs eventargs =
new TransportErrorOccuredEventArgs(e, transportMethodReportingError);
return eventargs;
}
/// <summary>
/// Helper method that escapes powershell parser recognized strings like "@{" from the error message
/// string. This is needed to make error messages look authentic. Some WSMan error messages provide a
/// command line to run to fix certain issues. WSMan command line has syntax that allows use of @{}.
/// PowerShell parser treats them differently..and so when user cut and paste the command line in a
/// PowerShell console, it wont work. This escape logic works around the issue.
/// </summary>
/// <param name="errorMessage"></param>
/// <returns></returns>
internal static string ParseEscapeWSManErrorMessage(string errorMessage)
{
// currently we do special processing only for "@{" construct.
if (string.IsNullOrEmpty(errorMessage) || (!errorMessage.Contains("@{")))
{
return errorMessage;
}
string result = errorMessage.Replace("@{", "'@{").Replace("}", "}'");
return result;
/*
* Use this pattern if we need to escape other characters.
*
try
{
StringBuilder msgSB = new StringBuilder(errorMessage);
Collection<PSParseError> parserErrors = new Collection<PSParseError>();
Collection<PSToken> tokens = PSParser.Tokenize(errorMessage, out parserErrors);
if (parserErrors.Count > 0)
{
tracer.WriteLine(string.Format(CultureInfo.InvariantCulture,
"There were errors parsing string '{0}'", errorMessage);
return errorMessage;
}
for (int index = tokens.Count - 1; index > 0; index--)
{
PSToken currentToken = tokens[index];
switch(currentToken.Type)
{
case PSTokenType.GroupStart:
msgSB.Insert(currentToken.StartColumn - 1, "'", 1);
break;
case PSTokenType.GroupEnd:
if (msgSB.Length <= currentToken.EndColumn)
{
msgSB.Append("'");
}
else
{
msgSB.Insert(currentToken.EndColumn - 1, ",", 1);
}
break;
}
}
return msgSB.ToString();
}
// ignore possible exceptions manipulating the string.
catch(ArgumentOutOfRangeException)
{
}
catch(RuntimeException)
{
}
return errorMessage;*/
}
internal enum tmStartModes
{
None = 1, Create = 2, Connect = 3
}
/// <summary>
/// Helper method to convert a transport error code value
/// to a fully qualified error Id string.
/// </summary>
/// <param name="transportErrorCode">Transport error code.</param>
/// <param name="defaultFQEID">Default FQEID.</param>
/// <returns>Fully qualified error Id string.</returns>
internal static string GetFQEIDFromTransportError(
int transportErrorCode,
string defaultFQEID)
{
string specificErrorId;
if (s_transportErrorCodeToFQEID.TryGetValue(transportErrorCode, out specificErrorId))
{
return specificErrorId + "," + defaultFQEID;
}
else if (transportErrorCode != 0)
{
// Provide error code to uniquely identify the error Id.
return transportErrorCode.ToString(System.Globalization.NumberFormatInfo.InvariantInfo) + "," + defaultFQEID;
}
return defaultFQEID;
}
#endregion
}
/// <summary>
/// Class that manages a server session. This doesn't implement IDisposable. Use Close method
/// to clean the resources.
/// </summary>
internal sealed class WSManClientSessionTransportManager : BaseClientSessionTransportManager
{
#region Consts
/// <summary>
/// Max uri redirection count session variable.
/// </summary>
internal const string MAX_URI_REDIRECTION_COUNT_VARIABLE = "WSManMaxRedirectionCount";
/// <summary>
/// Default max uri redirection count - wsman.
/// </summary>
internal const int MAX_URI_REDIRECTION_COUNT = 5;
#endregion
#region Enums
private enum CompletionNotification
{
DisconnectCompleted
}
#endregion
#region CompletionEventArgs
private sealed class CompletionEventArgs : EventArgs
{
internal CompletionEventArgs(CompletionNotification notification)
{
Notification = notification;
}
internal CompletionNotification Notification { get; }
}
#endregion
#region Private Data
// operation handles are owned by WSMan
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _wsManSessionHandle;
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _wsManShellOperationHandle;
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _wsManReceiveOperationHandle;
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _wsManSendOperationHandle;
// this is used with WSMan callbacks to represent a session transport manager.
private long _sessionContextID;
private WSManTransportManagerUtils.tmStartModes _startMode = WSManTransportManagerUtils.tmStartModes.None;
private readonly string _sessionName;
// callbacks
private readonly PrioritySendDataCollection.OnDataAvailableCallback _onDataAvailableToSendCallback;
// instance callback handlers
private WSManNativeApi.WSManShellAsync _createSessionCallback;
private WSManNativeApi.WSManShellAsync _receivedFromRemote;
private WSManNativeApi.WSManShellAsync _sendToRemoteCompleted;
private WSManNativeApi.WSManShellAsync _disconnectSessionCompleted;
private WSManNativeApi.WSManShellAsync _reconnectSessionCompleted;
private WSManNativeApi.WSManShellAsync _connectSessionCallback;
// TODO: This GCHandle is required as it seems WSMan is calling create callback
// after we call Close. This seems wrong. Opened bug on WSMan to track this.
private GCHandle _createSessionCallbackGCHandle;
private WSManNativeApi.WSManShellAsync _closeSessionCompleted;
// used by WSManCreateShell call to send additional data (like negotiation)
// during shell creation. This is an instance variable to allow for redirection.
private WSManNativeApi.WSManData_ManToUn _openContent;
// By default WSMan compresses data sent on the network..use this flag to not do
// this.
private bool _noCompression;
private bool _noMachineProfile;
private int _connectionRetryCount;
private const string resBaseName = "remotingerroridstrings";
// Robust connections maximum retry time value in milliseconds.
private int _maxRetryTime;
private void ProcessShellData(string data)
{
try
{
XmlReaderSettings settings = InternalDeserializer.XmlReaderSettingsForUntrustedXmlDocument.Clone();
settings.MaxCharactersFromEntities = 1024; // 1024 is a generous upperbound for shell Xml entries
settings.MaxCharactersInDocument = 1024 * 30;
settings.DtdProcessing = System.Xml.DtdProcessing.Prohibit;
using (XmlReader reader = XmlReader.Create(new StringReader(data), settings))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.LocalName.Equals("IdleTimeOut", StringComparison.OrdinalIgnoreCase) ||
reader.LocalName.Equals("MaxIdleTimeOut", StringComparison.OrdinalIgnoreCase))
{
bool settingIdleTimeout =
!reader.LocalName.Equals("MaxIdleTimeOut", StringComparison.OrdinalIgnoreCase);
string timeoutString = reader.ReadElementContentAsString();
Dbg.Assert(timeoutString.Substring(0, 2).Equals("PT", StringComparison.OrdinalIgnoreCase),
"IdleTimeout is not in expected format");
int decimalIndex = timeoutString.IndexOf('.');
try
{
int timeout = Convert.ToInt32(timeoutString.Substring(2, decimalIndex - 2), NumberFormatInfo.InvariantInfo) * 1000 + Convert.ToInt32(timeoutString.Substring(decimalIndex + 1, 3), NumberFormatInfo.InvariantInfo);
if (settingIdleTimeout)
{
ConnectionInfo.IdleTimeout = timeout;
}
else
{
ConnectionInfo.MaxIdleTimeout = timeout;
}
}
catch (InvalidCastException)
{
Dbg.Assert(false, "IdleTimeout is not in expected format");
}
}
else if (reader.LocalName.Equals("BufferMode", StringComparison.OrdinalIgnoreCase))
{
string bufferMode = reader.ReadElementContentAsString();
if (bufferMode.Equals("Block", StringComparison.OrdinalIgnoreCase))
{
ConnectionInfo.OutputBufferingMode = Runspaces.OutputBufferingMode.Block;
}
else if (bufferMode.Equals("Drop", StringComparison.OrdinalIgnoreCase))
{
ConnectionInfo.OutputBufferingMode = Runspaces.OutputBufferingMode.Drop;
}
else
{
Dbg.Assert(false, "unexpected buffer mode");
}
}
}
}
}
}
catch (XmlException)
{
Dbg.Assert(false, "shell xml is in unexpected format");
}
}
#endregion
#region Static Data
// static callback delegate
private static WSManNativeApi.WSManShellAsyncCallback s_sessionCreateCallback;
private static WSManNativeApi.WSManShellAsyncCallback s_sessionCloseCallback;
private static WSManNativeApi.WSManShellAsyncCallback s_sessionReceiveCallback;
private static WSManNativeApi.WSManShellAsyncCallback s_sessionSendCallback;
private static WSManNativeApi.WSManShellAsyncCallback s_sessionDisconnectCallback;
private static WSManNativeApi.WSManShellAsyncCallback s_sessionReconnectCallback;
private static WSManNativeApi.WSManShellAsyncCallback s_sessionConnectCallback;
// This dictionary maintains active session transport managers to be used from various
// callbacks.
private static readonly Dictionary<long, WSManClientSessionTransportManager> s_sessionTMHandles =
new Dictionary<long, WSManClientSessionTransportManager>();
private static long s_sessionTMSeed;
// generate unique session id
private static long GetNextSessionTMHandleId()
{
return System.Threading.Interlocked.Increment(ref s_sessionTMSeed);
}
// we need a synchronized add and remove so that multiple threads
// update the data store concurrently
private static void AddSessionTransportManager(long sessnTMId,
WSManClientSessionTransportManager sessnTransportManager)
{
lock (s_sessionTMHandles)
{
s_sessionTMHandles.Add(sessnTMId, sessnTransportManager);
}
}
private static void RemoveSessionTransportManager(long sessnTMId)
{
lock (s_sessionTMHandles)
{
s_sessionTMHandles.Remove(sessnTMId);
}
}
// we need a synchronized add and remove so that multiple threads
// update the data store concurrently
private static bool TryGetSessionTransportManager(IntPtr operationContext,
out WSManClientSessionTransportManager sessnTransportManager,
out long sessnTMId)
{
sessnTMId = operationContext.ToInt64();
sessnTransportManager = null;
lock (s_sessionTMHandles)
{
return s_sessionTMHandles.TryGetValue(sessnTMId, out sessnTransportManager);
}
}
#endregion
#region SHIM: Redirection delegates for test purposes
private static readonly Delegate s_sessionSendRedirect = null;
private static readonly Delegate s_protocolVersionRedirect = null;
#endregion
#region Constructors
/// <summary>
/// Static constructor to initialize WSMan Client stack.
/// </summary>
static WSManClientSessionTransportManager()
{
// Initialize callback delegates
WSManNativeApi.WSManShellCompletionFunction createDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnCreateSessionCallback);
s_sessionCreateCallback = new WSManNativeApi.WSManShellAsyncCallback(createDelegate);
WSManNativeApi.WSManShellCompletionFunction closeDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnCloseSessionCompleted);
s_sessionCloseCallback = new WSManNativeApi.WSManShellAsyncCallback(closeDelegate);
WSManNativeApi.WSManShellCompletionFunction receiveDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnRemoteSessionDataReceived);
s_sessionReceiveCallback = new WSManNativeApi.WSManShellAsyncCallback(receiveDelegate);
WSManNativeApi.WSManShellCompletionFunction sendDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnRemoteSessionSendCompleted);
s_sessionSendCallback = new WSManNativeApi.WSManShellAsyncCallback(sendDelegate);
WSManNativeApi.WSManShellCompletionFunction disconnectDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnRemoteSessionDisconnectCompleted);
s_sessionDisconnectCallback = new WSManNativeApi.WSManShellAsyncCallback(disconnectDelegate);
WSManNativeApi.WSManShellCompletionFunction reconnectDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnRemoteSessionReconnectCompleted);
s_sessionReconnectCallback = new WSManNativeApi.WSManShellAsyncCallback(reconnectDelegate);
WSManNativeApi.WSManShellCompletionFunction connectDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnRemoteSessionConnectCallback);
s_sessionConnectCallback = new WSManNativeApi.WSManShellAsyncCallback(connectDelegate);
}
/// <summary>
/// Constructor. This will create a new PrioritySendDataCollection which should be used to
/// send data to the server.
/// </summary>
/// <param name="runspacePoolInstanceId">
/// This is used for logging trace/operational crimson messages. Having this id in the logs
/// helps a user to map which transport is created for which runspace.
/// </param>
/// <param name="connectionInfo">
/// Connection info to use while connecting to the remote machine.
/// </param>
/// <param name="cryptoHelper">Crypto helper.</param>
/// <param name="sessionName">Session friendly name.</param>
/// <exception cref="PSInvalidOperationException">
/// 1. Create Session failed with a non-zero error code.
/// </exception>
internal WSManClientSessionTransportManager(
Guid runspacePoolInstanceId,
WSManConnectionInfo connectionInfo,
PSRemotingCryptoHelper cryptoHelper,
string sessionName)
: base(runspacePoolInstanceId, cryptoHelper)
{
// Initialize WSMan instance
WSManAPIData = new WSManAPIDataCommon();
if (WSManAPIData.WSManAPIHandle == IntPtr.Zero)
{
throw new PSRemotingTransportException(
StringUtil.Format(RemotingErrorIdStrings.WSManInitFailed, WSManAPIData.ErrorCode));
}
Dbg.Assert(connectionInfo != null, "connectionInfo cannot be null");
CryptoHelper = cryptoHelper;
dataToBeSent.Fragmentor = base.Fragmentor;
_sessionName = sessionName;
// session transport manager can receive unlimited data..however each object is limited
// by maxRecvdObjectSize. this is to allow clients to use a session for an unlimited time..
// also the messages that can be sent to a session are limited and very controlled.
// However a command transport manager can be restricted to receive only a fixed amount of data
// controlled by maxRecvdDataSizeCommand..This is because commands can accept any number of input
// objects.
ReceivedDataCollection.MaximumReceivedDataSize = null;
ReceivedDataCollection.MaximumReceivedObjectSize = connectionInfo.MaximumReceivedObjectSize;
_onDataAvailableToSendCallback =
new PrioritySendDataCollection.OnDataAvailableCallback(OnDataAvailableCallback);
Initialize(connectionInfo.ConnectionUri, connectionInfo);
}
#endregion
#region Set Session Options
/// <summary>
/// Sets default timeout for all client operations in milliseconds.
/// TODO: Sync with WSMan and figure out what the default is if we
/// dont set.
/// </summary>
/// <param name="milliseconds"></param>
/// <returns></returns>
/// <exception cref="PSInvalidOperationException">
/// Setting session option failed with a non-zero error code.
/// </exception>
internal void SetDefaultTimeOut(int milliseconds)
{
Dbg.Assert(_wsManSessionHandle != IntPtr.Zero, "Session handle cannot be null");
using (tracer.TraceMethod("Setting Default timeout: {0} milliseconds", milliseconds))
{
int result = WSManNativeApi.WSManSetSessionOption(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_DEFAULT_OPERATION_TIMEOUTMS,
new WSManNativeApi.WSManDataDWord(milliseconds));
if (result != 0)
{
// Get the error message from WSMan
string errorMessage = WSManNativeApi.WSManGetErrorMessage(WSManAPIData.WSManAPIHandle, result);
PSInvalidOperationException exception = new PSInvalidOperationException(errorMessage);
throw exception;
}
}
}
/// <summary>
/// Sets timeout for Create operation in milliseconds.
/// </summary>
/// <param name="milliseconds"></param>
/// <returns></returns>
/// <exception cref="PSInvalidOperationException">
/// Setting session option failed with a non-zero error code.
/// </exception>
internal void SetConnectTimeOut(int milliseconds)
{
Dbg.Assert(_wsManSessionHandle != IntPtr.Zero, "Session handle cannot be null");
using (tracer.TraceMethod("Setting CreateShell timeout: {0} milliseconds", milliseconds))
{
int result = WSManNativeApi.WSManSetSessionOption(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_TIMEOUTMS_CREATE_SHELL,
new WSManNativeApi.WSManDataDWord(milliseconds));
if (result != 0)
{
// Get the error message from WSMan
string errorMessage = WSManNativeApi.WSManGetErrorMessage(WSManAPIData.WSManAPIHandle, result);
PSInvalidOperationException exception = new PSInvalidOperationException(errorMessage);
throw exception;
}
}
}
/// <summary>
/// Sets timeout for Close operation in milliseconds.
/// </summary>
/// <param name="milliseconds"></param>
/// <returns></returns>
/// <exception cref="PSInvalidOperationException">
/// Setting session option failed with a non-zero error code.
/// </exception>
internal void SetCloseTimeOut(int milliseconds)
{
Dbg.Assert(_wsManSessionHandle != IntPtr.Zero, "Session handle cannot be null");
using (tracer.TraceMethod("Setting CloseShell timeout: {0} milliseconds", milliseconds))
{
int result = WSManNativeApi.WSManSetSessionOption(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_TIMEOUTMS_CLOSE_SHELL_OPERATION,
new WSManNativeApi.WSManDataDWord(milliseconds));
if (result != 0)
{
// Get the error message from WSMan
string errorMessage = WSManNativeApi.WSManGetErrorMessage(WSManAPIData.WSManAPIHandle, result);
PSInvalidOperationException exception = new PSInvalidOperationException(errorMessage);
throw exception;
}
}
}
/// <summary>
/// Sets timeout for SendShellInput operation in milliseconds.
/// </summary>
/// <param name="milliseconds"></param>
/// <returns></returns>
/// <exception cref="PSInvalidOperationException">
/// Setting session option failed with a non-zero error code.
/// </exception>
internal void SetSendTimeOut(int milliseconds)
{
Dbg.Assert(_wsManSessionHandle != IntPtr.Zero, "Session handle cannot be null");
using (tracer.TraceMethod("Setting SendShellInput timeout: {0} milliseconds", milliseconds))
{
int result = WSManNativeApi.WSManSetSessionOption(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_TIMEOUTMS_SEND_SHELL_INPUT,
new WSManNativeApi.WSManDataDWord(milliseconds));
if (result != 0)
{
// Get the error message from WSMan
string errorMessage = WSManNativeApi.WSManGetErrorMessage(WSManAPIData.WSManAPIHandle, result);
PSInvalidOperationException exception = new PSInvalidOperationException(errorMessage);
throw exception;
}
}
}
/// <summary>
/// Sets timeout for Receive operation in milliseconds.
/// </summary>
/// <param name="milliseconds"></param>
/// <returns></returns>
/// <exception cref="PSInvalidOperationException">
/// Setting session option failed with a non-zero error code.
/// </exception>
internal void SetReceiveTimeOut(int milliseconds)
{
Dbg.Assert(_wsManSessionHandle != IntPtr.Zero, "Session handle cannot be null");
using (tracer.TraceMethod("Setting ReceiveShellOutput timeout: {0} milliseconds", milliseconds))
{
int result = WSManNativeApi.WSManSetSessionOption(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_TIMEOUTMS_RECEIVE_SHELL_OUTPUT,
new WSManNativeApi.WSManDataDWord(milliseconds));
if (result != 0)
{
// Get the error message from WSMan
string errorMessage = WSManNativeApi.WSManGetErrorMessage(WSManAPIData.WSManAPIHandle, result);
PSInvalidOperationException exception = new PSInvalidOperationException(errorMessage);
throw exception;
}
}
}
/// <summary>
/// Sets timeout for Signal operation in milliseconds.
/// </summary>
/// <param name="milliseconds"></param>
/// <returns></returns>
/// <exception cref="PSInvalidOperationException">
/// Setting session option failed with a non-zero error code.
/// </exception>
internal void SetSignalTimeOut(int milliseconds)
{
Dbg.Assert(_wsManSessionHandle != IntPtr.Zero, "Session handle cannot be null");
using (tracer.TraceMethod("Setting SignalShell timeout: {0} milliseconds", milliseconds))
{
int result = WSManNativeApi.WSManSetSessionOption(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_TIMEOUTMS_SIGNAL_SHELL,
new WSManNativeApi.WSManDataDWord(milliseconds));
if (result != 0)
{
// Get the error message from WSMan
string errorMessage = WSManNativeApi.WSManGetErrorMessage(WSManAPIData.WSManAPIHandle, result);
PSInvalidOperationException exception = new PSInvalidOperationException(errorMessage);
throw exception;
}
}
}
/// <summary>
/// Sets a DWORD value for a WSMan Session option.
/// </summary>
/// <param name="option"></param>
/// <param name="dwordData"></param>
/// <exception cref="PSInvalidOperationException">
/// Setting session option failed with a non-zero error code.
/// </exception>
internal void SetWSManSessionOption(WSManNativeApi.WSManSessionOption option, int dwordData)
{
int result = WSManNativeApi.WSManSetSessionOption(_wsManSessionHandle,
option, new WSManNativeApi.WSManDataDWord(dwordData));
if (result != 0)
{
// Get the error message from WSMan
string errorMessage = WSManNativeApi.WSManGetErrorMessage(WSManAPIData.WSManAPIHandle, result);
PSInvalidOperationException exception = new PSInvalidOperationException(errorMessage);
throw exception;
}
}
/// <summary>
/// Sets a string value for a WSMan Session option.
/// </summary>
/// <param name="option"></param>
/// <param name="stringData"></param>
/// <exception cref="PSInvalidOperationException">
/// Setting session option failed with a non-zero error code.
/// </exception>
internal void SetWSManSessionOption(WSManNativeApi.WSManSessionOption option, string stringData)
{
using (WSManNativeApi.WSManData_ManToUn data = new WSManNativeApi.WSManData_ManToUn(stringData))
{
int result = WSManNativeApi.WSManSetSessionOption(_wsManSessionHandle,
option, data);
if (result != 0)
{
// Get the error message from WSMan
string errorMessage = WSManNativeApi.WSManGetErrorMessage(WSManAPIData.WSManAPIHandle, result);
PSInvalidOperationException exception = new PSInvalidOperationException(errorMessage);
throw exception;
}
}
}
#endregion
#region Internal Methods / Properties
internal WSManAPIDataCommon WSManAPIData { get; private set; }
internal bool SupportsDisconnect { get; private set; }
internal override void DisconnectAsync()
{
Dbg.Assert(!isClosed, "object already disposed");
// Pass the WSManConnectionInfo object IdleTimeout value if it is
// valid. Otherwise pass the default value that instructs the server
// to use its default IdleTimeout value.
uint uIdleTimeout = (ConnectionInfo.IdleTimeout > 0) ?
(uint)ConnectionInfo.IdleTimeout : UseServerDefaultIdleTimeoutUInt;
// startup info
WSManNativeApi.WSManShellDisconnectInfo disconnectInfo = new WSManNativeApi.WSManShellDisconnectInfo(uIdleTimeout);
// Add ETW traces
// disconnect Callback
_disconnectSessionCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_sessionContextID), s_sessionDisconnectCallback);
try
{
lock (syncObject)
{
if (isClosed)
{
// the transport is already closed
// anymore.
return;
}
int flags = 0;
flags |= (ConnectionInfo.OutputBufferingMode == Runspaces.OutputBufferingMode.Block) ?
(int)WSManNativeApi.WSManShellFlag.WSMAN_FLAG_SERVER_BUFFERING_MODE_BLOCK : 0;
flags |= (ConnectionInfo.OutputBufferingMode == Runspaces.OutputBufferingMode.Drop) ?
(int)WSManNativeApi.WSManShellFlag.WSMAN_FLAG_SERVER_BUFFERING_MODE_DROP : 0;
WSManNativeApi.WSManDisconnectShellEx(_wsManShellOperationHandle,
flags,
disconnectInfo,
_disconnectSessionCompleted);
}
}
finally
{
disconnectInfo.Dispose();
}
}
internal override void ReconnectAsync()
{
Dbg.Assert(!isClosed, "object already disposed");
ReceivedDataCollection.PrepareForStreamConnect();
// Add ETW traces
// reconnect Callback
_reconnectSessionCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_sessionContextID), s_sessionReconnectCallback);
lock (syncObject)
{
if (isClosed)
{
// the transport is already closed
// anymore.
return;
}
int flags = 0;
flags |= (ConnectionInfo.OutputBufferingMode == Runspaces.OutputBufferingMode.Block) ?
(int)WSManNativeApi.WSManShellFlag.WSMAN_FLAG_SERVER_BUFFERING_MODE_BLOCK : 0;
flags |= (ConnectionInfo.OutputBufferingMode == Runspaces.OutputBufferingMode.Drop) ?
(int)WSManNativeApi.WSManShellFlag.WSMAN_FLAG_SERVER_BUFFERING_MODE_DROP : 0;
WSManNativeApi.WSManReconnectShellEx(_wsManShellOperationHandle,
flags,
_reconnectSessionCompleted);
}
}
/// <summary>
/// Starts connecting to an existing remote session. This will result in a WSManConnectShellEx WSMan
/// async call. Piggy backs available data in input stream as openXml in connect SOAP.
/// DSHandler will push negotiation related messages through the open content.
/// </summary>
/// <exception cref="PSRemotingTransportException">
/// WSManConnectShellEx failed.
/// </exception>
internal override void ConnectAsync()
{
Dbg.Assert(!isClosed, "object already disposed");
Dbg.Assert(!string.IsNullOrEmpty(ConnectionInfo.ShellUri), "shell uri cannot be null or empty.");
ReceivedDataCollection.PrepareForStreamConnect();
// additional content with connect shell call. Negotiation and connect related messages
// should be included in payload
if (_openContent == null)
{
DataPriorityType additionalDataType;
byte[] additionalData = dataToBeSent.ReadOrRegisterCallback(null, out additionalDataType);
if (additionalData != null)
{
// WSMan expects the data to be in XML format (which is text + xml tags)
// so convert byte[] into base64 encoded format
string base64EncodedDataInXml = string.Format(CultureInfo.InvariantCulture, "<{0} xmlns=\"{1}\">{2}</{0}>",
WSManNativeApi.PS_CONNECT_XML_TAG,
WSManNativeApi.PS_XML_NAMESPACE,
Convert.ToBase64String(additionalData));
_openContent = new WSManNativeApi.WSManData_ManToUn(base64EncodedDataInXml);
}
// THERE SHOULD BE NO ADDITIONAL DATA. If there is, it means we are not able to push all initial negotiation related data
// as part of Connect SOAP. The connect algorithm is based on this assumption. So bail out.
additionalData = dataToBeSent.ReadOrRegisterCallback(null, out additionalDataType);
if (additionalData != null)
{
// Negotiation payload does not fit in ConnectShell. bail out.
// Assert for now. should be replaced with raising an exception so upper layers can catch.
Dbg.Assert(false, "Negotiation payload does not fit in ConnectShell");
return;
}
}
// Create and store context for this shell operation. This context is used from various callbacks
_sessionContextID = GetNextSessionTMHandleId();
AddSessionTransportManager(_sessionContextID, this);
// session is implicitly assumed to support disconnect
SupportsDisconnect = true;
// Create Callback
_connectSessionCallback = new WSManNativeApi.WSManShellAsync(new IntPtr(_sessionContextID), s_sessionConnectCallback);
lock (syncObject)
{
if (isClosed)
{
// the transport is already closed..so no need to connect
// anymore.
return;
}
Dbg.Assert(_startMode == WSManTransportManagerUtils.tmStartModes.None, "startMode is not in expected state");
_startMode = WSManTransportManagerUtils.tmStartModes.Connect;
int flags = 0;
flags |= (ConnectionInfo.OutputBufferingMode == Runspaces.OutputBufferingMode.Block) ?
(int)WSManNativeApi.WSManShellFlag.WSMAN_FLAG_SERVER_BUFFERING_MODE_BLOCK : 0;
flags |= (ConnectionInfo.OutputBufferingMode == Runspaces.OutputBufferingMode.Drop) ?
(int)WSManNativeApi.WSManShellFlag.WSMAN_FLAG_SERVER_BUFFERING_MODE_DROP : 0;
WSManNativeApi.WSManConnectShellEx(_wsManSessionHandle,
flags,
ConnectionInfo.ShellUri,
RunspacePoolInstanceId.ToString().ToUpperInvariant(), // wsman is case sensitive wrt shellId. so consistently using upper case
IntPtr.Zero,
_openContent,
_connectSessionCallback,
ref _wsManShellOperationHandle);
}
if (_wsManShellOperationHandle == IntPtr.Zero)
{
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(WSManAPIData.WSManAPIHandle,
this,
new WSManNativeApi.WSManError(),
TransportMethodEnum.ConnectShellEx,
RemotingErrorIdStrings.ConnectExFailed, this.ConnectionInfo.ComputerName);
ProcessWSManTransportError(eventargs);
return;
}
}
internal override void StartReceivingData()
{
lock (syncObject)
{
// make sure the transport is not closed.
if (isClosed)
{
tracer.WriteLine("Client Session TM: Transport manager is closed. So returning");
return;
}
if (receiveDataInitiated)
{
tracer.WriteLine("Client Session TM: ReceiveData has already been called.");
return;
}
receiveDataInitiated = true;
tracer.WriteLine("Client Session TM: Placing Receive request using WSManReceiveShellOutputEx");
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManReceiveShellOutputEx,
PSOpcode.Receive, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
RunspacePoolInstanceId.ToString(), Guid.Empty.ToString());
_receivedFromRemote = new WSManNativeApi.WSManShellAsync(new IntPtr(_sessionContextID), s_sessionReceiveCallback);
WSManNativeApi.WSManReceiveShellOutputEx(_wsManShellOperationHandle,
IntPtr.Zero, 0, WSManAPIData.OutputStreamSet, _receivedFromRemote,
ref _wsManReceiveOperationHandle);
}
}
/// <summary>
/// Starts connecting to remote end asynchronously. This will result in a WSManCreateShellEx WSMan
/// async call. By the time this call returns, we will have a valid handle, if the operation
/// succeeds. Make sure other methods are called only after this method returns. Thread
/// synchronization is left to the caller.
/// </summary>
/// <exception cref="PSRemotingTransportException">
/// WSManCreateShellEx failed.
/// </exception>
internal override void CreateAsync()
{
Dbg.Assert(!isClosed, "object already disposed");
Dbg.Assert(!string.IsNullOrEmpty(ConnectionInfo.ShellUri), "shell uri cannot be null or empty.");
Dbg.Assert(WSManAPIData != null, "WSManApiData should always be created before session creation.");
List<WSManNativeApi.WSManOption> shellOptions = new List<WSManNativeApi.WSManOption>(WSManAPIData.CommonOptionSet);
#region SHIM: Redirection code for protocol version
if (s_protocolVersionRedirect != null)
{
string newProtocolVersion = (string)s_protocolVersionRedirect.DynamicInvoke();
shellOptions.Clear();
WSManNativeApi.WSManOption newPrtVOption = new WSManNativeApi.WSManOption();
newPrtVOption.name = RemoteDataNameStrings.PS_STARTUP_PROTOCOL_VERSION_NAME;
newPrtVOption.value = newProtocolVersion;
newPrtVOption.mustComply = true;
shellOptions.Add(newPrtVOption);
}
#endregion
// Pass the WSManConnectionInfo object IdleTimeout value if it is
// valid. Otherwise pass the default value that instructs the server
// to use its default IdleTimeout value.
uint uIdleTimeout = (ConnectionInfo.IdleTimeout > 0) ?
(uint)ConnectionInfo.IdleTimeout : UseServerDefaultIdleTimeoutUInt;
// startup info
WSManNativeApi.WSManShellStartupInfo_ManToUn startupInfo =
new WSManNativeApi.WSManShellStartupInfo_ManToUn(WSManAPIData.InputStreamSet,
WSManAPIData.OutputStreamSet,
uIdleTimeout,
_sessionName);
// additional content with create shell call. Piggy back first fragment from
// the dataToBeSent buffer.
if (_openContent == null)
{
DataPriorityType additionalDataType;
byte[] additionalData = dataToBeSent.ReadOrRegisterCallback(null, out additionalDataType);
#region SHIM: Redirection code for session data send.
bool sendContinue = true;
if (s_sessionSendRedirect != null)
{
object[] arguments = new object[2] { null, additionalData };
sendContinue = (bool)s_sessionSendRedirect.DynamicInvoke(arguments);
additionalData = (byte[])arguments[0];
}
if (!sendContinue)
return;
#endregion
if (additionalData != null)
{
// WSMan expects the data to be in XML format (which is text + xml tags)
// so convert byte[] into base64 encoded format
string base64EncodedDataInXml = string.Format(CultureInfo.InvariantCulture, "<{0} xmlns=\"{1}\">{2}</{0}>",
WSManNativeApi.PS_CREATION_XML_TAG,
WSManNativeApi.PS_XML_NAMESPACE,
Convert.ToBase64String(additionalData));
_openContent = new WSManNativeApi.WSManData_ManToUn(base64EncodedDataInXml);
}
}
// Create the session context information only once. CreateAsync() can be called multiple
// times by RetrySessionCreation for flaky networks.
if (_sessionContextID == 0)
{
// Create and store context for this shell operation. This context is used from various callbacks
_sessionContextID = GetNextSessionTMHandleId();
AddSessionTransportManager(_sessionContextID, this);
// Create Callback
_createSessionCallback = new WSManNativeApi.WSManShellAsync(new IntPtr(_sessionContextID), s_sessionCreateCallback);
_createSessionCallbackGCHandle = GCHandle.Alloc(_createSessionCallback);
}
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCreateShell,
PSOpcode.Connect,
PSTask.CreateRunspace, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
RunspacePoolInstanceId.ToString());
try
{
lock (syncObject)
{
if (isClosed)
{
// the transport is already closed..so no need to create a connection
// anymore.
return;
}
Dbg.Assert(_startMode == WSManTransportManagerUtils.tmStartModes.None, "startMode is not in expected state");
_startMode = WSManTransportManagerUtils.tmStartModes.Create;
if (_noMachineProfile)
{
WSManNativeApi.WSManOption noProfile = new WSManNativeApi.WSManOption();
noProfile.name = WSManNativeApi.NoProfile;
noProfile.mustComply = true;
noProfile.value = "1";
shellOptions.Add(noProfile);
}
int flags = _noCompression ? (int)WSManNativeApi.WSManShellFlag.WSMAN_FLAG_NO_COMPRESSION : 0;
flags |= (ConnectionInfo.OutputBufferingMode == Runspaces.OutputBufferingMode.Block) ?
(int)WSManNativeApi.WSManShellFlag.WSMAN_FLAG_SERVER_BUFFERING_MODE_BLOCK : 0;
flags |= (ConnectionInfo.OutputBufferingMode == Runspaces.OutputBufferingMode.Drop) ?
(int)WSManNativeApi.WSManShellFlag.WSMAN_FLAG_SERVER_BUFFERING_MODE_DROP : 0;
using (WSManNativeApi.WSManOptionSet optionSet = new WSManNativeApi.WSManOptionSet(shellOptions.ToArray()))
{
WSManNativeApi.WSManCreateShellEx(_wsManSessionHandle,
flags,
ConnectionInfo.ShellUri,
RunspacePoolInstanceId.ToString().ToUpperInvariant(),
startupInfo,
optionSet,
_openContent,
_createSessionCallback,
ref _wsManShellOperationHandle);
}
}
if (_wsManShellOperationHandle == IntPtr.Zero)
{
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(WSManAPIData.WSManAPIHandle,
this,
new WSManNativeApi.WSManError(),
TransportMethodEnum.CreateShellEx,
RemotingErrorIdStrings.ConnectExFailed,
this.ConnectionInfo.ComputerName);
ProcessWSManTransportError(eventargs);
return;
}
}
finally
{
startupInfo.Dispose();
}
}
/// <summary>
/// Closes the pending Create,Send,Receive operations and then closes the shell and release all the resources.
/// The caller should make sure this method is called only after calling ConnectAsync.
/// </summary>
internal override void CloseAsync()
{
bool shouldRaiseCloseCompleted = false;
// let other threads release the lock before we clean up the resources.
lock (syncObject)
{
if (isClosed)
{
return;
}
if (_startMode == WSManTransportManagerUtils.tmStartModes.None)
{
shouldRaiseCloseCompleted = true;
}
else if (_startMode == WSManTransportManagerUtils.tmStartModes.Create ||
_startMode == WSManTransportManagerUtils.tmStartModes.Connect)
{
if (_wsManShellOperationHandle == IntPtr.Zero)
{
shouldRaiseCloseCompleted = true;
}
}
else
{
Dbg.Assert(false, "startMode is in unexpected state");
}
// Set boolean indicating that this session is closing.
isClosed = true;
}
base.CloseAsync();
if (shouldRaiseCloseCompleted)
{
try
{
RaiseCloseCompleted();
}
finally
{
RemoveSessionTransportManager(_sessionContextID);
}
return;
}
// TODO - On unexpected failures on a reconstructed session... we dont want to close server session
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCloseShell,
PSOpcode.Disconnect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
RunspacePoolInstanceId.ToString());
_closeSessionCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_sessionContextID), s_sessionCloseCallback);
WSManNativeApi.WSManCloseShell(_wsManShellOperationHandle, 0, _closeSessionCompleted);
}
/// <summary>
/// Adjusts for any variations in different protocol versions. Following changes are considered
/// - In V2, default max envelope size is 150KB while in V3 it has been changed to 500KB.
/// With default configuration remoting from V3 client to V2 server will break as V3 client can send upto 500KB in a single Send packet
/// So if server version is known to be V2, we'll downgrade the max env size to 150KB (V2's default) if the current value is 500KB (V3 default)
/// </summary>
/// <param name="serverProtocolVersion">Server negotiated protocol version.</param>
internal void AdjustForProtocolVariations(Version serverProtocolVersion)
{
if (serverProtocolVersion <= RemotingConstants.ProtocolVersionWin7RTM)
{
int maxEnvSize;
WSManNativeApi.WSManGetSessionOptionAsDword(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_MAX_ENVELOPE_SIZE_KB,
out maxEnvSize);
if (maxEnvSize == WSManNativeApi.WSMAN_DEFAULT_MAX_ENVELOPE_SIZE_KB_V3)
{
int result = WSManNativeApi.WSManSetSessionOption(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_MAX_ENVELOPE_SIZE_KB,
new WSManNativeApi.WSManDataDWord(WSManNativeApi.WSMAN_DEFAULT_MAX_ENVELOPE_SIZE_KB_V2));
if (result != 0)
{
// Get the error message from WSMan
string errorMessage = WSManNativeApi.WSManGetErrorMessage(WSManAPIData.WSManAPIHandle, result);
PSInvalidOperationException exception = new PSInvalidOperationException(errorMessage);
throw exception;
}
// retrieve the packet size again
int packetSize;
WSManNativeApi.WSManGetSessionOptionAsDword(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_SHELL_MAX_DATA_SIZE_PER_MESSAGE_KB,
out packetSize);
// packet size returned is in KB. Convert this into bytes
Fragmentor.FragmentSize = packetSize << 10;
}
}
}
/// <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.
/// This will close the internal WSMan Session handle. Callers must catch the close
/// completed event and call Redirect to perform the redirection.
/// </summary>
internal override void PrepareForRedirection()
{
Dbg.Assert(!isClosed, "Transport manager must not be closed while preparing for redirection.");
_closeSessionCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_sessionContextID), s_sessionCloseCallback);
WSManNativeApi.WSManCloseShell(_wsManShellOperationHandle, 0, _closeSessionCompleted);
}
/// <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>
/// <exception cref="PSInvalidOperationException">
/// 1. Create Session failed with a non-zero error code.
/// </exception>
internal override void Redirect(Uri newUri, RunspaceConnectionInfo connectionInfo)
{
CloseSessionAndClearResources();
tracer.WriteLine("Redirecting to URI: {0}", newUri);
PSEtwLog.LogAnalyticInformational(
PSEventId.URIRedirection,
PSOpcode.Connect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
RunspacePoolInstanceId.ToString(),
newUri.ToString());
Initialize(newUri, (WSManConnectionInfo)connectionInfo);
// reset startmode
_startMode = WSManTransportManagerUtils.tmStartModes.None;
CreateAsync();
}
/// <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 override BaseClientCommandTransportManager CreateClientCommandTransportManager(RunspaceConnectionInfo connectionInfo,
ClientRemotePowerShell cmd, bool noInput)
{
Dbg.Assert(cmd != null, "Cmd cannot be null");
WSManConnectionInfo wsmanConnectionInfo = connectionInfo as WSManConnectionInfo;
Dbg.Assert(wsmanConnectionInfo != null, "ConnectionInfo must be WSManConnectionInfo");
WSManClientCommandTransportManager result = new
WSManClientCommandTransportManager(wsmanConnectionInfo, _wsManShellOperationHandle, cmd, noInput, this);
return result;
}
/// <summary>
/// Initializes the session.
/// </summary>
/// <param name="connectionUri">
/// Uri to connect to.
/// </param>
/// <param name="connectionInfo">
/// Connection info object used for retrieving credential, auth. mechanism etc.
/// </param>
/// <exception cref="PSInvalidOperationException">
/// 1. Create Session failed with a non-zero error code.
/// </exception>
private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo)
{
Dbg.Assert(connectionInfo != null, "connectionInfo cannot be null.");
ConnectionInfo = connectionInfo;
// this will generate: http://ComputerName:port/appname?PSVersion=<version>
// PSVersion= pattern is needed to make Exchange compatible with PS V2 CTP3
// release. Using the PSVersion= logic, Exchange R4 server will redirect
// clients to an R3 endpoint.
bool isSSLSpecified = false;
string connectionStr = connectionUri.OriginalString;
if ((connectionUri == connectionInfo.ConnectionUri) &&
(connectionInfo.UseDefaultWSManPort))
{
connectionStr = WSManConnectionInfo.GetConnectionString(connectionInfo.ConnectionUri,
out isSSLSpecified);
}
// TODO: Remove this after RDS moved to $using
string additionalUriSuffixString = string.Empty;
if (PSSessionConfigurationData.IsServerManager)
{
additionalUriSuffixString = ";MSP=7a83d074-bb86-4e52-aa3e-6cc73cc066c8";
}
if (string.IsNullOrEmpty(connectionUri.Query))
{
// if there is no query string already, create one..see RFC 3986
connectionStr = string.Format(CultureInfo.InvariantCulture,
"{0}?PSVersion={1}{2}",
// Trimming the last '/' as this will allow WSMan to
// properly apply URLPrefix.
// Ex: http://localhost?PSVersion=2.0 will be converted
// to http://localhost:<port>/<urlprefix>?PSVersion=2.0
// by WSMan
connectionStr.TrimEnd('/'),
PSVersionInfo.PSVersion,
additionalUriSuffixString);
}
else
{
// if there is already a query string, append using & .. see RFC 3986
connectionStr = string.Format(CultureInfo.InvariantCulture,
"{0};PSVersion={1}{2}",
connectionStr,
PSVersionInfo.PSVersion,
additionalUriSuffixString);
}
WSManNativeApi.BaseWSManAuthenticationCredentials authCredentials;
// use certificate thumbprint for authentication
if (connectionInfo.CertificateThumbprint != null)
{
authCredentials = new WSManNativeApi.WSManCertificateThumbprintCredentials(connectionInfo.CertificateThumbprint);
}
else
{
// use credential based authentication
string userName = null;
System.Security.SecureString password = null;
if ((connectionInfo.Credential != null) && (!string.IsNullOrEmpty(connectionInfo.Credential.UserName)))
{
userName = connectionInfo.Credential.UserName;
password = connectionInfo.Credential.Password;
}
WSManNativeApi.WSManUserNameAuthenticationCredentials userNameCredentials =
new WSManNativeApi.WSManUserNameAuthenticationCredentials(userName,
password,
connectionInfo.WSManAuthenticationMechanism);
authCredentials = userNameCredentials;
}
// proxy related data
WSManNativeApi.WSManUserNameAuthenticationCredentials proxyAuthCredentials = null;
if (connectionInfo.ProxyCredential != null)
{
WSManNativeApi.WSManAuthenticationMechanism authMechanism = WSManNativeApi.WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_NEGOTIATE;
string userName = null;
System.Security.SecureString password = null;
switch (connectionInfo.ProxyAuthentication)
{
case AuthenticationMechanism.Negotiate:
authMechanism = WSManNativeApi.WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_NEGOTIATE;
break;
case AuthenticationMechanism.Basic:
authMechanism = WSManNativeApi.WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_BASIC;
break;
case AuthenticationMechanism.Digest:
authMechanism = WSManNativeApi.WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_DIGEST;
break;
}
if (!string.IsNullOrEmpty(connectionInfo.ProxyCredential.UserName))
{
userName = connectionInfo.ProxyCredential.UserName;
password = connectionInfo.ProxyCredential.Password;
}
// use credential based authentication
proxyAuthCredentials = new WSManNativeApi.WSManUserNameAuthenticationCredentials(userName, password, authMechanism);
}
WSManNativeApi.WSManProxyInfo proxyInfo = (connectionInfo.ProxyAccessType == ProxyAccessType.None) ?
null :
new WSManNativeApi.WSManProxyInfo(connectionInfo.ProxyAccessType, proxyAuthCredentials);
int result = 0;
try
{
result = WSManNativeApi.WSManCreateSession(WSManAPIData.WSManAPIHandle, connectionStr, 0,
authCredentials.GetMarshalledObject(),
(proxyInfo == null) ? IntPtr.Zero : (IntPtr)proxyInfo,
ref _wsManSessionHandle);
}
finally
{
// release resources
if (proxyAuthCredentials != null)
{
proxyAuthCredentials.Dispose();
}
if (proxyInfo != null)
{
proxyInfo.Dispose();
}
if (authCredentials != null)
{
authCredentials.Dispose();
}
}
if (result != 0)
{
// Get the error message from WSMan
string errorMessage = WSManNativeApi.WSManGetErrorMessage(WSManAPIData.WSManAPIHandle, result);
PSInvalidOperationException exception = new PSInvalidOperationException(errorMessage);
throw exception;
}
// set the packet size for this session
int packetSize;
WSManNativeApi.WSManGetSessionOptionAsDword(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_SHELL_MAX_DATA_SIZE_PER_MESSAGE_KB,
out packetSize);
// packet size returned is in KB. Convert this into bytes..
Fragmentor.FragmentSize = packetSize << 10;
// Get robust connections maximum retries time.
WSManNativeApi.WSManGetSessionOptionAsDword(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_MAX_RETRY_TIME,
out _maxRetryTime);
this.dataToBeSent.Fragmentor = base.Fragmentor;
_noCompression = !connectionInfo.UseCompression;
_noMachineProfile = connectionInfo.NoMachineProfile;
// set other WSMan session related defaults
if (isSSLSpecified)
{
// WSMan Port DCR related changes - BUG 542726
// this session option will tell WSMan to use port for HTTPS from
// config provider.
SetWSManSessionOption(WSManNativeApi.WSManSessionOption.WSMAN_OPTION_USE_SSL, 1);
}
#if UNIX
// Explicitly disallow Basic auth over HTTP on Unix.
if (connectionInfo.AuthenticationMechanism == AuthenticationMechanism.Basic && !isSSLSpecified && connectionUri.Scheme != Uri.UriSchemeHttps)
{
throw new PSRemotingTransportException(PSRemotingErrorId.ConnectFailed, RemotingErrorIdStrings.BasicAuthOverHttpNotSupported);
}
// The OMI client distributed with PowerShell does not support validating server certificates on Unix.
// Check if third-party psrpclient and MI support the verification.
// If WSManGetSessionOptionAsDword does not return 0 then it's not supported.
bool verificationAvailable = WSManNativeApi.WSManGetSessionOptionAsDword(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_SKIP_CA_CHECK, out _) == 0;
if (isSSLSpecified && !verificationAvailable && (!connectionInfo.SkipCACheck || !connectionInfo.SkipCNCheck))
{
throw new PSRemotingTransportException(PSRemotingErrorId.ConnectSkipCheckFailed, RemotingErrorIdStrings.UnixOnlyHttpsWithoutSkipCACheckNotSupported);
}
#endif
if (connectionInfo.NoEncryption)
{
// send unencrypted messages
SetWSManSessionOption(WSManNativeApi.WSManSessionOption.WSMAN_OPTION_UNENCRYPTED_MESSAGES, 1);
}
// check if implicit credentials can be used for Negotiate
if (connectionInfo.AllowImplicitCredentialForNegotiate)
{
result = WSManNativeApi.WSManSetSessionOption(_wsManSessionHandle,
WSManNativeApi.WSManSessionOption.WSMAN_OPTION_ALLOW_NEGOTIATE_IMPLICIT_CREDENTIALS,
new WSManNativeApi.WSManDataDWord(1));
}
if (connectionInfo.UseUTF16)
{
SetWSManSessionOption(WSManNativeApi.WSManSessionOption.WSMAN_OPTION_UTF16, 1);
}
if (connectionInfo.SkipCACheck)
{
SetWSManSessionOption(WSManNativeApi.WSManSessionOption.WSMAN_OPTION_SKIP_CA_CHECK, 1);
}
if (connectionInfo.SkipCNCheck)
{
SetWSManSessionOption(WSManNativeApi.WSManSessionOption.WSMAN_OPTION_SKIP_CN_CHECK, 1);
}
if (connectionInfo.SkipRevocationCheck)
{
SetWSManSessionOption(WSManNativeApi.WSManSessionOption.WSMAN_OPTION_SKIP_REVOCATION_CHECK, 1);
}
if (connectionInfo.IncludePortInSPN)
{
SetWSManSessionOption(WSManNativeApi.WSManSessionOption.WSMAN_OPTION_ENABLE_SPN_SERVER_PORT, 1);
}
// Set use interactive token flag based on EnableNetworkAccess property.
SetWSManSessionOption(WSManNativeApi.WSManSessionOption.WSMAN_OPTION_USE_INTERACTIVE_TOKEN,
(connectionInfo.EnableNetworkAccess) ? 1 : 0);
// set UI Culture for this session from current thread's UI Culture
string currentUICulture = connectionInfo.UICulture.Name;
if (!string.IsNullOrEmpty(currentUICulture))
{
// WSMan API cannot handle empty culture names
SetWSManSessionOption(WSManNativeApi.WSManSessionOption.WSMAN_OPTION_UI_LANGUAGE, currentUICulture);
}
// set Culture for this session from current thread's Culture
string currentCulture = connectionInfo.Culture.Name;
if (!string.IsNullOrEmpty(currentCulture))
{
SetWSManSessionOption(WSManNativeApi.WSManSessionOption.WSMAN_OPTION_LOCALE, currentCulture);
}
// set the PowerShell specific default client timeouts
SetDefaultTimeOut(connectionInfo.OperationTimeout);
SetConnectTimeOut(connectionInfo.OpenTimeout);
SetCloseTimeOut(connectionInfo.CancelTimeout);
SetSignalTimeOut(connectionInfo.CancelTimeout);
}
/// <summary>
/// Handle transport error - calls EnqueueAndStartProcessingThread to process transport exception
/// in a different thread
/// Logic in transport callbacks should always use this to process a transport error.
/// </summary>
internal void ProcessWSManTransportError(TransportErrorOccuredEventArgs eventArgs)
{
EnqueueAndStartProcessingThread(null, eventArgs, null);
}
/// <summary>
/// Log the error message in the Crimson logger and raise error handler.
/// </summary>
/// <param name="eventArgs"></param>
internal override void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArgs)
{
// Look for a valid stack trace.
string stackTrace;
if (!string.IsNullOrEmpty(eventArgs.Exception.StackTrace))
{
stackTrace = eventArgs.Exception.StackTrace;
}
else if (eventArgs.Exception.InnerException != null &&
!string.IsNullOrEmpty(eventArgs.Exception.InnerException.StackTrace))
{
stackTrace = eventArgs.Exception.InnerException.StackTrace;
}
else
{
stackTrace = string.Empty;
}
// Write errors into both Operational and Analytical channels
PSEtwLog.LogOperationalError(
PSEventId.TransportError, PSOpcode.Open, PSTask.None, PSKeyword.UseAlwaysOperational,
RunspacePoolInstanceId.ToString(),
Guid.Empty.ToString(),
eventArgs.Exception.ErrorCode.ToString(CultureInfo.InvariantCulture),
eventArgs.Exception.Message,
stackTrace);
PSEtwLog.LogAnalyticError(
PSEventId.TransportError_Analytic,
PSOpcode.Open, PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
RunspacePoolInstanceId.ToString(),
Guid.Empty.ToString(),
eventArgs.Exception.ErrorCode.ToString(CultureInfo.InvariantCulture),
eventArgs.Exception.Message,
stackTrace);
base.RaiseErrorHandler(eventArgs);
}
/// <summary>
/// Receive/send operation handles and callback handles should be released/disposed from
/// receive/send callback only. Releasing them after CloseOperation() may not cover all
/// the scenarios, as WSMan does not guarantee that a rcv/send callback is not called after
/// Close completed callback.
/// </summary>
/// <param name="flags"></param>
/// <param name="shouldClearSend"></param>
internal void ClearReceiveOrSendResources(int flags, bool shouldClearSend)
{
if (shouldClearSend)
{
if (_sendToRemoteCompleted != null)
{
_sendToRemoteCompleted.Dispose();
_sendToRemoteCompleted = null;
}
// For send..clear always
if (_wsManSendOperationHandle != IntPtr.Zero)
{
WSManNativeApi.WSManCloseOperation(_wsManSendOperationHandle, 0);
_wsManSendOperationHandle = IntPtr.Zero;
}
}
else
{
// clearing for receive..Clear only when the end of operation is reached.
if (flags == (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_END_OF_OPERATION)
{
if (_wsManReceiveOperationHandle != IntPtr.Zero)
{
WSManNativeApi.WSManCloseOperation(_wsManReceiveOperationHandle, 0);
_wsManReceiveOperationHandle = IntPtr.Zero;
}
if (_receivedFromRemote != null)
{
_receivedFromRemote.Dispose();
_receivedFromRemote = null;
}
}
}
}
/// <summary>
/// Call back from worker thread / queue to raise Robust Connection notification event.
/// </summary>
/// <param name="privateData">ConnectionStatusEventArgs.</param>
internal override void ProcessPrivateData(object privateData)
{
// Raise the Robust
ConnectionStatusEventArgs rcArgs = privateData as ConnectionStatusEventArgs;
if (rcArgs != null)
{
RaiseRobustConnectionNotification(rcArgs);
return;
}
CompletionEventArgs completionArgs = privateData as CompletionEventArgs;
if (completionArgs != null)
{
switch (completionArgs.Notification)
{
case CompletionNotification.DisconnectCompleted:
RaiseDisconnectCompleted();
break;
default:
Dbg.Assert(false, "Currently only DisconnectCompleted notification is handled on the worker thread queue.");
break;
}
return;
}
Dbg.Assert(false, "Worker thread callback should always have ConnectionStatusEventArgs or CompletionEventArgs type for privateData.");
}
/// <summary>
/// Robust connection maximum retry time in milliseconds.
/// </summary>
internal int MaxRetryConnectionTime
{
get { return _maxRetryTime; }
}
/// <summary>
/// Returns the WSMan's session handle that this Session transportmanager
/// is proxying.
/// </summary>
internal IntPtr SessionHandle
{
get { return _wsManSessionHandle; }
}
/// <summary>
/// Returns the WSManConnectionInfo used to make the connection.
/// </summary>
internal WSManConnectionInfo ConnectionInfo { get; private set; }
/// <summary>
/// Examine the session create error code and if the error is one where a
/// session create/connect retry attempt may be beneficial then do the
/// retry attempt.
/// </summary>
/// <param name="sessionCreateErrorCode">Error code returned from Create response.</param>
/// <returns>True if a session create retry has been started.</returns>
private bool RetrySessionCreation(int sessionCreateErrorCode)
{
if (_connectionRetryCount >= ConnectionInfo.MaxConnectionRetryCount) { return false; }
bool retryConnect;
switch (sessionCreateErrorCode)
{
// Continue with connect retry for these errors.
case WSManNativeApi.ERROR_WSMAN_SENDDATA_CANNOT_CONNECT:
case WSManNativeApi.ERROR_WSMAN_OPERATION_ABORTED:
case WSManNativeApi.ERROR_WSMAN_IMPROPER_RESPONSE:
case WSManNativeApi.ERROR_WSMAN_URL_NOTAVAILABLE:
case WSManNativeApi.ERROR_WSMAN_CANNOT_CONNECT_INVALID:
case WSManNativeApi.ERROR_WSMAN_CANNOT_CONNECT_MISMATCH:
case WSManNativeApi.ERROR_WSMAN_HTTP_SERVICE_UNAVAILABLE:
case WSManNativeApi.ERROR_WSMAN_HTTP_SERVICE_ERROR:
retryConnect = true;
break;
// For any other errors don't do connect retry.
default:
retryConnect = false;
break;
}
if (retryConnect)
{
++_connectionRetryCount;
// Write trace output
tracer.WriteLine("Attempting session creation retry {0} for error code {1} on session Id {2}",
_connectionRetryCount, sessionCreateErrorCode, RunspacePoolInstanceId);
// Create ETW log entry
PSEtwLog.LogOperationalInformation(
PSEventId.RetrySessionCreation, PSOpcode.Open, PSTask.None,
PSKeyword.UseAlwaysOperational,
_connectionRetryCount.ToString(CultureInfo.InvariantCulture),
sessionCreateErrorCode.ToString(CultureInfo.InvariantCulture),
RunspacePoolInstanceId.ToString());
// Use worker pool thread to initiate retry, since WSMan does not allow method
// calls on its own call back thread.
System.Threading.ThreadPool.QueueUserWorkItem(StartCreateRetry);
}
return retryConnect;
}
private void StartCreateRetry(object state)
{
// Begin new session create attempt.
_startMode = WSManTransportManagerUtils.tmStartModes.None;
CreateAsync();
}
#endregion
#region Static Callbacks from WSMan
// callback that gets called when createshellex returns.
private static void OnCreateSessionCallback(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("Client Session TM: CreateShell callback received");
long sessionTMHandle = 0;
WSManClientSessionTransportManager sessionTM = null;
if (!TryGetSessionTransportManager(operationContext, out sessionTM, out sessionTMHandle))
{
// We dont have the session TM handle..just return.
tracer.WriteLine("Unable to find a transport manager for context {0}.", sessionTMHandle);
return;
}
// This callback is also used for robust connection notifications. Check for and
// handle these notifications here.
if (HandleRobustConnectionCallback(flags, sessionTM))
{
return;
}
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCreateShellCallbackReceived,
PSOpcode.Connect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
sessionTM.RunspacePoolInstanceId.ToString());
// TODO: 188098 wsManShellOperationHandle should be populated by WSManCreateShellEx,
// but there is a thread timing bug in WSMan layer causing the callback to
// be called before WSManCreateShellEx returns. since we already validated the
// shell context exists, safely assigning the shellOperationHandle to shell transport manager.
// Remove this once WSMan fixes its code.
sessionTM._wsManShellOperationHandle = shellOperationHandle;
lock (sessionTM.syncObject)
{
// Already close request is made. So return
if (sessionTM.isClosed)
{
return;
}
}
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
if (errorStruct.errorCode != 0)
{
tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail);
// Test error code for possible session connection retry.
if (sessionTM.RetrySessionCreation(errorStruct.errorCode))
{
// If a connection retry is being attempted (on
// another thread) then return without processing error.
return;
}
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
sessionTM.WSManAPIData.WSManAPIHandle,
sessionTM,
errorStruct,
TransportMethodEnum.CreateShellEx,
RemotingErrorIdStrings.ConnectExCallBackError,
new object[] { sessionTM.ConnectionInfo.ComputerName, WSManTransportManagerUtils.ParseEscapeWSManErrorMessage(errorStruct.errorDetail) });
sessionTM.ProcessWSManTransportError(eventargs);
return;
}
}
// check if the session supports disconnect
sessionTM.SupportsDisconnect = (flags & (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_SHELL_SUPPORTS_DISCONNECT) != 0;
// openContent is used by redirection ie., while redirecting to
// a new machine.. this is not needed anymore as the connection
// is successfully established.
if (sessionTM._openContent != null)
{
sessionTM._openContent.Dispose();
sessionTM._openContent = null;
}
if (data != IntPtr.Zero)
{
WSManNativeApi.WSManCreateShellDataResult shellData = WSManNativeApi.WSManCreateShellDataResult.UnMarshal(data);
if (shellData.data != null)
{
string returnXml = shellData.data;
sessionTM.ProcessShellData(returnXml);
}
}
lock (sessionTM.syncObject)
{
// make sure the transport is not closed yet.
if (sessionTM.isClosed)
{
tracer.WriteLine("Client Session TM: Transport manager is closed. So returning");
return;
}
// Successfully made a connection. Now report this by raising the CreateCompleted event.
// Pass updated connection information to event.
sessionTM.RaiseCreateCompleted(
new CreateCompleteEventArgs(sessionTM.ConnectionInfo.Copy()));
// Since create shell is successful, put a receive request.
sessionTM.StartReceivingData();
}
// Start sending data if any.
sessionTM.SendOneItem();
}
// callback that gets called when closeshellex returns.
private static void OnCloseSessionCompleted(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("Client Session TM: CloseShell callback received");
long sessionTMHandle = 0;
WSManClientSessionTransportManager sessionTM = null;
if (!TryGetSessionTransportManager(operationContext, out sessionTM, out sessionTMHandle))
{
// We dont have the session TM handle..just return.
tracer.WriteLine("Unable to find a transport manager for context {0}.", sessionTMHandle);
return;
}
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCloseShellCallbackReceived,
PSOpcode.Disconnect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
sessionTM.RunspacePoolInstanceId.ToString(),
"OnCloseSessionCompleted");
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
if (errorStruct.errorCode != 0)
{
tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail);
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
sessionTM.WSManAPIData.WSManAPIHandle,
sessionTM,
errorStruct,
TransportMethodEnum.CloseShellOperationEx,
RemotingErrorIdStrings.CloseExCallBackError,
new object[] { WSManTransportManagerUtils.ParseEscapeWSManErrorMessage(errorStruct.errorDetail) });
sessionTM.RaiseErrorHandler(eventargs);
return;
}
}
sessionTM.RaiseCloseCompleted();
}
private static void OnRemoteSessionDisconnectCompleted(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("Client Session TM: CreateShell callback received");
long sessionTMHandle = 0;
WSManClientSessionTransportManager sessionTM = null;
if (!TryGetSessionTransportManager(operationContext, out sessionTM, out sessionTMHandle))
{
// We dont have the session TM handle..just return.
tracer.WriteLine("Unable to find a transport manager for context {0}.", sessionTMHandle);
return;
}
// LOG ETW EVENTS
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCloseShellCallbackReceived,
PSOpcode.Disconnect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
sessionTM.RunspacePoolInstanceId.ToString(),
"OnRemoteSessionDisconnectCompleted");
// Dispose the OnDisconnect callback as it is not needed anymore
if (sessionTM._disconnectSessionCompleted != null)
{
sessionTM._disconnectSessionCompleted.Dispose();
sessionTM._disconnectSessionCompleted = null;
}
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
if (errorStruct.errorCode != 0)
{
tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail);
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
sessionTM.WSManAPIData.WSManAPIHandle,
sessionTM,
errorStruct,
TransportMethodEnum.DisconnectShellEx,
RemotingErrorIdStrings.DisconnectShellExFailed,
new object[] { sessionTM.ConnectionInfo.ComputerName, WSManTransportManagerUtils.ParseEscapeWSManErrorMessage(errorStruct.errorDetail) });
sessionTM.ProcessWSManTransportError(eventargs);
return;
}
}
lock (sessionTM.syncObject)
{
// make sure the transport is not closed yet.
if (sessionTM.isClosed)
{
tracer.WriteLine("Client Session TM: Transport manager is closed. So returning");
return;
}
// successfully made a connection. Now report this by raising the ConnectCompleted event.
sessionTM.EnqueueAndStartProcessingThread(null, null,
new CompletionEventArgs(CompletionNotification.DisconnectCompleted));
// Log ETW traces
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCloseShellCallbackReceived,
PSOpcode.Disconnect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
sessionTM.RunspacePoolInstanceId.ToString(),
"OnRemoteSessionReconnectCompleted: DisconnectCompleted");
}
return;
}
private static void OnRemoteSessionReconnectCompleted(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("Client Session TM: CreateShell callback received");
long sessionTMHandle = 0;
WSManClientSessionTransportManager sessionTM = null;
if (!TryGetSessionTransportManager(operationContext, out sessionTM, out sessionTMHandle))
{
// We dont have the session TM handle..just return.
tracer.WriteLine("Unable to find a transport manager for context {0}.", sessionTMHandle);
return;
}
// Add ETW events
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCloseShellCallbackReceived,
PSOpcode.Disconnect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
sessionTM.RunspacePoolInstanceId.ToString(),
"OnRemoteSessionReconnectCompleted");
// Dispose the OnCreate callback as it is not needed anymore
if (sessionTM._reconnectSessionCompleted != null)
{
sessionTM._reconnectSessionCompleted.Dispose();
sessionTM._reconnectSessionCompleted = null;
}
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
if (errorStruct.errorCode != 0)
{
tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail);
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
sessionTM.WSManAPIData.WSManAPIHandle,
sessionTM,
errorStruct,
TransportMethodEnum.ReconnectShellEx,
RemotingErrorIdStrings.ReconnectShellExCallBackErrr,
new object[] { sessionTM.ConnectionInfo.ComputerName, WSManTransportManagerUtils.ParseEscapeWSManErrorMessage(errorStruct.errorDetail) });
sessionTM.ProcessWSManTransportError(eventargs);
return;
}
}
lock (sessionTM.syncObject)
{
// make sure the transport is not closed yet.
if (sessionTM.isClosed)
{
tracer.WriteLine("Client Session TM: Transport manager is closed. So returning");
return;
}
// successfully made a connection. Now report this by raising the ConnectCompleted event.
sessionTM.RaiseReconnectCompleted();
}
}
private static bool HandleRobustConnectionCallback(int flags, WSManClientSessionTransportManager sessionTM)
{
if (flags != (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_SHELL_AUTODISCONNECTED &&
flags != (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_NETWORK_FAILURE_DETECTED &&
flags != (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_RETRYING_AFTER_NETWORK_FAILURE &&
flags != (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_RECONNECTED_AFTER_NETWORK_FAILURE &&
flags != (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_SHELL_AUTODISCONNECTING &&
flags != (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_RETRY_ABORTED_DUE_TO_INTERNAL_ERROR)
{
return false;
}
// Raise transport event notifying start of robust connections.
if (flags == (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_NETWORK_FAILURE_DETECTED)
{
try
{
sessionTM.RobustConnectionsInitiated.SafeInvoke(sessionTM, EventArgs.Empty);
}
catch (ObjectDisposedException)
{ }
}
// Send robust notification to client.
sessionTM.QueueRobustConnectionNotification(flags);
// Raise transport event notifying completion of robust connections.
if (flags == (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_SHELL_AUTODISCONNECTED ||
flags == (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_RECONNECTED_AFTER_NETWORK_FAILURE ||
flags == (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_RETRY_ABORTED_DUE_TO_INTERNAL_ERROR)
{
try
{
sessionTM.RobustConnectionsCompleted.SafeInvoke(sessionTM, EventArgs.Empty);
}
catch (ObjectDisposedException)
{ }
}
return true;
}
private static void OnRemoteSessionConnectCallback(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("Client Session TM: Connect callback received");
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManSendShellInputExCallbackReceived,
PSOpcode.Connect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
"OnRemoteSessionConnectCallback:Client Session TM: Connect callback received");
long sessionTMHandle = 0;
WSManClientSessionTransportManager sessionTM = null;
if (!TryGetSessionTransportManager(operationContext, out sessionTM, out sessionTMHandle))
{
// We dont have the session TM handle..just return.
tracer.WriteLine("Unable to find a transport manager for context {0}.", sessionTMHandle);
return;
}
// This callback is also used for robust connection notifications. Check for and
// handle these notifications here.
if (HandleRobustConnectionCallback(flags, sessionTM))
{
return;
}
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
if (errorStruct.errorCode != 0)
{
tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail);
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
sessionTM.WSManAPIData.WSManAPIHandle,
sessionTM,
errorStruct,
TransportMethodEnum.ConnectShellEx,
RemotingErrorIdStrings.ConnectExCallBackError,
new object[] { sessionTM.ConnectionInfo.ComputerName, WSManTransportManagerUtils.ParseEscapeWSManErrorMessage(errorStruct.errorDetail) });
sessionTM.ProcessWSManTransportError(eventargs);
return;
}
}
// dispose openContent
if (sessionTM._openContent != null)
{
sessionTM._openContent.Dispose();
sessionTM._openContent = null;
}
lock (sessionTM.syncObject)
{
// make sure the transport is not closed yet.
if (sessionTM.isClosed)
{
tracer.WriteLine("Client Session TM: Transport manager is closed. So returning");
return;
}
}
// process returned Xml
Dbg.Assert(data != IntPtr.Zero, "WSManConnectShell callback returned null data");
WSManNativeApi.WSManConnectDataResult connectData = WSManNativeApi.WSManConnectDataResult.UnMarshal(data);
if (connectData.data != null)
{
byte[] connectResponse = ServerOperationHelpers.ExtractEncodedXmlElement(connectData.data, WSManNativeApi.PS_CONNECTRESPONSE_XML_TAG);
sessionTM.ProcessRawData(connectResponse, WSManNativeApi.WSMAN_STREAM_ID_STDOUT);
}
// Set up the data-to-send callback.
sessionTM.SendOneItem();
// successfully made a connection. Now report this by raising the ConnectCompleted event.
// Microsoft's PS 3.0 Server will return all negotiation related data in one shot in connect Data
// Note that we are not starting to receive data yet. the DSHandlers above will do that when the session
// gets to an established state.
sessionTM.RaiseConnectCompleted();
}
private static void OnRemoteSessionSendCompleted(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("Client Session TM: SendComplete callback received");
long sessionTMHandle = 0;
WSManClientSessionTransportManager sessionTM = null;
if (!TryGetSessionTransportManager(operationContext, out sessionTM, out sessionTMHandle))
{
// We dont have the session TM handle..just return.
tracer.WriteLine("Unable to find a transport manager for context {0}.", sessionTMHandle);
return;
}
// do the logging for this send
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManSendShellInputExCallbackReceived,
PSOpcode.Connect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
sessionTM.RunspacePoolInstanceId.ToString(),
Guid.Empty.ToString());
if (!shellOperationHandle.Equals(sessionTM._wsManShellOperationHandle))
{
// WSMan returned data from a wrong shell..notify the caller
// about the same.
PSRemotingTransportException e = new PSRemotingTransportException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.SendExFailed, sessionTM.ConnectionInfo.ComputerName));
TransportErrorOccuredEventArgs eventargs =
new TransportErrorOccuredEventArgs(e, TransportMethodEnum.SendShellInputEx);
sessionTM.ProcessWSManTransportError(eventargs);
return;
}
sessionTM.ClearReceiveOrSendResources(flags, true);
// if the session is already closed ignore the errors and return.
if (sessionTM.isClosed)
{
tracer.WriteLine("Client Session TM: Transport manager is closed. So returning");
return;
}
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
// Ignore operation aborted error. operation aborted is raised by WSMan to
// notify operation complete. PowerShell protocol has its own
// way of notifying the same using state change events.
if ((errorStruct.errorCode != 0) && (errorStruct.errorCode != 995))
{
tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail);
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
sessionTM.WSManAPIData.WSManAPIHandle,
sessionTM,
errorStruct,
TransportMethodEnum.SendShellInputEx,
RemotingErrorIdStrings.SendExCallBackError,
new object[] { sessionTM.ConnectionInfo.ComputerName, WSManTransportManagerUtils.ParseEscapeWSManErrorMessage(errorStruct.errorDetail) });
sessionTM.ProcessWSManTransportError(eventargs);
return;
}
}
// Send the next item, if available
sessionTM.SendOneItem();
}
// WSMan will make sure this callback is synchronously called ie., if 1 callback
// is active, the callback will not be called from a different thread.
private static void OnRemoteSessionDataReceived(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("Client Session TM: OnRemoteDataReceived callback.");
long sessionTMHandle = 0;
WSManClientSessionTransportManager sessionTM = null;
if (!TryGetSessionTransportManager(operationContext, out sessionTM, out sessionTMHandle))
{
// We dont have the session TM handle..just return.
tracer.WriteLine("Unable to find a transport manager for context {0}.", sessionTMHandle);
return;
}
sessionTM.ClearReceiveOrSendResources(flags, false);
if (sessionTM.isClosed)
{
tracer.WriteLine("Client Session TM: Transport manager is closed. So returning");
return;
}
if (!shellOperationHandle.Equals(sessionTM._wsManShellOperationHandle))
{
// WSMan returned data from a wrong shell..notify the caller
// about the same.
PSRemotingTransportException e = new PSRemotingTransportException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.ReceiveExFailed, sessionTM.ConnectionInfo.ComputerName));
TransportErrorOccuredEventArgs eventargs =
new TransportErrorOccuredEventArgs(e, TransportMethodEnum.ReceiveShellOutputEx);
sessionTM.ProcessWSManTransportError(eventargs);
return;
}
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
if (errorStruct.errorCode != 0)
{
tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail);
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
sessionTM.WSManAPIData.WSManAPIHandle,
sessionTM,
errorStruct,
TransportMethodEnum.ReceiveShellOutputEx,
RemotingErrorIdStrings.ReceiveExCallBackError,
new object[] { sessionTM.ConnectionInfo.ComputerName, WSManTransportManagerUtils.ParseEscapeWSManErrorMessage(errorStruct.errorDetail) });
sessionTM.ProcessWSManTransportError(eventargs);
return;
}
}
WSManNativeApi.WSManReceiveDataResult dataReceived = WSManNativeApi.WSManReceiveDataResult.UnMarshal(data);
if (dataReceived.data != null)
{
tracer.WriteLine("Session Received Data : {0}", dataReceived.data.Length);
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManReceiveShellOutputExCallbackReceived, PSOpcode.Receive, PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
sessionTM.RunspacePoolInstanceId.ToString(),
Guid.Empty.ToString(),
dataReceived.data.Length.ToString(CultureInfo.InvariantCulture));
sessionTM.ProcessRawData(dataReceived.data, dataReceived.stream);
}
}
#endregion
#region Send Data Handling
private void SendOneItem()
{
DataPriorityType priorityType;
// This will either return data or register callback but doesn't do both.
byte[] data = dataToBeSent.ReadOrRegisterCallback(_onDataAvailableToSendCallback,
out priorityType);
if (data != null)
{
SendData(data, priorityType);
}
}
private void OnDataAvailableCallback(byte[] data, DataPriorityType priorityType)
{
Dbg.Assert(data != null, "data cannot be null in the data available callback");
tracer.WriteLine("Received data to be sent from the callback.");
SendData(data, priorityType);
}
private void SendData(byte[] data, DataPriorityType priorityType)
{
tracer.WriteLine("Session sending data of size : {0}", data.Length);
byte[] package = data;
#region SHIM: Redirection code for session data send.
bool sendContinue = true;
if (s_sessionSendRedirect != null)
{
object[] arguments = new object[2] { null, package };
sendContinue = (bool)s_sessionSendRedirect.DynamicInvoke(arguments);
package = (byte[])arguments[0];
}
if (!sendContinue)
return;
#endregion
using (WSManNativeApi.WSManData_ManToUn serializedContent =
new WSManNativeApi.WSManData_ManToUn(package))
{
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManSendShellInputEx, PSOpcode.Send, PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
RunspacePoolInstanceId.ToString(),
Guid.Empty.ToString(),
serializedContent.BufferLength.ToString(CultureInfo.InvariantCulture));
lock (syncObject)
{
// make sure the transport is not closed.
if (isClosed)
{
tracer.WriteLine("Client Session TM: Transport manager is closed. So returning");
return;
}
// send callback
_sendToRemoteCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_sessionContextID), s_sessionSendCallback);
WSManNativeApi.WSManSendShellInputEx(_wsManShellOperationHandle, IntPtr.Zero, 0,
priorityType == DataPriorityType.Default ?
WSManNativeApi.WSMAN_STREAM_ID_STDIN : WSManNativeApi.WSMAN_STREAM_ID_PROMPTRESPONSE,
serializedContent,
_sendToRemoteCompleted,
ref _wsManSendOperationHandle);
}
}
}
#endregion
#region Dispose / Destructor pattern
[SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed")]
internal override void Dispose(bool isDisposing)
{
tracer.WriteLine("Disposing session with session context: {0} Operation Context: {1}", _sessionContextID, _wsManShellOperationHandle);
CloseSessionAndClearResources();
DisposeWSManAPIDataAsync();
// openContent is used by redirection ie., while redirecting to
// a new machine and hence this is cleared only when the session
// is disposing.
if (isDisposing && (_openContent != null))
{
_openContent.Dispose();
_openContent = null;
}
base.Dispose(isDisposing);
}
/// <summary>
/// Closes current session handle by calling WSManCloseSession and clears
/// session related resources.
/// </summary>
private void CloseSessionAndClearResources()
{
tracer.WriteLine("Clearing session with session context: {0} Operation Context: {1}", _sessionContextID, _wsManShellOperationHandle);
// Taking a copy of session handle as we should call WSManCloseSession only once and
// clear the original value. This will protect us if Dispose() is called twice.
IntPtr tempWSManSessionHandle = _wsManSessionHandle;
_wsManSessionHandle = IntPtr.Zero;
// Call WSManCloseSession on a different thread as Dispose can be called from one of
// the WSMan callback threads. WSMan does not support closing a session in the callbacks.
ThreadPool.QueueUserWorkItem(new WaitCallback(
// wsManSessionHandle is passed as parameter to allow the thread to be independent
// of the rest of the parent object.
(object state) =>
{
IntPtr sessionHandle = (IntPtr)state;
if (sessionHandle != IntPtr.Zero)
{
WSManNativeApi.WSManCloseSession(sessionHandle, 0);
}
}), tempWSManSessionHandle);
// remove session context from session handles dictionary
RemoveSessionTransportManager(_sessionContextID);
if (_closeSessionCompleted != null)
{
_closeSessionCompleted.Dispose();
_closeSessionCompleted = null;
}
// Dispose the create session completed callback here, since it is
// used for periodic robust connection retry/auto-disconnect
// notifications while the shell is active.
if (_createSessionCallback != null)
{
_createSessionCallbackGCHandle.Free();
_createSessionCallback.Dispose();
_createSessionCallback = null;
}
// Dispose the OnConnect callback if one present
if (_connectSessionCallback != null)
{
_connectSessionCallback.Dispose();
_connectSessionCallback = null;
}
// Reset the session context Id to zero so that a new one will be generated for
// any following redirected session.
_sessionContextID = 0;
}
private void DisposeWSManAPIDataAsync()
{
WSManAPIDataCommon tempWSManApiData = WSManAPIData;
if (tempWSManApiData == null) { return; }
WSManAPIData = null;
// Dispose and de-initialize the WSManAPIData instance object on separate worker thread to ensure
// it is not run on a WinRM thread (which will fail).
// Note that WSManAPIData.Dispose() method is thread safe.
ThreadPool.QueueUserWorkItem((_) => tempWSManApiData.Dispose());
}
#endregion
#region WSManAPIDataCommon
/// <summary>
/// Class that manages WSManAPI data. Has information like APIHandle which is created
/// using WSManInitialize, InputStreamSet, OutputStreamSet.
/// </summary>
internal class WSManAPIDataCommon : IDisposable
{
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _handle;
// if any
private WSManNativeApi.WSManStreamIDSet_ManToUn _inputStreamSet;
private WSManNativeApi.WSManStreamIDSet_ManToUn _outputStreamSet;
// Dispose
private bool _isDisposed;
private readonly object _syncObject = new object();
#if !UNIX
private readonly WindowsIdentity _identityToImpersonate;
#endif
/// <summary>
/// Initializes handle by calling WSManInitialize API.
/// </summary>
internal WSManAPIDataCommon()
{
#if !UNIX
// Check for thread impersonation and save identity for later de-initialization.
Utils.TryGetWindowsImpersonatedIdentity(out _identityToImpersonate);
#endif
_handle = IntPtr.Zero;
try
{
ErrorCode = WSManNativeApi.WSManInitialize(WSManNativeApi.WSMAN_FLAG_REQUESTED_API_VERSION_1_1, ref _handle);
}
catch (DllNotFoundException ex)
{
PSEtwLog.LogOperationalError(
PSEventId.TransportError,
PSOpcode.Open,
PSTask.None,
PSKeyword.UseAlwaysOperational,
"WSManAPIDataCommon.ctor",
"WSManInitialize",
ex.HResult.ToString(CultureInfo.InvariantCulture),
ex.Message,
ex.StackTrace);
throw new PSRemotingTransportException(RemotingErrorIdStrings.WSManClientDllNotAvailable, ex);
}
// input / output streams common to all connections
_inputStreamSet = new WSManNativeApi.WSManStreamIDSet_ManToUn(
new string[] {
WSManNativeApi.WSMAN_STREAM_ID_STDIN,
WSManNativeApi.WSMAN_STREAM_ID_PROMPTRESPONSE
});
_outputStreamSet = new WSManNativeApi.WSManStreamIDSet_ManToUn(
new string[] { WSManNativeApi.WSMAN_STREAM_ID_STDOUT });
// startup options common to all connections
WSManNativeApi.WSManOption protocolStartupOption = new WSManNativeApi.WSManOption();
protocolStartupOption.name = RemoteDataNameStrings.PS_STARTUP_PROTOCOL_VERSION_NAME;
protocolStartupOption.value = RemotingConstants.ProtocolVersion.ToString();
protocolStartupOption.mustComply = true;
CommonOptionSet = new List<WSManNativeApi.WSManOption>();
CommonOptionSet.Add(protocolStartupOption);
}
internal int ErrorCode { get; }
internal WSManNativeApi.WSManStreamIDSet_ManToUn InputStreamSet { get { return _inputStreamSet; } }
internal WSManNativeApi.WSManStreamIDSet_ManToUn OutputStreamSet { get { return _outputStreamSet; } }
internal List<WSManNativeApi.WSManOption> CommonOptionSet { get; }
internal IntPtr WSManAPIHandle { get { return _handle; } }
/// <summary>
/// Dispose.
/// </summary>
// Suppress this message. The result is actually used, but only in checked builds....
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults",
MessageId = "System.Management.Automation.Remoting.Client.WSManNativeApi.WSManDeinitialize(System.IntPtr,System.Int32)")]
[SuppressMessage("Microsoft.Usage", "CA2216:Disposabletypesshoulddeclarefinalizer")]
public void Dispose()
{
lock (_syncObject)
{
if (_isDisposed) { return; }
_isDisposed = true;
}
_inputStreamSet.Dispose();
_outputStreamSet.Dispose();
if (_handle != IntPtr.Zero)
{
int result = 0;
#if !UNIX
// If we initialized with thread impersonation, make sure de-initialize is run with the same.
if (_identityToImpersonate != null)
{
result = WindowsIdentity.RunImpersonated(
_identityToImpersonate.AccessToken,
() => WSManNativeApi.WSManDeinitialize(_handle, 0));
}
else
{
#endif
result = WSManNativeApi.WSManDeinitialize(_handle, 0);
#if !UNIX
}
#endif
Dbg.Assert(result == 0, "WSManDeinitialize returned non-zero value");
_handle = IntPtr.Zero;
}
}
}
#endregion
#region EventHandlers
internal event EventHandler<EventArgs> RobustConnectionsInitiated;
internal event EventHandler<EventArgs> RobustConnectionsCompleted;
#endregion
}
/// <summary>
/// A class maintaining the transport of a command for the shell. Multiple commands will have
/// multiple transport managers. The Transport manager manages creating / sending /receiving
/// data and closing (terminating) the command.
/// </summary>
internal sealed class WSManClientCommandTransportManager : BaseClientCommandTransportManager
{
#region Consts
internal const string StopSignal = @"powershell/signal/crtl_c";
#endregion
#region Private Data
// operation handles
private readonly IntPtr _wsManShellOperationHandle;
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _wsManCmdOperationHandle;
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _cmdSignalOperationHandle;
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _wsManReceiveOperationHandle;
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _wsManSendOperationHandle;
// this is used with WSMan callbacks to represent a command transport manager.
private long _cmdContextId;
private readonly PrioritySendDataCollection.OnDataAvailableCallback _onDataAvailableToSendCallback;
// should be integrated with receiveDataInitiated
private bool _shouldStartReceivingData;
// bools used to track and send stop signal only after Create is completed.
private bool _isCreateCallbackReceived;
private bool _isStopSignalPending;
private bool _isDisconnectPending;
private bool _isSendingInput;
private bool _isDisconnectedOnInvoke;
// callbacks
private WSManNativeApi.WSManShellAsync _createCmdCompleted;
private WSManNativeApi.WSManShellAsync _receivedFromRemote;
private WSManNativeApi.WSManShellAsync _sendToRemoteCompleted;
private WSManNativeApi.WSManShellAsync _reconnectCmdCompleted;
private WSManNativeApi.WSManShellAsync _connectCmdCompleted;
// TODO: This GCHandle is required as it seems WSMan is calling create callback
// after we call Close. This seems wrong. Opened bug on WSMan to track this.
private GCHandle _createCmdCompletedGCHandle;
private WSManNativeApi.WSManShellAsync _closeCmdCompleted;
private WSManNativeApi.WSManShellAsync _signalCmdCompleted;
// this is the chunk that got delivered on onDataAvailableToSendCallback
// will be sent during subsequent SendOneItem()
private SendDataChunk _chunkToSend;
private readonly string _cmdLine;
private readonly WSManClientSessionTransportManager _sessnTm;
private sealed class SendDataChunk
{
public SendDataChunk(byte[] data, DataPriorityType type)
{
Data = data;
Type = type;
}
public byte[] Data { get; }
public DataPriorityType Type { get; }
}
#endregion
#region Static Data
// static callback delegate
private static WSManNativeApi.WSManShellAsyncCallback s_cmdCreateCallback;
private static WSManNativeApi.WSManShellAsyncCallback s_cmdCloseCallback;
private static WSManNativeApi.WSManShellAsyncCallback s_cmdReceiveCallback;
private static WSManNativeApi.WSManShellAsyncCallback s_cmdSendCallback;
private static WSManNativeApi.WSManShellAsyncCallback s_cmdSignalCallback;
private static WSManNativeApi.WSManShellAsyncCallback s_cmdReconnectCallback;
private static WSManNativeApi.WSManShellAsyncCallback s_cmdConnectCallback;
static WSManClientCommandTransportManager()
{
// Initialize callback delegates
WSManNativeApi.WSManShellCompletionFunction createDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnCreateCmdCompleted);
s_cmdCreateCallback = new WSManNativeApi.WSManShellAsyncCallback(createDelegate);
WSManNativeApi.WSManShellCompletionFunction closeDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnCloseCmdCompleted);
s_cmdCloseCallback = new WSManNativeApi.WSManShellAsyncCallback(closeDelegate);
WSManNativeApi.WSManShellCompletionFunction receiveDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnRemoteCmdDataReceived);
s_cmdReceiveCallback = new WSManNativeApi.WSManShellAsyncCallback(receiveDelegate);
WSManNativeApi.WSManShellCompletionFunction sendDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnRemoteCmdSendCompleted);
s_cmdSendCallback = new WSManNativeApi.WSManShellAsyncCallback(sendDelegate);
WSManNativeApi.WSManShellCompletionFunction signalDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnRemoteCmdSignalCompleted);
s_cmdSignalCallback = new WSManNativeApi.WSManShellAsyncCallback(signalDelegate);
WSManNativeApi.WSManShellCompletionFunction reconnectDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnReconnectCmdCompleted);
s_cmdReconnectCallback = new WSManNativeApi.WSManShellAsyncCallback(reconnectDelegate);
WSManNativeApi.WSManShellCompletionFunction connectDelegate =
new WSManNativeApi.WSManShellCompletionFunction(OnConnectCmdCompleted);
s_cmdConnectCallback = new WSManNativeApi.WSManShellAsyncCallback(connectDelegate);
}
#endregion
#region Constructor
/// <summary>
/// This is an internal constructor used by WSManClientSessionTransportManager.
/// </summary>
/// <param name="connectionInfo">
/// connection info to be used for creating the command.
/// </param>
/// <param name="wsManShellOperationHandle">
/// Shell operation handle in whose context this transport manager sends/receives
/// data packets.
/// </param>
/// <param name="shell">
/// The command to be sent to the remote end.
/// </param>
/// <param name="noInput">
/// true if the command has input, false otherwise.
/// </param>
/// <param name="sessnTM">
/// Session transport manager creating this command transport manager instance.
/// Used by Command TM to apply session specific properties
/// </param>
internal WSManClientCommandTransportManager(
WSManConnectionInfo connectionInfo,
IntPtr wsManShellOperationHandle,
ClientRemotePowerShell shell,
bool noInput,
WSManClientSessionTransportManager sessnTM)
: base(shell, sessnTM.CryptoHelper, sessnTM)
{
Dbg.Assert(wsManShellOperationHandle != IntPtr.Zero, "Shell operation handle cannot be IntPtr.Zero.");
Dbg.Assert(connectionInfo != null, "connectionInfo cannot be null");
_wsManShellOperationHandle = wsManShellOperationHandle;
// Apply quota limits.. allow for data to be unlimited..
ReceivedDataCollection.MaximumReceivedDataSize = connectionInfo.MaximumReceivedDataSizePerCommand;
ReceivedDataCollection.MaximumReceivedObjectSize = connectionInfo.MaximumReceivedObjectSize;
_cmdLine = shell.PowerShell.Commands.Commands.GetCommandStringForHistory();
_onDataAvailableToSendCallback =
new PrioritySendDataCollection.OnDataAvailableCallback(OnDataAvailableCallback);
_sessnTm = sessnTM;
// Suspend queue on robust connections initiated event.
sessnTM.RobustConnectionsInitiated += HandleRobustConnectionsInitiated;
// Resume queue on robust connections completed event.
sessnTM.RobustConnectionsCompleted += HandleRobusConnectionsCompleted;
}
#endregion
#region SHIM: Redirection delegate for command code send.
private static readonly Delegate s_commandCodeSendRedirect = null;
#endregion
#region Internal Methods / Properties
private void HandleRobustConnectionsInitiated(object sender, EventArgs e)
{
SuspendQueue();
}
private void HandleRobusConnectionsCompleted(object sender, EventArgs e)
{
ResumeQueue();
}
/// <summary>
/// </summary>
/// <exception cref="PSRemotingTransportException">
/// WSManConnectShellCommandEx failed.
/// </exception>
internal override void ConnectAsync()
{
Dbg.Assert(!isClosed, "object already disposed");
ReceivedDataCollection.PrepareForStreamConnect();
// Empty the serializedPipeline data that contains PowerShell command information created in the
// constructor. We are connecting to an existing command on the server and don't want to send
// information on a new command.
serializedPipeline.Read();
// create cmdContextId
_cmdContextId = GetNextCmdTMHandleId();
AddCmdTransportManager(_cmdContextId, this);
// Create Callback
_connectCmdCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_cmdContextId), s_cmdConnectCallback);
_reconnectCmdCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_cmdContextId), s_cmdReconnectCallback);
lock (syncObject)
{
if (isClosed)
{
// the transport is already closed..so no need to create a connection
// anymore.
return;
}
WSManNativeApi.WSManConnectShellCommandEx(_wsManShellOperationHandle,
0,
PowershellInstanceId.ToString().ToUpperInvariant(),
IntPtr.Zero,
IntPtr.Zero,
_connectCmdCompleted,
ref _wsManCmdOperationHandle);
}
if (_wsManCmdOperationHandle == IntPtr.Zero)
{
PSRemotingTransportException e = new PSRemotingTransportException(RemotingErrorIdStrings.RunShellCommandExFailed);
TransportErrorOccuredEventArgs eventargs =
new TransportErrorOccuredEventArgs(e, TransportMethodEnum.ConnectShellCommandEx);
ProcessWSManTransportError(eventargs);
return;
}
}
/// <summary>
/// </summary>
/// <exception cref="PSRemotingTransportException">
/// WSManRunShellCommandEx failed.
/// </exception>
internal override void CreateAsync()
{
byte[] cmdPart1 = serializedPipeline.ReadOrRegisterCallback(null);
if (cmdPart1 != null)
{
#region SHIM: Redirection code for command code send.
bool sendContinue = true;
if (s_commandCodeSendRedirect != null)
{
object[] arguments = new object[2] { null, cmdPart1 };
sendContinue = (bool)s_commandCodeSendRedirect.DynamicInvoke(arguments);
cmdPart1 = (byte[])arguments[0];
}
if (!sendContinue)
return;
#endregion
WSManNativeApi.WSManCommandArgSet argSet = new WSManNativeApi.WSManCommandArgSet(cmdPart1);
// create cmdContextId
_cmdContextId = GetNextCmdTMHandleId();
AddCmdTransportManager(_cmdContextId, this);
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCreateCommand,
PSOpcode.Connect,
PSTask.CreateRunspace,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
RunspacePoolInstanceId.ToString(),
powershellInstanceId.ToString());
_createCmdCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_cmdContextId), s_cmdCreateCallback);
_createCmdCompletedGCHandle = GCHandle.Alloc(_createCmdCompleted);
_reconnectCmdCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_cmdContextId), s_cmdReconnectCallback);
using (argSet)
{
lock (syncObject)
{
if (!isClosed)
{
WSManNativeApi.WSManRunShellCommandEx(_wsManShellOperationHandle,
0,
PowershellInstanceId.ToString().ToUpperInvariant(),
// WSManRunsShellCommand doesn't accept empty string "".
(_cmdLine == null || _cmdLine.Length == 0) ? " " : (_cmdLine.Length <= 256 ? _cmdLine : _cmdLine.Substring(0, 255)),
argSet,
IntPtr.Zero,
_createCmdCompleted,
ref _wsManCmdOperationHandle);
tracer.WriteLine("Started cmd with command context : {0} Operation context: {1}", _cmdContextId, _wsManCmdOperationHandle);
}
}
}
}
if (_wsManCmdOperationHandle == IntPtr.Zero)
{
PSRemotingTransportException e = new PSRemotingTransportException(RemotingErrorIdStrings.RunShellCommandExFailed);
TransportErrorOccuredEventArgs eventargs =
new TransportErrorOccuredEventArgs(e, TransportMethodEnum.RunShellCommandEx);
ProcessWSManTransportError(eventargs);
return;
}
}
/// <summary>
/// Restores connection on a disconnected command.
/// </summary>
internal override void ReconnectAsync()
{
ReceivedDataCollection.PrepareForStreamConnect();
lock (syncObject)
{
if (isClosed)
{
return;
}
WSManNativeApi.WSManReconnectShellCommandEx(_wsManCmdOperationHandle, 0, _reconnectCmdCompleted);
}
}
/// <summary>
/// Used by powershell/pipeline to send a stop message to the server command.
/// </summary>
internal override void SendStopSignal()
{
lock (syncObject)
{
if (isClosed)
{
return;
}
// WSMan API do not allow a signal/input/receive be sent until RunShellCommand is
// successful (ie., callback is received)..so note that a signal is to be sent
// here and return.
if (!_isCreateCallbackReceived)
{
_isStopSignalPending = true;
return;
}
// we are about to send a signal..so clear pending bit.
_isStopSignalPending = false;
tracer.WriteLine("Sending stop signal with command context: {0} Operation Context {1}", _cmdContextId, _wsManCmdOperationHandle);
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManSignal,
PSOpcode.Disconnect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
RunspacePoolInstanceId.ToString(),
powershellInstanceId.ToString(),
StopSignal);
_signalCmdCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_cmdContextId), s_cmdSignalCallback);
WSManNativeApi.WSManSignalShellEx(_wsManShellOperationHandle, _wsManCmdOperationHandle, 0,
StopSignal, _signalCmdCompleted, ref _cmdSignalOperationHandle);
}
}
/// <summary>
/// Closes the pending Create,Send,Receive operations and then closes the shell and release all the resources.
/// </summary>
internal override void CloseAsync()
{
tracer.WriteLine("Closing command with command context: {0} Operation Context {1}", _cmdContextId, _wsManCmdOperationHandle);
bool shouldRaiseCloseCompleted = false;
// then let other threads release the lock before we cleaning up the resources.
lock (syncObject)
{
if (isClosed)
{
return;
}
// first change the state..so other threads
// will know that we are closing.
isClosed = true;
// There is no valid cmd operation handle..so just
// raise close completed.
if (_wsManCmdOperationHandle == IntPtr.Zero)
{
shouldRaiseCloseCompleted = true;
}
}
base.CloseAsync();
if (shouldRaiseCloseCompleted)
{
try
{
RaiseCloseCompleted();
}
finally
{
RemoveCmdTransportManager(_cmdContextId);
}
return;
}
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCloseCommand,
PSOpcode.Disconnect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
RunspacePoolInstanceId.ToString(),
powershellInstanceId.ToString());
_closeCmdCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_cmdContextId), s_cmdCloseCallback);
Dbg.Assert((IntPtr)_closeCmdCompleted != IntPtr.Zero, "closeCmdCompleted callback is null in cmdTM.CloseAsync()");
WSManNativeApi.WSManCloseCommand(_wsManCmdOperationHandle, 0, _closeCmdCompleted);
}
/// <summary>
/// Handle transport error - calls EnqueueAndStartProcessingThread to process transport exception
/// in a different thread
/// Logic in transport callbacks should always use this to process a transport error.
/// </summary>
internal void ProcessWSManTransportError(TransportErrorOccuredEventArgs eventArgs)
{
EnqueueAndStartProcessingThread(null, eventArgs, null);
}
/// <summary>
/// Log the error message in the Crimson logger and raise error handler.
/// </summary>
/// <param name="eventArgs"></param>
internal override void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArgs)
{
// Look for a valid stack trace.
string stackTrace;
if (!string.IsNullOrEmpty(eventArgs.Exception.StackTrace))
{
stackTrace = eventArgs.Exception.StackTrace;
}
else if (eventArgs.Exception.InnerException != null &&
!string.IsNullOrEmpty(eventArgs.Exception.InnerException.StackTrace))
{
stackTrace = eventArgs.Exception.InnerException.StackTrace;
}
else
{
stackTrace = string.Empty;
}
PSEtwLog.LogOperationalError(
PSEventId.TransportError, PSOpcode.Open, PSTask.None,
PSKeyword.UseAlwaysOperational,
RunspacePoolInstanceId.ToString(),
powershellInstanceId.ToString(),
eventArgs.Exception.ErrorCode.ToString(CultureInfo.InvariantCulture),
eventArgs.Exception.Message,
stackTrace);
PSEtwLog.LogAnalyticError(
PSEventId.TransportError_Analytic, PSOpcode.Open, PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
RunspacePoolInstanceId.ToString(),
powershellInstanceId.ToString(),
eventArgs.Exception.ErrorCode.ToString(CultureInfo.InvariantCulture),
eventArgs.Exception.Message,
stackTrace);
base.RaiseErrorHandler(eventArgs);
}
/// <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 override void ProcessPrivateData(object privateData)
{
Dbg.Assert(privateData != null, "privateData cannot be null.");
// For this version...only a boolean can be used for privateData.
bool shouldRaiseSignalCompleted = (bool)privateData;
if (shouldRaiseSignalCompleted)
{
base.RaiseSignalCompleted();
}
}
/// <summary>
/// Receive/send operation handles and callback handles should be released/disposed from
/// receive/send callback only. Releasing them after CloseOperation() may not cover all
/// the scenarios, as WSMan does not guarantee that a rcv/send callback is not called after
/// Close completed callback.
/// </summary>
/// <param name="flags"></param>
/// <param name="shouldClearSend"></param>
internal void ClearReceiveOrSendResources(int flags, bool shouldClearSend)
{
if (shouldClearSend)
{
if (_sendToRemoteCompleted != null)
{
_sendToRemoteCompleted.Dispose();
_sendToRemoteCompleted = null;
}
// For send..clear always
if (_wsManSendOperationHandle != IntPtr.Zero)
{
WSManNativeApi.WSManCloseOperation(_wsManSendOperationHandle, 0);
_wsManSendOperationHandle = IntPtr.Zero;
}
}
else
{
// clearing for receive..Clear only when the end of operation is reached.
if (flags == (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_END_OF_OPERATION)
{
if (_wsManReceiveOperationHandle != IntPtr.Zero)
{
WSManNativeApi.WSManCloseOperation(_wsManReceiveOperationHandle, 0);
_wsManReceiveOperationHandle = IntPtr.Zero;
}
if (_receivedFromRemote != null)
{
_receivedFromRemote.Dispose();
_receivedFromRemote = null;
}
}
}
}
/// <summary>
/// Method to have transport prepare for a disconnect operation.
/// </summary>
internal override void PrepareForDisconnect()
{
_isDisconnectPending = true;
// If there is not input processing and the command has already been created
// on the server then this object is ready for Disconnect now.
// Otherwise let the sending input data call back handle it.
if (this.isClosed || _isDisconnectedOnInvoke ||
(_isCreateCallbackReceived &&
this.serializedPipeline.Length == 0 &&
!_isSendingInput))
{
RaiseReadyForDisconnect();
}
}
/// <summary>
/// Method to resume post disconnect operations.
/// </summary>
internal override void PrepareForConnect()
{
_isDisconnectPending = false;
}
#endregion
#region Callbacks from WSMan
// callback that gets called when WSManRunShellCommandEx returns.
private static void OnCreateCmdCompleted(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("OnCreateCmdCompleted callback received");
long cmdContextId = 0;
WSManClientCommandTransportManager cmdTM = null;
if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId))
{
// We dont have the command TM handle..just return.
tracer.WriteLine("OnCreateCmdCompleted: Unable to find a transport manager for the command context {0}.", cmdContextId);
return;
}
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCreateCommandCallbackReceived,
PSOpcode.Connect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
cmdTM.RunspacePoolInstanceId.ToString(),
cmdTM.powershellInstanceId.ToString());
// dispose the cmdCompleted callback as it is not needed any more
if (cmdTM._createCmdCompleted != null)
{
cmdTM._createCmdCompletedGCHandle.Free();
cmdTM._createCmdCompleted.Dispose();
cmdTM._createCmdCompleted = null;
}
// TODO: 188098 wsManCmdOperationHandle should be populated by WSManRunShellCommandEx,
// but there is a thread timing bug in WSMan layer causing the callback to
// be called before WSManRunShellCommandEx returns. since we already validated the
// cmd context exists, safely assigning the commandOperationHandle to cmd transport manager.
// Remove this once WSMan fixes its code.
cmdTM._wsManCmdOperationHandle = commandOperationHandle;
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
if (errorStruct.errorCode != 0)
{
tracer.WriteLine("OnCreateCmdCompleted callback: WSMan reported an error: {0}", errorStruct.errorDetail);
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
cmdTM._sessnTm.WSManAPIData.WSManAPIHandle,
null,
errorStruct,
TransportMethodEnum.RunShellCommandEx,
RemotingErrorIdStrings.RunShellCommandExCallBackError,
new object[] { WSManTransportManagerUtils.ParseEscapeWSManErrorMessage(errorStruct.errorDetail) });
cmdTM.ProcessWSManTransportError(eventargs);
return;
}
}
// Send remaining cmd / parameter fragments.
lock (cmdTM.syncObject)
{
cmdTM._isCreateCallbackReceived = true;
// make sure the transport is not closed yet.
if (cmdTM.isClosed)
{
tracer.WriteLine("Client Session TM: Transport manager is closed. So returning");
if (cmdTM._isDisconnectPending)
{
cmdTM.RaiseReadyForDisconnect();
}
return;
}
// If a disconnect is pending at this point then we should not start
// receiving data or sending input and let the disconnect take place.
if (cmdTM._isDisconnectPending)
{
cmdTM.RaiseReadyForDisconnect();
return;
}
if (cmdTM.serializedPipeline.Length == 0)
{
cmdTM._shouldStartReceivingData = true;
}
// Start sending data if any..and see if we can initiate a receive.
cmdTM.SendOneItem();
// WSMan API does not allow a signal/input/receive be sent until RunShellCommand is
// successful (ie., callback is received)
if (cmdTM._isStopSignalPending)
{
cmdTM.SendStopSignal();
}
}
}
private static void OnConnectCmdCompleted(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("OnConnectCmdCompleted callback received");
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCreateCommandCallbackReceived,
PSOpcode.Connect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
"OnConnectCmdCompleted: OnConnectCmdCompleted callback received");
long cmdContextId = 0;
WSManClientCommandTransportManager cmdTM = null;
if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId))
{
// We dont have the command TM handle..just return.
tracer.WriteLine("OnConnectCmdCompleted: Unable to find a transport manager for the command context {0}.", cmdContextId);
return;
}
// dispose the cmdCompleted callback as it is not needed any more
if (cmdTM._connectCmdCompleted != null)
{
cmdTM._connectCmdCompleted.Dispose();
cmdTM._connectCmdCompleted = null;
}
cmdTM._wsManCmdOperationHandle = commandOperationHandle;
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
if (errorStruct.errorCode != 0)
{
tracer.WriteLine("OnConnectCmdCompleted callback: WSMan reported an error: {0}", errorStruct.errorDetail);
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
cmdTM._sessnTm.WSManAPIData.WSManAPIHandle,
null,
errorStruct,
TransportMethodEnum.ReconnectShellCommandEx,
RemotingErrorIdStrings.ReconnectShellCommandExCallBackError,
new object[] { WSManTransportManagerUtils.ParseEscapeWSManErrorMessage(errorStruct.errorDetail) });
cmdTM.ProcessWSManTransportError(eventargs);
return;
}
}
lock (cmdTM.syncObject)
{
// If the transport is already closed then we are done.
if (cmdTM.isClosed)
{
tracer.WriteLine("Client Session TM: Transport manager is closed. So returning");
// Release disconnect pending, if any.
if (cmdTM._isDisconnectPending)
{
cmdTM.RaiseReadyForDisconnect();
}
return;
}
// If a disconnect is pending at this point then we should not start
// receiving data or sending input and let the disconnect take place.
if (cmdTM._isDisconnectPending)
{
cmdTM.RaiseReadyForDisconnect();
return;
}
// Allow SendStopSignal.
cmdTM._isCreateCallbackReceived = true;
// Send stop signal if it is pending.
if (cmdTM._isStopSignalPending)
{
cmdTM.SendStopSignal();
}
}
// Establish a client data to server callback so that the client can respond to prompts.
cmdTM.SendOneItem();
cmdTM.RaiseConnectCompleted();
cmdTM.StartReceivingData();
}
private static void OnCloseCmdCompleted(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("OnCloseCmdCompleted callback received for operation context {0}", commandOperationHandle);
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCloseCommandCallbackReceived,
PSOpcode.Connect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
"OnCloseCmdCompleted: OnCloseCmdCompleted callback received");
long cmdContextId = 0;
WSManClientCommandTransportManager cmdTM = null;
if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId))
{
// We dont have the command TM handle..just return.
tracer.WriteLine("OnCloseCmdCompleted: Unable to find a transport manager for the command context {0}.", cmdContextId);
return;
}
tracer.WriteLine("Close completed callback received for command: {0}", cmdTM._cmdContextId);
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManCloseCommandCallbackReceived,
PSOpcode.Disconnect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
cmdTM.RunspacePoolInstanceId.ToString(),
cmdTM.powershellInstanceId.ToString());
if (cmdTM._isDisconnectPending)
{
cmdTM.RaiseReadyForDisconnect();
}
cmdTM.RaiseCloseCompleted();
}
private static void OnRemoteCmdSendCompleted(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("SendComplete callback received");
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManSendShellInputExCallbackReceived,
PSOpcode.Connect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
"OnRemoteCmdSendCompleted: SendComplete callback received");
long cmdContextId = 0;
WSManClientCommandTransportManager cmdTM = null;
if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId))
{
// We dont have the command TM handle..just return.
tracer.WriteLine("Unable to find a transport manager for the command context {0}.", cmdContextId);
return;
}
cmdTM._isSendingInput = false;
// do the logging for this send
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManSendShellInputExCallbackReceived,
PSOpcode.Connect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
cmdTM.RunspacePoolInstanceId.ToString(),
cmdTM.powershellInstanceId.ToString());
if ((!shellOperationHandle.Equals(cmdTM._wsManShellOperationHandle)) ||
(!commandOperationHandle.Equals(cmdTM._wsManCmdOperationHandle)))
{
tracer.WriteLine("SendShellInputEx callback: ShellOperationHandles are not the same as the Send is initiated with");
// WSMan returned data from a wrong shell..notify the caller
// about the same.
PSRemotingTransportException e = new PSRemotingTransportException(RemotingErrorIdStrings.CommandSendExFailed);
TransportErrorOccuredEventArgs eventargs =
new TransportErrorOccuredEventArgs(e, TransportMethodEnum.CommandInputEx);
cmdTM.ProcessWSManTransportError(eventargs);
return;
}
// release the resources related to send
cmdTM.ClearReceiveOrSendResources(flags, true);
// if the transport manager is already closed..ignore the errors and return
if (cmdTM.isClosed)
{
tracer.WriteLine("Client Command TM: Transport manager is closed. So returning");
if (cmdTM._isDisconnectPending)
{
cmdTM.RaiseReadyForDisconnect();
}
return;
}
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
// Ignore Command aborted error. Command aborted is raised by WSMan to
// notify command operation complete. PowerShell protocol has its own
// way of notifying the same using state change events.
if ((errorStruct.errorCode != 0) && (errorStruct.errorCode != 995))
{
tracer.WriteLine("CmdSend callback: WSMan reported an error: {0}", errorStruct.errorDetail);
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
cmdTM._sessnTm.WSManAPIData.WSManAPIHandle,
null,
errorStruct,
TransportMethodEnum.CommandInputEx,
RemotingErrorIdStrings.CommandSendExCallBackError,
new object[] { WSManTransportManagerUtils.ParseEscapeWSManErrorMessage(errorStruct.errorDetail) });
cmdTM.ProcessWSManTransportError(eventargs);
return;
}
}
// Send the next item, if available
cmdTM.SendOneItem();
}
private static void OnRemoteCmdDataReceived(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("Remote Command DataReceived callback.");
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManReceiveShellOutputExCallbackReceived,
PSOpcode.Connect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
"OnRemoteCmdDataReceived: Remote Command DataReceived callback");
long cmdContextId = 0;
WSManClientCommandTransportManager cmdTM = null;
if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId))
{
// We dont have the command TM handle..just return.
tracer.WriteLine("Unable to find a transport manager for the given command context {0}.", cmdContextId);
return;
}
if ((!shellOperationHandle.Equals(cmdTM._wsManShellOperationHandle)) ||
(!commandOperationHandle.Equals(cmdTM._wsManCmdOperationHandle)))
{
// WSMan returned data from a wrong shell..notify the caller
// about the same.
tracer.WriteLine("CmdReceive callback: ShellOperationHandles are not the same as the Receive is initiated with");
PSRemotingTransportException e = new PSRemotingTransportException(RemotingErrorIdStrings.CommandReceiveExFailed);
TransportErrorOccuredEventArgs eventargs =
new TransportErrorOccuredEventArgs(e, TransportMethodEnum.ReceiveCommandOutputEx);
cmdTM.ProcessWSManTransportError(eventargs);
return;
}
// release the resources related to receive
cmdTM.ClearReceiveOrSendResources(flags, false);
// if the transport manager is already closed..ignore the errors and return
if (cmdTM.isClosed)
{
tracer.WriteLine("Client Command TM: Transport manager is closed. So returning");
return;
}
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
if (errorStruct.errorCode != 0)
{
tracer.WriteLine("CmdReceive callback: WSMan reported an error: {0}", errorStruct.errorDetail);
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
cmdTM._sessnTm.WSManAPIData.WSManAPIHandle,
null,
errorStruct,
TransportMethodEnum.ReceiveCommandOutputEx,
RemotingErrorIdStrings.CommandReceiveExCallBackError,
new object[] { errorStruct.errorDetail });
cmdTM.ProcessWSManTransportError(eventargs);
return;
}
}
if (flags == (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_RECEIVE_DELAY_STREAM_REQUEST_PROCESSED)
{
cmdTM._isDisconnectedOnInvoke = true;
cmdTM.RaiseDelayStreamProcessedEvent();
return;
}
WSManNativeApi.WSManReceiveDataResult dataReceived = WSManNativeApi.WSManReceiveDataResult.UnMarshal(data);
if (dataReceived.data != null)
{
tracer.WriteLine("Cmd Received Data : {0}", dataReceived.data.Length);
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManReceiveShellOutputExCallbackReceived, PSOpcode.Receive, PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
cmdTM.RunspacePoolInstanceId.ToString(),
cmdTM.powershellInstanceId.ToString(),
dataReceived.data.Length.ToString(CultureInfo.InvariantCulture));
cmdTM.ProcessRawData(dataReceived.data, dataReceived.stream);
}
}
private static void OnReconnectCmdCompleted(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
long cmdContextId = 0;
WSManClientCommandTransportManager cmdTM = null;
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManReceiveShellOutputExCallbackReceived,
PSOpcode.Connect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
"OnReconnectCmdCompleted");
if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId))
{
// We dont have the command TM handle..just return.
tracer.WriteLine("Unable to find a transport manager for the given command context {0}.", cmdContextId);
return;
}
if ((!shellOperationHandle.Equals(cmdTM._wsManShellOperationHandle)) ||
(!commandOperationHandle.Equals(cmdTM._wsManCmdOperationHandle)))
{
// WSMan returned data from a wrong shell..notify the caller
// about the same.
tracer.WriteLine("Cmd Signal callback: ShellOperationHandles are not the same as the signal is initiated with");
PSRemotingTransportException e = new PSRemotingTransportException(RemotingErrorIdStrings.ReconnectShellCommandExCallBackError);
TransportErrorOccuredEventArgs eventargs =
new TransportErrorOccuredEventArgs(e, TransportMethodEnum.ReconnectShellCommandEx);
cmdTM.ProcessWSManTransportError(eventargs);
return;
}
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
if (errorStruct.errorCode != 0)
{
tracer.WriteLine("OnReconnectCmdCompleted callback: WSMan reported an error: {0}", errorStruct.errorDetail);
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
cmdTM._sessnTm.WSManAPIData.WSManAPIHandle,
null,
errorStruct,
TransportMethodEnum.ReconnectShellCommandEx,
RemotingErrorIdStrings.ReconnectShellCommandExCallBackError,
new object[] { WSManTransportManagerUtils.ParseEscapeWSManErrorMessage(errorStruct.errorDetail) });
cmdTM.ProcessWSManTransportError(eventargs);
return;
}
}
// The command may have been disconnected before all input was read or
// the returned command data started to be received.
cmdTM._shouldStartReceivingData = true;
cmdTM.SendOneItem();
cmdTM.RaiseReconnectCompleted();
}
private static void OnRemoteCmdSignalCompleted(IntPtr operationContext,
int flags,
IntPtr error,
IntPtr shellOperationHandle,
IntPtr commandOperationHandle,
IntPtr operationHandle,
IntPtr data)
{
tracer.WriteLine("Signal Completed callback received.");
PSEtwLog.LogAnalyticInformational(PSEventId.WSManSignalCallbackReceived, PSOpcode.Disconnect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, "OnRemoteCmdSignalCompleted");
long cmdContextId = 0;
WSManClientCommandTransportManager cmdTM = null;
if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId))
{
// We dont have the command TM handle..just return.
tracer.WriteLine("Unable to find a transport manager for the given command context {0}.", cmdContextId);
return;
}
// log the callback received event.
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManSignalCallbackReceived,
PSOpcode.Disconnect,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
cmdTM.RunspacePoolInstanceId.ToString(),
cmdTM.powershellInstanceId.ToString());
if ((!shellOperationHandle.Equals(cmdTM._wsManShellOperationHandle)) ||
(!commandOperationHandle.Equals(cmdTM._wsManCmdOperationHandle)))
{
// WSMan returned data from a wrong shell..notify the caller
// about the same.
tracer.WriteLine("Cmd Signal callback: ShellOperationHandles are not the same as the signal is initiated with");
PSRemotingTransportException e = new PSRemotingTransportException(RemotingErrorIdStrings.CommandSendExFailed);
TransportErrorOccuredEventArgs eventargs =
new TransportErrorOccuredEventArgs(e, TransportMethodEnum.CommandInputEx);
cmdTM.ProcessWSManTransportError(eventargs);
return;
}
// release the resources related to signal
if (cmdTM._cmdSignalOperationHandle != IntPtr.Zero)
{
WSManNativeApi.WSManCloseOperation(cmdTM._cmdSignalOperationHandle, 0);
cmdTM._cmdSignalOperationHandle = IntPtr.Zero;
}
if (cmdTM._signalCmdCompleted != null)
{
cmdTM._signalCmdCompleted.Dispose();
cmdTM._signalCmdCompleted = null;
}
// if the transport manager is already closed..ignore the errors and return
if (cmdTM.isClosed)
{
tracer.WriteLine("Client Command TM: Transport manager is closed. So returning");
return;
}
if (error != IntPtr.Zero)
{
WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error);
if (errorStruct.errorCode != 0)
{
tracer.WriteLine("Cmd Signal callback: WSMan reported an error: {0}", errorStruct.errorDetail);
TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs(
cmdTM._sessnTm.WSManAPIData.WSManAPIHandle,
null,
errorStruct,
TransportMethodEnum.CommandInputEx,
RemotingErrorIdStrings.CommandSendExCallBackError,
new object[] { WSManTransportManagerUtils.ParseEscapeWSManErrorMessage(errorStruct.errorDetail) });
cmdTM.ProcessWSManTransportError(eventargs);
return;
}
}
cmdTM.EnqueueAndStartProcessingThread(null, null, true);
}
private void SendOneItem()
{
// If a disconnect is completing then do not send any more data.
// Also raise the readyfordisconnect event.
if (_isDisconnectPending)
{
RaiseReadyForDisconnect();
return;
}
byte[] data = null;
DataPriorityType priorityType = DataPriorityType.Default;
// serializedPipeline is static ie., data is added to this collection at construction time only
// and data is accessed by only one thread at any given time..so we can depend on this count
if (serializedPipeline.Length > 0)
{
data = serializedPipeline.ReadOrRegisterCallback(null);
// if there are no command / parameter fragments need to be sent
// start receiving data. Reason: Command will start its execution
// once command string + parameters are sent.
if (serializedPipeline.Length == 0)
{
_shouldStartReceivingData = true;
}
}
else if (_chunkToSend != null) // there is a pending chunk to be sent
{
data = _chunkToSend.Data;
priorityType = _chunkToSend.Type;
_chunkToSend = null;
}
else
{
// This will either return data or register callback but doesn't do both.
data = dataToBeSent.ReadOrRegisterCallback(_onDataAvailableToSendCallback, out priorityType);
}
if (data != null)
{
_isSendingInput = true;
SendData(data, priorityType);
}
if (_shouldStartReceivingData)
{
StartReceivingData();
}
}
private void OnDataAvailableCallback(byte[] data, DataPriorityType priorityType)
{
Dbg.Assert(data != null, "data cannot be null in the data available callback");
tracer.WriteLine("Received data from dataToBeSent store.");
Dbg.Assert(_chunkToSend == null, "data callback received while a chunk is pending to be sent");
_chunkToSend = new SendDataChunk(data, priorityType);
SendOneItem();
}
#region SHIM: Redirection delegate for command data send.
private static readonly Delegate s_commandSendRedirect = null;
#endregion
private void SendData(byte[] data, DataPriorityType priorityType)
{
tracer.WriteLine("Command sending data of size : {0}", data.Length);
byte[] package = data;
#region SHIM: Redirection code for command data send.
bool sendContinue = true;
if (s_commandSendRedirect != null)
{
object[] arguments = new object[2] { null, package };
sendContinue = (bool)s_commandSendRedirect.DynamicInvoke(arguments);
package = (byte[])arguments[0];
}
if (!sendContinue)
return;
#endregion
using (WSManNativeApi.WSManData_ManToUn serializedContent =
new WSManNativeApi.WSManData_ManToUn(package))
{
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManSendShellInputEx, PSOpcode.Send, PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
RunspacePoolInstanceId.ToString(),
powershellInstanceId.ToString(),
serializedContent.BufferLength.ToString(CultureInfo.InvariantCulture));
lock (syncObject)
{
// make sure the transport is not closed.
if (isClosed)
{
tracer.WriteLine("Client Session TM: Transport manager is closed. So returning");
return;
}
// send callback
_sendToRemoteCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_cmdContextId), s_cmdSendCallback);
WSManNativeApi.WSManSendShellInputEx(_wsManShellOperationHandle, _wsManCmdOperationHandle, 0,
priorityType == DataPriorityType.Default ?
WSManNativeApi.WSMAN_STREAM_ID_STDIN : WSManNativeApi.WSMAN_STREAM_ID_PROMPTRESPONSE,
serializedContent,
_sendToRemoteCompleted,
ref _wsManSendOperationHandle);
}
}
}
internal override void StartReceivingData()
{
PSEtwLog.LogAnalyticInformational(
PSEventId.WSManReceiveShellOutputEx,
PSOpcode.Receive, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
RunspacePoolInstanceId.ToString(), powershellInstanceId.ToString());
// We should call Receive only once.. WSMan will call the callback multiple times.
_shouldStartReceivingData = false;
lock (syncObject)
{
// make sure the transport is not closed.
if (isClosed)
{
tracer.WriteLine("Client Session TM: Transport manager is closed. So returning");
return;
}
if (receiveDataInitiated)
{
tracer.WriteLine("Client Session TM: ReceiveData has already been called.");
return;
}
receiveDataInitiated = true;
// receive callback
_receivedFromRemote = new WSManNativeApi.WSManShellAsync(new IntPtr(_cmdContextId), s_cmdReceiveCallback);
WSManNativeApi.WSManReceiveShellOutputEx(_wsManShellOperationHandle,
_wsManCmdOperationHandle, startInDisconnectedMode ? (int)WSManNativeApi.WSManShellFlag.WSMAN_FLAG_RECEIVE_DELAY_OUTPUT_STREAM : 0,
_sessnTm.WSManAPIData.OutputStreamSet,
_receivedFromRemote, ref _wsManReceiveOperationHandle);
}
}
#endregion
#region Dispose / Destructor pattern
internal override void Dispose(bool isDisposing)
{
tracer.WriteLine("Disposing command with command context: {0} Operation Context: {1}", _cmdContextId, _wsManCmdOperationHandle);
base.Dispose(isDisposing);
// remove command context from cmd handles dictionary
RemoveCmdTransportManager(_cmdContextId);
// unregister event handlers
if (_sessnTm != null)
{
_sessnTm.RobustConnectionsInitiated -= HandleRobustConnectionsInitiated;
_sessnTm.RobustConnectionsCompleted -= HandleRobusConnectionsCompleted;
}
if (_closeCmdCompleted != null)
{
_closeCmdCompleted.Dispose();
_closeCmdCompleted = null;
}
if (_reconnectCmdCompleted != null)
{
_reconnectCmdCompleted.Dispose();
_reconnectCmdCompleted = null;
}
_wsManCmdOperationHandle = IntPtr.Zero;
}
#endregion
#region Static Data / Methods
// This dictionary maintains active command transport managers to be used from various
// callbacks.
private static readonly Dictionary<long, WSManClientCommandTransportManager> s_cmdTMHandles =
new Dictionary<long, WSManClientCommandTransportManager>();
private static long s_cmdTMSeed;
// Generate command transport manager unique id
private static long GetNextCmdTMHandleId()
{
return System.Threading.Interlocked.Increment(ref s_cmdTMSeed);
}
// we need a synchronized add and remove so that multiple threads
// update the data store concurrently
private static void AddCmdTransportManager(long cmdTMId,
WSManClientCommandTransportManager cmdTransportManager)
{
lock (s_cmdTMHandles)
{
s_cmdTMHandles.Add(cmdTMId, cmdTransportManager);
}
}
private static void RemoveCmdTransportManager(long cmdTMId)
{
lock (s_cmdTMHandles)
{
s_cmdTMHandles.Remove(cmdTMId);
}
}
// we need a synchronized add and remove so that multiple threads
// update the data store concurrently
private static bool TryGetCmdTransportManager(IntPtr operationContext,
out WSManClientCommandTransportManager cmdTransportManager,
out long cmdTMId)
{
cmdTMId = operationContext.ToInt64();
cmdTransportManager = null;
lock (s_cmdTMHandles)
{
return s_cmdTMHandles.TryGetValue(cmdTMId, out cmdTransportManager);
}
}
#endregion
}
}