/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/System.Management.Automation/engine/remoting/server/ServerRemotingProtocol2.cs
810 строк
33 KB
spaette
typos (#19058)
30 янв 2023, 20:48
Не верифицирован
30 янв 2023, 20:48
d29f34b
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; using System.Management.Automation.Remoting; using System.Management.Automation.Remoting.Server; using System.Management.Automation.Runspaces; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// <summary> /// Handles all data structure handler communication with the client /// runspace pool. /// </summary> internal class ServerRunspacePoolDataStructureHandler { #region Constructors /// <summary> /// Constructor which takes a server runspace pool driver and /// creates an associated ServerRunspacePoolDataStructureHandler. /// </summary> /// <param name="driver"></param> /// <param name="transportManager"></param> internal ServerRunspacePoolDataStructureHandler(ServerRunspacePoolDriver driver, AbstractServerSessionTransportManager transportManager) { _clientRunspacePoolId = driver.InstanceId; _transportManager = transportManager; } #endregion Constructors #region Data Structure Handler Methods /// <summary> /// Send a message with application private data to the client. /// </summary> /// <param name="applicationPrivateData">ApplicationPrivateData to send.</param> /// <param name="serverCapability">Server capability negotiated during initial exchange of remoting messages / session capabilities of client and server.</param> internal void SendApplicationPrivateDataToClient(PSPrimitiveDictionary applicationPrivateData, RemoteSessionCapability serverCapability) { // make server's PSVersionTable available to the client using ApplicationPrivateData PSPrimitiveDictionary applicationPrivateDataWithVersionTable = PSPrimitiveDictionary.CloneAndAddPSVersionTable(applicationPrivateData); // override the hardcoded version numbers with the stuff that was reported to the client during negotiation PSPrimitiveDictionary versionTable = (PSPrimitiveDictionary)applicationPrivateDataWithVersionTable[PSVersionInfo.PSVersionTableName]; versionTable[PSVersionInfo.PSRemotingProtocolVersionName] = serverCapability.ProtocolVersion; versionTable[PSVersionInfo.SerializationVersionName] = serverCapability.SerializationVersion; // Pass back the true PowerShell version to the client via application private data. versionTable[PSVersionInfo.PSVersionName] = PSVersionInfo.PSVersion; RemoteDataObject data = RemotingEncoder.GenerateApplicationPrivateData( _clientRunspacePoolId, applicationPrivateDataWithVersionTable); SendDataAsync(data); } /// <summary> /// Send a message with the RunspacePoolStateInfo to the client. /// </summary> /// <param name="stateInfo">State info to send.</param> internal void SendStateInfoToClient(RunspacePoolStateInfo stateInfo) { RemoteDataObject data = RemotingEncoder.GenerateRunspacePoolStateInfo( _clientRunspacePoolId, stateInfo); SendDataAsync(data); } /// <summary> /// Send a message with the PSEventArgs to the client. /// </summary> /// <param name="e">Event to send.</param> internal void SendPSEventArgsToClient(PSEventArgs e) { RemoteDataObject data = RemotingEncoder.GeneratePSEventArgs(_clientRunspacePoolId, e); SendDataAsync(data); } /// <summary> /// Called when session is connected from a new client /// call into the sessionconnect handlers for each associated powershell dshandler. /// </summary> internal void ProcessConnect() { List<ServerPowerShellDataStructureHandler> dsHandlers; lock (_associationSyncObject) { dsHandlers = new List<ServerPowerShellDataStructureHandler>(_associatedShells.Values); } foreach (var dsHandler in dsHandlers) { dsHandler.ProcessConnect(); } } /// <summary> /// Process the data received from the runspace pool on /// the server. /// </summary> /// <param name="receivedData">Data received.</param> internal void ProcessReceivedData(RemoteDataObject<PSObject> receivedData) { if (receivedData == null) { throw PSTraceSource.NewArgumentNullException(nameof(receivedData)); } Dbg.Assert(receivedData.TargetInterface == RemotingTargetInterface.RunspacePool, "RemotingTargetInterface must be Runspace"); switch (receivedData.DataType) { case RemotingDataType.CreatePowerShell: { Dbg.Assert(CreateAndInvokePowerShell != null, "The ServerRunspacePoolDriver should subscribe to all data structure handler events"); CreateAndInvokePowerShell.SafeInvoke(this, new RemoteDataEventArgs<RemoteDataObject<PSObject>>(receivedData)); } break; case RemotingDataType.GetCommandMetadata: { Dbg.Assert(GetCommandMetadata != null, "The ServerRunspacePoolDriver should subscribe to all data structure handler events"); GetCommandMetadata.SafeInvoke(this, new RemoteDataEventArgs<RemoteDataObject<PSObject>>(receivedData)); } break; case RemotingDataType.RemoteRunspaceHostResponseData: { Dbg.Assert(HostResponseReceived != null, "The ServerRunspacePoolDriver should subscribe to all data structure handler events"); RemoteHostResponse remoteHostResponse = RemoteHostResponse.Decode(receivedData.Data); // part of host message robustness algo. Now the host response is back, report to transport that // execution status is back to running _transportManager.ReportExecutionStatusAsRunning(); HostResponseReceived.SafeInvoke(this, new RemoteDataEventArgs<RemoteHostResponse>(remoteHostResponse)); } break; case RemotingDataType.SetMaxRunspaces: { Dbg.Assert(SetMaxRunspacesReceived != null, "The ServerRunspacePoolDriver should subscribe to all data structure handler events"); SetMaxRunspacesReceived.SafeInvoke(this, new RemoteDataEventArgs<PSObject>(receivedData.Data)); } break; case RemotingDataType.SetMinRunspaces: { Dbg.Assert(SetMinRunspacesReceived != null, "The ServerRunspacePoolDriver should subscribe to all data structure handler events"); SetMinRunspacesReceived.SafeInvoke(this, new RemoteDataEventArgs<PSObject>(receivedData.Data)); } break; case RemotingDataType.AvailableRunspaces: { Dbg.Assert(GetAvailableRunspacesReceived != null, "The ServerRunspacePoolDriver should subscribe to all data structure handler events"); GetAvailableRunspacesReceived.SafeInvoke(this, new RemoteDataEventArgs<PSObject>(receivedData.Data)); } break; case RemotingDataType.ResetRunspaceState: { Dbg.Assert(ResetRunspaceState != null, "The ServerRunspacePoolDriver should subscribe to all data structure handler events."); ResetRunspaceState.SafeInvoke(this, new RemoteDataEventArgs<PSObject>(receivedData.Data)); } break; } } /// <summary> /// Creates a powershell data structure handler from this runspace pool. /// </summary> /// <param name="instanceId">Powershell instance id.</param> /// <param name="runspacePoolId">Runspace pool id.</param> /// <param name="remoteStreamOptions">Remote stream options.</param> /// <param name="localPowerShell">Local PowerShell object.</param> /// <returns>ServerPowerShellDataStructureHandler.</returns> internal ServerPowerShellDataStructureHandler CreatePowerShellDataStructureHandler( Guid instanceId, Guid runspacePoolId, RemoteStreamOptions remoteStreamOptions, PowerShell localPowerShell) { // start with pool's transport manager. AbstractServerTransportManager cmdTransportManager = _transportManager; if (instanceId != Guid.Empty) { cmdTransportManager = _transportManager.GetCommandTransportManager(instanceId); Dbg.Assert(cmdTransportManager.TypeTable != null, "This should be already set in managed C++ code"); } ServerPowerShellDataStructureHandler dsHandler = new ServerPowerShellDataStructureHandler(instanceId, runspacePoolId, remoteStreamOptions, cmdTransportManager, localPowerShell); lock (_associationSyncObject) { _associatedShells.Add(dsHandler.PowerShellId, dsHandler); } dsHandler.RemoveAssociation += HandleRemoveAssociation; return dsHandler; } /// <summary> /// Returns the currently active PowerShell datastructure handler. /// </summary> /// <returns> /// ServerPowerShellDataStructureHandler if one is present, null otherwise. /// </returns> internal ServerPowerShellDataStructureHandler GetPowerShellDataStructureHandler() { lock (_associationSyncObject) { if (_associatedShells.Count > 0) { foreach (object o in _associatedShells.Values) { ServerPowerShellDataStructureHandler result = o as ServerPowerShellDataStructureHandler; if (result != null) { return result; } } } } return null; } /// <summary> /// Dispatch the message to the associated powershell data structure handler. /// </summary> /// <param name="rcvdData">Message to dispatch.</param> internal void DispatchMessageToPowerShell(RemoteDataObject<PSObject> rcvdData) { ServerPowerShellDataStructureHandler dsHandler = GetAssociatedPowerShellDataStructureHandler(rcvdData.PowerShellId); // if data structure handler is not found, then association has already been // removed, discard message dsHandler?.ProcessReceivedData(rcvdData); } /// <summary> /// Send the specified response to the client. The client call will /// be blocked on the same. /// </summary> /// <param name="callId">Call id on the client.</param> /// <param name="response">Response to send.</param> internal void SendResponseToClient(long callId, object response) { RemoteDataObject message = RemotingEncoder.GenerateRunspacePoolOperationResponse(_clientRunspacePoolId, response, callId); SendDataAsync(message); } /// <summary> /// TypeTable used for Serialization/Deserialization. /// </summary> internal TypeTable TypeTable { get { return _transportManager.TypeTable; } set { _transportManager.TypeTable = value; } } #endregion Data Structure Handler Methods #region Data Structure Handler events /// <summary> /// This event is raised whenever there is a request from the /// client to create a powershell on the server and invoke it. /// </summary> internal event EventHandler<RemoteDataEventArgs<RemoteDataObject<PSObject>>> CreateAndInvokePowerShell; /// <summary> /// This event is raised whenever there is a request from the /// client to run command discovery pipeline. /// </summary> internal event EventHandler<RemoteDataEventArgs<RemoteDataObject<PSObject>>> GetCommandMetadata; /// <summary> /// This event is raised when a host call response is received. /// </summary> internal event EventHandler<RemoteDataEventArgs<RemoteHostResponse>> HostResponseReceived; /// <summary> /// This event is raised when there is a request to modify the /// maximum runspaces in the runspace pool. /// </summary> internal event EventHandler<RemoteDataEventArgs<PSObject>> SetMaxRunspacesReceived; /// <summary> /// This event is raised when there is a request to modify the /// minimum runspaces in the runspace pool. /// </summary> internal event EventHandler<RemoteDataEventArgs<PSObject>> SetMinRunspacesReceived; /// <summary> /// This event is raised when there is a request to get the /// available runspaces in the runspace pool. /// </summary> internal event EventHandler<RemoteDataEventArgs<PSObject>> GetAvailableRunspacesReceived; /// <summary> /// This event is raised when the client requests the runspace state /// to be reset. /// </summary> internal event EventHandler<RemoteDataEventArgs<PSObject>> ResetRunspaceState; #endregion Data Structure Handler events #region Private Methods /// <summary> /// Send the data specified as a RemoteDataObject asynchronously /// to the runspace pool on the remote session. /// </summary> /// <param name="data">Data to send.</param> /// <remarks>This overload takes a RemoteDataObject and should /// be the one that's used to send data from within this /// data structure handler class</remarks> private void SendDataAsync(RemoteDataObject data) { Dbg.Assert(data != null, "Cannot send null object."); _transportManager.SendDataToClient(data, true); } /// <summary> /// Get the associated powershell data structure handler for the specified /// powershell id. /// </summary> /// <param name="clientPowerShellId">powershell id for the /// powershell data structure handler</param> /// <returns>ServerPowerShellDataStructureHandler.</returns> internal ServerPowerShellDataStructureHandler GetAssociatedPowerShellDataStructureHandler (Guid clientPowerShellId) { ServerPowerShellDataStructureHandler dsHandler = null; lock (_associationSyncObject) { bool success = _associatedShells.TryGetValue(clientPowerShellId, out dsHandler); if (!success) { dsHandler = null; } } return dsHandler; } /// <summary> /// Remove the association of the powershell from the runspace pool. /// </summary> /// <param name="sender">Sender of this event.</param> /// <param name="e">Unused.</param> private void HandleRemoveAssociation(object sender, EventArgs e) { Dbg.Assert(sender is ServerPowerShellDataStructureHandler, @"sender of the event must be ServerPowerShellDataStructureHandler"); ServerPowerShellDataStructureHandler dsHandler = sender as ServerPowerShellDataStructureHandler; lock (_associationSyncObject) { _associatedShells.Remove(dsHandler.PowerShellId); } // let session transport manager remove its association of command transport manager. _transportManager.RemoveCommandTransportManager(dsHandler.PowerShellId); } #endregion Private Methods #region Private Members private readonly Guid _clientRunspacePoolId; // transport manager using which this // runspace pool driver handles all client // communication private readonly AbstractServerSessionTransportManager _transportManager; private readonly Dictionary<Guid, ServerPowerShellDataStructureHandler> _associatedShells = new Dictionary<Guid, ServerPowerShellDataStructureHandler>(); // powershell data structure handlers associated with this // runspace pool data structure handler private readonly object _associationSyncObject = new object(); // object to synchronize operations to above #endregion Private Members } /// <summary> /// Handles all PowerShell data structure handler communication /// with the client side PowerShell. /// </summary> internal class ServerPowerShellDataStructureHandler { #region Private Members // transport manager using which this // powershell driver handles all client // communication private readonly AbstractServerTransportManager _transportManager; private readonly Guid _clientRunspacePoolId; private readonly Guid _clientPowerShellId; private readonly RemoteStreamOptions _streamSerializationOptions; private Runspace _rsUsedToInvokePowerShell; #endregion Private Members #region Constructors /// <summary> /// Default constructor for creating ServerPowerShellDataStructureHandler /// instance. /// </summary> /// <param name="instanceId">Powershell instance id.</param> /// <param name="runspacePoolId">Runspace pool id.</param> /// <param name="remoteStreamOptions">Remote stream options.</param> /// <param name="transportManager">Transport manager.</param> /// <param name="localPowerShell">Local powershell object.</param> internal ServerPowerShellDataStructureHandler(Guid instanceId, Guid runspacePoolId, RemoteStreamOptions remoteStreamOptions, AbstractServerTransportManager transportManager, PowerShell localPowerShell) { _clientPowerShellId = instanceId; _clientRunspacePoolId = runspacePoolId; _transportManager = transportManager; _streamSerializationOptions = remoteStreamOptions; transportManager.Closing += HandleTransportClosing; if (localPowerShell != null) { localPowerShell.RunspaceAssigned += LocalPowerShell_RunspaceAssigned; } } private void LocalPowerShell_RunspaceAssigned(object sender, PSEventArgs<Runspace> e) { _rsUsedToInvokePowerShell = e.Args; } #endregion Constructors #region Data Structure Handler Methods /// <summary> /// Prepare transport manager to send data to client. /// </summary> internal void Prepare() { // When Guid.Empty is used, PowerShell must be using pool's transport manager // to send data to client. so we dont need to prepare command transport manager if (_clientPowerShellId != Guid.Empty) { _transportManager.Prepare(); } } /// <summary> /// Send the state information to the client. /// </summary> /// <param name="stateInfo">state information to be /// sent to the client</param> internal void SendStateChangedInformationToClient(PSInvocationStateInfo stateInfo) { Dbg.Assert((stateInfo.State == PSInvocationState.Completed) || (stateInfo.State == PSInvocationState.Failed) || (stateInfo.State == PSInvocationState.Stopped), "SendStateChangedInformationToClient should be called to notify a termination state"); SendDataAsync(RemotingEncoder.GeneratePowerShellStateInfo( stateInfo, _clientPowerShellId, _clientRunspacePoolId)); // Close the transport manager only if the PowerShell Guid != Guid.Empty. // When Guid.Empty is used, PowerShell must be using pool's transport manager // to send data to client. if (_clientPowerShellId != Guid.Empty) { // no need to listen for closing events as we are initiating the close _transportManager.Closing -= HandleTransportClosing; // if terminal state is reached close the transport manager instead of letting // the client initiate the close. _transportManager.Close(null); } } /// <summary> /// Send the output data to the client. /// </summary> /// <param name="data">Data to send.</param> internal void SendOutputDataToClient(PSObject data) { SendDataAsync(RemotingEncoder.GeneratePowerShellOutput(data, _clientPowerShellId, _clientRunspacePoolId)); } /// <summary> /// Send the error record to client. /// </summary> /// <param name="errorRecord">Error record to send.</param> internal void SendErrorRecordToClient(ErrorRecord errorRecord) { errorRecord.SerializeExtendedInfo = (_streamSerializationOptions & RemoteStreamOptions.AddInvocationInfoToErrorRecord) != 0; SendDataAsync(RemotingEncoder.GeneratePowerShellError( errorRecord, _clientRunspacePoolId, _clientPowerShellId)); } /// <summary> /// Send the specified warning record to client. /// </summary> /// <param name="record">Warning record.</param> internal void SendWarningRecordToClient(WarningRecord record) { record.SerializeExtendedInfo = (_streamSerializationOptions & RemoteStreamOptions.AddInvocationInfoToWarningRecord) != 0; SendDataAsync(RemotingEncoder.GeneratePowerShellInformational( record, _clientRunspacePoolId, _clientPowerShellId, RemotingDataType.PowerShellWarning)); } /// <summary> /// Send the specified debug record to client. /// </summary> /// <param name="record">Debug record.</param> internal void SendDebugRecordToClient(DebugRecord record) { record.SerializeExtendedInfo = (_streamSerializationOptions & RemoteStreamOptions.AddInvocationInfoToDebugRecord) != 0; SendDataAsync(RemotingEncoder.GeneratePowerShellInformational( record, _clientRunspacePoolId, _clientPowerShellId, RemotingDataType.PowerShellDebug)); } /// <summary> /// Send the specified verbose record to client. /// </summary> /// <param name="record">Warning record.</param> internal void SendVerboseRecordToClient(VerboseRecord record) { record.SerializeExtendedInfo = (_streamSerializationOptions & RemoteStreamOptions.AddInvocationInfoToVerboseRecord) != 0; SendDataAsync(RemotingEncoder.GeneratePowerShellInformational( record, _clientRunspacePoolId, _clientPowerShellId, RemotingDataType.PowerShellVerbose)); } /// <summary> /// Send the specified progress record to client. /// </summary> /// <param name="record">Progress record.</param> internal void SendProgressRecordToClient(ProgressRecord record) { SendDataAsync(RemotingEncoder.GeneratePowerShellInformational( record, _clientRunspacePoolId, _clientPowerShellId)); } /// <summary> /// Send the specified information record to client. /// </summary> /// <param name="record">Information record.</param> internal void SendInformationRecordToClient(InformationRecord record) { SendDataAsync(RemotingEncoder.GeneratePowerShellInformational( record, _clientRunspacePoolId, _clientPowerShellId)); } /// <summary> /// Called when session is connected from a new client /// calls into observers of this event. /// observers include corresponding driver that shutdown /// input stream is present. /// </summary> internal void ProcessConnect() { OnSessionConnected.SafeInvoke(this, EventArgs.Empty); } /// <summary> /// Process the data received from the powershell on /// the client. /// </summary> /// <param name="receivedData">Data received.</param> internal void ProcessReceivedData(RemoteDataObject<PSObject> receivedData) { if (receivedData == null) { throw PSTraceSource.NewArgumentNullException(nameof(receivedData)); } Dbg.Assert(receivedData.TargetInterface == RemotingTargetInterface.PowerShell, "RemotingTargetInterface must be PowerShell"); switch (receivedData.DataType) { case RemotingDataType.StopPowerShell: { Dbg.Assert(StopPowerShellReceived != null, "ServerPowerShellDriver should subscribe to all data structure handler events"); StopPowerShellReceived.SafeInvoke(this, EventArgs.Empty); } break; case RemotingDataType.PowerShellInput: { Dbg.Assert(InputReceived != null, "ServerPowerShellDriver should subscribe to all data structure handler events"); InputReceived.SafeInvoke(this, new RemoteDataEventArgs<object>(receivedData.Data)); } break; case RemotingDataType.PowerShellInputEnd: { Dbg.Assert(InputEndReceived != null, "ServerPowerShellDriver should subscribe to all data structure handler events"); InputEndReceived.SafeInvoke(this, EventArgs.Empty); } break; case RemotingDataType.RemotePowerShellHostResponseData: { Dbg.Assert(HostResponseReceived != null, "ServerPowerShellDriver should subscribe to all data structure handler events"); RemoteHostResponse remoteHostResponse = RemoteHostResponse.Decode(receivedData.Data); // part of host message robustness algo. Now the host response is back, report to transport that // execution status is back to running _transportManager.ReportExecutionStatusAsRunning(); HostResponseReceived.SafeInvoke(this, new RemoteDataEventArgs<RemoteHostResponse>(remoteHostResponse)); } break; } } /// <summary> /// Raise a remove association event. This is raised /// when the powershell has gone into a terminal state /// and the runspace pool need not maintain any further /// associations. /// </summary> internal void RaiseRemoveAssociationEvent() { Dbg.Assert(RemoveAssociation != null, @"The ServerRunspacePoolDataStructureHandler should subscribe to the RemoveAssociation event of ServerPowerShellDataStructureHandler"); RemoveAssociation.SafeInvoke(this, EventArgs.Empty); } /// <summary> /// Creates a ServerRemoteHost which is associated with this powershell. /// </summary> /// <param name="powerShellHostInfo">Host information about the host associated /// PowerShell object on the client.</param> /// <param name="runspaceServerRemoteHost">Host associated with the RunspacePool /// on the server.</param> /// <returns>A new ServerRemoteHost for the PowerShell.</returns> internal ServerRemoteHost GetHostAssociatedWithPowerShell( HostInfo powerShellHostInfo, ServerRemoteHost runspaceServerRemoteHost) { HostInfo hostInfo; // If host was null use the runspace's host for this powershell; otherwise, // use the HostInfo to create a proxy host of the powershell's host. if (powerShellHostInfo.UseRunspaceHost) { hostInfo = runspaceServerRemoteHost.HostInfo; } else { hostInfo = powerShellHostInfo; } // If the host was not null on the client, then the PowerShell object should // get a brand spanking new host. return new ServerRemoteHost(_clientRunspacePoolId, _clientPowerShellId, hostInfo, _transportManager, runspaceServerRemoteHost.Runspace, runspaceServerRemoteHost as ServerDriverRemoteHost); } #endregion Data Structure Handler Methods #region Data Structure Handler events /// <summary> /// This event is raised when the state of associated /// powershell is terminal and the runspace pool has /// to detach the association. /// </summary> internal event EventHandler RemoveAssociation; /// <summary> /// This event is raised when the a message to stop the /// powershell is received from the client. /// </summary> internal event EventHandler StopPowerShellReceived; /// <summary> /// This event is raised when an input object is received /// from the client. /// </summary> internal event EventHandler<RemoteDataEventArgs<object>> InputReceived; /// <summary> /// This event is raised when end of input is received from /// the client. /// </summary> internal event EventHandler InputEndReceived; /// <summary> /// Raised when server session is connected from a new client. /// </summary> internal event EventHandler OnSessionConnected; /// <summary> /// This event is raised when a host response is received. /// </summary> internal event EventHandler<RemoteDataEventArgs<RemoteHostResponse>> HostResponseReceived; #endregion Data Structure Handler events #region Internal Methods /// <summary> /// Client powershell id. /// </summary> internal Guid PowerShellId { get { return _clientPowerShellId; } } /// <summary> /// Runspace used to invoke PowerShell, this is used by the steppable /// pipeline driver. /// </summary> internal Runspace RunspaceUsedToInvokePowerShell { get { return _rsUsedToInvokePowerShell; } } #endregion Internal Methods #region Private Methods /// <summary> /// Send the data specified as a RemoteDataObject asynchronously /// to the runspace pool on the remote session. /// </summary> /// <param name="data">Data to send.</param> /// <remarks>This overload takes a RemoteDataObject and should /// be the one that's used to send data from within this /// data structure handler class</remarks> private void SendDataAsync(RemoteDataObject data) { Dbg.Assert(data != null, "Cannot send null object."); // this is from a command execution..let transport manager collect // as much data as possible and send bigger buffer to client. _transportManager.SendDataToClient(data, false); } /// <summary> /// Handle transport manager's closing event. /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private void HandleTransportClosing(object sender, EventArgs args) { StopPowerShellReceived.SafeInvoke(this, args); } #endregion Private Methods } }