/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Management.Automation.Remoting.Client; using System.Management.Automation.Host; using System.Threading; using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands { /// /// This cmdlet connects PS sessions (RemoteRunspaces) that are in the Disconnected /// state and returns those PS session objects in the Opened state. One or more /// session objects can be specified for connecting, or a remote computer name can /// be specified and in this case all disconnected remote runspaces found on the /// remote computer will be be connected and PSSession objects created on the local /// machine. /// /// The cmdlet can be used in the following ways: /// /// Connect a PS session object: /// > $session = New-PSSession serverName /// > Disconnect-PSSession $session /// > Connect-PSSession $session /// /// Connect a PS session by name: /// > Connect-PSSession $session.Name /// /// Connect a PS session by Id: /// > Connect-PSSession $session.Id /// /// Connect a collection of PS session: /// > Get-PSSession | Connect-PSSession /// /// Connect all disconnected PS sessions on a remote computer /// > Connect-PSSession serverName /// /// [SuppressMessage("Microsoft.PowerShell", "PS1012:CallShouldProcessOnlyIfDeclaringSupport")] [Cmdlet(VerbsCommunications.Connect, "PSSession", SupportsShouldProcess = true, DefaultParameterSetName = ConnectPSSessionCommand.NameParameterSet, HelpUri = "http://go.microsoft.com/fwlink/?LinkID=210604", RemotingCapability = RemotingCapability.OwnedByCommand)] [OutputType(typeof(PSSession))] public class ConnectPSSessionCommand : PSRunspaceCmdlet, IDisposable { #region Parameters private const string ComputerNameGuidParameterSet = "ComputerNameGuid"; private const string ConnectionUriParameterSet = "ConnectionUri"; private const string ConnectionUriGuidParameterSet = "ConnectionUriGuid"; /// /// The PSSession object or objects to be connected. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ValueFromPipeline = true, ParameterSetName = ConnectPSSessionCommand.SessionParameterSet)] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public PSSession[] Session { get; set; } /// /// Computer names to connect to. /// [Parameter(Position = 0, ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet, Mandatory = true)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet, Mandatory = true)] [ValidateNotNullOrEmpty] [Alias("Cn")] public override String[] ComputerName { get; set; } /// /// This parameters specifies the appname which identifies the connection /// end point on the remote machine. If this parameter is not specified /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If thats /// not specified as well, then "WSMAN" will be used /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] public String ApplicationName { get { return _appName; } set { _appName = ResolveAppName(value); } } private String _appName; /// /// If this parameter is not specified then the value specified in /// the environment variable DEFAULTREMOTESHELLNAME will be used. If /// this is not set as well, then Microsoft.PowerShell is used. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ConnectPSSessionCommand.ConnectionUriParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] public String ConfigurationName { get { return _shell; } set { _shell = ResolveShell(value); } } private String _shell; /// /// A complete URI(s) specified for the remote computer and shell to /// connect to and create a runspace for. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = ConnectPSSessionCommand.ConnectionUriParameterSet)] [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] [ValidateNotNullOrEmpty] [Alias("URI", "CU")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public Uri[] ConnectionUri { get; set; } /// /// The AllowRedirection parameter enables the implicit redirection functionality. /// [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] public SwitchParameter AllowRedirection { get { return _allowRedirection; } set { _allowRedirection = value; } } private bool _allowRedirection = false; /// /// RemoteRunspaceId to retrieve corresponding PSSession /// object /// [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet, Mandatory = true)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet, Mandatory = true)] [Parameter(ParameterSetName = ConnectPSSessionCommand.InstanceIdParameterSet, Mandatory = true)] [ValidateNotNull] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public override Guid[] InstanceId { get { return base.InstanceId; } set { base.InstanceId = value; } } /// /// Name of the remote runspaceinfo object /// [Parameter(ParameterSetName = ConnectPSSessionCommand.NameParameterSet, Mandatory = true)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriParameterSet)] [ValidateNotNullOrEmpty] public override String[] Name { get { return base.Name; } set { base.Name = value; } } /// /// Specifies the credentials of the user to impersonate in the /// remote machine. If this parameter is not specified then the /// credentials of the current user process will be assumed. /// [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] [Credential()] public PSCredential Credential { get { return _psCredential; } set { _psCredential = value; PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } private PSCredential _psCredential; /// /// Use basic authentication to authenticate the user. /// [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] public AuthenticationMechanism Authentication { get { return _authentication; } set { _authentication = value; PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } private AuthenticationMechanism _authentication; /// /// Specifies the certificate thumbprint to be used to impersonate the user on the /// remote machine. /// [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] public string CertificateThumbprint { get { return _thumbprint; } set { _thumbprint = value; PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } private string _thumbprint; /// /// Port specifies the alternate port to be used in case the /// default ports are not used for the transport mechanism /// (port 80 for http and port 443 for useSSL) /// /// /// Currently this is being accepted as a parameter. But in future /// support will be added to make this a part of a policy setting. /// When a policy setting is in place this parameter can be used /// to override the policy setting /// [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] [ValidateRange((Int32)1, (Int32)UInt16.MaxValue)] public Int32 Port { get; set; } /// /// This parameter suggests that the transport scheme to be used for /// remote connections is useSSL instead of the default http.Since /// there are only two possible transport schemes that are possible /// at this point, a SwitchParameter is being used to switch between /// the two. /// [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SSL")] public SwitchParameter UseSSL { get; set; } /// /// Extended session options. Used in this cmdlet to set server disconnect options. /// [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] public PSSessionOption SessionOption { get; set; } /// /// Allows the user of the cmdlet to specify a throttling value /// for throttling the number of remote operations that can /// be executed simultaneously. /// [Parameter(ParameterSetName = ConnectPSSessionCommand.SessionParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.IdParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.NameParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.InstanceIdParameterSet)] public Int32 ThrottleLimit { get; set; } = 0; /// /// Overriding to suppress this parameter /// public override string[] ContainerId { get { return null; } } /// /// Overriding to suppress this parameter /// public override Guid[] VMId { get { return null; } } /// /// Overriding to suppress this parameter /// public override string[] VMName { get { return null; } } #endregion #region Cmdlet Overrides /// /// Set up the ThrottleManager for runspace connect processing. /// protected override void BeginProcessing() { base.BeginProcessing(); _throttleManager.ThrottleLimit = ThrottleLimit; _throttleManager.ThrottleComplete += new EventHandler(HandleThrottleConnectComplete); } /// /// Perform runspace connect processing on all input. /// protected override void ProcessRecord() { Collection psSessions; try { if (ParameterSetName == ConnectPSSessionCommand.ComputerNameParameterSet || ParameterSetName == ConnectPSSessionCommand.ComputerNameGuidParameterSet || ParameterSetName == ConnectPSSessionCommand.ConnectionUriParameterSet || ParameterSetName == ConnectPSSessionCommand.ConnectionUriGuidParameterSet) { // Query remote computers for disconnected sessions. psSessions = QueryForDisconnectedSessions(); } else { // Collect provided disconnected sessions. psSessions = CollectDisconnectedSessions(); } } catch (PSRemotingDataStructureException) { // Allow cmdlet to end and then re-throw exception. _operationsComplete.Set(); throw; } catch (PSRemotingTransportException) { // Allow cmdlet to end and then re-throw exception. _operationsComplete.Set(); throw; } catch (RemoteException) { // Allow cmdlet to end and then re-throw exception. _operationsComplete.Set(); throw; } catch (InvalidRunspaceStateException) { // Allow cmdlet to end and then re-throw exception. _operationsComplete.Set(); throw; } ConnectSessions(psSessions); } /// /// End processing clean up. /// protected override void EndProcessing() { _throttleManager.EndSubmitOperations(); // Wait for all connect operations to complete. _operationsComplete.WaitOne(); // If there are failed connect operations due to stale // session state then perform the query retry here. if (_failedSessions.Count > 0) { RetryFailedSessions(); } // Read all objects in the stream pipeline. while (_stream.ObjectReader.Count > 0) { Object streamObject = _stream.ObjectReader.Read(); WriteStreamObject((Action)streamObject); } _stream.ObjectWriter.Close(); // Add all successfully connected sessions to local repository. foreach (PSSession psSession in _allSessions) { if (psSession.Runspace.RunspaceStateInfo.State == RunspaceState.Opened) { // Make sure that this session is included in the PSSession repository. // If it already exists then replace it because we want the latest/connected session in the repository. this.RunspaceRepository.AddOrReplace(psSession); } } } /// /// User has signaled a stop for this cmdlet. /// protected override void StopProcessing() { // Close the output stream for any further writes. _stream.ObjectWriter.Close(); // Stop any remote server queries that may be running. _queryRunspaces.StopAllOperations(); // Signal the ThrottleManager to stop any further // PSSession connect processing. _throttleManager.StopAllOperations(); // Signal the Retry throttle manager in case it is running. _retryThrottleManager.StopAllOperations(); } #endregion #region ConnectRunspaceOperation Class /// /// Throttle class to perform a remoterunspace connect operation. /// private class ConnectRunspaceOperation : IThrottleOperation { private PSSession _session; private PSSession _oldSession; private ObjectStream _writeStream; private Collection _retryList; private PSHost _host; private QueryRunspaces _queryRunspaces; private static object s_LockObject = new object(); internal ConnectRunspaceOperation( PSSession session, ObjectStream stream, PSHost host, QueryRunspaces queryRunspaces, Collection retryList) { _session = session; _writeStream = stream; _host = host; _queryRunspaces = queryRunspaces; _retryList = retryList; _session.Runspace.StateChanged += StateCallBackHandler; } internal override void StartOperation() { bool startedSuccessfully = true; Exception ex = null; try { if (_queryRunspaces != null) { PSSession newSession = QueryForSession(_session); if (newSession != null) { _session.Runspace.StateChanged -= StateCallBackHandler; _oldSession = _session; _session = newSession; _session.Runspace.StateChanged += StateCallBackHandler; _session.Runspace.ConnectAsync(); } else { startedSuccessfully = false; } } else { _session.Runspace.ConnectAsync(); } } catch (PSInvalidOperationException e) { ex = e; } catch (InvalidRunspacePoolStateException e) { ex = e; } catch (RuntimeException e) { ex = e; } if (ex != null) { startedSuccessfully = false; WriteConnectFailed(ex, _session); } if (!startedSuccessfully) { // We are done at this point. Notify throttle manager. _session.Runspace.StateChanged -= StateCallBackHandler; SendStartComplete(); } } internal override void StopOperation() { if (_queryRunspaces != null) { _queryRunspaces.StopAllOperations(); } _session.Runspace.StateChanged -= StateCallBackHandler; SendStopComplete(); } internal override event EventHandler OperationComplete; internal PSSession QueryForSession(PSSession session) { Collection wsManConnectionInfos = new Collection(); wsManConnectionInfos.Add(session.Runspace.ConnectionInfo as WSManConnectionInfo); Exception ex = null; Collection sessions = null; try { sessions = _queryRunspaces.GetDisconnectedSessions(wsManConnectionInfos, _host, _writeStream, null, 0, SessionFilterState.Disconnected, new Guid[] { session.InstanceId }, null, null); } catch (RuntimeException e) { ex = e; } if (ex != null) { WriteConnectFailed(ex, session); return null; } if (sessions.Count != 1) { ex = new RuntimeException(StringUtil.Format(RemotingErrorIdStrings.CannotFindSessionForConnect, session.Name, session.ComputerName)); WriteConnectFailed(ex, session); return null; } return sessions[0]; } private void StateCallBackHandler(object sender, RunspaceStateEventArgs eArgs) { if (eArgs.RunspaceStateInfo.State == RunspaceState.Connecting || eArgs.RunspaceStateInfo.State == RunspaceState.Disconnecting || eArgs.RunspaceStateInfo.State == RunspaceState.Disconnected) { return; } Dbg.Assert(eArgs.RunspaceStateInfo.State != RunspaceState.BeforeOpen, "Can't reconnect a session that hasn't been previously Opened"); Dbg.Assert(eArgs.RunspaceStateInfo.State != RunspaceState.Opening, "Can't reconnect a session that hasn't been previously Opened"); if (eArgs.RunspaceStateInfo.State == RunspaceState.Opened) { // Connect operation succeeded, write the PSSession object. WriteConnectedPSSession(); } else { // Check to see if failure is due to stale PSSession error and // add to retry list if this is the case. bool writeError = true; if (_queryRunspaces == null) { PSRemotingTransportException transportException = eArgs.RunspaceStateInfo.Reason as PSRemotingTransportException; if (transportException != null && transportException.ErrorCode == WSManNativeApi.ERROR_WSMAN_INUSE_CANNOT_RECONNECT) { lock (s_LockObject) { _retryList.Add(_session); } writeError = false; } } if (writeError) { // Connect operation failed, write error. WriteConnectFailed(eArgs.RunspaceStateInfo.Reason, _session); } } _session.Runspace.StateChanged -= StateCallBackHandler; SendStartComplete(); } private void SendStartComplete() { OperationStateEventArgs operationStateEventArgs = new OperationStateEventArgs(); operationStateEventArgs.OperationState = OperationState.StartComplete; OperationComplete.SafeInvoke(this, operationStateEventArgs); } private void SendStopComplete() { OperationStateEventArgs operationStateEventArgs = new OperationStateEventArgs(); operationStateEventArgs.OperationState = OperationState.StopComplete; OperationComplete.SafeInvoke(this, operationStateEventArgs); } private void WriteConnectedPSSession() { // Use temporary variable because we need to preserve _session class variable // for later clean up. PSSession outSession = _session; if (_queryRunspaces != null) { lock (s_LockObject) { // Pass back the original session if possible. if (_oldSession != null && _oldSession.InsertRunspace(_session.Runspace as RemoteRunspace)) { outSession = _oldSession; _retryList.Add(_oldSession); } else { _retryList.Add(_session); } } } if (_writeStream.ObjectWriter.IsOpen) { // This code is based on ThrottleManager infrastructure // and this particular method may be called on a thread that // is different from Pipeline Execution Thread. Hence using // a delegate to perform the WriteObject. Action outputWriter = delegate (Cmdlet cmdlet) { cmdlet.WriteObject(outSession); }; _writeStream.ObjectWriter.Write(outputWriter); } } private void WriteConnectFailed( Exception e, PSSession session) { if (_writeStream.ObjectWriter.IsOpen) { string FQEID = "PSSessionConnectFailed"; Exception reason; if (e != null && !string.IsNullOrEmpty(e.Message)) { // Update fully qualified error Id if we have a transport error. PSRemotingTransportException transportException = e as PSRemotingTransportException; if (transportException != null) { FQEID = WSManTransportManagerUtils.GetFQEIDFromTransportError(transportException.ErrorCode, FQEID); } reason = new RuntimeException( StringUtil.Format(RemotingErrorIdStrings.RunspaceConnectFailedWithMessage, session.Name, e.Message), e); } else { reason = new RuntimeException( StringUtil.Format(RemotingErrorIdStrings.RunspaceConnectFailed, session.Name, session.Runspace.RunspaceStateInfo.State.ToString()), null); } ErrorRecord errorRecord = new ErrorRecord(reason, FQEID, ErrorCategory.InvalidOperation, null); Action errorWriter = delegate (Cmdlet cmdlet) { cmdlet.WriteError(errorRecord); }; _writeStream.ObjectWriter.Write(errorWriter); } } } #endregion #region Private Methods /// /// Enum indicating an override on which parameter is used to filter /// local sessions. /// private enum OverrideParameter { /// /// No override. /// None = 0, /// /// Use the Name parameter as a filter. /// Name = 1, /// /// Use the InstanceId parameter as a filter. /// InstanceId = 2 } /// /// Retrieves a collection of disconnected PSSession objects queried from /// remote computers. /// /// Collection of disconnected PSSession objects. private Collection QueryForDisconnectedSessions() { Collection connectionInfos = GetConnectionObjects(); Collection psSessions = _queryRunspaces.GetDisconnectedSessions(connectionInfos, this.Host, _stream, this.RunspaceRepository, ThrottleLimit, SessionFilterState.Disconnected, this.InstanceId, this.Name, ConfigurationName); // Write any error output from stream object. Collection streamObjects = _stream.ObjectReader.NonBlockingRead(); foreach (object streamObject in streamObjects) { WriteStreamObject((Action)streamObject); } return psSessions; } /// /// Creates a collection of PSSession objects based on cmdlet parameters. /// /// OverrideParameter /// Collection of PSSession objects in disconnected state. private Collection CollectDisconnectedSessions(OverrideParameter overrideParam = OverrideParameter.None) { Collection psSessions = new Collection(); // Get all remote runspaces to disconnect. if (ParameterSetName == DisconnectPSSessionCommand.SessionParameterSet) { if (Session != null) { foreach (PSSession psSession in Session) { psSessions.Add(psSession); } } } else { Dictionary entries = null; switch (overrideParam) { case OverrideParameter.None: entries = GetMatchingRunspaces(false, true); break; case OverrideParameter.Name: entries = GetMatchingRunspacesByName(false, true); break; case OverrideParameter.InstanceId: entries = GetMatchingRunspacesByRunspaceId(false, true); break; } if (entries != null) { foreach (PSSession psSession in entries.Values) { psSessions.Add(psSession); } } } return psSessions; } /// /// Connect all disconnected sessions. /// private void ConnectSessions(Collection psSessions) { List connectOperations = new List(); // Create a disconnect operation for each runspace to disconnect. foreach (PSSession psSession in psSessions) { if (ShouldProcess(psSession.Name, VerbsCommunications.Connect)) { if (psSession.ComputerType != TargetMachineType.RemoteMachine) { // PS session disconnection is not supported for VM/Container sessions. string msg = StringUtil.Format(RemotingErrorIdStrings.RunspaceCannotBeConnectedForVMContainerSession, psSession.Name, psSession.ComputerName, psSession.ComputerType); Exception reason = new PSNotSupportedException(msg); ErrorRecord errorRecord = new ErrorRecord(reason, "CannotConnectVMContainerSession", ErrorCategory.InvalidOperation, psSession); WriteError(errorRecord); } else if (psSession.Runspace.RunspaceStateInfo.State == RunspaceState.Disconnected && psSession.Runspace.RunspaceAvailability == RunspaceAvailability.None) { // Can only connect sessions that are in Disconnected state. // Update session connection information based on cmdlet parameters. UpdateConnectionInfo(psSession.Runspace.ConnectionInfo as WSManConnectionInfo); ConnectRunspaceOperation connectOperation = new ConnectRunspaceOperation( psSession, _stream, this.Host, null, _failedSessions); connectOperations.Add(connectOperation); } else if (psSession.Runspace.RunspaceStateInfo.State != RunspaceState.Opened) { // Write error record if runspace is not already in the Opened state. string msg = StringUtil.Format(RemotingErrorIdStrings.RunspaceCannotBeConnected, psSession.Name); Exception reason = new RuntimeException(msg); ErrorRecord errorRecord = new ErrorRecord(reason, "PSSessionConnectFailed", ErrorCategory.InvalidOperation, psSession); WriteError(errorRecord); } else { // Session is already connected. Write to output. WriteObject(psSession); } } _allSessions.Add(psSession); } if (connectOperations.Count > 0) { // Make sure operations are not set as complete while processing input. _operationsComplete.Reset(); // Submit list of connect operations. _throttleManager.SubmitOperations(connectOperations); // Write any output now. Collection streamObjects = _stream.ObjectReader.NonBlockingRead(); foreach (object streamObject in streamObjects) { WriteStreamObject((Action)streamObject); } } } /// /// Handles the connect throttling complete event from the ThrottleManager. /// /// Sender /// EventArgs private void HandleThrottleConnectComplete(object sender, EventArgs eventArgs) { _operationsComplete.Set(); } private Collection GetConnectionObjects() { Collection connectionInfos = new Collection(); if (ParameterSetName == ConnectPSSessionCommand.ComputerNameParameterSet || ParameterSetName == ConnectPSSessionCommand.ComputerNameGuidParameterSet) { string scheme = UseSSL.IsPresent ? WSManConnectionInfo.HttpsScheme : WSManConnectionInfo.HttpScheme; foreach (string computerName in ComputerName) { WSManConnectionInfo connectionInfo = new WSManConnectionInfo(); connectionInfo.Scheme = scheme; connectionInfo.ComputerName = ResolveComputerName(computerName); connectionInfo.AppName = ApplicationName; connectionInfo.ShellUri = ConfigurationName; connectionInfo.Port = Port; if (CertificateThumbprint != null) { connectionInfo.CertificateThumbprint = CertificateThumbprint; } else { connectionInfo.Credential = Credential; } connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); connectionInfos.Add(connectionInfo); } } else if (ParameterSetName == ConnectPSSessionCommand.ConnectionUriParameterSet || ParameterSetName == ConnectPSSessionCommand.ConnectionUriGuidParameterSet) { foreach (var connectionUri in ConnectionUri) { WSManConnectionInfo connectionInfo = new WSManConnectionInfo(); connectionInfo.ConnectionUri = connectionUri; connectionInfo.ShellUri = ConfigurationName; if (CertificateThumbprint != null) { connectionInfo.CertificateThumbprint = CertificateThumbprint; } else { connectionInfo.Credential = Credential; } connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); connectionInfos.Add(connectionInfo); } } return connectionInfos; } /// /// Updates connection info with the data read from cmdlet's parameters. /// /// private void UpdateConnectionInfo(WSManConnectionInfo connectionInfo) { if (ParameterSetName != ConnectPSSessionCommand.ConnectionUriParameterSet && ParameterSetName != ConnectPSSessionCommand.ConnectionUriGuidParameterSet) { // uri redirection is supported only with URI parmeter set connectionInfo.MaximumConnectionRedirectionCount = 0; } if (!_allowRedirection) { // uri redirection required explicit user consent connectionInfo.MaximumConnectionRedirectionCount = 0; } // Update the connectionInfo object with passed in session options. if (SessionOption != null) { connectionInfo.SetSessionOptions(SessionOption); } } private void RetryFailedSessions() { using (ManualResetEvent retrysComplete = new ManualResetEvent(false)) { Collection connectedSessions = new Collection(); List retryConnectionOperations = new List(); _retryThrottleManager.ThrottleLimit = ThrottleLimit; _retryThrottleManager.ThrottleComplete += (sender, eventArgs) => { try { retrysComplete.Set(); } catch (ObjectDisposedException) { } }; foreach (var session in _failedSessions) { retryConnectionOperations.Add(new ConnectRunspaceOperation( session, _stream, this.Host, new QueryRunspaces(), connectedSessions)); } _retryThrottleManager.SubmitOperations(retryConnectionOperations); _retryThrottleManager.EndSubmitOperations(); retrysComplete.WaitOne(); // Add or replace all successfully connected sessions to the local repository. foreach (var session in connectedSessions) { this.RunspaceRepository.AddOrReplace(session); } } } #endregion #region IDisposable /// /// Dispose method of IDisposable. Gets called in the following cases: /// 1. Pipeline explicitly calls dispose on cmdlets /// 2. Called by the garbage collector /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Internal dispose method which does the actual /// dispose operations and finalize suppressions /// /// Whether method is called /// from Dispose or destructor private void Dispose(bool disposing) { if (disposing) { _throttleManager.Dispose(); _operationsComplete.WaitOne(); _operationsComplete.Dispose(); _throttleManager.ThrottleComplete -= new EventHandler(HandleThrottleConnectComplete); _retryThrottleManager.Dispose(); _stream.Dispose(); } } #endregion #region Private Members // Collection of PSSessions to be connected. private Collection _allSessions = new Collection(); // Object used to perform network disconnect operations in a limited manner. private ThrottleManager _throttleManager = new ThrottleManager(); // Event indicating that all disconnect operations through the ThrottleManager // are complete. private ManualResetEvent _operationsComplete = new ManualResetEvent(true); // Object used for querying remote runspaces. private QueryRunspaces _queryRunspaces = new QueryRunspaces(); // Object to collect output data from multiple threads. private ObjectStream _stream = new ObjectStream(); // Support for connection retry on failure. private ThrottleManager _retryThrottleManager = new ThrottleManager(); private Collection _failedSessions = new Collection(); #endregion } #region QueryRunspaces internal class QueryRunspaces { #region Constructor internal QueryRunspaces() { _stopProcessing = false; } #endregion #region Internal Methods /// /// Queries all remote computers specified in collection of WSManConnectionInfo objects /// and returns disconnected PSSession objects ready for connection to server. /// Returned sessions can be matched to Guids or Names. /// /// Collection of WSManConnectionInfo objects. /// Host for PSSession objects. /// Out stream object. /// Runspace repository. /// Throttle limit. /// Runspace state filter value. /// Array of session Guids to match to. /// Array of session Names to match to. /// Configuration name to match to. /// Collection of disconnected PSSession objects. internal Collection GetDisconnectedSessions(Collection connectionInfos, PSHost host, ObjectStream stream, RunspaceRepository runspaceRepository, int throttleLimit, SessionFilterState filterState, Guid[] matchIds, string[] matchNames, string configurationName) { Collection filteredPSSesions = new Collection(); // Create a query operation for each connection information object. foreach (WSManConnectionInfo connectionInfo in connectionInfos) { Runspace[] runspaces = null; try { runspaces = Runspace.GetRunspaces(connectionInfo, host, BuiltInTypesTable); } catch (System.Management.Automation.RuntimeException e) { if (e.InnerException is InvalidOperationException) { // The Get-WSManInstance cmdlet used to query remote computers for runspaces will throw // an Invalid Operation (inner) exception if the connectInfo object is invalid, including // invalid computer names. // We don't want to propagate the exception so just write error here. if (stream.ObjectWriter != null && stream.ObjectWriter.IsOpen) { int errorCode; string msg = StringUtil.Format(RemotingErrorIdStrings.QueryForRunspacesFailed, connectionInfo.ComputerName, ExtractMessage(e.InnerException, out errorCode)); string FQEID = WSManTransportManagerUtils.GetFQEIDFromTransportError(errorCode, "RemotePSSessionQueryFailed"); Exception reason = new RuntimeException(msg, e.InnerException); ErrorRecord errorRecord = new ErrorRecord(reason, FQEID, ErrorCategory.InvalidOperation, connectionInfo); stream.ObjectWriter.Write((Action)(cmdlet => cmdlet.WriteError(errorRecord))); } } else { throw; } } if (_stopProcessing) { break; } // Add all runspaces meeting filter criteria to collection. if (runspaces != null) { // Convert configuration name into shell Uri for comparison. string shellUri = null; if (!string.IsNullOrEmpty(configurationName)) { shellUri = (configurationName.IndexOf( System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix, StringComparison.OrdinalIgnoreCase) != -1) ? configurationName : System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix + configurationName; } foreach (Runspace runspace in runspaces) { // Filter returned runspaces by ConfigurationName if provided. if (shellUri != null) { // Compare with returned shell Uri in connection info. WSManConnectionInfo wsmanConnectionInfo = runspace.ConnectionInfo as WSManConnectionInfo; if (wsmanConnectionInfo != null && !shellUri.Equals(wsmanConnectionInfo.ShellUri, StringComparison.OrdinalIgnoreCase)) { continue; } } // Check the repository for an existing viable PSSession for // this runspace (based on instanceId). Use the existing // local runspace instead of the one returned from the server // query. PSSession existingPSSession = null; if (runspaceRepository != null) { existingPSSession = runspaceRepository.GetItem(runspace.InstanceId); } if (existingPSSession != null && UseExistingRunspace(existingPSSession.Runspace, runspace)) { if (TestRunspaceState(existingPSSession.Runspace, filterState)) { filteredPSSesions.Add(existingPSSession); } } else if (TestRunspaceState(runspace, filterState)) { filteredPSSesions.Add(new PSSession(runspace as RemoteRunspace)); } } } } // Return only PSSessions that match provided Ids or Names. if ((matchIds != null) && (filteredPSSesions.Count > 0)) { Collection matchIdsSessions = new Collection(); foreach (Guid id in matchIds) { bool matchFound = false; foreach (PSSession psSession in filteredPSSesions) { if (_stopProcessing) { break; } if (psSession.Runspace.InstanceId.Equals(id)) { matchFound = true; matchIdsSessions.Add(psSession); break; } } if (!matchFound && stream.ObjectWriter != null && stream.ObjectWriter.IsOpen) { string msg = StringUtil.Format(RemotingErrorIdStrings.SessionIdMatchFailed, id); Exception reason = new RuntimeException(msg); ErrorRecord errorRecord = new ErrorRecord(reason, "PSSessionIdMatchFail", ErrorCategory.InvalidOperation, id); stream.ObjectWriter.Write((Action)(cmdlet => cmdlet.WriteError(errorRecord))); } } // Return all found sessions. return matchIdsSessions; } else if ((matchNames != null) && (filteredPSSesions.Count > 0)) { Collection matchNamesSessions = new Collection(); foreach (string name in matchNames) { WildcardPattern namePattern = WildcardPattern.Get(name, WildcardOptions.IgnoreCase); bool matchFound = false; foreach (PSSession psSession in filteredPSSesions) { if (_stopProcessing) { break; } if (namePattern.IsMatch(((RemoteRunspace)psSession.Runspace).RunspacePool.RemoteRunspacePoolInternal.Name)) { matchFound = true; matchNamesSessions.Add(psSession); } } if (!matchFound && stream.ObjectWriter != null && stream.ObjectWriter.IsOpen) { string msg = StringUtil.Format(RemotingErrorIdStrings.SessionNameMatchFailed, name); Exception reason = new RuntimeException(msg); ErrorRecord errorRecord = new ErrorRecord(reason, "PSSessionNameMatchFail", ErrorCategory.InvalidOperation, name); stream.ObjectWriter.Write((Action)(cmdlet => cmdlet.WriteError(errorRecord))); } } return matchNamesSessions; } else { // Return all collected sessions. return filteredPSSesions; } } /// /// Returns true if the existing runspace should be returned to the user /// a. If the existing runspace is not broken /// b. If the queried runspace is not connected to a different user. /// /// /// /// private static bool UseExistingRunspace( Runspace existingRunspace, Runspace queriedrunspace) { Dbg.Assert(existingRunspace != null, "Invalid parameter."); Dbg.Assert(queriedrunspace != null, "Invalid parameter."); if (existingRunspace.RunspaceStateInfo.State == RunspaceState.Broken) { return false; } if (existingRunspace.RunspaceStateInfo.State == RunspaceState.Disconnected && queriedrunspace.RunspaceAvailability == RunspaceAvailability.Busy) { return false; } // Update existing runspace to have latest DisconnectedOn/ExpiresOn data. existingRunspace.DisconnectedOn = queriedrunspace.DisconnectedOn; existingRunspace.ExpiresOn = queriedrunspace.ExpiresOn; return true; } /// /// Returns Exception message. If message is WSMan Xml then /// the WSMan message and error code is extracted and returned. /// /// Exception /// Returned WSMan error code /// WSMan message internal static string ExtractMessage( Exception e, out int errorCode) { errorCode = 0; if (e == null || e.Message == null) { return string.Empty; } string rtnMsg = null; try { System.Xml.XmlReaderSettings xmlReaderSettings = InternalDeserializer.XmlReaderSettingsForUntrustedXmlDocument.Clone(); xmlReaderSettings.MaxCharactersInDocument = 4096; xmlReaderSettings.MaxCharactersFromEntities = 1024; xmlReaderSettings.DtdProcessing = System.Xml.DtdProcessing.Prohibit; using (System.Xml.XmlReader reader = System.Xml.XmlReader.Create( new System.IO.StringReader(e.Message), xmlReaderSettings)) { while (reader.Read()) { if (reader.NodeType == System.Xml.XmlNodeType.Element) { if (reader.LocalName.Equals("Message", StringComparison.OrdinalIgnoreCase)) { rtnMsg = reader.ReadElementContentAsString(); } else if (reader.LocalName.Equals("WSManFault", StringComparison.OrdinalIgnoreCase)) { string errorCodeString = reader.GetAttribute("Code"); if (errorCodeString != null) { try { // WinRM returns both signed and unsigned 32 bit string values. Convert to signed 32 bit integer. Int64 eCode = Convert.ToInt64(errorCodeString, System.Globalization.NumberFormatInfo.InvariantInfo); unchecked { errorCode = (int)eCode; } } catch (FormatException) { } catch (OverflowException) { } } } } } } } catch (System.Xml.XmlException) { } return rtnMsg ?? e.Message; } /// /// Discontinue all remote server query operations. /// internal void StopAllOperations() { _stopProcessing = true; } /// /// Compares the runspace filter state with the runspace state. /// /// Runspace object to test. /// Filter state to compare. /// Result of test. public static bool TestRunspaceState(Runspace runspace, SessionFilterState filterState) { bool result; switch (filterState) { case SessionFilterState.All: result = true; break; case SessionFilterState.Opened: result = (runspace.RunspaceStateInfo.State == RunspaceState.Opened); break; case SessionFilterState.Closed: result = (runspace.RunspaceStateInfo.State == RunspaceState.Closed); break; case SessionFilterState.Disconnected: result = (runspace.RunspaceStateInfo.State == RunspaceState.Disconnected); break; case SessionFilterState.Broken: result = (runspace.RunspaceStateInfo.State == RunspaceState.Broken); break; default: Dbg.Assert(false, "Invalid SessionFilterState value."); result = false; break; } return result; } /// /// Returns the default type table for built-in PowerShell types. /// internal static TypeTable BuiltInTypesTable { get { if (s_TypeTable == null) { lock (s_SyncObject) { if (s_TypeTable == null) { s_TypeTable = TypeTable.LoadDefaultTypeFiles(); } } } return s_TypeTable; } } #endregion #region Private Members private bool _stopProcessing; private static readonly object s_SyncObject = new object(); private static TypeTable s_TypeTable; #endregion } #endregion # region Public SessionFilterState Enum /// /// Runspace states that can be used as filters for querying remote runspaces. /// public enum SessionFilterState { /// /// Return runspaces in any state. /// All = 0, /// /// Return runspaces in Opened state. /// Opened = 1, /// /// Return runspaces in Disconnected state. /// Disconnected = 2, /// /// Return runspaces in Closed state. /// Closed = 3, /// /// Return runspaces in Broken state. /// Broken = 4 } #endregion }