/// <summary> /// if flush is true, data will be sent immediately to the client. This is accomplished /// by using WSMAN_FLAG_RECEIVE_FLUSH flag provided by WSMan API. /// </summary> /// <param name="data"></param> /// <param name="flush"></param> /// <param name="reportAsPending"></param> /// <param name="reportAsDataBoundary"></param> protected override void SendDataToClient( byte[] data, bool flush, bool reportAsPending, bool reportAsDataBoundary) { if (true == _isClosed) { return; } // double-check locking mechanism is used here to avoid entering into lock // every time data is sent..entering/exiting from lock is costly. if (!_isRequestPending) { // Dont send data until we have received request from client. // The following blocks the calling thread. The thread is // unblocked once a request from client arrives. _waitHandle.WaitOne(); _isRequestPending = true; // at this point request must be pending..so dispose waitHandle _waitHandle.Dispose(); } int result = (int)WSManPluginErrorCodes.NoError; // at this point we have pending request from client. so it is safe // to send data to client using WSMan API. using (WSManNativeApi.WSManData_ManToUn dataToBeSent = new WSManNativeApi.WSManData_ManToUn(data)) { lock (_syncObject) { if (!_isClosed) { int flags = 0; if (flush) flags |= (int)WSManNativeApi.WSManFlagReceive.WSMAN_FLAG_RECEIVE_FLUSH; if (reportAsDataBoundary) //currently assigning hardcoded value for this flag, this is a new change in wsman.h and needs to be replaced with the actual definition once // modified wsman.h is in public headers flags |= (int)WSManNativeApi.WSManFlagReceive.WSMAN_FLAG_RECEIVE_RESULT_DATA_BOUNDARY; result = WSManNativeApi.WSManPluginReceiveResult( _requestDetails.unmanagedHandle, flags, WSManPluginConstants.SupportedOutputStream, dataToBeSent, reportAsPending ? WSManNativeApi.WSMAN_COMMAND_STATE_PENDING : null, 0); } } } if ((int)WSManPluginErrorCodes.NoError != result) { ReportError(result, "WSManPluginReceiveResult"); } }
/// <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 (null == _openContent) { DataPriorityType additionalDataType; byte[] additionalData = dataToBeSent.ReadOrRegisterCallback(null, out additionalDataType); if (null != additionalData) { // 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; } }
/// <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; } } }
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 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 && (null != _openContent)) { _openContent.Dispose(); _openContent = null; } base.Dispose(isDisposing); }
/// <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 (null == _openContent) { 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 (null != additionalData) { // 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> /// if flush is true, data will be sent immediately to the client. This is accomplished /// by using WSMAN_FLAG_RECEIVE_FLUSH flag provided by WSMan API. /// </summary> /// <param name="data"></param> /// <param name="flush"></param> /// <param name="reportAsPending"></param> /// <param name="reportAsDataBoundary"></param> protected override void SendDataToClient( byte[] data, bool flush, bool reportAsPending, bool reportAsDataBoundary) { if (true == _isClosed) { return; } // double-check locking mechanism is used here to avoid entering into lock // every time data is sent..entering/exiting from lock is costly. if (!_isRequestPending) { // Dont send data until we have received request from client. // The following blocks the calling thread. The thread is // unblocked once a request from client arrives. _waitHandle.WaitOne(); _isRequestPending = true; // at this point request must be pending..so dispose waitHandle _waitHandle.Dispose(); } int result = (int)WSManPluginErrorCodes.NoError; // at this point we have pending request from client. so it is safe // to send data to client using WSMan API. using (WSManNativeApi.WSManData_ManToUn dataToBeSent = new WSManNativeApi.WSManData_ManToUn(data)) { lock (_syncObject) { if (!_isClosed) { int flags = 0; if (flush) flags |= (int)WSManNativeApi.WSManFlagReceive.WSMAN_FLAG_RECEIVE_FLUSH; if (reportAsDataBoundary) //currently assigning hardcoded value for this flag, this is a new change in wsman.h and needs to be replaced with the actual definition once // modified wsman.h is in public headers flags |= (int)WSManNativeApi.WSManFlagReceive.WSMAN_FLAG_RECEIVE_RESULT_DATA_BOUNDARY; result = WSManNativeApi.WSManPluginReceiveResult( _requestDetails.unmanagedHandle, flags, WSManPluginConstants.SupportedOutputStream, dataToBeSent, reportAsPending ? WSManNativeApi.WSMAN_COMMAND_STATE_PENDING : null, 0); } } } if ((int)WSManPluginErrorCodes.NoError != result) { ReportError(result, "WSManPluginReceiveResult"); } }