MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Collections.ObjectModel;6using System.Management.Automation.Host;7using System.Management.Automation.Remoting;8using System.Management.Automation.Remoting.Client;9using System.Management.Automation.Runspaces;10using System.Management.Automation.Runspaces.Internal;11using System.Management.Automation.Tracing;12using System.Threading;13 14using Dbg = System.Management.Automation.Diagnostics;15 16namespace System.Management.Automation.Internal17{18 /// <summary>19 /// Handles all PowerShell data structure handler communication with the20 /// server side RunspacePool.21 /// </summary>22 internal sealed class ClientRunspacePoolDataStructureHandler : IDisposable23 {24 private bool _reconnecting = false;25 26 #region Constructors27 28 /// <summary>29 /// Constructor which takes a client runspace pool and creates30 /// an associated ClientRunspacePoolDataStructureHandler.31 /// </summary>32 /// <param name="clientRunspacePool">Client runspace pool object.</param>33 /// <param name="typeTable">Typetable to use for serialization/deserialization.</param>34 internal ClientRunspacePoolDataStructureHandler(RemoteRunspacePoolInternal clientRunspacePool,35 TypeTable typeTable)36 {37 _clientRunspacePoolId = clientRunspacePool.InstanceId;38 _minRunspaces = clientRunspacePool.GetMinRunspaces();39 _maxRunspaces = clientRunspacePool.GetMaxRunspaces();40 _host = clientRunspacePool.Host;41 _applicationArguments = clientRunspacePool.ApplicationArguments;42 RemoteSession = CreateClientRemoteSession(clientRunspacePool);43 // TODO: Assign remote session name.. should be passed from clientRunspacePool44 _transportManager = RemoteSession.SessionDataStructureHandler.TransportManager;45 _transportManager.TypeTable = typeTable;46 RemoteSession.StateChanged += HandleClientRemoteSessionStateChanged;47 _reconnecting = false;48 49 _transportManager.RobustConnectionNotification += HandleRobustConnectionNotification;50 _transportManager.CreateCompleted += HandleSessionCreateCompleted;51 }52 53 #endregion Constructors54 55 #region Data Structure Handler Methods56 57 /// <summary>58 /// Create a runspace pool asynchronously (and opens) it59 /// on the server.60 /// </summary>61 internal void CreateRunspacePoolAndOpenAsync()62 {63 // #1: Connect to remote session64 Dbg.Assert(RemoteSession.SessionDataStructureHandler.StateMachine.State == RemoteSessionState.Idle,65 "State of ClientRemoteSession is expected to be idle before connection is established");66 RemoteSession.CreateAsync();67 68 // #2: send the message for runspace pool creation69 // this is done in HandleClientRemoteSessionStateChanged70 }71 72 /// <summary>73 /// Closes the server runspace pool asynchronously.74 /// </summary>75 internal void CloseRunspacePoolAsync()76 {77 RemoteSession.CloseAsync();78 }79 80 /// <summary>81 /// Suspends connection to a runspace pool asynchronously.82 /// </summary>83 internal void DisconnectPoolAsync()84 {85 // Prepare running commands for disconnect and start disconnect86 // when ready.87 PrepareForAndStartDisconnect();88 }89 90 /// <summary>91 /// Restore connection to a runspace pool asynchronously.92 /// </summary>93 internal void ReconnectPoolAsync()94 {95 // TODO: Integrate this into state machine96 _reconnecting = true;97 PrepareForConnect();98 RemoteSession.ReconnectAsync();99 }100 101 /// <summary>102 /// Creates a connection to an existing remote runspace pool.103 /// </summary>104 internal void ConnectPoolAsync()105 {106 PrepareForConnect();107 RemoteSession.ConnectAsync();108 }109 110 /// <summary>111 /// Process the data received from the runspace pool112 /// on the server.113 /// </summary>114 /// <param name="receivedData">Data received.</param>115 internal void ProcessReceivedData(RemoteDataObject<PSObject> receivedData)116 {117 // verify if this data structure handler is the intended recipient118 if (receivedData.RunspacePoolId != _clientRunspacePoolId)119 {120 throw new PSRemotingDataStructureException(RemotingErrorIdStrings.RunspaceIdsDoNotMatch,121 receivedData.RunspacePoolId, _clientRunspacePoolId);122 }123 124 // take appropriate action based on the action type125 Dbg.Assert(receivedData.TargetInterface == RemotingTargetInterface.RunspacePool,126 "Target interface is expected to be RunspacePool");127 128 switch (receivedData.DataType)129 {130 case RemotingDataType.RemoteHostCallUsingRunspaceHost:131 {132 Dbg.Assert(RemoteHostCallReceived != null,133 "RemoteRunspacePoolInternal should subscribe to all data structure handler events");134 135 RemoteHostCall remoteHostCall = RemoteHostCall.Decode(receivedData.Data);136 RemoteHostCallReceived.SafeInvoke(this, new RemoteDataEventArgs<RemoteHostCall>(remoteHostCall));137 }138 139 break;140 141 case RemotingDataType.RunspacePoolInitData:142 {143 RunspacePoolInitInfo initInfo = RemotingDecoder.GetRunspacePoolInitInfo(receivedData.Data);144 145 Dbg.Assert(RSPoolInitInfoReceived != null,146 "RemoteRunspacePoolInternal should subscribe to all data structure handler events");147 RSPoolInitInfoReceived.SafeInvoke(this,148 new RemoteDataEventArgs<RunspacePoolInitInfo>(initInfo));149 }150 151 break;152 153 case RemotingDataType.RunspacePoolStateInfo:154 {155 RunspacePoolStateInfo stateInfo =156 RemotingDecoder.GetRunspacePoolStateInfo(receivedData.Data);157 158 Dbg.Assert(StateInfoReceived != null,159 "RemoteRunspacePoolInternal should subscribe to all data structure handler events");160 StateInfoReceived.SafeInvoke(this,161 new RemoteDataEventArgs<RunspacePoolStateInfo>(stateInfo));162 163 NotifyAssociatedPowerShells(stateInfo);164 }165 166 break;167 168 case RemotingDataType.ApplicationPrivateData:169 {170 PSPrimitiveDictionary applicationPrivateData = RemotingDecoder.GetApplicationPrivateData(receivedData.Data);171 Dbg.Assert(ApplicationPrivateDataReceived != null,172 "RemoteRunspacePoolInternal should subscribe to all data structure handler events");173 ApplicationPrivateDataReceived.SafeInvoke(this,174 new RemoteDataEventArgs<PSPrimitiveDictionary>(applicationPrivateData));175 }176 177 break;178 179 case RemotingDataType.RunspacePoolOperationResponse:180 {181 Dbg.Assert(SetMaxMinRunspacesResponseReceived != null,182 "RemoteRunspacePoolInternal should subscribe to all data structure handler events");183 184 SetMaxMinRunspacesResponseReceived.SafeInvoke(this, new RemoteDataEventArgs<PSObject>(receivedData.Data));185 }186 187 break;188 189 case RemotingDataType.PSEventArgs:190 {191 PSEventArgs psEventArgs = RemotingDecoder.GetPSEventArgs(receivedData.Data);192 193 Dbg.Assert(PSEventArgsReceived != null,194 "RemoteRunspacePoolInternal should subscribe to all data structure handler events");195 196 PSEventArgsReceived.SafeInvoke(this, new RemoteDataEventArgs<PSEventArgs>(psEventArgs));197 }198 199 break;200 }201 }202 203 /// <summary>204 /// Creates a PowerShell data structure handler instance associated205 /// with this runspace pool data structure handler.206 /// </summary>207 /// <param name="shell">Associated powershell.</param>208 /// <returns>PowerShell data structure handler object.</returns>209 internal ClientPowerShellDataStructureHandler CreatePowerShellDataStructureHandler(210 ClientRemotePowerShell shell)211 {212 BaseClientCommandTransportManager clientTransportMgr =213 RemoteSession.SessionDataStructureHandler.CreateClientCommandTransportManager(shell, shell.NoInput);214 215 return new ClientPowerShellDataStructureHandler(216 clientTransportMgr, _clientRunspacePoolId, shell.InstanceId);217 }218 219 /// <summary>220 /// Creates a PowerShell instances on the server, associates it221 /// with this runspace pool and invokes.222 /// </summary>223 /// <param name="shell">The client remote powershell.</param>224 internal void CreatePowerShellOnServerAndInvoke(ClientRemotePowerShell shell)225 {226 // add to associated powershell list and send request to server227 lock (_associationSyncObject)228 {229 _associatedPowerShellDSHandlers.Add(shell.InstanceId, shell.DataStructureHandler);230 }231 232 shell.DataStructureHandler.RemoveAssociation += HandleRemoveAssociation;233 234 // Find out if this is an invoke and disconnect operation and if so whether the endpoint235 // supports disconnect. Throw exception if disconnect is not supported.236 bool invokeAndDisconnect = shell.Settings != null && shell.Settings.InvokeAndDisconnect;237 if (invokeAndDisconnect && !EndpointSupportsDisconnect)238 {239 throw new PSRemotingDataStructureException(RemotingErrorIdStrings.EndpointDoesNotSupportDisconnect);240 }241 242 if (RemoteSession == null)243 {244 throw new ObjectDisposedException("ClientRunspacePoolDataStructureHandler");245 }246 247 shell.DataStructureHandler.Start(RemoteSession.SessionDataStructureHandler.StateMachine, invokeAndDisconnect);248 }249 250 /// <summary>251 /// Add a ClientPowerShellDataStructureHandler to association list.252 /// </summary>253 /// <param name="psShellInstanceId">PowerShell Instance Id.</param>254 /// <param name="psDSHandler">ClientPowerShellDataStructureHandler for PowerShell.</param>255 internal void AddRemotePowerShellDSHandler(Guid psShellInstanceId, ClientPowerShellDataStructureHandler psDSHandler)256 {257 lock (_associationSyncObject)258 {259 // Remove old DSHandler and replace with new.260 _associatedPowerShellDSHandlers[psShellInstanceId] = psDSHandler;261 }262 263 psDSHandler.RemoveAssociation += HandleRemoveAssociation;264 }265 266 /// <summary>267 /// Dispatch the message to the associated powershell data structure handler.268 /// </summary>269 /// <param name="rcvdData">Message received.</param>270 internal void DispatchMessageToPowerShell(RemoteDataObject<PSObject> rcvdData)271 {272 ClientPowerShellDataStructureHandler dsHandler =273 GetAssociatedPowerShellDataStructureHandler(rcvdData.PowerShellId);274 275 // if a data structure handler does not exist it means276 // the association has been removed -277 // discard messages278 dsHandler?.ProcessReceivedData(rcvdData);279 }280 281 /// <summary>282 /// Send the host response to the server.283 /// </summary>284 /// <param name="hostResponse">Host response object to send.</param>285 internal void SendHostResponseToServer(RemoteHostResponse hostResponse)286 {287 SendDataAsync(hostResponse.Encode(), DataPriorityType.PromptResponse);288 }289 290 /// <summary>291 /// Send a message to the server instructing it to reset its runspace state.292 /// </summary>293 /// <param name="callId">Caller Id.</param>294 internal void SendResetRunspaceStateToServer(long callId)295 {296 RemoteDataObject message =297 RemotingEncoder.GenerateResetRunspaceState(_clientRunspacePoolId, callId);298 299 SendDataAsync(message);300 }301 302 /// <summary>303 /// Sent a message to modify the max runspaces of the runspace pool.304 /// </summary>305 /// <param name="maxRunspaces">New maxrunspaces to set.</param>306 /// <param name="callId">call id on which the calling method will307 /// be blocked on</param>308 internal void SendSetMaxRunspacesToServer(int maxRunspaces, long callId)309 {310 RemoteDataObject message =311 RemotingEncoder.GenerateSetMaxRunspaces(_clientRunspacePoolId, maxRunspaces, callId);312 313 SendDataAsync(message);314 }315 316 /// <summary>317 /// Send a message to modify the min runspaces of the runspace pool.318 /// </summary>319 /// <param name="minRunspaces">New minrunspaces to set.</param>320 /// <param name="callId">call id on which the calling method will321 /// be blocked on</param>322 internal void SendSetMinRunspacesToServer(int minRunspaces, long callId)323 {324 RemoteDataObject message =325 RemotingEncoder.GenerateSetMinRunspaces(_clientRunspacePoolId, minRunspaces, callId);326 327 SendDataAsync(message);328 }329 330 /// <summary>331 /// Send a message to get the available runspaces from the server.332 /// </summary>333 /// <param name="callId">call id on which the calling method will334 /// be blocked on</param>335 internal void SendGetAvailableRunspacesToServer(long callId)336 {337 SendDataAsync(RemotingEncoder.GenerateGetAvailableRunspaces(_clientRunspacePoolId, callId));338 }339 340 #endregion Data Structure Handler Methods341 342 #region Data Structure Handler events343 344 /// <summary>345 /// Event raised when a host call is received.346 /// </summary>347 internal event EventHandler<RemoteDataEventArgs<RemoteHostCall>> RemoteHostCallReceived;348 349 /// <summary>350 /// Event raised when state information is received.351 /// </summary>352 internal event EventHandler<RemoteDataEventArgs<RunspacePoolStateInfo>> StateInfoReceived;353 354 /// <summary>355 /// Event raised when RunspacePoolInitInfo is received. This is the first runspace pool message expected356 /// after connecting to an existing remote runspace pool. RemoteRunspacePoolInternal should use this357 /// notification to set the state of a reconstructed runspace to "Opened State" and use the358 /// minRunspace and MaxRunspaces information to set its state.359 /// </summary>360 internal event EventHandler<RemoteDataEventArgs<RunspacePoolInitInfo>> RSPoolInitInfoReceived;361 362 /// <summary>363 /// Event raised when application private data is received.364 /// </summary>365 internal event EventHandler<RemoteDataEventArgs<PSPrimitiveDictionary>> ApplicationPrivateDataReceived;366 367 /// <summary>368 /// Event raised when a PSEventArgs is received.369 /// </summary>370 internal event EventHandler<RemoteDataEventArgs<PSEventArgs>> PSEventArgsReceived;371 372 /// <summary>373 /// Event raised when the session is closed.374 /// </summary>375 internal event EventHandler<RemoteDataEventArgs<Exception>> SessionClosed;376 377 /// <summary>378 /// </summary>379 internal event EventHandler<RemoteDataEventArgs<Exception>> SessionDisconnected;380 381 /// <summary>382 /// </summary>383 internal event EventHandler<RemoteDataEventArgs<Exception>> SessionReconnected;384 385 /// <summary>386 /// Event raised when the session is closing.387 /// </summary>388 internal event EventHandler<RemoteDataEventArgs<Exception>> SessionClosing;389 390 /// <summary>391 /// Event raised when a response to a SetMaxRunspaces or SetMinRunspaces call392 /// is received.393 /// </summary>394 internal event EventHandler<RemoteDataEventArgs<PSObject>> SetMaxMinRunspacesResponseReceived;395 396 /// <summary>397 /// EventHandler used to report connection URI redirections to the application.398 /// </summary>399 internal event EventHandler<RemoteDataEventArgs<Uri>> URIRedirectionReported;400 401 /// <summary>402 /// Indicates that a disconnect has been initiated by the WinRM robust connections layer.403 /// </summary>404 internal event EventHandler<RemoteDataEventArgs<Exception>> SessionRCDisconnecting;405 406 /// <summary>407 /// Notification that session creation has completed.408 /// </summary>409 internal event EventHandler<CreateCompleteEventArgs> SessionCreateCompleted;410 411 #endregion Data Structure Handler events412 413 #region Private Methods414 415 /// <summary>416 /// Send the data specified as a RemoteDataObject asynchronously417 /// to the runspace pool on the remote session.418 /// </summary>419 /// <param name="data">Data to send.</param>420 /// <remarks>This overload takes a RemoteDataObject and should be421 /// the one used within the code</remarks>422 private void SendDataAsync(RemoteDataObject data)423 {424 _transportManager.DataToBeSentCollection.Add<object>(data);425 }426 427 /// <summary>428 /// Send the data asynchronously to runspace pool driver on remote429 /// session with the specified priority.430 /// </summary>431 /// <typeparam name="T"></typeparam>432 /// <param name="data">Data to be sent to server.</param>433 /// <param name="priority">Priority with which to send data.</param>434 internal void SendDataAsync<T>(RemoteDataObject<T> data, DataPriorityType priority)435 {436 _transportManager.DataToBeSentCollection.Add<T>(data, priority);437 }438 439 /// <summary>440 /// Send the data asynchronously to runspace pool driver on remote441 /// session with the specified priority.442 /// </summary>443 /// <param name="data">Data object to send.</param>444 /// <param name="priority">Priority with which to send data.</param>445 internal void SendDataAsync(PSObject data, DataPriorityType priority)446 {447 RemoteDataObject<PSObject> dataToBeSent = RemoteDataObject<PSObject>.CreateFrom(RemotingDestination.Server,448 RemotingDataType.InvalidDataType, _clientRunspacePoolId, Guid.Empty, data);449 450 _transportManager.DataToBeSentCollection.Add<PSObject>(dataToBeSent);451 }452 453 /// <summary>454 /// Create a client remote session based on the connection info.455 /// </summary>456 /// <param name="rsPoolInternal">457 /// The RunspacePool object this session should map to.458 /// </param>459 private ClientRemoteSessionImpl CreateClientRemoteSession(460 RemoteRunspacePoolInternal rsPoolInternal)461 {462 ClientRemoteSession.URIDirectionReported uriRedirectionHandler =463 new ClientRemoteSession.URIDirectionReported(HandleURIDirectionReported);464 return new ClientRemoteSessionImpl(rsPoolInternal,465 uriRedirectionHandler);466 }467 468 /// <summary>469 /// Handler for handling all session events.470 /// </summary>471 /// <param name="sender">Sender of this event.</param>472 /// <param name="e">Object describing this event.</param>473 private void HandleClientRemoteSessionStateChanged(474 object sender, RemoteSessionStateEventArgs e)475 {476 // send create runspace request while sending negotiation packet. This will477 // save 1 network call to create a runspace on the server.478 if (e.SessionStateInfo.State == RemoteSessionState.NegotiationSending)479 {480 if (_createRunspaceCalled)481 {482 return;483 }484 485 lock (_syncObject)486 {487 // We are doing this check because Established event488 // is raised more than once489 if (_createRunspaceCalled)490 {491 // TODO: Put an assert here. NegotiationSending cannot492 // occur multiple time in v2 remoting.493 return;494 }495 496 _createRunspaceCalled = true;497 }498 499 // make client's PSVersionTable available to the server using applicationArguments500 PSPrimitiveDictionary argumentsWithVersionTable =501 PSPrimitiveDictionary.CloneAndAddPSVersionTable(_applicationArguments);502 // send a message to the server..503 SendDataAsync(RemotingEncoder.GenerateCreateRunspacePool(504 _clientRunspacePoolId, _minRunspaces, _maxRunspaces, RemoteSession.RemoteRunspacePoolInternal, _host,505 argumentsWithVersionTable));506 }507 508 if (e.SessionStateInfo.State == RemoteSessionState.NegotiationSendingOnConnect)509 {510 // send connect message to the server.511 SendDataAsync(RemotingEncoder.GenerateConnectRunspacePool(512 _clientRunspacePoolId, _minRunspaces, _maxRunspaces));513 }514 else if (e.SessionStateInfo.State == RemoteSessionState.ClosingConnection)515 {516 // use the first reason which caused the error517 Exception reason = _closingReason;518 if (reason == null)519 {520 reason = e.SessionStateInfo.Reason;521 _closingReason = reason;522 }523 524 // close transport managers of the associated commands525 List<ClientPowerShellDataStructureHandler> dsHandlers;526 lock (_associationSyncObject)527 {528 dsHandlers = new List<ClientPowerShellDataStructureHandler>(_associatedPowerShellDSHandlers.Values);529 }530 531 foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers)532 {533 dsHandler.CloseConnectionAsync(_closingReason);534 }535 536 SessionClosing.SafeInvoke(this, new RemoteDataEventArgs<Exception>(reason));537 }538 else if (e.SessionStateInfo.State == RemoteSessionState.Closed)539 {540 // use the first reason which caused the error541 Exception reason = _closingReason;542 if (reason == null)543 {544 reason = e.SessionStateInfo.Reason;545 _closingReason = reason;546 }547 548 // if there is a reason associated, then most likely the549 // runspace pool has broken, so notify accordingly550 if (reason != null)551 {552 NotifyAssociatedPowerShells(new RunspacePoolStateInfo(RunspacePoolState.Broken, reason));553 }554 else555 {556 // notify the associated powershells that this557 // runspace pool has closed558 NotifyAssociatedPowerShells(new RunspacePoolStateInfo(RunspacePoolState.Closed, reason));559 }560 561 SessionClosed.SafeInvoke(this, new RemoteDataEventArgs<Exception>(reason));562 }563 else if (e.SessionStateInfo.State == RemoteSessionState.Connected)564 {565 // write a transfer event here566 PSEtwLog.ReplaceActivityIdForCurrentThread(_clientRunspacePoolId, PSEventId.OperationalTransferEventRunspacePool,567 PSEventId.AnalyticTransferEventRunspacePool, PSKeyword.Runspace, PSTask.CreateRunspace);568 }569 else if (e.SessionStateInfo.State == RemoteSessionState.Disconnected)570 {571 NotifyAssociatedPowerShells(new RunspacePoolStateInfo(572 RunspacePoolState.Disconnected,573 e.SessionStateInfo.Reason));574 SessionDisconnected.SafeInvoke(this, new RemoteDataEventArgs<Exception>(e.SessionStateInfo.Reason));575 }576 else if (_reconnecting && e.SessionStateInfo.State == RemoteSessionState.Established)577 {578 SessionReconnected.SafeInvoke(this, new RemoteDataEventArgs<Exception>(null));579 _reconnecting = false;580 }581 else if (e.SessionStateInfo.State == RemoteSessionState.RCDisconnecting)582 {583 SessionRCDisconnecting.SafeInvoke(this, new RemoteDataEventArgs<Exception>(null));584 }585 else586 {587 if (e.SessionStateInfo.Reason != null)588 {589 _closingReason = e.SessionStateInfo.Reason;590 }591 }592 }593 594 /// <summary>595 /// Session is reporting that URI is getting redirected.596 /// Report this information to the user by writing a warning message.597 /// </summary>598 /// <param name="newURI"></param>599 private void HandleURIDirectionReported(Uri newURI)600 {601 URIRedirectionReported.SafeInvoke(this, new RemoteDataEventArgs<Uri>(newURI));602 }603 604 /// <summary>605 /// Notifies associated powershell's of the runspace pool state change.606 /// </summary>607 /// <param name="stateInfo">state information that need to608 /// be notified</param>609 private void NotifyAssociatedPowerShells(RunspacePoolStateInfo stateInfo)610 {611 List<ClientPowerShellDataStructureHandler> dsHandlers;612 613 if (stateInfo.State == RunspacePoolState.Disconnected)614 {615 lock (_associationSyncObject)616 {617 dsHandlers = new List<ClientPowerShellDataStructureHandler>(_associatedPowerShellDSHandlers.Values);618 }619 620 foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers)621 {622 dsHandler.ProcessDisconnect(stateInfo);623 }624 625 return;626 }627 628 // if the runspace pool is broken or closed then set all629 // associated powershells to stopped630 if (stateInfo.State == RunspacePoolState.Broken || stateInfo.State == RunspacePoolState.Closed)631 {632 lock (_associationSyncObject)633 {634 dsHandlers = new List<ClientPowerShellDataStructureHandler>(_associatedPowerShellDSHandlers.Values);635 _associatedPowerShellDSHandlers.Clear();636 }637 638 if (stateInfo.State == RunspacePoolState.Broken)639 {640 // set the state to failed, outside the lock641 foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers)642 {643 dsHandler.SetStateToFailed(stateInfo.Reason);644 }645 }646 else if (stateInfo.State == RunspacePoolState.Closed)647 {648 foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers)649 {650 dsHandler.SetStateToStopped(stateInfo.Reason);651 }652 }653 654 return;655 }656 }657 658 /// <summary>659 /// Gets the ClientPowerShellDataStructureHandler instance for the specified id.660 /// </summary>661 /// <param name="clientPowerShellId">Id of the client remote powershell.</param>662 /// <returns>ClientPowerShellDataStructureHandler object.</returns>663 private ClientPowerShellDataStructureHandler GetAssociatedPowerShellDataStructureHandler664 (Guid clientPowerShellId)665 {666 ClientPowerShellDataStructureHandler dsHandler = null;667 668 lock (_associationSyncObject)669 {670 bool success = _associatedPowerShellDSHandlers.TryGetValue(clientPowerShellId, out dsHandler);671 672 if (!success)673 {674 dsHandler = null;675 }676 }677 678 return dsHandler;679 }680 681 /// <summary>682 /// Remove the association of the powershell from the runspace pool.683 /// </summary>684 /// <param name="sender">Sender of this event.</param>685 /// <param name="e">Unused.</param>686 private void HandleRemoveAssociation(object sender, EventArgs e)687 {688 Dbg.Assert(sender is ClientPowerShellDataStructureHandler, @"sender of the event689 must be ClientPowerShellDataStructureHandler");690 691 ClientPowerShellDataStructureHandler dsHandler =692 sender as ClientPowerShellDataStructureHandler;693 694 lock (_associationSyncObject)695 {696 _associatedPowerShellDSHandlers.Remove(dsHandler.PowerShellId);697 }698 699 _transportManager.RemoveCommandTransportManager(dsHandler.PowerShellId);700 }701 702 /// <summary>703 /// Calls each running command Transport manager PrepareForDisconnect method.704 /// Each transport manager object will raise an event when the command/transport705 /// is ready to be disconnected. Disconnect will begin when all is ready.706 /// </summary>707 private void PrepareForAndStartDisconnect()708 {709 bool startDisconnectNow;710 711 lock (_associationSyncObject)712 {713 if (_associatedPowerShellDSHandlers.Count == 0)714 {715 // There are no running commands associated with this runspace pool.716 startDisconnectNow = true;717 _preparingForDisconnectList = null;718 }719 else720 {721 // Delay starting the disconnect operation until all running commands are prepared.722 startDisconnectNow = false;723 724 // Create and fill list of active transportmanager objects to be disconnected.725 // Add ready-for-disconnect callback handler to DSHandler transportmanager objects.726 Dbg.Assert(_preparingForDisconnectList == null, "Cannot prepare for disconnect while disconnect is pending.");727 _preparingForDisconnectList = new List<BaseClientCommandTransportManager>();728 foreach (ClientPowerShellDataStructureHandler dsHandler in _associatedPowerShellDSHandlers.Values)729 {730 _preparingForDisconnectList.Add(dsHandler.TransportManager);731 dsHandler.TransportManager.ReadyForDisconnect += HandleReadyForDisconnect;732 }733 }734 }735 736 if (startDisconnectNow)737 {738 // Ok to start on this thread.739 StartDisconnectAsync(RemoteSession);740 }741 else742 {743 // Start preparation for disconnect. The HandleReadyForDisconnect callback will be744 // called when a transportManager is ready for disconnect.745 List<ClientPowerShellDataStructureHandler> dsHandlers;746 lock (_associationSyncObject)747 {748 dsHandlers = new List<ClientPowerShellDataStructureHandler>(_associatedPowerShellDSHandlers.Values);749 }750 751 foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers)752 {753 dsHandler.TransportManager.PrepareForDisconnect();754 }755 }756 }757 758 /// <summary>759 /// Allows each running command to resume processing command input for when760 /// the runspacepool and running commands are connected.761 /// </summary>762 private void PrepareForConnect()763 {764 List<ClientPowerShellDataStructureHandler> dsHandlers;765 lock (_associationSyncObject)766 {767 dsHandlers = new List<ClientPowerShellDataStructureHandler>(_associatedPowerShellDSHandlers.Values);768 }769 770 foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers)771 {772 dsHandler.TransportManager.ReadyForDisconnect -= HandleReadyForDisconnect;773 dsHandler.TransportManager.PrepareForConnect();774 }775 }776 777 /// <summary>778 /// Handler of the transport ReadyForDisconnect event. When all command779 /// transports are ready for disconnect we can start the disconnect process.780 /// </summary>781 /// <param name="sender"></param>782 /// <param name="args"></param>783 private void HandleReadyForDisconnect(object sender, EventArgs args)784 {785 if (sender == null)786 {787 return;788 }789 790 BaseClientCommandTransportManager bcmdTM = (BaseClientCommandTransportManager)sender;791 792 lock (_associationSyncObject)793 {794 // Ignore extra event calls after disconnect is started.795 if (_preparingForDisconnectList == null)796 {797 return;798 }799 800 _preparingForDisconnectList.Remove(bcmdTM);801 802 if (_preparingForDisconnectList.Count == 0)803 {804 _preparingForDisconnectList = null;805 806 // Start the asynchronous disconnect on a worker thread because we don't know807 // what thread this callback is made from. If it was made from a transport808 // callback event then a deadlock may occur when DisconnectAsync is called on809 // that same thread.810 ThreadPool.QueueUserWorkItem(new WaitCallback(StartDisconnectAsync));811 }812 }813 }814 815 /// <summary>816 /// WaitCallback method to start an asynchronous disconnect.817 /// </summary>818 /// <param name="state"></param>819 private void StartDisconnectAsync(object state)820 {821 var remoteSession = RemoteSession;822 try823 {824 remoteSession?.DisconnectAsync();825 }826 catch827 {828 // remoteSession may have already been disposed resulting in unexpected exceptions.829 }830 }831 832 /// <summary>833 /// Forwards robust connection notifications to associated PowerShell clients.834 /// </summary>835 /// <param name="sender"></param>836 /// <param name="e"></param>837 private void HandleRobustConnectionNotification(838 object sender,839 ConnectionStatusEventArgs e)840 {841 List<ClientPowerShellDataStructureHandler> dsHandlers;842 lock (_associationSyncObject)843 {844 dsHandlers = new List<ClientPowerShellDataStructureHandler>(_associatedPowerShellDSHandlers.Values);845 }846 847 foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers)848 {849 dsHandler.ProcessRobustConnectionNotification(e);850 }851 }852 853 /// <summary>854 /// Forwards the session create completion event.855 /// </summary>856 /// <param name="sender">Transport sender.</param>857 /// <param name="eventArgs">CreateCompleteEventArgs.</param>858 private void HandleSessionCreateCompleted(object sender, CreateCompleteEventArgs eventArgs)859 {860 SessionCreateCompleted.SafeInvoke<CreateCompleteEventArgs>(this, eventArgs);861 }862 863 #endregion Private Methods864 865 #region Private Members866 867 private readonly Guid _clientRunspacePoolId;868 private readonly object _syncObject = new object();869 private bool _createRunspaceCalled = false;870 private Exception _closingReason;871 private readonly int _minRunspaces;872 private readonly int _maxRunspaces;873 private readonly PSHost _host;874 private readonly PSPrimitiveDictionary _applicationArguments;875 876 private readonly Dictionary<Guid, ClientPowerShellDataStructureHandler> _associatedPowerShellDSHandlers877 = new Dictionary<Guid, ClientPowerShellDataStructureHandler>();878 879 // data structure handlers of all ClientRemotePowerShell which are880 // associated with this runspace pool881 private readonly object _associationSyncObject = new object();882 // object to synchronize operations to above883 private readonly BaseClientSessionTransportManager _transportManager;884 // session transport manager associated with this runspace885 886 private List<BaseClientCommandTransportManager> _preparingForDisconnectList;887 888 #endregion Private Members889 890 #region Internal Properties891 892 /// <summary>893 /// The remote session associated with this runspace pool894 /// data structure handler.895 /// </summary>896 internal ClientRemoteSession RemoteSession { get; private set; }897 898 /// <summary>899 /// Transport manager used by this data structure handler.900 /// </summary>901 internal BaseClientSessionTransportManager TransportManager902 {903 get904 {905 if (RemoteSession != null)906 {907 return RemoteSession.SessionDataStructureHandler.TransportManager;908 }909 else910 {911 return null;912 }913 }914 }915 916 /// <summary>917 /// Returns robust connection maximum retry time in milliseconds, if supported918 /// by underlying transport manager.919 /// </summary>920 internal int MaxRetryConnectionTime921 {922 get923 {924 if (_transportManager != null &&925 _transportManager is WSManClientSessionTransportManager)926 {927 return ((WSManClientSessionTransportManager)(_transportManager)).MaxRetryConnectionTime;928 }929 930 return 0;931 }932 }933 934 /// <summary>935 /// Indicates whether the currently connected runspace endpoint supports936 /// disconnect/connect semantics.937 /// </summary>938 internal bool EndpointSupportsDisconnect939 {940 get941 {942 WSManClientSessionTransportManager wsmanTransportManager = _transportManager as WSManClientSessionTransportManager;943 return wsmanTransportManager != null && wsmanTransportManager.SupportsDisconnect;944 }945 }946 947 #endregion Internal Properties948 949 #region IDisposable950 951 /// <summary>952 /// Public interface for dispose.953 /// </summary>954 public void Dispose()955 {956 Dispose(true);957 958 GC.SuppressFinalize(this);959 }960 961 /// <summary>962 /// Release all resources.963 /// </summary>964 /// <param name="disposing">If true, release all managed resources.</param>965 public void Dispose(bool disposing)966 {967 if (disposing)968 {969 if (RemoteSession != null)970 {971 ((ClientRemoteSessionImpl)RemoteSession).Dispose();972 RemoteSession = null;973 }974 }975 }976 977 #endregion IDisposable978 }979 980 /// <summary>981 /// Base class for ClientPowerShellDataStructureHandler to handle all982 /// references.983 /// </summary>984 internal sealed class ClientPowerShellDataStructureHandler985 {986 #region Data Structure Handler events987 988 /// <summary>989 /// This event is raised when the state of associated990 /// powershell is terminal and the runspace pool has991 /// to detach the association.992 /// </summary>993 internal event EventHandler RemoveAssociation;994 995 /// <summary>996 /// This event is raised when a state information object997 /// is received from the server.998 /// </summary>999 internal event EventHandler<RemoteDataEventArgs<PSInvocationStateInfo>> InvocationStateInfoReceived;1000 1001 /// <summary>1002 /// This event is raised when an output object is received1003 /// from the server.1004 /// </summary>1005 internal event EventHandler<RemoteDataEventArgs<object>> OutputReceived;1006 1007 /// <summary>1008 /// This event is raised when an error record is received1009 /// from the server.1010 /// </summary>1011 internal event EventHandler<RemoteDataEventArgs<ErrorRecord>> ErrorReceived;1012 1013 /// <summary>1014 /// This event is raised when an informational message -1015 /// debug, verbose, warning, progress is received from1016 /// the server.1017 /// </summary>1018 internal event EventHandler<RemoteDataEventArgs<InformationalMessage>> InformationalMessageReceived;1019 1020 /// <summary>1021 /// This event is raised when a host call is targeted to the1022 /// powershell.1023 /// </summary>1024 internal event EventHandler<RemoteDataEventArgs<RemoteHostCall>> HostCallReceived;1025 1026 /// <summary>1027 /// This event is raised when a runspace pool data structure handler notifies an1028 /// associated powershell data structure handler that its closed.1029 /// </summary>1030 internal event EventHandler<RemoteDataEventArgs<Exception>> ClosedNotificationFromRunspacePool;1031 1032 /// <summary>1033 /// Event that is raised when a remote connection is successfully closed. The event is raised1034 /// from a WSMan transport thread. Since this thread can hold on to a HTTP1035 /// connection, the event handler should complete processing as fast as possible.1036 /// Importantly the event handler should not generate any call that results in a1037 /// user request like host.ReadLine().1038 ///1039 /// Errors (occurred during connection attempt) are reported through WSManTransportErrorOccured1040 /// event.1041 /// </summary>1042 /// <remarks>1043 /// The eventhandler should make sure not to throw any exceptions.1044 /// </remarks>1045 internal event EventHandler<EventArgs> CloseCompleted;1046 1047 /// <summary>1048 /// This event is raised when a runspace pool data structure handler notifies an1049 /// associated powershell data structure handler that its broken.1050 /// </summary>1051 internal event EventHandler<RemoteDataEventArgs<Exception>> BrokenNotificationFromRunspacePool;1052 1053 /// <summary>1054 /// This event is raised when reconnect async operation on the associated powershell/pipeline instance is completed.1055 /// </summary>1056 internal event EventHandler<RemoteDataEventArgs<Exception>> ReconnectCompleted;1057 1058 /// <summary>1059 /// This event is raised when connect async operation on the associated powershell/pipeline instance is completed.1060 /// </summary>1061 internal event EventHandler<RemoteDataEventArgs<Exception>> ConnectCompleted;1062 1063 /// <summary>1064 /// This event is raised when a Robust Connection layer notification is available.1065 /// </summary>1066 internal event EventHandler<ConnectionStatusEventArgs> RobustConnectionNotification;1067 1068 #endregion Data Structure Handler events1069 1070 #region Data Structure Handler Methods1071 1072 /// <summary>1073 /// Start the command operation.1074 /// </summary>1075 internal void Start(ClientRemoteSessionDSHandlerStateMachine stateMachine, bool inDisconnectMode)1076 {1077 // Add all callbacks to transport manager.1078 SetupTransportManager(inDisconnectMode);1079 TransportManager.CreateAsync();1080 }1081 1082 private void HandleDelayStreamRequestProcessed(object sender, EventArgs e)1083 {1084 // client's request to start pipeline in disconnected mode has been successfully processed1085 ProcessDisconnect(null);1086 }1087 1088 internal void HandleReconnectCompleted(object sender, EventArgs args)1089 {1090 int currentState = Interlocked.CompareExchange(ref _connectionState, (int)connectionStates.Connected, (int)connectionStates.Reconnecting);1091 1092 ReconnectCompleted.SafeInvoke(this, new RemoteDataEventArgs<Exception>(null));1093 return;1094 }1095 1096 internal void HandleConnectCompleted(object sender, EventArgs args)1097 {1098 int currentState = Interlocked.CompareExchange(ref _connectionState, (int)connectionStates.Connected, (int)connectionStates.Connecting);1099 1100 ConnectCompleted.SafeInvoke(this, new RemoteDataEventArgs<Exception>(null));1101 return;1102 }1103 1104 /// <summary>1105 /// Handler which handles transport errors.1106 /// </summary>1107 /// <param name="sender"></param>1108 /// <param name="e"></param>1109 internal void HandleTransportError(object sender, TransportErrorOccuredEventArgs e)1110 {1111 // notify associated powershell about the error and close transport manager1112 PSInvocationStateInfo stateInfo = new PSInvocationStateInfo(PSInvocationState.Failed, e.Exception);1113 InvocationStateInfoReceived.SafeInvoke(this, new RemoteDataEventArgs<PSInvocationStateInfo>(stateInfo));1114 1115 // The handler to InvocationStateInfoReceived would have already1116 // closed the connection. No need to do it here again1117 }1118 1119 /// <summary>1120 /// Send a stop powershell message to the server.1121 /// </summary>1122 internal void SendStopPowerShellMessage()1123 {1124 TransportManager.CryptoHelper.CompleteKeyExchange();1125 TransportManager.SendStopSignal();1126 }1127 1128 /// <summary>1129 /// Event that gets raised when stop signal is completed.1130 /// </summary>1131 /// <param name="sender"></param>1132 /// <param name="e"></param>1133 private void OnSignalCompleted(object sender, EventArgs e)1134 {1135 // Raise stopped event locally...By the time this event1136 // is raised, the remote server would have sent state changed info.1137 // A bad server may not send appropriate sate info, in which case we1138 // fail safely1139 PSRemotingDataStructureException exception = new PSRemotingDataStructureException(1140 RemotingErrorIdStrings.PipelineStopped);1141 InvocationStateInfoReceived.SafeInvoke(this,1142 new RemoteDataEventArgs<PSInvocationStateInfo>(1143 new PSInvocationStateInfo(PSInvocationState.Stopped, exception)));1144 }1145 1146 /// <summary>1147 /// Send the host response to the server.1148 /// </summary>1149 /// <param name="hostResponse">Host response to send.</param>1150 internal void SendHostResponseToServer(RemoteHostResponse hostResponse)1151 {1152 RemoteDataObject<PSObject> dataToBeSent =1153 RemoteDataObject<PSObject>.CreateFrom(RemotingDestination.Server,1154 RemotingDataType.RemotePowerShellHostResponseData,1155 _clientRunspacePoolId,1156 _clientPowerShellId,1157 hostResponse.Encode());1158 1159 TransportManager.DataToBeSentCollection.Add<PSObject>(dataToBeSent,1160 DataPriorityType.PromptResponse);1161 }1162 1163 /// <summary>1164 /// Attach the specified data collection as input1165 /// to the remote powershell.1166 /// </summary>1167 /// <param name="inputstream"></param>1168 internal void SendInput(ObjectStreamBase inputstream)1169 {1170 if (!inputstream.IsOpen && inputstream.Count == 0)1171 {1172 // there is no input, send an end of input1173 // message1174 lock (_inputSyncObject)1175 {1176 // send input closed information to server1177 SendDataAsync(RemotingEncoder.GeneratePowerShellInputEnd(1178 _clientRunspacePoolId, _clientPowerShellId));1179 }1180 }1181 else1182 {1183 // its possible that in client input data is written in a thread1184 // other than the current thread. Since we want to write input1185 // to the server in the order in which it was received, this1186 // operation of writing to the server need to be synced1187 // Also we need to ensure that all the data currently available1188 // for enumeration are written out before any newly added data1189 // is written. Hence the lock is made even before the handler is1190 // registered1191 lock (_inputSyncObject)1192 {1193 inputstream.DataReady += HandleInputDataReady;1194 WriteInput(inputstream);1195 }1196 }1197 }1198 1199 /// <summary>1200 /// Process the data received from the runspace pool