mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-17 12:45:47 +00:00
1214 lines
57 KiB
C#
1214 lines
57 KiB
C#
/********************************************************************++
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* --********************************************************************/
|
|
|
|
using System.Collections.ObjectModel;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Threading;
|
|
using System.Management.Automation;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Management.Automation.Internal;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using Microsoft.Win32.SafeHandles;
|
|
|
|
using System.Management.Automation.Remoting;
|
|
using System.Management.Automation.Remoting.Server;
|
|
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
|
|
namespace System.Management.Automation.Remoting
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// By design, on the server side, each remote connection is represented by
|
|
/// a ServerRemoteSession object, which contains one instance of this class.
|
|
///
|
|
/// This class holds 4 pieces of information.
|
|
/// 1. Client capability: This is the capability received during the negotiation process.
|
|
/// 2. Server capability: This comes from default parameters.
|
|
/// 3. Client configuration: This holds the remote session related configuration parameters that
|
|
/// the client sent to the server. This parameters can be changed and resent after the connection
|
|
/// is established.
|
|
/// 4. Server configuration: this holds the server sider configuration parameters.
|
|
///
|
|
/// All these together define the connection level parameters.
|
|
/// </summary>
|
|
internal class ServerRemoteSessionContext
|
|
{
|
|
private RemoteSessionCapability _clientCapability;
|
|
private RemoteSessionCapability _serverCapability;
|
|
private bool isNegotiationSucceeded;
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// The constructor instantiates a server capability object and a server configuration
|
|
/// using default values.
|
|
/// </summary>
|
|
internal ServerRemoteSessionContext()
|
|
{
|
|
_serverCapability = RemoteSessionCapability.CreateServerCapability();
|
|
}
|
|
|
|
#endregion Constructors
|
|
|
|
/// <summary>
|
|
/// This property represents the capability that the server receives from the client.
|
|
/// </summary>
|
|
internal RemoteSessionCapability ClientCapability
|
|
{
|
|
get
|
|
{
|
|
return _clientCapability;
|
|
}
|
|
set
|
|
{
|
|
_clientCapability = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This property is the server capability generated on the server side.
|
|
/// </summary>
|
|
internal RemoteSessionCapability ServerCapability
|
|
{
|
|
get
|
|
{
|
|
return _serverCapability;
|
|
}
|
|
set
|
|
{
|
|
_serverCapability = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if negotiation from client is succeeded...in which case ClienCapability
|
|
/// is the capability that server agreed with.
|
|
/// </summary>
|
|
internal bool IsNegotiationSucceeded
|
|
{
|
|
get { return isNegotiationSucceeded; }
|
|
set { isNegotiationSucceeded = value; }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class is designed to be the server side controller of a remote connection.
|
|
///
|
|
/// It contains a static entry point that the PowerShell server process will get into
|
|
/// the server mode. At this entry point, a runspace configuration is passed in. This runspace
|
|
/// configuration is used to instantiate a server side runspace.
|
|
///
|
|
/// This class controls a remote connection by using a Session data structure handler, which
|
|
/// in turn contains a Finite State Machine, and a transport mechanism.
|
|
///
|
|
/// </summary>
|
|
internal class ServerRemoteSession : RemoteSession
|
|
{
|
|
[TraceSourceAttribute("ServerRemoteSession", "ServerRemoteSession")]
|
|
static private PSTraceSource _trace = PSTraceSource.GetTracer("ServerRemoteSession", "ServerRemoteSession");
|
|
|
|
private ServerRemoteSessionContext _context;
|
|
private ServerRemoteSessionDataStructureHandler _sessionDSHandler;
|
|
private PSSenderInfo _senderInfo;
|
|
private string _configProviderId;
|
|
private string _initParameters;
|
|
private string _initScriptForOutOfProcRS;
|
|
private PSSessionConfiguration _sessionConfigProvider;
|
|
|
|
// used to apply quotas on command and session transportmanagers.
|
|
private Nullable<int> maxRecvdObjectSize;
|
|
private Nullable<int> maxRecvdDataSizeCommand;
|
|
|
|
private ServerRunspacePoolDriver _runspacePoolDriver;
|
|
private PSRemotingCryptoHelperServer _cryptoHelper;
|
|
|
|
// Specifies an optional endpoint configuration for out-of-proc session use.
|
|
// Creates a pushed remote runspace session created with this configuration name.
|
|
private string _configurationName;
|
|
|
|
#region Events
|
|
/// <summary>
|
|
/// Raised when session is closed.
|
|
/// </summary>
|
|
internal EventHandler<RemoteSessionStateMachineEventArgs> Closed;
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// This constructor inistantiates a ServerRemoteSession object and
|
|
/// a ServerRemoteSessionDataStructureHandler object.
|
|
/// </summary>
|
|
/// <param name="senderInfo">
|
|
/// Details about the user creating this session.
|
|
/// </param>
|
|
/// <param name="configurationProviderId">
|
|
/// The resource URI for which this session is being created
|
|
/// </param>
|
|
/// <param name="initializationParameters">
|
|
/// Initialization Parameters xml passed by WSMan API. This data is read from the config
|
|
/// xml.
|
|
/// </param>
|
|
/// <param name="transportManager">
|
|
/// The transport manager this session should use to send/receive data
|
|
/// </param>
|
|
/// <returns></returns>
|
|
internal ServerRemoteSession(PSSenderInfo senderInfo,
|
|
string configurationProviderId,
|
|
string initializationParameters,
|
|
AbstractServerSessionTransportManager transportManager)
|
|
{
|
|
Dbg.Assert(null != transportManager, "transportManager cannot be null.");
|
|
|
|
// let input,output and error from native commands redirect as we have
|
|
// to send (or receive) them back to client for action.
|
|
NativeCommandProcessor.IsServerSide = true;
|
|
_senderInfo = senderInfo;
|
|
_configProviderId = configurationProviderId;
|
|
_initParameters = initializationParameters;
|
|
if (Platform.IsWindows)
|
|
{
|
|
_cryptoHelper = (PSRemotingCryptoHelperServer)transportManager.CryptoHelper;
|
|
_cryptoHelper.Session = this;
|
|
}
|
|
|
|
_context = new ServerRemoteSessionContext();
|
|
_sessionDSHandler = new ServerRemoteSessionDSHandlerlImpl(this, transportManager);
|
|
BaseSessionDataStructureHandler = _sessionDSHandler;
|
|
_sessionDSHandler.CreateRunspacePoolReceived += HandleCreateRunspacePool;
|
|
_sessionDSHandler.NegotiationReceived += HandleNegotiationReceived;
|
|
_sessionDSHandler.SessionClosing += HandleSessionDSHandlerClosing;
|
|
_sessionDSHandler.PublicKeyReceived +=
|
|
new EventHandler<RemoteDataEventArgs<string>>(HandlePublicKeyReceived);
|
|
transportManager.Closing += HandleResourceClosing;
|
|
|
|
// update the quotas from sessionState..start with default size..and
|
|
// when Custom Session Configuration is loaded (during runsapce creation) update this.
|
|
transportManager.ReceivedDataCollection.MaximumReceivedObjectSize =
|
|
BaseTransportManager.MaximumReceivedObjectSize;
|
|
|
|
// session transport manager can recieve 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
|
|
transportManager.ReceivedDataCollection.MaximumReceivedDataSize = null;
|
|
}
|
|
|
|
#endregion Constructors
|
|
|
|
#region Creation Factory
|
|
|
|
/// <summary>
|
|
/// Creates a server remote session for the supplied <paramref name="configuratioinProviderId"/>
|
|
/// and <paramref name="transportManager"/>.
|
|
/// </summary>
|
|
/// <param name="senderInfo"></param>
|
|
/// <param name="configurationProviderId"></param>
|
|
/// <param name="initializationParameters">
|
|
/// Initialization Parameters xml passed by WSMan API. This data is read from the config
|
|
/// xml.
|
|
/// </param>
|
|
/// <param name="transportManager"></param>
|
|
/// <param name="configurationName">Optional configuration endpoint name for OutOfProc sessions</param>
|
|
/// <returns></returns>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// InitialSessionState provider with <paramref name="configurationProviderId"/> does
|
|
/// not exist on the remote server.
|
|
/// </exception>
|
|
/*
|
|
<InitializationParameters>
|
|
<Param Name="PSVersion" Value="2.0" />
|
|
<Param Name="ApplicationBase" Value="<folder path>" />
|
|
...
|
|
</InitializationParameters>
|
|
*/
|
|
internal static ServerRemoteSession CreateServerRemoteSession(PSSenderInfo senderInfo,
|
|
string configurationProviderId,
|
|
string initializationParameters,
|
|
AbstractServerSessionTransportManager transportManager,
|
|
string configurationName = null)
|
|
{
|
|
Dbg.Assert((senderInfo != null) & (senderInfo.UserInfo != null),
|
|
"senderInfo and userInfo cannot be null.");
|
|
|
|
_trace.WriteLine("Finding InitialSessionState provider for id : {0}", configurationProviderId);
|
|
|
|
if (string.IsNullOrEmpty(configurationProviderId))
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException("RemotingErrorIdStrings.NonExistentInitialSessionStateProvider", configurationProviderId);
|
|
}
|
|
|
|
ServerRemoteSession result = new ServerRemoteSession(senderInfo,
|
|
configurationProviderId,
|
|
initializationParameters,
|
|
transportManager)
|
|
{
|
|
_configurationName = configurationName
|
|
};
|
|
|
|
// start state machine.
|
|
RemoteSessionStateMachineEventArgs startEventArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.CreateSession);
|
|
result._sessionDSHandler.StateMachine.RaiseEvent(startEventArg);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used by OutOfProcessServerMediator to create a remote session.
|
|
/// </summary>
|
|
/// <param name="senderInfo"></param>
|
|
/// <param name="initializationScriptForOutOfProcessRunspace"></param>
|
|
/// <param name="transportManager"></param>
|
|
/// <param name="configurationName"></param>
|
|
/// <returns></returns>
|
|
internal static ServerRemoteSession CreateServerRemoteSession(PSSenderInfo senderInfo,
|
|
string initializationScriptForOutOfProcessRunspace,
|
|
AbstractServerSessionTransportManager transportManager,
|
|
string configurationName)
|
|
{
|
|
ServerRemoteSession result = CreateServerRemoteSession(senderInfo,
|
|
"Microsoft.PowerShell", "", transportManager, configurationName);
|
|
result._initScriptForOutOfProcRS = initializationScriptForOutOfProcessRunspace;
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Overrides
|
|
|
|
/// <summary>
|
|
/// This indicates the remote session object is Client, Server or Listener.
|
|
/// </summary>
|
|
internal override RemotingDestination MySelf
|
|
{
|
|
get
|
|
{
|
|
return RemotingDestination.Server;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is the data dispatcher for the whole remote connection.
|
|
/// This dispatcher is registered with the server side input queue's InputDataReady event.
|
|
/// When the input queue has received data from client, it calls the InputDataReady listeners.
|
|
///
|
|
/// This dispatcher distinguishes the negotiation packet as a special case. For all other data,
|
|
/// it dispatches the data through Finite State Machines DoMessageReceived handler by raising the event
|
|
/// MessageReceived. The FSM's DoMessageReceived handler further dispatches to the receiving
|
|
/// components: such as runspace or pipeline which have their own data dispatching methods.
|
|
/// </summary>
|
|
/// <param name="sender">
|
|
/// This parameter is not used by the method, in this implementation.
|
|
/// </param>
|
|
/// <param name="dataEventArg">
|
|
/// This parameter contains the remote data received from client.
|
|
/// </param>
|
|
///
|
|
/// <exception cref="ArgumentNullException">
|
|
/// If the parameter <paramref name="dataEventArg" /> is null.
|
|
/// </exception>
|
|
///
|
|
/// <exception cref="ArgumentException">
|
|
/// If the parameter <paramref name="dataEventArg" /> does not contain remote data.
|
|
/// </exception>
|
|
///
|
|
/// <exception cref="PSRemotingDataStructureException">
|
|
/// If the destination of the data is not for server.
|
|
/// </exception>
|
|
internal void DispatchInputQueueData(object sender, RemoteDataEventArgs dataEventArg)
|
|
{
|
|
if (dataEventArg == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("dataEventArg");
|
|
}
|
|
|
|
RemoteDataObject<PSObject> rcvdData = dataEventArg.ReceivedData;
|
|
|
|
if (rcvdData == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentException("dataEventArg");
|
|
}
|
|
|
|
RemotingDestination destination = rcvdData.Destination;
|
|
|
|
if ((destination & MySelf) != MySelf)
|
|
{
|
|
// this packet is not target for me.
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.RemotingDestinationNotForMe, MySelf, destination);
|
|
}
|
|
|
|
RemotingTargetInterface targetInterface = rcvdData.TargetInterface;
|
|
RemotingDataType dataType = rcvdData.DataType;
|
|
|
|
RemoteSessionStateMachineEventArgs messageReceivedArg = null;
|
|
|
|
switch (targetInterface)
|
|
{
|
|
case RemotingTargetInterface.Session:
|
|
{
|
|
switch (dataType)
|
|
{
|
|
// TODO: Directly calling an event handler in StateMachine bypassing the StateMachine's
|
|
// loop of ProcessEvents()..This is needed as connection is already established and the
|
|
// following message does not change state. An ideal solution would be to process
|
|
// non-session messages in this class rather than by StateMachine.
|
|
case RemotingDataType.CreateRunspacePool:
|
|
messageReceivedArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.MessageReceived);
|
|
if (SessionDataStructureHandler.StateMachine.CanByPassRaiseEvent(messageReceivedArg))
|
|
{
|
|
messageReceivedArg.RemoteData = rcvdData;
|
|
SessionDataStructureHandler.StateMachine.DoMessageReceived(this, messageReceivedArg);
|
|
}
|
|
else
|
|
{
|
|
SessionDataStructureHandler.StateMachine.RaiseEvent(messageReceivedArg);
|
|
}
|
|
break;
|
|
|
|
case RemotingDataType.CloseSession:
|
|
_sessionDSHandler.RaiseDataReceivedEvent(dataEventArg);
|
|
break;
|
|
|
|
case RemotingDataType.SessionCapability:
|
|
_sessionDSHandler.RaiseDataReceivedEvent(dataEventArg);
|
|
break;
|
|
|
|
case RemotingDataType.PublicKey:
|
|
_sessionDSHandler.RaiseDataReceivedEvent(dataEventArg);
|
|
break;
|
|
|
|
default:
|
|
Dbg.Assert(false, "Should never reach here");
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
// TODO: Directly calling an event handler in StateMachine bypassing the StateMachine's
|
|
// loop of ProcessEvents()..This is needed as connection is already established and the
|
|
// following message does not change state. An ideal solution would be to process
|
|
// non-session messages in this class rather than by StateMachine.
|
|
case RemotingTargetInterface.RunspacePool:
|
|
case RemotingTargetInterface.PowerShell:
|
|
// GETBACK
|
|
messageReceivedArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.MessageReceived);
|
|
if (SessionDataStructureHandler.StateMachine.CanByPassRaiseEvent(messageReceivedArg))
|
|
{
|
|
messageReceivedArg.RemoteData = rcvdData;
|
|
SessionDataStructureHandler.StateMachine.DoMessageReceived(this, messageReceivedArg);
|
|
}
|
|
else
|
|
{
|
|
SessionDataStructureHandler.StateMachine.RaiseEvent(messageReceivedArg);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Have received a public key from the other side
|
|
/// Import or take other action based on the state
|
|
/// </summary>
|
|
/// <param name="sender">sender of this event, unused</param>
|
|
/// <param name="eventArgs">event arguments which contains the
|
|
/// remote public key</param>
|
|
private void HandlePublicKeyReceived(object sender, RemoteDataEventArgs<string> eventArgs)
|
|
{
|
|
if (SessionDataStructureHandler.StateMachine.State == RemoteSessionState.Established ||
|
|
SessionDataStructureHandler.StateMachine.State == RemoteSessionState.EstablishedAndKeyRequested || //this is only for legacy clients
|
|
SessionDataStructureHandler.StateMachine.State == RemoteSessionState.EstablishedAndKeyExchanged)
|
|
{
|
|
string remotePublicKey = eventArgs.Data;
|
|
|
|
bool ret = _cryptoHelper.ImportRemotePublicKey(remotePublicKey);
|
|
|
|
RemoteSessionStateMachineEventArgs args = null;
|
|
if (!ret)
|
|
{
|
|
// importing remote public key failed
|
|
// set state to closed
|
|
args = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.KeyReceiveFailed);
|
|
|
|
SessionDataStructureHandler.StateMachine.RaiseEvent(args);
|
|
}
|
|
|
|
args = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.KeyReceived);
|
|
SessionDataStructureHandler.StateMachine.RaiseEvent(args);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start the key exchange process
|
|
/// </summary>
|
|
internal override void StartKeyExchange()
|
|
{
|
|
if (SessionDataStructureHandler.StateMachine.State == RemoteSessionState.Established)
|
|
{
|
|
// send using data structure handler
|
|
SessionDataStructureHandler.SendRequestForPublicKey();
|
|
|
|
RemoteSessionStateMachineEventArgs eventArgs =
|
|
new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.KeyRequested);
|
|
SessionDataStructureHandler.StateMachine.RaiseEvent(eventArgs);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Complete the Key exchange process
|
|
/// </summary>
|
|
internal override void CompleteKeyExchange()
|
|
{
|
|
_cryptoHelper.CompleteKeyExchange();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send an encrypted session key to the client
|
|
/// </summary>
|
|
internal void SendEncryptedSessionKey()
|
|
{
|
|
Dbg.Assert(SessionDataStructureHandler.StateMachine.State == RemoteSessionState.EstablishedAndKeyReceived,
|
|
"Sever must be in EstablishedAndKeyReceived state before you can attempt to send encrypted session key");
|
|
|
|
string encryptedSessionKey = null;
|
|
bool ret = _cryptoHelper.ExportEncryptedSessionKey(out encryptedSessionKey);
|
|
|
|
RemoteSessionStateMachineEventArgs args = null;
|
|
if (!ret)
|
|
{
|
|
args =
|
|
new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.KeySendFailed);
|
|
SessionDataStructureHandler.StateMachine.RaiseEvent(args);
|
|
}
|
|
|
|
SessionDataStructureHandler.SendEncryptedSessionKey(encryptedSessionKey);
|
|
|
|
// complete the key exchange process
|
|
CompleteKeyExchange();
|
|
|
|
args = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.KeySent);
|
|
SessionDataStructureHandler.StateMachine.RaiseEvent(args);
|
|
}
|
|
|
|
#endregion Overrides
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// This property returns the ServerRemoteSessionContext object created inside
|
|
/// this object's contructor.
|
|
/// </summary>
|
|
internal ServerRemoteSessionContext Context
|
|
{
|
|
get
|
|
{
|
|
return _context;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This property returns the ServerRemoteSessionDataStructureHandler object created inside
|
|
/// this object's contructor.
|
|
/// </summary>
|
|
internal ServerRemoteSessionDataStructureHandler SessionDataStructureHandler
|
|
{
|
|
get
|
|
{
|
|
return _sessionDSHandler;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private/Internal Methods
|
|
|
|
/// <summary>
|
|
/// Let the session clear its resources.
|
|
/// </summary>
|
|
/// <param name="reasonForClose"></param>
|
|
internal void Close(RemoteSessionStateMachineEventArgs reasonForClose)
|
|
{
|
|
Closed.SafeInvoke(this, reasonForClose);
|
|
if (_runspacePoolDriver != null)
|
|
_runspacePoolDriver.Closed -= HandleResourceClosing;
|
|
}
|
|
|
|
/// <summary>
|
|
/// ExecutesConnect. expects client capability and connect_runspacepool PSRP
|
|
/// messages in connectData.
|
|
/// If negotiation is successful and max and min runspaces in connect_runspacepool
|
|
/// match the assiciated runspace pool parameters, it builds up server capability
|
|
/// and runspace_initinfo in connectResponseData.
|
|
/// This is a version of Connect that executes the whole connect alogirithm in one single
|
|
/// hop.
|
|
/// This algorithm is being executed synchronously without associating with state machine.
|
|
/// </summary>
|
|
/// <param name="connectData"></param>
|
|
/// <param name="connectResponseData"></param>
|
|
/// The operation is being outside the statemachine becuase of multiple reasons assiciated with design simplicity
|
|
/// - Support automatic disconnect and let wsman server stack take care of connection state
|
|
/// - The response data should not travel in transports output stream but as part of connect response
|
|
/// - We want this operation to be synchronous
|
|
internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData)
|
|
{
|
|
connectResponseData = null;
|
|
Fragmentor fragmentor = new Fragmentor(int.MaxValue, null);
|
|
Fragmentor defragmentor = fragmentor;
|
|
|
|
int totalDataLen = connectData.Length;
|
|
|
|
if (totalDataLen < FragmentedRemoteObject.HeaderLength)
|
|
{
|
|
//raise exception
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
|
|
//TODO: Follow up on comment from Krishna regarding having the serialization/deserialization separate for this
|
|
// operation. This could be integrated as helper functions in fragmenter/serializer components
|
|
long fragmentId = FragmentedRemoteObject.GetFragmentId(connectData, 0);
|
|
bool sFlag = FragmentedRemoteObject.GetIsStartFragment(connectData, 0);
|
|
bool eFlag = FragmentedRemoteObject.GetIsEndFragment(connectData, 0);
|
|
int blobLength = FragmentedRemoteObject.GetBlobLength(connectData, 0);
|
|
|
|
if (blobLength > totalDataLen - FragmentedRemoteObject.HeaderLength)
|
|
{
|
|
//raise exception
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
|
|
if (!sFlag || !eFlag)
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
|
|
//if session is not in expected state
|
|
RemoteSessionState currentState = SessionDataStructureHandler.StateMachine.State;
|
|
if (currentState != RemoteSessionState.Established &&
|
|
currentState != RemoteSessionState.EstablishedAndKeyExchanged)
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnServerStateValidation);
|
|
}
|
|
|
|
//process first message
|
|
MemoryStream serializedStream = new MemoryStream();
|
|
serializedStream.Write(connectData, FragmentedRemoteObject.HeaderLength, blobLength);
|
|
|
|
serializedStream.Seek(0, SeekOrigin.Begin);
|
|
RemoteDataObject<PSObject> capabilityObject = RemoteDataObject<PSObject>.CreateFrom(serializedStream, defragmentor);
|
|
|
|
if (capabilityObject == null)
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
|
|
if ((capabilityObject.Destination != RemotingDestination.Server) || (capabilityObject.DataType != RemotingDataType.SessionCapability))
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
|
|
//process second message
|
|
int secondFragmentLength = totalDataLen - FragmentedRemoteObject.HeaderLength - blobLength;
|
|
if (secondFragmentLength < FragmentedRemoteObject.HeaderLength)
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
|
|
byte[] secondFragment = new byte[secondFragmentLength];
|
|
Array.Copy(connectData, FragmentedRemoteObject.HeaderLength + blobLength, secondFragment, 0, secondFragmentLength);
|
|
|
|
fragmentId = FragmentedRemoteObject.GetFragmentId(secondFragment, 0);
|
|
sFlag = FragmentedRemoteObject.GetIsStartFragment(secondFragment, 0);
|
|
eFlag = FragmentedRemoteObject.GetIsEndFragment(secondFragment, 0);
|
|
blobLength = FragmentedRemoteObject.GetBlobLength(secondFragment, 0);
|
|
|
|
if (blobLength != secondFragmentLength - FragmentedRemoteObject.HeaderLength)
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
|
|
if (!sFlag || !eFlag)
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
|
|
//process second message
|
|
serializedStream = new MemoryStream();
|
|
serializedStream.Write(secondFragment, FragmentedRemoteObject.HeaderLength, blobLength);
|
|
|
|
serializedStream.Seek(0, SeekOrigin.Begin);
|
|
RemoteDataObject<PSObject> connectRunspacePoolObject = RemoteDataObject<PSObject>.CreateFrom(serializedStream, defragmentor);
|
|
|
|
if (connectRunspacePoolObject == null)
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnServerStateValidation);
|
|
}
|
|
|
|
if ((connectRunspacePoolObject.Destination != RemotingDestination.Server) || (connectRunspacePoolObject.DataType != RemotingDataType.ConnectRunspacePool))
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
|
|
//We have the two objects required for validating the connect operation
|
|
RemoteSessionCapability clientCapability;
|
|
try
|
|
{
|
|
clientCapability = RemotingDecoder.GetSessionCapability(capabilityObject.Data);
|
|
}
|
|
catch (PSRemotingDataStructureException)
|
|
{
|
|
// this will happen if expected properties are not
|
|
// received for session capability
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
|
|
try
|
|
{
|
|
RunServerNegotiationAlgorithm(clientCapability, true);
|
|
}
|
|
catch (PSRemotingDataStructureException ex)
|
|
{
|
|
throw ex;
|
|
}
|
|
|
|
//validate client connect_runspacepool request
|
|
int clientRequestedMinRunspaces = -1;
|
|
int clientRequestedMaxRunspaces = -1;
|
|
bool clientRequestedRunspaceCount = false;
|
|
if (connectRunspacePoolObject.Data.Properties[RemoteDataNameStrings.MinRunspaces] != null && connectRunspacePoolObject.Data.Properties[RemoteDataNameStrings.MinRunspaces] != null)
|
|
{
|
|
try
|
|
{
|
|
clientRequestedMinRunspaces = RemotingDecoder.GetMinRunspaces(connectRunspacePoolObject.Data);
|
|
clientRequestedMaxRunspaces = RemotingDecoder.GetMaxRunspaces(connectRunspacePoolObject.Data);
|
|
clientRequestedRunspaceCount = true;
|
|
}
|
|
catch (PSRemotingDataStructureException)
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
}
|
|
|
|
//these should be positive and max should be greater than min
|
|
if (clientRequestedRunspaceCount &&
|
|
(clientRequestedMinRunspaces == -1 || clientRequestedMaxRunspaces == -1 || clientRequestedMinRunspaces > clientRequestedMaxRunspaces))
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
|
|
if (_runspacePoolDriver == null)
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnServerStateValidation);
|
|
}
|
|
|
|
//currently only one runspace pool per session is allowed. make sure this ID in connect message is the same
|
|
if (connectRunspacePoolObject.RunspacePoolId != _runspacePoolDriver.InstanceId)
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation);
|
|
}
|
|
|
|
//we currently dont support adjusting runspace count on a connect operation.
|
|
//there is a potential race here where in the runspace pool driver is still yet to process a queued
|
|
//setMax or setMinrunspacees request.
|
|
//TODO: resolve this race.. probably by letting the runspace pool consume all messages before we execute this.
|
|
if (clientRequestedRunspaceCount
|
|
&& (_runspacePoolDriver.RunspacePool.GetMaxRunspaces() != clientRequestedMaxRunspaces)
|
|
&& (_runspacePoolDriver.RunspacePool.GetMinRunspaces() != clientRequestedMinRunspaces))
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnMismatchedRunspacePoolProperties);
|
|
}
|
|
|
|
// all client messages are validated
|
|
// now build up the server capabilites and connect response messages to be piggybacked on connect response
|
|
RemoteDataObject capability = RemotingEncoder.GenerateServerSessionCapability(_context.ServerCapability, _runspacePoolDriver.InstanceId);
|
|
RemoteDataObject runspacepoolInitData = RemotingEncoder.GenerateRunspacePoolInitData(_runspacePoolDriver.InstanceId,
|
|
_runspacePoolDriver.RunspacePool.GetMaxRunspaces(),
|
|
_runspacePoolDriver.RunspacePool.GetMinRunspaces());
|
|
|
|
//having this stream operating separately will result in out of sync fragment Ids. but this is still OK
|
|
//as this is executed only when connecting from a new client that does not have any previous fragments context.
|
|
//no problem even if fragment Ids in this respose and the sessiontransport stream clash (interfere) and its guaranteed
|
|
// that the fragments in connect response are always complete (enclose a complete object).
|
|
SerializedDataStream stream = new SerializedDataStream(4*1024);//Each message with fragment headers cannot cross 4k
|
|
stream.Enter();
|
|
capability.Serialize(stream, fragmentor);
|
|
stream.Exit();
|
|
stream.Enter();
|
|
runspacepoolInitData.Serialize(stream, fragmentor);
|
|
stream.Exit();
|
|
byte[] outbuffer = stream.Read();
|
|
Dbg.Assert(outbuffer != null, "connet response data should be serialized");
|
|
stream.Dispose();
|
|
//we are done
|
|
connectResponseData = outbuffer;
|
|
|
|
//enque a connect event in state machine to let session do any other post-connect operation
|
|
// Do this outside of the sychronous connect operation, as otherwise connect can easily get deadlocked
|
|
ThreadPool.QueueUserWorkItem(new WaitCallback(
|
|
delegate(object state)
|
|
{
|
|
RemoteSessionStateMachineEventArgs startEventArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.ConnectSession);
|
|
_sessionDSHandler.StateMachine.RaiseEvent(startEventArg);
|
|
}));
|
|
|
|
_runspacePoolDriver.DataStructureHandler.ProcessConnect();
|
|
return;
|
|
}
|
|
|
|
//pass on application private data when session is connected from new client
|
|
internal void HandlePostConnect()
|
|
{
|
|
if (_runspacePoolDriver != null)
|
|
{
|
|
_runspacePoolDriver.SendApplicationPrivateDataToClient();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="createRunspaceEventArg"></param>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// 1. InitialSessionState cannot be null.
|
|
/// 2. Non existent InitialSessionState provider for the shellID
|
|
/// </exception>
|
|
private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createRunspaceEventArg)
|
|
{
|
|
if (createRunspaceEventArg == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("createRunspaceEventArg");
|
|
}
|
|
RemoteDataObject<PSObject> rcvdData = createRunspaceEventArg.ReceivedData;
|
|
Dbg.Assert(rcvdData != null, "rcvdData must be non-null");
|
|
|
|
// set the PSSenderInfo sent in the first packets
|
|
// This is used by the initial session state configuration providers like Exchange.
|
|
if (_context != null)
|
|
{
|
|
#if !CORECLR // TimeZone Not In CoreCLR
|
|
_senderInfo.ClientTimeZone = _context.ClientCapability.TimeZone;
|
|
#endif
|
|
}
|
|
|
|
_senderInfo.ApplicationArguments = RemotingDecoder.GetApplicationArguments(rcvdData.Data);
|
|
|
|
// Get Initial Session State from custom session config suppliers
|
|
// like Exchange.
|
|
ConfigurationDataFromXML configurationData =
|
|
PSSessionConfiguration.LoadEndPointConfiguration(_configProviderId,
|
|
_initParameters);
|
|
// used by Out-Of-Proc (IPC) runspace.
|
|
configurationData.InitializationScriptForOutOfProcessRunspace = _initScriptForOutOfProcRS;
|
|
// start with data from configuration XML and then override with data
|
|
// from EndPointConfiguration type.
|
|
maxRecvdObjectSize = configurationData.MaxReceivedObjectSizeMB;
|
|
maxRecvdDataSizeCommand = configurationData.MaxReceivedCommandSizeMB;
|
|
|
|
DISCPowerShellConfiguration discProvider = null;
|
|
|
|
if (String.IsNullOrEmpty(configurationData.ConfigFilePath))
|
|
{
|
|
_sessionConfigProvider = configurationData.CreateEndPointConfigurationInstance();
|
|
}
|
|
else
|
|
{
|
|
System.Security.Principal.WindowsPrincipal windowsPrincipal = new System.Security.Principal.WindowsPrincipal(_senderInfo.UserInfo.WindowsIdentity);
|
|
|
|
Func<string, bool> validator = (role) => windowsPrincipal.IsInRole(role);
|
|
|
|
discProvider = new DISCPowerShellConfiguration(configurationData.ConfigFilePath, validator);
|
|
_sessionConfigProvider = discProvider;
|
|
}
|
|
|
|
// exchange of ApplicationArguments and ApplicationPrivateData is be done as early as possible
|
|
// (i.e. to let the _sessionConfigProvider bail out if it can't comply with client's versioning request)
|
|
PSPrimitiveDictionary applicationPrivateData = _sessionConfigProvider.GetApplicationPrivateData(_senderInfo);
|
|
|
|
InitialSessionState rsSessionStateToUse = null;
|
|
|
|
if (configurationData.SessionConfigurationData != null)
|
|
{
|
|
try
|
|
{
|
|
rsSessionStateToUse =
|
|
_sessionConfigProvider.GetInitialSessionState(configurationData.SessionConfigurationData, _senderInfo, _configProviderId);
|
|
}
|
|
catch (NotImplementedException)
|
|
{
|
|
rsSessionStateToUse = _sessionConfigProvider.GetInitialSessionState(_senderInfo);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rsSessionStateToUse = _sessionConfigProvider.GetInitialSessionState(_senderInfo);
|
|
}
|
|
|
|
if (null == rsSessionStateToUse)
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.InitialSessionStateNull, _configProviderId);
|
|
}
|
|
|
|
rsSessionStateToUse.ThrowOnRunspaceOpenError = true;
|
|
|
|
// this might throw if the sender info is already present
|
|
rsSessionStateToUse.Variables.Add(
|
|
new SessionStateVariableEntry(RemoteDataNameStrings.SenderInfoPreferenceVariable,
|
|
_senderInfo,
|
|
Remoting.PSRemotingErrorInvariants.FormatResourceString(
|
|
RemotingErrorIdStrings.PSSenderInfoDescription),
|
|
ScopedItemOptions.ReadOnly));
|
|
|
|
// check if the current scenario is Win7(client) to Win8(server). Add back the PSv2 version TabExpansion
|
|
// function if necessary.
|
|
Version psClientVersion = null;
|
|
if (_senderInfo.ApplicationArguments != null && _senderInfo.ApplicationArguments.ContainsKey("PSversionTable"))
|
|
{
|
|
var value = PSObject.Base(_senderInfo.ApplicationArguments["PSversionTable"]) as PSPrimitiveDictionary;
|
|
if (value != null)
|
|
{
|
|
if (value.ContainsKey("WSManStackVersion"))
|
|
{
|
|
var wsmanStackVersion = PSObject.Base(value["WSManStackVersion"]) as Version;
|
|
if (wsmanStackVersion != null && wsmanStackVersion.Major < 3)
|
|
{
|
|
// The client side is PSv2. This is the Win7 to Win8 scenario. We need to add the PSv2
|
|
// TabExpansion function back in to keep the tab expansion functionable on the client side.
|
|
rsSessionStateToUse.Commands.Add(
|
|
new SessionStateFunctionEntry(
|
|
RemoteDataNameStrings.PSv2TabExpansionFunction,
|
|
RemoteDataNameStrings.PSv2TabExpansionFunctionText));
|
|
}
|
|
}
|
|
if (value.ContainsKey("PSVersion"))
|
|
{
|
|
psClientVersion = PSObject.Base(value["PSVersion"]) as Version;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(configurationData.EndPointConfigurationTypeName))
|
|
{
|
|
// user specified a type to load for configuration..use the values from this type.
|
|
maxRecvdObjectSize = _sessionConfigProvider.GetMaximumReceivedObjectSize(_senderInfo);
|
|
maxRecvdDataSizeCommand = _sessionConfigProvider.GetMaximumReceivedDataSizePerCommand(_senderInfo);
|
|
}
|
|
_sessionDSHandler.TransportManager.ReceivedDataCollection.MaximumReceivedObjectSize = maxRecvdObjectSize;
|
|
// MaximumReceivedDataSize is not set for session transport manager...see the constructor
|
|
// for more info.
|
|
|
|
Guid clientRunspacePoolId = rcvdData.RunspacePoolId;
|
|
int minRunspaces = RemotingDecoder.GetMinRunspaces(rcvdData.Data);
|
|
int maxRunspaces = RemotingDecoder.GetMaxRunspaces(rcvdData.Data);
|
|
PSThreadOptions threadOptions = RemotingDecoder.GetThreadOptions(rcvdData.Data);
|
|
#if !CORECLR // No ApartmentState In CoreCLR
|
|
ApartmentState apartmentState = RemotingDecoder.GetApartmentState(rcvdData.Data);
|
|
#endif
|
|
HostInfo hostInfo = RemotingDecoder.GetHostInfo(rcvdData.Data);
|
|
|
|
if (_runspacePoolDriver != null)
|
|
{
|
|
throw new PSRemotingDataStructureException(RemotingErrorIdStrings.RunspaceAlreadyExists,
|
|
_runspacePoolDriver.InstanceId);
|
|
}
|
|
|
|
bool isAdministrator = _senderInfo.UserInfo.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
|
|
|
|
ServerRunspacePoolDriver tmpDriver = new ServerRunspacePoolDriver(
|
|
clientRunspacePoolId,
|
|
minRunspaces,
|
|
maxRunspaces,
|
|
threadOptions,
|
|
#if !CORECLR // No ApartmentState In CoreCLR
|
|
apartmentState,
|
|
#endif
|
|
hostInfo,
|
|
rsSessionStateToUse,
|
|
applicationPrivateData,
|
|
configurationData,
|
|
this.SessionDataStructureHandler.TransportManager,
|
|
isAdministrator,
|
|
_context.ServerCapability,
|
|
psClientVersion,
|
|
_configurationName);
|
|
|
|
// attach the necessary event handlers and start the driver.
|
|
Interlocked.Exchange(ref _runspacePoolDriver, tmpDriver);
|
|
_runspacePoolDriver.Closed += HandleResourceClosing;
|
|
_runspacePoolDriver.Start();
|
|
}
|
|
|
|
/// <summary>
|
|
/// This handler method runs the negotiation algorithm. It decides if the negotiation is succesful,
|
|
/// or fails.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="negotiationEventArg">
|
|
/// This parameter contains the client negotiation capability packet.
|
|
/// </param>
|
|
///
|
|
/// <exception cref="ArgumentNullException">
|
|
/// If the parameter <paramref name="negotiationEventArg"/> is null.
|
|
/// </exception>
|
|
private void HandleNegotiationReceived(object sender, RemoteSessionNegotiationEventArgs negotiationEventArg)
|
|
{
|
|
if (negotiationEventArg == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("negotiationEventArg");
|
|
}
|
|
|
|
try
|
|
{
|
|
_context.ClientCapability = negotiationEventArg.RemoteSessionCapability;
|
|
// This will throw if there is an error running the algorithm
|
|
RunServerNegotiationAlgorithm(negotiationEventArg.RemoteSessionCapability, false);
|
|
|
|
// Send server's capability to client.
|
|
RemoteSessionStateMachineEventArgs sendingNegotiationArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.NegotiationSending);
|
|
_sessionDSHandler.StateMachine.RaiseEvent(sendingNegotiationArg);
|
|
|
|
// if negotiation succeeded change the state to neg. completed.
|
|
RemoteSessionStateMachineEventArgs negotiationCompletedArg =
|
|
new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.NegotiationCompleted);
|
|
_sessionDSHandler.StateMachine.RaiseEvent(negotiationCompletedArg);
|
|
}
|
|
catch(PSRemotingDataStructureException dse)
|
|
{
|
|
// Before setting to negotiation failed..send servers capability...that
|
|
// way client can communicate differently if it wants to.
|
|
RemoteSessionStateMachineEventArgs sendingNegotiationArg =
|
|
new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.NegotiationSending);
|
|
_sessionDSHandler.StateMachine.RaiseEvent(sendingNegotiationArg);
|
|
|
|
RemoteSessionStateMachineEventArgs negotiationFailedArg =
|
|
new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.NegotiationFailed,
|
|
dse);
|
|
_sessionDSHandler.StateMachine.RaiseEvent(negotiationFailedArg);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle session closing event to close runspace pool drivers this sesion is hosting.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="eventArgs"></param>
|
|
private void HandleSessionDSHandlerClosing(object sender, EventArgs eventArgs)
|
|
{
|
|
if (null != _runspacePoolDriver)
|
|
{
|
|
_runspacePoolDriver.Close();
|
|
}
|
|
|
|
// dispose the session configuration object..this will let them
|
|
// clean their resources.
|
|
if (null != _sessionConfigProvider)
|
|
{
|
|
_sessionConfigProvider.Dispose();
|
|
_sessionConfigProvider = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This handles closing of any resource used by this session.
|
|
/// Resources used are RunspacePoolDriver, TransportManager.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="args"></param>
|
|
private void HandleResourceClosing(object sender, EventArgs args)
|
|
{
|
|
RemoteSessionStateMachineEventArgs closeSessionArgs = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.Close);
|
|
closeSessionArgs.RemoteData = null;
|
|
_sessionDSHandler.StateMachine.RaiseEvent(closeSessionArgs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is the server side remote session capability negotiation algorithm.
|
|
/// </summary>
|
|
/// <param name="clientCapability">
|
|
/// This is the client capability that the server received from client.
|
|
/// </param>
|
|
/// <param name="onConnect">
|
|
/// If the negotiation is on a connect (and not create)
|
|
/// </param>
|
|
/// <returns>
|
|
/// The method returns true if the capability negotiation is successful.
|
|
/// Otherwise, it returns false.
|
|
/// </returns>
|
|
/// <exception cref="PSRemotingDataStructureException">
|
|
/// 1. PowerShell server does not support the PSVersion {1} negotiated by the client.
|
|
/// Make sure the client is compatible with the build {2} of PowerShell.
|
|
/// 2. PowerShell server does not support the SerializationVersion {1} negotiated by the client.
|
|
/// Make sure the client is compatible with the build {2} of PowerShell.
|
|
/// </exception>
|
|
private bool RunServerNegotiationAlgorithm(RemoteSessionCapability clientCapability, bool onConnect)
|
|
{
|
|
Dbg.Assert(clientCapability != null, "Client capability cache must be non-null");
|
|
|
|
Version clientProtocolVersion = clientCapability.ProtocolVersion;
|
|
Version serverProtocolVersion = _context.ServerCapability.ProtocolVersion;
|
|
|
|
if (onConnect)
|
|
{
|
|
bool connectSupported = false;
|
|
|
|
// Win10 server can support reconstruct/reconnect for all 2.x protocol versions
|
|
// that support reconstruct/reconnect, Protocol 2.2+
|
|
// Major protocol version differences (2.x -> 3.x) are not supported.
|
|
if ((serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) &&
|
|
(clientProtocolVersion.Major == serverProtocolVersion.Major))
|
|
{
|
|
if (clientProtocolVersion.Minor == RemotingConstants.ProtocolVersionWin8RTM.Minor)
|
|
{
|
|
// Report that server is Win8 version to the client
|
|
// Protocol: 2.2
|
|
connectSupported = true;
|
|
serverProtocolVersion = RemotingConstants.ProtocolVersionWin8RTM;
|
|
_context.ServerCapability.ProtocolVersion = serverProtocolVersion;
|
|
}
|
|
else if (clientProtocolVersion.Minor > RemotingConstants.ProtocolVersionWin8RTM.Minor)
|
|
{
|
|
// All other minor versions are supported and the server returns its full capability
|
|
// Protocol: 2.3, 2.4, 2.5 ...
|
|
connectSupported = true;
|
|
}
|
|
}
|
|
|
|
if (!connectSupported)
|
|
{
|
|
// Throw for protocol versions 2.x that don't support disconnect/reconnect.
|
|
// Protocol: < 2.2
|
|
PSRemotingDataStructureException reasonOfFailure =
|
|
new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnNegotiation,
|
|
RemoteDataNameStrings.PS_STARTUP_PROTOCOL_VERSION_NAME,
|
|
clientProtocolVersion,
|
|
PSVersionInfo.BuildVersion,
|
|
RemotingConstants.ProtocolVersion);
|
|
throw reasonOfFailure;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Win10 server can support Win8 client
|
|
if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM &&
|
|
(
|
|
(serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM)
|
|
))
|
|
{
|
|
// - report that server is Win8 version to the client
|
|
serverProtocolVersion = RemotingConstants.ProtocolVersionWin8RTM;
|
|
_context.ServerCapability.ProtocolVersion = serverProtocolVersion;
|
|
}
|
|
|
|
// Win8, Win10 server can support Win7 client
|
|
if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM &&
|
|
(
|
|
(serverProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM) ||
|
|
(serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM)
|
|
))
|
|
{
|
|
// - report that server is Win7 version to the client
|
|
serverProtocolVersion = RemotingConstants.ProtocolVersionWin7RTM;
|
|
_context.ServerCapability.ProtocolVersion = serverProtocolVersion;
|
|
}
|
|
|
|
// Win7, Win8, Win10 server can support Win7 RC client
|
|
if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin7RC &&
|
|
(
|
|
(serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM) ||
|
|
(serverProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM) ||
|
|
(serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM)
|
|
))
|
|
{
|
|
// - report that server is RC version to the client
|
|
serverProtocolVersion = RemotingConstants.ProtocolVersionWin7RC;
|
|
_context.ServerCapability.ProtocolVersion = serverProtocolVersion;
|
|
}
|
|
|
|
if (!((clientProtocolVersion.Major == serverProtocolVersion.Major) &&
|
|
(clientProtocolVersion.Minor >= serverProtocolVersion.Minor)))
|
|
{
|
|
PSRemotingDataStructureException reasonOfFailure =
|
|
new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerNegotiationFailed,
|
|
RemoteDataNameStrings.PS_STARTUP_PROTOCOL_VERSION_NAME,
|
|
clientProtocolVersion,
|
|
PSVersionInfo.BuildVersion,
|
|
RemotingConstants.ProtocolVersion);
|
|
throw reasonOfFailure;
|
|
}
|
|
}
|
|
|
|
// PSVersion Check
|
|
Version clientPSVersion = clientCapability.PSVersion;
|
|
Version serverPSVersion = _context.ServerCapability.PSVersion;
|
|
if (!((clientPSVersion.Major == serverPSVersion.Major) &&
|
|
(clientPSVersion.Minor >= serverPSVersion.Minor)))
|
|
{
|
|
PSRemotingDataStructureException reasonOfFailure =
|
|
new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerNegotiationFailed,
|
|
RemoteDataNameStrings.PSVersion,
|
|
clientPSVersion,
|
|
PSVersionInfo.BuildVersion,
|
|
RemotingConstants.ProtocolVersion);
|
|
throw reasonOfFailure;
|
|
}
|
|
|
|
// SerializationVersion check
|
|
Version clientSerVersion = clientCapability.SerializationVersion;
|
|
Version serverSerVersion = _context.ServerCapability.SerializationVersion;
|
|
if (!((clientSerVersion.Major == serverSerVersion.Major) &&
|
|
(clientSerVersion.Minor >= serverSerVersion.Minor)))
|
|
{
|
|
PSRemotingDataStructureException reasonOfFailure =
|
|
new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerNegotiationFailed,
|
|
RemoteDataNameStrings.SerializationVersion,
|
|
clientSerVersion,
|
|
PSVersionInfo.BuildVersion,
|
|
RemotingConstants.ProtocolVersion);
|
|
throw reasonOfFailure;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="clientRunspacePoolId"></param>
|
|
/// <returns></returns>
|
|
internal ServerRunspacePoolDriver GetRunspacePoolDriver(Guid clientRunspacePoolId)
|
|
{
|
|
if (_runspacePoolDriver == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (_runspacePoolDriver.InstanceId == clientRunspacePoolId)
|
|
{
|
|
return _runspacePoolDriver;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used by Command Session to apply quotas on the command transport manager.
|
|
/// This method is here because ServerRemoteSession knows about InitialSessionState.
|
|
/// </summary>
|
|
/// <param name="cmdTransportManager">
|
|
/// Command TransportManager to apply the quota on.
|
|
/// </param>
|
|
internal void ApplyQuotaOnCommandTransportManager(AbstractServerTransportManager cmdTransportManager)
|
|
{
|
|
Dbg.Assert(null != cmdTransportManager, "cmdTransportManager cannot be null");
|
|
cmdTransportManager.ReceivedDataCollection.MaximumReceivedDataSize = maxRecvdDataSizeCommand;
|
|
cmdTransportManager.ReceivedDataCollection.MaximumReceivedObjectSize = maxRecvdObjectSize;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|