MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Diagnostics.CodeAnalysis;8using System.Management.Automation;9using System.Management.Automation.Internal;10using System.Management.Automation.Remoting;11using System.Management.Automation.Remoting.Client;12using System.Management.Automation.Runspaces;13using System.Threading;14 15using Dbg = System.Management.Automation.Diagnostics;16 17namespace Microsoft.PowerShell.Commands18{19 /// <summary>20 /// This cmdlet connects a running command associated with a PS session and then21 /// directs the command output either:22 /// a) To Host. This is the synchronous mode of the cmdlet which won't return23 /// until the running command completes and all output data is received on24 /// the client.25 /// b) To a job object. This is the asynchronous mode of the cmdlet which will26 /// return immediately providing the job object that is collecting the27 /// running command output data.28 ///29 /// The running command becomes disconnected when the associated runspace is30 /// disconnected (via the Disconnect-PSSession cmdlet).31 ///32 /// The associated runspace object must be in the Opened state (connected) before33 /// the running command can be connected. If the associated runspace object is34 /// in the disconnected state, it will first be connected before the running35 /// command is connected.36 ///37 /// The user can specify how command output data is returned by using the public38 /// OutTarget enumeration (Host, Job).39 /// The default actions of this cmdlet is to always direct output to host unless40 /// a job object already exists on the client that is associated with the running41 /// command. In this case the existing job object is connected to the running42 /// command and returned.43 ///44 /// The cmdlet can be used in the following ways:45 ///46 /// Receive PS session data by session object47 /// > $session = New-PSSession serverName48 /// > $job1 = Invoke-Command $session { [script] } -asjob49 /// > Disconnect-PSSession $session50 /// > Connect-PSSession $session51 /// > Receive-PSSession $session // command output continues collecting at job object.52 ///53 /// Receive PS session data by session Id54 /// > Receive-PSSession $session.Id55 ///56 /// Receive PS session data by session instance Id57 /// > Receive-PSSession $session.InstanceId58 ///59 /// Receive PS session data by session Name. Direct output to job60 /// > Receive-PSSession $session.Name61 ///62 /// Receive a running command from a computer.63 /// > $job = Receive-PSSession -ComputerName ServerOne -Name SessionName -OutTarget Job.64 /// </summary>65 [SuppressMessage("Microsoft.PowerShell", "PS1012:CallShouldProcessOnlyIfDeclaringSupport")]66 [Cmdlet(VerbsCommunications.Receive, "PSSession", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low,67 DefaultParameterSetName = ReceivePSSessionCommand.SessionParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096800",68 RemotingCapability = RemotingCapability.OwnedByCommand)]69 public class ReceivePSSessionCommand : PSRemotingCmdlet70 {71 #region Parameters72 73 private const string IdParameterSet = "Id";74 private const string InstanceIdParameterSet = "InstanceId";75 private const string NameParameterSet = "SessionName";76 private const string ComputerSessionNameParameterSet = "ComputerSessionName"; // Computer name and session Name.77 private const string ConnectionUriSessionNameParameterSet = "ConnectionUriSessionName";78 private const string ConnectionUriInstanceIdParameterSet = "ConnectionUriInstanceId";79 80 /// <summary>81 /// The PSSession object to receive data from.82 /// </summary>83 [Parameter(Position = 0,84 Mandatory = true,85 ValueFromPipelineByPropertyName = true,86 ValueFromPipeline = true,87 ParameterSetName = ReceivePSSessionCommand.SessionParameterSet)]88 [ValidateNotNullOrEmpty]89 public PSSession Session { get; set; }90 91 /// <summary>92 /// Session Id of PSSession object to receive data from.93 /// </summary>94 [Parameter(Position = 0,95 Mandatory = true,96 ValueFromPipelineByPropertyName = true,97 ValueFromPipeline = true,98 ParameterSetName = ReceivePSSessionCommand.IdParameterSet)]99 public int Id { get; set; }100 101 /// <summary>102 /// Computer name to receive session data from.103 /// </summary>104 [Parameter(Position = 0,105 Mandatory = true,106 ValueFromPipelineByPropertyName = true,107 ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)]108 [Parameter(Position = 0,109 Mandatory = true,110 ValueFromPipelineByPropertyName = true,111 ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)]112 [ValidateNotNullOrEmpty]113 [Alias("Cn")]114 public string ComputerName { get; set; }115 116 /// <summary>117 /// This parameters specifies the appname which identifies the connection118 /// end point on the remote machine. If this parameter is not specified119 /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If that's120 /// not specified as well, then "WSMAN" will be used.121 /// </summary>122 [Parameter(ValueFromPipelineByPropertyName = true,123 ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)]124 [Parameter(ValueFromPipelineByPropertyName = true,125 ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)]126 public string ApplicationName127 {128 get129 {130 return _appName;131 }132 133 set134 {135 _appName = ResolveAppName(value);136 }137 }138 139 private string _appName;140 141 /// <summary>142 /// If this parameter is not specified then the value specified in143 /// the environment variable DEFAULTREMOTESHELLNAME will be used. If144 /// this is not set as well, then Microsoft.PowerShell is used.145 /// </summary>146 [Parameter(ValueFromPipelineByPropertyName = true,147 ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)]148 [Parameter(ValueFromPipelineByPropertyName = true,149 ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)]150 [Parameter(ValueFromPipelineByPropertyName = true,151 ParameterSetName = ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)]152 [Parameter(ValueFromPipelineByPropertyName = true,153 ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)]154 public string ConfigurationName155 {156 get157 {158 return _shell;159 }160 161 set162 {163 _shell = ResolveShell(value);164 }165 }166 167 private string _shell;168 169 /// <summary>170 /// A complete URI(s) specified for the remote computer and shell to171 /// connect to and create a runspace for.172 /// </summary>173 [Parameter(Position = 0, Mandatory = true,174 ValueFromPipelineByPropertyName = true,175 ParameterSetName = ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)]176 [Parameter(Position = 0, Mandatory = true,177 ValueFromPipelineByPropertyName = true,178 ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)]179 [ValidateNotNullOrEmpty]180 [Alias("URI", "CU")]181 public Uri ConnectionUri { get; set; }182 183 /// <summary>184 /// The AllowRedirection parameter enables the implicit redirection functionality.185 /// </summary>186 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)]187 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)]188 public SwitchParameter AllowRedirection189 {190 get { return _allowRedirection; }191 192 set { _allowRedirection = value; }193 }194 195 private bool _allowRedirection = false;196 197 /// <summary>198 /// Instance Id of PSSession object to receive data from.199 /// </summary>200 [Parameter(Position = 0,201 Mandatory = true,202 ValueFromPipelineByPropertyName = true,203 ValueFromPipeline = true,204 ParameterSetName = ReceivePSSessionCommand.InstanceIdParameterSet)]205 [Parameter(Mandatory = true,206 ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)]207 [Parameter(Mandatory = true,208 ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)]209 [ValidateNotNullOrEmpty]210 public Guid InstanceId { get; set; }211 212 /// <summary>213 /// Name of PSSession object to receive data from.214 /// </summary>215 [Parameter(Position = 0,216 Mandatory = true,217 ValueFromPipelineByPropertyName = true,218 ValueFromPipeline = true,219 ParameterSetName = ReceivePSSessionCommand.NameParameterSet)]220 [Parameter(Mandatory = true,221 ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)]222 [Parameter(Mandatory = true,223 ParameterSetName = ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)]224 [ValidateNotNullOrEmpty]225 public string Name { get; set; }226 227 /// <summary>228 /// Determines how running command output is returned on client.229 /// </summary>230 [Parameter(ParameterSetName = ReceivePSSessionCommand.SessionParameterSet)]231 [Parameter(ParameterSetName = ReceivePSSessionCommand.IdParameterSet)]232 [Parameter(ParameterSetName = ReceivePSSessionCommand.InstanceIdParameterSet)]233 [Parameter(ParameterSetName = ReceivePSSessionCommand.NameParameterSet)]234 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)]235 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)]236 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)]237 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)]238 public OutTarget OutTarget { get; set; } = OutTarget.Default;239 240 /// <summary>241 /// Provides job name when job is created for returned data.242 /// </summary>243 [Parameter(ParameterSetName = ReceivePSSessionCommand.SessionParameterSet)]244 [Parameter(ParameterSetName = ReceivePSSessionCommand.IdParameterSet)]245 [Parameter(ParameterSetName = ReceivePSSessionCommand.InstanceIdParameterSet)]246 [Parameter(ParameterSetName = ReceivePSSessionCommand.NameParameterSet)]247 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)]248 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)]249 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)]250 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)]251 [ValidateNotNullOrEmpty]252 public string JobName { get; set; } = string.Empty;253 254 /// <summary>255 /// Specifies the credentials of the user to impersonate in the256 /// remote machine. If this parameter is not specified then the257 /// credentials of the current user process will be assumed.258 /// </summary>259 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)]260 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)]261 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)]262 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)]263 [Credential]264 public PSCredential Credential265 {266 get267 {268 return _psCredential;269 }270 271 set272 {273 _psCredential = value;274 275 PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication);276 }277 }278 279 private PSCredential _psCredential;280 281 /// <summary>282 /// Use basic authentication to authenticate the user.283 /// </summary>284 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)]285 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)]286 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)]287 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)]288 public AuthenticationMechanism Authentication289 {290 get291 {292 return _authentication;293 }294 295 set296 {297 _authentication = value;298 299 PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication);300 }301 }302 303 private AuthenticationMechanism _authentication;304 305 /// <summary>306 /// Specifies the certificate thumbprint to be used to impersonate the user on the307 /// remote machine.308 /// </summary>309 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)]310 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)]311 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)]312 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)]313 public string CertificateThumbprint314 {315 get316 {317 return _thumbprint;318 }319 320 set321 {322 _thumbprint = value;323 324 PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication);325 }326 }327 328 private string _thumbprint;329 330 /// <summary>331 /// Port specifies the alternate port to be used in case the332 /// default ports are not used for the transport mechanism333 /// (port 80 for http and port 443 for useSSL)334 /// </summary>335 /// <remarks>336 /// Currently this is being accepted as a parameter. But in future337 /// support will be added to make this a part of a policy setting.338 /// When a policy setting is in place this parameter can be used339 /// to override the policy setting340 /// </remarks>341 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)]342 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)]343 [ValidateRange((int)1, (int)UInt16.MaxValue)]344 public int Port { get; set; }345 346 /// <summary>347 /// This parameter suggests that the transport scheme to be used for348 /// remote connections is useSSL instead of the default http.Since349 /// there are only two possible transport schemes that are possible350 /// at this point, a SwitchParameter is being used to switch between351 /// the two.352 /// </summary>353 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)]354 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)]355 [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SSL")]356 public SwitchParameter UseSSL { get; set; }357 358 /// <summary>359 /// Session options.360 /// </summary>361 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)]362 [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)]363 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)]364 [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)]365 public PSSessionOption SessionOption { get; set; }366 367 #endregion368 369 #region Cmdlet Overrides370 371 /// <summary>372 /// Process input.373 /// </summary>374 protected override void ProcessRecord()375 {376 if (ParameterSetName == ReceivePSSessionCommand.ComputerSessionNameParameterSet ||377 ParameterSetName == ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)378 {379 QueryForAndConnectCommands(Name, Guid.Empty);380 }381 else if (ParameterSetName == ReceivePSSessionCommand.ComputerInstanceIdParameterSet ||382 ParameterSetName == ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)383 {384 QueryForAndConnectCommands(string.Empty, InstanceId);385 }386 else387 {388 GetAndConnectSessionCommand();389 }390 }391 392 /// <summary>393 /// User has signaled a stop for this cmdlet.394 /// </summary>395 protected override void StopProcessing()396 {397 RemotePipeline tmpPipeline;398 Job tmpJob;399 400 lock (_syncObject)401 {402 _stopProcessing = true;403 tmpPipeline = _remotePipeline;404 tmpJob = _job;405 }406 407 tmpPipeline?.StopAsync();408 tmpJob?.StopJob();409 }410 411 #endregion412 413 #region Private Methods414 415 /// <summary>416 /// Queries the remote computer for the specified session, creates a disconnected417 /// session object, connects the runspace/command and collects command data.418 /// Command output is either returned (OutTarget.Host) or collected419 /// in a job object that is returned (OutTarget.Job).420 /// </summary>421 /// <param name="name">Name of session to find.</param>422 /// <param name="instanceId">Instance Id of session to find.</param>423 private void QueryForAndConnectCommands(string name, Guid instanceId)424 {425 WSManConnectionInfo connectionInfo = GetConnectionObject();426 427 // Retrieve all disconnected runspaces on the remote computer.428 Runspace[] runspaces;429 try430 {431 runspaces = Runspace.GetRunspaces(connectionInfo, this.Host, QueryRunspaces.BuiltInTypesTable);432 }433 catch (System.Management.Automation.RuntimeException e)434 {435 int errorCode;436 string msg = StringUtil.Format(RemotingErrorIdStrings.QueryForRunspacesFailed, connectionInfo.ComputerName,437 QueryRunspaces.ExtractMessage(e.InnerException, out errorCode));438 string FQEID = WSManTransportManagerUtils.GetFQEIDFromTransportError(errorCode, "ReceivePSSessionQueryForSessionFailed");439 Exception reason = new RuntimeException(msg, e.InnerException);440 ErrorRecord errorRecord = new ErrorRecord(reason, FQEID, ErrorCategory.InvalidOperation, connectionInfo);441 WriteError(errorRecord);442 return;443 }444 445 // Convert configuration name into shell Uri for comparison.446 string shellUri = null;447 if (!string.IsNullOrEmpty(ConfigurationName))448 {449 shellUri = ConfigurationName.Contains(WSManNativeApi.ResourceURIPrefix, StringComparison.OrdinalIgnoreCase)450 ? ConfigurationName451 : WSManNativeApi.ResourceURIPrefix + ConfigurationName;452 }453 454 // Connect selected runspace/command and direct command output to host455 // or job objects.456 foreach (Runspace runspace in runspaces)457 {458 if (_stopProcessing)459 {460 break;461 }462 463 // Filter returned runspaces by ConfigurationName if provided.464 if (shellUri != null)465 {466 // Compare with returned shell Uri in connection info.467 WSManConnectionInfo wsmanConnectionInfo = runspace.ConnectionInfo as WSManConnectionInfo;468 if (wsmanConnectionInfo != null &&469 !shellUri.Equals(wsmanConnectionInfo.ShellUri, StringComparison.OrdinalIgnoreCase))470 {471 continue;472 }473 }474 475 // Find specified session.476 bool haveMatch = false;477 if (!string.IsNullOrEmpty(name) &&478 string.Equals(name, ((RemoteRunspace)runspace).RunspacePool.RemoteRunspacePoolInternal.Name, StringComparison.OrdinalIgnoreCase))479 {480 // Selected by friendly name.481 haveMatch = true;482 }483 else if (instanceId.Equals(runspace.InstanceId))484 {485 // Selected by instance Id (note that session/runspace/runspacepool instanceIds are identical.)486 haveMatch = true;487 }488 489 if (haveMatch &&490 ShouldProcess(((RemoteRunspace)runspace).PSSessionName, VerbsCommunications.Receive))491 {492 // Check the local repository for an existing viable session.493 PSSession locSession = this.RunspaceRepository.GetItem(runspace.InstanceId);494 495 // Connect the session here. If it fails (connectedSession == null) revert to the496 // reconstruct method.497 Exception ex;498 PSSession connectedSession = ConnectSession(locSession, out ex);499 500 if (connectedSession != null)501 {502 // Make sure that this connected session is included in the PSSession repository.503 // If it already exists then replace it because we want the latest/connected session in the repository.504 this.RunspaceRepository.AddOrReplace(connectedSession);505 506 // Since we have a local runspace we will do a *reconnect* operation and will507 // need the corresponding job object.508 PSRemotingJob job = FindJobForSession(connectedSession);509 if (this.OutTarget == OutTarget.Host)510 {511 ConnectSessionToHost(connectedSession, job);512 }513 else514 {515 // Connection to Job is default option.516 ConnectSessionToJob(connectedSession, job);517 }518 }519 else520 {521 // Otherwise create a new session from the queried runspace object.522 // This will be a *reconstruct* operation.523 // Create and connect session.524 PSSession newSession = new PSSession(runspace as RemoteRunspace);525 connectedSession = ConnectSession(newSession, out ex);526 if (connectedSession != null)527 {528 // Try to reuse the existing local repository PSSession object.529 if (locSession != null)530 {531 connectedSession = locSession.InsertRunspace(connectedSession.Runspace as RemoteRunspace) ? locSession : connectedSession;532 }533 534 // Make sure that this connected session is included in the PSSession repository.535 // If it already exists then replace it because we want the latest/connected session in the repository.536 this.RunspaceRepository.AddOrReplace(connectedSession);537 538 if (this.OutTarget == OutTarget.Job)539 {540 ConnectSessionToJob(connectedSession);541 }542 else543 {544 // Connection to Host is default option.545 ConnectSessionToHost(connectedSession);546 }547 }548 else549 {550 string message = StringUtil.Format(RemotingErrorIdStrings.RunspaceCannotBeConnected, newSession.Name);551 WriteError(new ErrorRecord(new ArgumentException(message, ex), "ReceivePSSessionCannotConnectSession",552 ErrorCategory.InvalidOperation, newSession));553 }554 }555 556 break;557 }558 }559 }560 561 private WSManConnectionInfo GetConnectionObject()562 {563 WSManConnectionInfo connectionInfo = new WSManConnectionInfo();564 565 if (ParameterSetName == ReceivePSSessionCommand.ComputerSessionNameParameterSet ||566 ParameterSetName == ReceivePSSessionCommand.ComputerInstanceIdParameterSet)567 {568 // Create the WSManConnectionInfo object for the specified computer name.569 string scheme = UseSSL.IsPresent ? WSManConnectionInfo.HttpsScheme : WSManConnectionInfo.HttpScheme;570 571 connectionInfo.Scheme = scheme;572 connectionInfo.ComputerName = ResolveComputerName(ComputerName);573 connectionInfo.AppName = ApplicationName;574 connectionInfo.ShellUri = ConfigurationName;575 connectionInfo.Port = Port;576 if (CertificateThumbprint != null)577 {578 connectionInfo.CertificateThumbprint = CertificateThumbprint;579 }580 else581 {582 connectionInfo.Credential = Credential;583 }584 585 connectionInfo.AuthenticationMechanism = Authentication;586 UpdateConnectionInfo(connectionInfo);587 }588 else589 {590 connectionInfo.ConnectionUri = ConnectionUri;591 connectionInfo.ShellUri = ConfigurationName;592 if (CertificateThumbprint != null)593 {594 connectionInfo.CertificateThumbprint = CertificateThumbprint;595 }596 else597 {598 connectionInfo.Credential = Credential;599 }600 601 connectionInfo.AuthenticationMechanism = Authentication;602 UpdateConnectionInfo(connectionInfo);603 }604 605 return connectionInfo;606 }607 608 /// <summary>609 /// Updates connection info with the data read from cmdlet's parameters.610 /// </summary>611 /// <param name="connectionInfo"></param>612 private void UpdateConnectionInfo(WSManConnectionInfo connectionInfo)613 {614 if (ParameterSetName != ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet &&615 ParameterSetName != ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)616 {617 // uri redirection is supported only with URI parameter set618 connectionInfo.MaximumConnectionRedirectionCount = 0;619 }620 621 if (!_allowRedirection)622 {623 // uri redirection required explicit user consent624 connectionInfo.MaximumConnectionRedirectionCount = 0;625 }626 627 // Update the connectionInfo object with passed in session options.628 if (SessionOption != null)629 {630 connectionInfo.SetSessionOptions(SessionOption);631 }632 }633 634 /// <summary>635 /// Gets the PSSession object to connect based on Id, Name, etc.636 /// Connects the running command associated with the PSSession runspace object.637 /// Command output is either returned (OutTarget.Host) or collected638 /// in a job object that is returned (OutTarget.Job).639 /// </summary>640 private void GetAndConnectSessionCommand()641 {642 PSSession session = null;643 644 if (ParameterSetName == ReceivePSSessionCommand.SessionParameterSet)645 {646 session = Session;647 }648 else if (ParameterSetName == ReceivePSSessionCommand.IdParameterSet)649 {650 session = GetSessionById(Id);651 if (session == null)652 {653 WriteInvalidArgumentError(PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedSessionId,654 RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedSessionId,655 Id);656 657 return;658 }659 }660 else if (ParameterSetName == ReceivePSSessionCommand.NameParameterSet)661 {662 session = GetSessionByName(Name);663 if (session == null)664 {665 WriteInvalidArgumentError(PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedName,666 RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedName,667 Name);668 669 return;670 }671 }672 else if (ParameterSetName == ReceivePSSessionCommand.InstanceIdParameterSet)673 {674 session = GetSessionByInstanceId(InstanceId);675 if (session == null)676 {677 WriteInvalidArgumentError(PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedRunspaceId,678 RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedRunspaceId,679 InstanceId);680 681 return;682 }683 }684 else685 {686 Dbg.Assert(false, "Invalid Parameter Set");687 }688 689 // PS session disconnection is not supported for VM/Container sessions.690 if (session.ComputerType != TargetMachineType.RemoteMachine)691 {692 string msg = StringUtil.Format(RemotingErrorIdStrings.RunspaceCannotBeReceivedForVMContainerSession,693 session.Name, session.ComputerName, session.ComputerType);694 Exception reason = new PSNotSupportedException(msg);695 ErrorRecord errorRecord = new ErrorRecord(reason, "CannotReceiveVMContainerSession", ErrorCategory.InvalidOperation, session);696 WriteError(errorRecord);697 return;698 }699 700 if (ShouldProcess(session.Name, VerbsCommunications.Receive))701 {702 Exception ex;703 if (ConnectSession(session, out ex) == null)704 {705 // Unable to connect runspace. If this was a *reconnect* runspace then try706 // obtaining a connectable runspace directly from the server and do a707 // *reconstruct* connect.708 PSSession oldSession = session;709 session = TryGetSessionFromServer(oldSession);710 if (session == null)711 {712 // No luck. Return error.713 string message = StringUtil.Format(RemotingErrorIdStrings.RunspaceCannotBeConnected, oldSession.Name);714 WriteError(new ErrorRecord(new ArgumentException(message, ex), "ReceivePSSessionCannotConnectSession",715 ErrorCategory.InvalidOperation, oldSession));716 717 return;718 }719 }720 721 // Look to see if there exists a job associated with this runspace.722 // If so then we use this job object, unless the user explicitly specifies723 // output to host.724 PSRemotingJob job = FindJobForSession(session);725 if (job != null)726 {727 // Default is to route data to job.728 if (OutTarget == OutTarget.Host)729 {730 // This performs a *reconstruct* connection scenario where a new731 // pipeline object is created and connected.732 ConnectSessionToHost(session, job);733 }734 else735 {736 // This preforms a *reconnect* scenario where the existing job737 // and runspace objects are reconnected.738 ConnectSessionToJob(session, job);739 }740 }741 else742 {743 // Default is to route data to host.744 if (OutTarget == OutTarget.Job)745 {746 // This performs a *reconstruct* connection scenario where new747 // pipeline/job objects are created and connected.748 ConnectSessionToJob(session);749 }750 else751 {752 // This performs a *reconstruct* connection scenario where a new753 // pipeline object is created and connected.754 ConnectSessionToHost(session);755 }756 }757 758 // Make sure that if this session is successfully connected that it is included759 // in the PSSession repository. If it already exists then replace it because we760 // want the latest/connected session in the repository.761 if (session.Runspace.RunspaceStateInfo.State != RunspaceState.Disconnected)762 {763 this.RunspaceRepository.AddOrReplace(session);764 }765 }766 }767 768 private bool CheckForDebugMode(PSSession session, bool monitorAvailabilityChange)769 {770 RemoteRunspace remoteRunspace = session.Runspace as RemoteRunspace;771 if (remoteRunspace.RunspaceAvailability == RunspaceAvailability.RemoteDebug)772 {773 DisconnectAndStopRunningCmds(remoteRunspace);774 WriteDebugStopWarning();775 return true;776 }777 778 if (monitorAvailabilityChange)779 {780 // Monitor runspace availability transition to RemoteDebug781 remoteRunspace.AvailabilityChanged += HandleRunspaceAvailabilityChanged;782 }783 784 return false;785 }786 787 private void HandleRunspaceAvailabilityChanged(object sender, RunspaceAvailabilityEventArgs e)788 {789 if ((e.RunspaceAvailability == RunspaceAvailability.RemoteDebug))790 {791 RemoteRunspace remoteRunspace = sender as RemoteRunspace;792 remoteRunspace.AvailabilityChanged -= HandleRunspaceAvailabilityChanged;793 794 DisconnectAndStopRunningCmds(remoteRunspace);795 }796 }797 798 private void DisconnectAndStopRunningCmds(RemoteRunspace remoteRunspace)799 {800 // Disconnect runspace to stop command from running and to allow reconnect801 // via the Enter-PSSession cmdlet.802 if (remoteRunspace.RunspaceStateInfo.State == RunspaceState.Opened)803 {804 Job job;805 ManualResetEvent stopPipelineReceive;806 lock (_syncObject)807 {808 job = _job;809 stopPipelineReceive = _stopPipelineReceive;810 }811 812 remoteRunspace.Disconnect();813 814 try815 {816 stopPipelineReceive?.Set();817 }818 catch (ObjectDisposedException) { }819 820 job?.StopJob();821 }822 }823 824 private void WriteDebugStopWarning()825 {826 WriteWarning(827 GetMessage(RemotingErrorIdStrings.ReceivePSSessionInDebugMode));828 WriteObject(string.Empty);829 }830 831 /// <summary>832 /// Connects session, retrieves command output data and writes to host.833 /// </summary>834 /// <param name="session">PSSession object.</param>835 /// <param name="job">Job object associated with session.</param>836 private void ConnectSessionToHost(PSSession session, PSRemotingJob job = null)837 {838 RemoteRunspace remoteRunspace = session.Runspace as RemoteRunspace;839 Dbg.Assert(remoteRunspace != null, "PS sessions can only contain RemoteRunspace type.");840 841 if (job != null)842 {843 // If we have a job object associated with the session then this means844 // the user explicitly chose to connect and return data synchronously.845 846 // Reconnect the job object and stream data to host.847 lock (_syncObject) { _job = job; _stopPipelineReceive = new ManualResetEvent(false); }848 849 using (_stopPipelineReceive)850 using (job)851 {852 Job childJob = job.ChildJobs[0];853 job.ConnectJobs();854 if (CheckForDebugMode(session, true))855 {856 return;857 }858 859 do860 {861 // Retrieve and display results from child job as they become862 // available.863 int index = WaitHandle.WaitAny(new WaitHandle[] {864 _stopPipelineReceive,865 childJob.Results.WaitHandle });866 867 foreach (var result in childJob.ReadAll())868 {869 result?.WriteStreamObject(this);870 }871 872 if (index == 0)873 {874 WriteDebugStopWarning();875 return;876 }877 }878 while (!job.IsFinishedState(job.JobStateInfo.State));879 }880 881 lock (_syncObject) { _job = null; _stopPipelineReceive = null; }882 883 return;884 }885 886 // Otherwise this must be a new disconnected session object that has a running command887 // associated with it.888 if (remoteRunspace.RemoteCommand == null)889 {890 // There is no associated running command for this runspace, so we cannot proceed.891 // Check to see if session is in debug mode.892 CheckForDebugMode(session, false);893 return;894 }895 896 // Create a RemotePipeline object for this command and attempt to connect.897 lock (_syncObject)898 {899 _remotePipeline = (RemotePipeline)session.Runspace.CreateDisconnectedPipeline();900 _stopPipelineReceive = new ManualResetEvent(false);901 }902 903 using (_stopPipelineReceive)904 {905 using (_remotePipeline)906 {907 // Connect to remote running command.908 ManualResetEvent pipelineConnectedEvent = new ManualResetEvent(false);909 using (pipelineConnectedEvent)910 {911 _remotePipeline.StateChanged += (sender, args) =>912 {913 if (pipelineConnectedEvent != null &&914 (args.PipelineStateInfo.State == PipelineState.Running ||915 args.PipelineStateInfo.State == PipelineState.Stopped ||916 args.PipelineStateInfo.State == PipelineState.Failed))917 {918 pipelineConnectedEvent.Set();919 }920 };921 _remotePipeline.ConnectAsync();922 pipelineConnectedEvent.WaitOne();923 }924 925 pipelineConnectedEvent = null;926 927 if (CheckForDebugMode(session, true))928 {929 return;930 }931 932 // Wait for remote command to complete, while writing any available data.933 while (!_remotePipeline.Output.EndOfPipeline)934 {935 if (_stopProcessing)936 {937 break;938 }939 940 int index = WaitHandle.WaitAny(new WaitHandle[] {941 _stopPipelineReceive,942 _remotePipeline.Output.WaitHandle });943 944 if (index == 0)945 {946 WriteDebugStopWarning();947 return;948 }949 950 while (_remotePipeline.Output.Count > 0)951 {952 if (_stopProcessing)953 {954 break;955 }956 957 PSObject psObject = _remotePipeline.Output.Read();958 WriteRemoteObject(psObject, session);959 }960 }961 962 // Write pipeline object errors.963 if (_remotePipeline.Error.Count > 0)964 {965 while (!_remotePipeline.Error.EndOfPipeline)966 {967 object errorObj = _remotePipeline.Error.Read();968 if (errorObj is Collection<ErrorRecord>)969 {970 Collection<ErrorRecord> errorCollection = (Collection<ErrorRecord>)errorObj;971 foreach (ErrorRecord errorRecord in errorCollection)972 {973 WriteError(errorRecord);974 }975 }976 else if (errorObj is ErrorRecord)977 {978 WriteError((ErrorRecord)errorObj);979 }980 else981 {982 Dbg.Assert(false, "Objects in pipeline Error collection must be ErrorRecord type.");983 }984 }985 }986 987 // Wait for pipeline to finish.988 int wIndex = WaitHandle.WaitAny(new WaitHandle[] {989 _stopPipelineReceive,990 _remotePipeline.PipelineFinishedEvent });991 992 if (wIndex == 0)993 {994 WriteDebugStopWarning();995 return;996 }997 998 // Set the runspace RemoteCommand to null. It is not needed anymore and it999 // allows the runspace to become available after pipeline completes.1000 remoteRunspace.RunspacePool.RemoteRunspacePoolInternal.ConnectCommands = null;1001 1002 // Check for any terminating errors to report.1003 if (_remotePipeline.PipelineStateInfo.State == PipelineState.Failed)1004 {1005 Exception reason = _remotePipeline.PipelineStateInfo.Reason;1006 string msg;1007 if (reason != null && !string.IsNullOrEmpty(reason.Message))1008 {1009 msg = StringUtil.Format(RemotingErrorIdStrings.PipelineFailedWithReason, reason.Message);1010 }1011 else1012 {1013 msg = RemotingErrorIdStrings.PipelineFailedWithoutReason;1014 }1015 1016 ErrorRecord errorRecord = new ErrorRecord(new RuntimeException(msg, reason),1017 "ReceivePSSessionPipelineFailed",1018 ErrorCategory.OperationStopped,1019 _remotePipeline1020 );1021 1022 WriteError(errorRecord);1023 }1024 }1025 }1026 1027 lock (_syncObject) { _remotePipeline = null; _stopPipelineReceive = null; }1028 }1029 1030 /// <summary>1031 /// Helper method to append computer name and session GUID1032 /// note properties to the PSObject before it is written.1033 /// </summary>1034 /// <param name="psObject">PSObject.</param>1035 /// <param name="session">PSSession.</param>1036 private void WriteRemoteObject(1037 PSObject psObject,1038 PSSession session)1039 {1040 if (psObject == null)1041 {1042 return;1043 }1044 1045 // Add note properties for this session if they don't already exist.1046 if (psObject.Properties[RemotingConstants.ComputerNameNoteProperty] == null)1047 {1048 psObject.Properties.Add(new PSNoteProperty(RemotingConstants.ComputerNameNoteProperty, session.ComputerName));1049 }1050 1051 if (psObject.Properties[RemotingConstants.RunspaceIdNoteProperty] == null)1052 {1053 psObject.Properties.Add(new PSNoteProperty(RemotingConstants.RunspaceIdNoteProperty, session.InstanceId));1054 }1055 1056 if (psObject.Properties[RemotingConstants.ShowComputerNameNoteProperty] == null)1057 {1058 psObject.Properties.Add(new PSNoteProperty(RemotingConstants.ShowComputerNameNoteProperty, true));1059 }1060 1061 WriteObject(psObject);1062 }1063 1064 /// <summary>1065 /// Connects session, collects command output data in a job object.1066 /// If a PSRemotingJob object is passed in then that job will be1067 /// (re)connected. Otherwise a new job object will be created that1068 /// will be connected to the session's running command.1069 /// </summary>1070 /// <param name="session">PSSession object.</param>1071 /// <param name="job">Job object to connect to.</param>1072 private void ConnectSessionToJob(PSSession session, PSRemotingJob job = null)1073 {1074 // Otherwise create a new job object in the disconnected state for this1075 // session and then connect it.1076 bool newJobCreated = false;1077 if (job == null)1078 {1079 // The PSRemoting job object uses helper objects to track remote command execution.1080 List<IThrottleOperation> helpers = new List<IThrottleOperation>();1081 1082 // Create the remote pipeline object that will represent the running command1083 // on the server machine. This object will be in the disconnected state.1084 Pipeline remotePipeline = session.Runspace.CreateDisconnectedPipeline();1085 1086 // Create a disconnected runspace helper for this remote command.1087 helpers.Add(new DisconnectedJobOperation(remotePipeline));1088 1089 // Create the job object in a disconnected state. Note that the job name1090 // will be autogenerated.1091 job = new PSRemotingJob(helpers, 0, JobName, false);1092 job.PSJobTypeName = InvokeCommandCommand.RemoteJobType;1093 job.HideComputerName = false;1094 newJobCreated = true;1095 }1096 1097 if (job.JobStateInfo.State == JobState.Disconnected)1098 {1099 // Connect the job to the remote command running on the server.1100 job.ConnectJob(session.Runspace.InstanceId);1101 1102 // Add the created job to the store if it was connected successfully.1103 if (newJobCreated)1104 {1105 JobRepository.Add(job);1106 }1107 }1108 1109 if (CheckForDebugMode(session, true))1110 {1111 return;1112 }1113 1114 // Write the job object to output.1115 WriteObject(job);1116 }1117 1118 /// <summary>1119 /// Helper method to connect the runspace. If the session/runspace can't1120 /// be connected or fails to be connected then a null PSSessionobject is1121 /// returned.1122 /// </summary>1123 /// <param name="session">Session to connect.</param>1124 /// <param name="ex">Optional exception object.</param>1125 /// <returns>Connected session or null.</returns>1126 private static PSSession ConnectSession(PSSession session, out Exception ex)1127 {1128 ex = null;1129 1130 if (session == null ||1131 (session.Runspace.RunspaceStateInfo.State != RunspaceState.Opened &&1132 session.Runspace.RunspaceStateInfo.State != RunspaceState.Disconnected))1133 {1134 return null;1135 }1136 else if (session.Runspace.RunspaceStateInfo.State == RunspaceState.Opened)1137 {1138 return session;1139 }1140 1141 try1142 {1143 session.Runspace.Connect();1144 }1145 catch (PSInvalidOperationException e)1146 {1147 ex = e;1148 }1149 catch (InvalidRunspaceStateException e)1150 {1151 ex = e;1152 }1153 catch (RuntimeException e)1154 {1155 ex = e;1156 }1157 1158 return (ex == null) ? session : null;1159 }1160 1161 /// <summary>1162 /// Helper method to attempt to retrieve a disconnected runspace object1163 /// from the server, based on the provided session object.1164 /// </summary>1165 /// <param name="session">PSSession session object.</param>1166 /// <returns>PSSession disconnected runspace object.</returns>1167 private PSSession TryGetSessionFromServer(PSSession session)1168 {1169 if (session.Runspace is not RemoteRunspace remoteRunspace)1170 {1171 return null;1172 }1173 1174 remoteRunspace = null;1175 Runspace[] runspaces = Runspace.GetRunspaces(session.Runspace.ConnectionInfo, this.Host, QueryRunspaces.BuiltInTypesTable);1176 foreach (Runspace runspace in runspaces)1177 {1178 if (runspace.InstanceId == session.Runspace.InstanceId)1179 {1180 remoteRunspace = runspace as RemoteRunspace;1181 break;1182 }1183 }1184 1185 if (remoteRunspace != null)1186 {1187 // Try inserting connected runspace into existing PSSession.1188 session = session.InsertRunspace(remoteRunspace) ? session : new PSSession(remoteRunspace);1189 return session;1190 }1191 1192 return null;1193 }1194 1195 /// <summary>1196 /// Helper method to search the local PS client job repository1197 /// for a job associated with the provided session.1198 /// </summary>1199 /// <param name="session">PSSession object.</param>1200 /// <returns>Associated job object from the job repository.</returns>