MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections;6using System.Collections.Generic;7using System.Collections.ObjectModel;8using System.Diagnostics.CodeAnalysis;9using System.Globalization;10using System.Linq;11using System.Management.Automation;12using System.Management.Automation.Host;13using System.Management.Automation.Internal;14using System.Management.Automation.Language;15using System.Management.Automation.Remoting;16using System.Management.Automation.Remoting.Client;17using System.Management.Automation.Runspaces;18using System.Threading;19 20using Dbg = System.Management.Automation.Diagnostics;21 22namespace Microsoft.PowerShell.Commands23{24 /// <summary>25 /// This class defines most of the common functionality used26 /// across remoting cmdlets.27 ///28 /// It contains tons of utility functions which are used all29 /// across the remoting cmdlets.30 /// </summary>31 public abstract class PSRemotingCmdlet : PSCmdlet32 {33 #region Overrides34 35 /// <summary>36 /// Verifies if remoting cmdlets can be used.37 /// </summary>38 protected override void BeginProcessing()39 {40 if (!SkipWinRMCheck)41 {42 RemotingCommandUtil.CheckRemotingCmdletPrerequisites();43 }44 }45 46 #endregion Overrides47 48 #region Utility functions49 50 /// <summary>51 /// Handle the object obtained from an ObjectStream's reader52 /// based on its type.53 /// </summary>54 internal void WriteStreamObject(Action<Cmdlet> action)55 {56 action(this);57 }58 59 /// <summary>60 /// Resolve all the machine names provided. Basically, if a machine61 /// name is '.' assume localhost.62 /// </summary>63 /// <param name="computerNames">Array of computer names to resolve.</param>64 /// <param name="resolvedComputerNames">Resolved array of machine names.</param>65 protected void ResolveComputerNames(string[] computerNames, out string[] resolvedComputerNames)66 {67 if (computerNames == null)68 {69 resolvedComputerNames = new string[1];70 71 resolvedComputerNames[0] = ResolveComputerName(".");72 }73 else if (computerNames.Length == 0)74 {75 resolvedComputerNames = Array.Empty<string>();76 }77 else78 {79 resolvedComputerNames = new string[computerNames.Length];80 81 for (int i = 0; i < resolvedComputerNames.Length; i++)82 {83 resolvedComputerNames[i] = ResolveComputerName(computerNames[i]);84 }85 }86 }87 88 /// <summary>89 /// Resolves a computer name. If its null or empty90 /// its assumed to be localhost.91 /// </summary>92 /// <param name="computerName">Computer name to resolve.</param>93 /// <returns>Resolved computer name.</returns>94 protected string ResolveComputerName(string computerName)95 {96 Diagnostics.Assert(computerName != null, "Null ComputerName");97 98 if (string.Equals(computerName, ".", StringComparison.OrdinalIgnoreCase))99 {100 // tracer.WriteEvent(ref PSEventDescriptors.PS_EVENT_HOSTNAMERESOLVE);101 // tracer.Dispose();102 // tracer.OperationalChannel.WriteVerbose(PSEventId.HostNameResolve, PSOpcode.Method, PSTask.CreateRunspace);103 return s_LOCALHOST;104 }105 else106 {107 return computerName;108 }109 }110 111 /// <summary>112 /// Load the resource corresponding to the specified errorId and113 /// return the message as a string.114 /// </summary>115 /// <param name="resourceString">resource String which holds the message116 /// </param>117 /// <returns>Error message loaded from appropriate resource cache.</returns>118 internal string GetMessage(string resourceString)119 {120 string message = GetMessage(resourceString, null);121 122 return message;123 }124 125 /// <summary>126 /// </summary>127 /// <param name="resourceString"></param>128 /// <param name="args"></param>129 /// <returns></returns>130 internal string GetMessage(string resourceString, params object[] args)131 {132 string message;133 134 if (args != null)135 {136 message = StringUtil.Format(resourceString, args);137 }138 else139 {140 message = resourceString;141 }142 143 return message;144 }145 146 #endregion Utility functions147 148 #region Private Members149 150 private static readonly string s_LOCALHOST = "localhost";151 152 // private PSETWTracer tracer = PSETWTracer.GetETWTracer(PSKeyword.Cmdlets);153 154 #endregion Private Members155 156 #region Protected Members157 158 /// <summary>159 /// Computername parameter set.160 /// </summary>161 protected const string ComputerNameParameterSet = "ComputerName";162 163 /// <summary>164 /// Computername with session instance ID parameter set.165 /// </summary>166 protected const string ComputerInstanceIdParameterSet = "ComputerInstanceId";167 168 /// <summary>169 /// Container ID parameter set.170 /// </summary>171 protected const string ContainerIdParameterSet = "ContainerId";172 173 /// <summary>174 /// VM guid parameter set.175 /// </summary>176 protected const string VMIdParameterSet = "VMId";177 178 /// <summary>179 /// VM name parameter set.180 /// </summary>181 protected const string VMNameParameterSet = "VMName";182 183 /// <summary>184 /// SSH host parameter set.185 /// </summary>186 protected const string SSHHostParameterSet = "SSHHost";187 188 /// <summary>189 /// SSH host parmeter set supporting hash connection parameters.190 /// </summary>191 protected const string SSHHostHashParameterSet = "SSHHostHashParam";192 193 /// <summary>194 /// Runspace parameter set.195 /// </summary>196 protected const string SessionParameterSet = "Session";197 198 /// <summary>199 /// Parameter set to use Windows PowerShell.200 /// </summary>201 protected const string UseWindowsPowerShellParameterSet = "UseWindowsPowerShellParameterSet";202 203 /// <summary>204 /// Default shellname.205 /// </summary>206 protected const string DefaultPowerShellRemoteShellName = WSManNativeApi.ResourceURIPrefix + "Microsoft.PowerShell";207 208 /// <summary>209 /// Default application name for the connection uri.210 /// </summary>211 protected const string DefaultPowerShellRemoteShellAppName = "WSMan";212 213 #endregion Protected Members214 215 #region Internal Members216 217 /// <summary>218 /// Skip checking for WinRM.219 /// </summary>220 internal bool SkipWinRMCheck { get; set; } = false;221 222 #endregion Internal Members223 224 #region Protected Methods225 226 /// <summary>227 /// Determines the shellname to use based on the following order:228 /// 1. ShellName parameter specified229 /// 2. DEFAULTREMOTESHELLNAME variable set230 /// 3. PowerShell.231 /// </summary>232 /// <returns>The shell to launch in the remote machine.</returns>233 protected string ResolveShell(string shell)234 {235 string resolvedShell;236 237 if (!string.IsNullOrEmpty(shell))238 {239 resolvedShell = shell;240 }241 else242 {243 resolvedShell = (string)SessionState.Internal.ExecutionContext.GetVariableValue(244 SpecialVariables.PSSessionConfigurationNameVarPath, DefaultPowerShellRemoteShellName);245 }246 247 return resolvedShell;248 }249 250 /// <summary>251 /// Determines the appname to be used based on the following order:252 /// 1. AppName parameter specified253 /// 2. DEFAULTREMOTEAPPNAME variable set254 /// 3. WSMan.255 /// </summary>256 /// <param name="appName">Application name to resolve.</param>257 /// <returns>Resolved appname.</returns>258 protected string ResolveAppName(string appName)259 {260 string resolvedAppName;261 262 if (!string.IsNullOrEmpty(appName))263 {264 resolvedAppName = appName;265 }266 else267 {268 resolvedAppName = (string)SessionState.Internal.ExecutionContext.GetVariableValue(269 SpecialVariables.PSSessionApplicationNameVarPath,270 DefaultPowerShellRemoteShellAppName);271 }272 273 return resolvedAppName;274 }275 276 #endregion277 }278 279 /// <summary>280 /// Contains SSH connection information.281 /// </summary>282 internal struct SSHConnection283 {284 public string ComputerName;285 public string UserName;286 public string KeyFilePath;287 public int Port;288 public string Subsystem;289 public int ConnectingTimeout;290 public Hashtable Options;291 }292 293 /// <summary>294 /// Base class for any cmdlet which takes a -Session parameter295 /// or a -ComputerName parameter (along with its other associated296 /// parameters). The following cmdlets currently fall under this297 /// category:298 /// 1. New-PSSession299 /// 2. Invoke-Expression300 /// 3. Start-PSJob.301 /// </summary>302 public abstract class PSRemotingBaseCmdlet : PSRemotingCmdlet303 {304 #region Enums305 306 /// <summary>307 /// State of virtual machine. This is the same as VMState in308 /// \vm\ux\powershell\objects\common\Types.cs.309 /// </summary>310 internal enum VMState311 {312 /// <summary>313 /// Other. Corresponds to CIM_EnabledLogicalElement.EnabledState = Other.314 /// </summary>315 Other = 1,316 317 /// <summary>318 /// Running. Corresponds to CIM_EnabledLogicalElement.EnabledState = Enabled.319 /// </summary>320 Running = 2,321 322 /// <summary>323 /// Off. Corresponds to CIM_EnabledLogicalElement.EnabledState = Disabled.324 /// </summary>325 Off = 3,326 327 /// <summary>328 /// Stopping. Corresponds to CIM_EnabledLogicalElement.EnabledState = ShuttingDown.329 /// </summary>330 Stopping = 4,331 332 /// <summary>333 /// Saved. Corresponds to CIM_EnabledLogicalElement.EnabledState = Enabled but offline.334 /// </summary>335 Saved = 6,336 337 /// <summary>338 /// Paused. Corresponds to CIM_EnabledLogicalElement.EnabledState = Quiesce.339 /// </summary>340 Paused = 9,341 342 /// <summary>343 /// Starting. EnabledStateStarting. State transition from PowerOff or Saved to Running.344 /// </summary>345 Starting = 10,346 347 /// <summary>348 /// Reset. Corresponds to CIM_EnabledLogicalElement.EnabledState = Reset.349 /// </summary>350 Reset = 11,351 352 /// <summary>353 /// Saving. Corresponds to EnabledStateSaving.354 /// </summary>355 Saving = 32773,356 357 /// <summary>358 /// Pausing. Corresponds to EnabledStatePausing.359 /// </summary>360 Pausing = 32776,361 362 /// <summary>363 /// Resuming. Corresponds to EnabledStateResuming.364 /// </summary>365 Resuming = 32777,366 367 /// <summary>368 /// FastSaved. EnabledStateFastSuspend.369 /// </summary>370 FastSaved = 32779,371 372 /// <summary>373 /// FastSaving. EnabledStateFastSuspending.374 /// </summary>375 FastSaving = 32780,376 377 /// <summary>378 /// ForceShutdown. Used to force a graceful shutdown of the virtual machine.379 /// </summary>380 ForceShutdown = 32781,381 382 /// <summary>383 /// ForceReboot. Used to force a graceful reboot of the virtual machine.384 /// </summary>385 ForceReboot = 32782,386 387 /// <summary>388 /// RunningCritical. Critical states.389 /// </summary>390 RunningCritical,391 392 /// <summary>393 /// OffCritical. Critical states.394 /// </summary>395 OffCritical,396 397 /// <summary>398 /// StoppingCritical. Critical states.399 /// </summary>400 StoppingCritical,401 402 /// <summary>403 /// SavedCritical. Critical states.404 /// </summary>405 SavedCritical,406 407 /// <summary>408 /// PausedCritical. Critical states.409 /// </summary>410 PausedCritical,411 412 /// <summary>413 /// StartingCritical. Critical states.414 /// </summary>415 StartingCritical,416 417 /// <summary>418 /// ResetCritical. Critical states.419 /// </summary>420 ResetCritical,421 422 /// <summary>423 /// SavingCritical. Critical states.424 /// </summary>425 SavingCritical,426 427 /// <summary>428 /// PausingCritical. Critical states.429 /// </summary>430 PausingCritical,431 432 /// <summary>433 /// ResumingCritical. Critical states.434 /// </summary>435 ResumingCritical,436 437 /// <summary>438 /// FastSavedCritical. Critical states.439 /// </summary>440 FastSavedCritical,441 442 /// <summary>443 /// FastSavingCritical. Critical states.444 /// </summary>445 FastSavingCritical,446 }447 448#nullable enable449 /// <summary>450 /// Get the State property from Get-VM result.451 /// </summary>452 /// <param name="value">The raw PSObject as returned by Get-VM.</param>453 /// <returns>The VMState value of the State property if present and parsable, otherwise null.</returns>454 internal VMState? GetVMStateProperty(PSObject value)455 {456 object? rawState = value.Properties["State"].Value;457 if (rawState is Enum enumState)458 {459 // If the Hyper-V module was directly importable we have the VMState enum460 // value which we can just cast to our VMState type.461 return (VMState)enumState;462 }463 else if (rawState is string stringState && Enum.TryParse(stringState, true, out VMState result))464 {465 // If the Hyper-V module was imported through implicit remoting on old466 // Windows versions we get a string back which we will try and parse467 // as the enum label.468 return result;469 }470 471 // Unknown scenario, this should not happen.472 string message = PSRemotingErrorInvariants.FormatResourceString(473 RemotingErrorIdStrings.HyperVFailedToGetStateUnknownType,474 rawState?.GetType()?.FullName ?? "null");475 throw new InvalidOperationException(message);476 }477#nullable disable478 479 #endregion480 481 #region Tracer482 483 // PSETWTracer tracer = PSETWTracer.GetETWTracer(PSKeyword.Runspace);484 485 #endregion Tracer486 487 #region Properties488 489 /// <summary>490 /// The PSSession object describing the remote runspace491 /// using which the specified cmdlet operation will be performed.492 /// </summary>493 [Parameter(Position = 0,494 ValueFromPipelineByPropertyName = true,495 ParameterSetName = PSRemotingBaseCmdlet.SessionParameterSet)]496 [ValidateNotNullOrEmpty]497 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]498 public virtual PSSession[] Session { get; set; }499 500 /// <summary>501 /// This parameter represents the address(es) of the remote502 /// computer(s). The following formats are supported:503 /// (a) Computer name504 /// (b) IPv4 address : 132.3.4.5505 /// (c) IPv6 address: 3ffe:8311:ffff:f70f:0:5efe:172.30.162.18.506 /// </summary>507 [Parameter(Position = 0,508 ValueFromPipelineByPropertyName = true,509 ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]510 [Alias("Cn")]511 public virtual string[] ComputerName { get; set; }512 513 /// <summary>514 /// Computer names after they have been resolved515 /// (null, empty string, "." resolves to localhost)516 /// </summary>517 /// <remarks>If Null or empty string is specified, then localhost is assumed.518 /// The ResolveComputerNames will include this.519 /// </remarks>520 protected string[] ResolvedComputerNames { get; set; }521 522 /// <summary>523 /// Guid of target virtual machine.524 /// </summary>525 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays",526 Justification = "This is by spec.")]527 [Parameter(Position = 0,528 Mandatory = true,529 ValueFromPipelineByPropertyName = true,530 ParameterSetName = PSRemotingBaseCmdlet.VMIdParameterSet)]531 [ValidateNotNullOrEmpty]532 [Alias("VMGuid")]533 public virtual Guid[] VMId { get; set; }534 535 /// <summary>536 /// Name of target virtual machine.537 /// </summary>538 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays",539 Justification = "This is by spec.")]540 [Parameter(Mandatory = true,541 ValueFromPipelineByPropertyName = true,542 ParameterSetName = PSRemotingBaseCmdlet.VMNameParameterSet)]543 [ValidateNotNullOrEmpty]544 public virtual string[] VMName { get; set; }545 546 /// <summary>547 /// Specifies the credentials of the user to impersonate in the548 /// remote machine. If this parameter is not specified then the549 /// credentials of the current user process will be assumed.550 /// </summary>551 [Parameter(ValueFromPipelineByPropertyName = true,552 ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]553 [Parameter(ValueFromPipelineByPropertyName = true,554 ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)]555 [Parameter(ValueFromPipelineByPropertyName = true,556 ParameterSetName = PSRemotingBaseCmdlet.VMIdParameterSet)]557 [Parameter(ValueFromPipelineByPropertyName = true,558 ParameterSetName = PSRemotingBaseCmdlet.VMNameParameterSet)]559 [Credential()]560 public virtual PSCredential Credential561 {562 get563 {564 return _pscredential;565 }566 567 set568 {569 _pscredential = value;570 ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication);571 }572 }573 574 private PSCredential _pscredential;575 576 /// <summary>577 /// ID of target container.578 /// </summary>579 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays",580 Justification = "This is by spec.")]581 [Parameter(Mandatory = true,582 ValueFromPipelineByPropertyName = true,583 ParameterSetName = PSRemotingBaseCmdlet.ContainerIdParameterSet)]584 [ValidateNotNullOrEmpty]585 public virtual string[] ContainerId { get; set; }586 587 /// <summary>588 /// When set, PowerShell process inside container will be launched with589 /// high privileged account.590 /// Otherwise (default case), PowerShell process inside container will be launched591 /// with low privileged account.592 /// </summary>593 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ContainerIdParameterSet)]594 public virtual SwitchParameter RunAsAdministrator { get; set; }595 596 /// <summary>597 /// Port specifies the alternate port to be used in case the598 /// default ports are not used for the transport mechanism599 /// (port 80 for http and port 443 for useSSL)600 /// </summary>601 /// <remarks>602 /// Currently this is being accepted as a parameter. But in future603 /// support will be added to make this a part of a policy setting.604 /// When a policy setting is in place this parameter can be used605 /// to override the policy setting606 /// </remarks>607 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]608 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]609 [ValidateRange((int)1, (int)UInt16.MaxValue)]610 public virtual int Port { get; set; }611 612 /// <summary>613 /// This parameter suggests that the transport scheme to be used for614 /// remote connections is useSSL instead of the default http.Since615 /// there are only two possible transport schemes that are possible616 /// at this point, a SwitchParameter is being used to switch between617 /// the two.618 /// </summary>619 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]620 [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SSL")]621 public virtual SwitchParameter UseSSL { get; set; }622 623 /// <summary>624 /// This parameters specifies the appname which identifies the connection625 /// end point on the remote machine. If this parameter is not specified626 /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If that's627 /// not specified as well, then "WSMAN" will be used.628 /// </summary>629 [Parameter(ValueFromPipelineByPropertyName = true,630 ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]631 public virtual string ApplicationName632 {633 get634 {635 return _appName;636 }637 638 set639 {640 _appName = ResolveAppName(value);641 }642 }643 644 private string _appName;645 646 /// <summary>647 /// Allows the user of the cmdlet to specify a throttling value648 /// for throttling the number of remote operations that can649 /// be executed simultaneously.650 /// </summary>651 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]652 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SessionParameterSet)]653 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)]654 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ContainerIdParameterSet)]655 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.VMIdParameterSet)]656 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.VMNameParameterSet)]657 public virtual int ThrottleLimit { get; set; } = 0;658 659 /// <summary>660 /// A complete URI(s) specified for the remote computer and shell to661 /// connect to and create runspace for.662 /// </summary>663 [Parameter(Position = 0, Mandatory = true,664 ValueFromPipelineByPropertyName = true,665 ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)]666 [ValidateNotNullOrEmpty]667 [Alias("URI", "CU")]668 public virtual Uri[] ConnectionUri { get; set; }669 670 /// <summary>671 /// The AllowRedirection parameter enables the implicit redirection functionality.672 /// </summary>673 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)]674 public virtual SwitchParameter AllowRedirection675 {676 get { return _allowRedirection; }677 678 set { _allowRedirection = value; }679 }680 681 private bool _allowRedirection = false;682 683 /// <summary>684 /// Extended Session Options for controlling the session creation. Use685 /// "New-WSManSessionOption" cmdlet to supply value for this parameter.686 /// </summary>687 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]688 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)]689 [ValidateNotNull]690 public virtual PSSessionOption SessionOption691 {692 get693 {694 if (_sessionOption == null)695 {696 object tmp = this.SessionState.PSVariable.GetValue(DEFAULT_SESSION_OPTION);697 if (tmp == null || !LanguagePrimitives.TryConvertTo<PSSessionOption>(tmp, out _sessionOption))698 {699 _sessionOption = new PSSessionOption();700 }701 }702 703 return _sessionOption;704 }705 706 set707 {708 _sessionOption = value;709 }710 }711 712 private PSSessionOption _sessionOption;713 714 internal const string DEFAULT_SESSION_OPTION = "PSSessionOption";715 716 // Quota related variables.717 /// <summary>718 /// Use basic authentication to authenticate the user.719 /// </summary>720 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]721 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)]722 public virtual AuthenticationMechanism Authentication723 {724 get725 {726 return _authMechanism;727 }728 729 set730 {731 _authMechanism = value;732 // Validate if a user can specify this authentication.733 ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication);734 }735 }736 737 private AuthenticationMechanism _authMechanism = AuthenticationMechanism.Default;738 739 /// <summary>740 /// Specifies the certificate thumbprint to be used to impersonate the user on the741 /// remote machine.742 /// </summary>743 [Parameter(ParameterSetName = NewPSSessionCommand.ComputerNameParameterSet)]744 [Parameter(ParameterSetName = NewPSSessionCommand.UriParameterSet)]745 public virtual string CertificateThumbprint746 {747 get748 {749 return _thumbPrint;750 }751 752 set753 {754 _thumbPrint = value;755 ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication);756 }757 }758 759 private string _thumbPrint = null;760 761 #region SSHHostParameters762 763 /// <summary>764 /// Host name for an SSH remote connection.765 /// </summary>766 [Parameter(Position = 0, Mandatory = true,767 ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]768 [ValidateNotNullOrEmpty()]769 public virtual string[] HostName770 {771 get;772 set;773 }774 775 /// <summary>776 /// SSH User Name.777 /// </summary>778 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]779 [ValidateNotNullOrEmpty()]780 public virtual string UserName781 {782 get;783 set;784 }785 786 /// <summary>787 /// SSH Key File Path.788 /// </summary>789 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]790 [ValidateNotNullOrEmpty()]791 [Alias("IdentityFilePath")]792 public virtual string KeyFilePath793 {794 get;795 set;796 }797 798 /// <summary>799 /// Gets or sets a value for the SSH subsystem to use for the remote connection.800 /// </summary>801 [Parameter(ValueFromPipelineByPropertyName = true,802 ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]803 public virtual string Subsystem { get; set; }804 805 /// <summary>806 /// Gets or sets a value in milliseconds that limits the time allowed for an SSH connection to be established.807 /// Default timeout value is infinite.808 /// </summary>809 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]810 public virtual int ConnectingTimeout { get; set; } = Timeout.Infinite;811 812 /// <summary>813 /// This parameter specifies that SSH is used to establish the remote814 /// connection and act as the remoting transport. By default WinRM is used815 /// as the remoting transport. Using the SSH transport requires that SSH is816 /// installed and PowerShell remoting is enabled on both client and remote machines.817 /// </summary>818 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]819 [ValidateSet("true")]820 public virtual SwitchParameter SSHTransport821 {822 get;823 set;824 }825 826 /// <summary>827 /// Hashtable array containing SSH connection parameters for each remote target828 /// ComputerName (Alias: HostName) (required)829 /// UserName (optional)830 /// KeyFilePath (Alias: IdentityFilePath) (optional)831 /// </summary>832 [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostHashParameterSet, Mandatory = true)]833 [ValidateNotNullOrEmpty()]834 public virtual Hashtable[] SSHConnection835 {836 get;837 set;838 }839 840 /// <summary>841 /// Gets or sets the Hashtable containing options to be passed to OpenSSH.842 /// </summary>843 [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]844 [ValidateNotNullOrEmpty]845 public virtual Hashtable Options { get; set; }846 847 #endregion848 849 #endregion Properties850 851 #region Internal Static Methods852 853 /// <summary>854 /// Used to resolve authentication from the parameters chosen by the user.855 /// User has the following options:856 /// 1. AuthMechanism + Credential857 /// 2. CertificateThumbPrint858 ///859 /// All the above are mutually exclusive.860 /// </summary>861 /// <exception cref="InvalidOperationException">862 /// If there is ambiguity as specified above.863 /// </exception>864 internal static void ValidateSpecifiedAuthentication(PSCredential credential, string thumbprint, AuthenticationMechanism authentication)865 {866 if ((credential != null) && (thumbprint != null))867 {868 string message = PSRemotingErrorInvariants.FormatResourceString(869 RemotingErrorIdStrings.NewRunspaceAmbiguousAuthentication,870 "CertificateThumbPrint", "Credential");871 872 throw new InvalidOperationException(message);873 }874 875 if ((authentication != AuthenticationMechanism.Default) && (thumbprint != null))876 {877 string message = PSRemotingErrorInvariants.FormatResourceString(878 RemotingErrorIdStrings.NewRunspaceAmbiguousAuthentication,879 "CertificateThumbPrint", authentication.ToString());880 881 throw new InvalidOperationException(message);882 }883 884 if ((authentication == AuthenticationMechanism.NegotiateWithImplicitCredential) &&885 (credential != null))886 {887 string message = PSRemotingErrorInvariants.FormatResourceString(888 RemotingErrorIdStrings.NewRunspaceAmbiguousAuthentication,889 "Credential", authentication.ToString());890 throw new InvalidOperationException(message);891 }892 }893 894 #endregion895 896 #region Internal Methods897 898 #region SSH Connection Strings899 900 private const string ComputerNameParameter = "ComputerName";901 private const string HostNameAlias = "HostName";902 private const string UserNameParameter = "UserName";903 private const string KeyFilePathParameter = "KeyFilePath";904 private const string IdentityFilePathAlias = "IdentityFilePath";905 private const string PortParameter = "Port";906 private const string SubsystemParameter = "Subsystem";907 private const string ConnectingTimeoutParameter = "ConnectingTimeout";908 private const string OptionsParameter = "Options";909 910 #endregion911 912 /// <summary>913 /// Parse a hostname used with SSH Transport to get embedded914 /// username and/or port.915 /// </summary>916 /// <param name="hostname">Host name to parse.</param>917 /// <param name="host">Resolved target host.</param>918 /// <param name="userName">Resolved target user name.</param>919 /// <param name="port">Resolved target port.</param>920 protected void ParseSshHostName(string hostname, out string host, out string userName, out int port)921 {922 host = hostname;923 userName = this.UserName;924 port = this.Port;925 try926 {927 Uri uri = new System.Uri("ssh://" + hostname);928 host = ResolveComputerName(uri.Host);929 ValidateComputerName(new string[] { host });930 if (uri.UserInfo != string.Empty)931 {932 userName = uri.UserInfo;933 }934 935 if (uri.Port != -1)936 {937 port = uri.Port;938 }939 }940 catch (UriFormatException)941 {942 ThrowTerminatingError(new ErrorRecord(943 new ArgumentException(PSRemotingErrorInvariants.FormatResourceString(944 RemotingErrorIdStrings.InvalidComputerName)), "PSSessionInvalidComputerName",945 ErrorCategory.InvalidArgument, hostname));946 }947 }948 949 /// <summary>950 /// Parse the Connection parameter HashTable array.951 /// </summary>952 /// <returns>Array of SSHConnection objects.</returns>953 internal SSHConnection[] ParseSSHConnectionHashTable()954 {955 List<SSHConnection> connections = new();956 foreach (var item in this.SSHConnection)957 {958 if (item.ContainsKey(ComputerNameParameter) && item.ContainsKey(HostNameAlias))959 {960 throw new PSArgumentException(RemotingErrorIdStrings.SSHConnectionDuplicateHostName);961 }962 963 if (item.ContainsKey(KeyFilePathParameter) && item.ContainsKey(IdentityFilePathAlias))964 {965 throw new PSArgumentException(RemotingErrorIdStrings.SSHConnectionDuplicateKeyPath);966 }967 968 SSHConnection connectionInfo = new();969 foreach (var key in item.Keys)970 {971 string paramName = key as string;972 if (string.IsNullOrEmpty(paramName))973 {974 throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter);975 }976 977 if (paramName.Equals(ComputerNameParameter, StringComparison.OrdinalIgnoreCase) || paramName.Equals(HostNameAlias, StringComparison.OrdinalIgnoreCase))978 {979 var resolvedComputerName = ResolveComputerName(GetSSHConnectionStringParameter(item[paramName]));980 ParseSshHostName(resolvedComputerName, out string host, out string userName, out int port);981 connectionInfo.ComputerName = host;982 if (userName != string.Empty)983 {984 connectionInfo.UserName = userName;985 }986 987 if (port != -1)988 {989 connectionInfo.Port = port;990 }991 }992 else if (paramName.Equals(UserNameParameter, StringComparison.OrdinalIgnoreCase))993 {994 connectionInfo.UserName = GetSSHConnectionStringParameter(item[paramName]);995 }996 else if (paramName.Equals(KeyFilePathParameter, StringComparison.OrdinalIgnoreCase) || paramName.Equals(IdentityFilePathAlias, StringComparison.OrdinalIgnoreCase))997 {998 connectionInfo.KeyFilePath = GetSSHConnectionStringParameter(item[paramName]);999 }1000 else if (paramName.Equals(PortParameter, StringComparison.OrdinalIgnoreCase))1001 {1002 connectionInfo.Port = GetSSHConnectionIntParameter(item[paramName]);1003 }1004 else if (paramName.Equals(SubsystemParameter, StringComparison.OrdinalIgnoreCase))1005 {1006 connectionInfo.Subsystem = GetSSHConnectionStringParameter(item[paramName]);1007 }1008 else if (paramName.Equals(ConnectingTimeoutParameter, StringComparison.OrdinalIgnoreCase))1009 {1010 connectionInfo.ConnectingTimeout = GetSSHConnectionIntParameter(item[paramName]);1011 }1012 else if (paramName.Equals(OptionsParameter, StringComparison.OrdinalIgnoreCase))1013 {1014 connectionInfo.Options = item[paramName] as Hashtable;1015 }1016 else1017 {1018 throw new PSArgumentException(1019 StringUtil.Format(RemotingErrorIdStrings.UnknownSSHConnectionParameter, paramName));1020 }1021 }1022 1023 if (string.IsNullOrEmpty(connectionInfo.ComputerName))1024 {1025 throw new PSArgumentException(RemotingErrorIdStrings.MissingRequiredSSHParameter);1026 }1027 1028 connections.Add(connectionInfo);1029 }1030 1031 return connections.ToArray();1032 }1033 1034 #endregion1035 1036 #region Private Methods1037 1038 /// <summary>1039 /// Validate the PSSession objects specified and write1040 /// appropriate error records.1041 /// </summary>1042 /// <remarks>This function will lead in terminating errors when any of1043 /// the validations fail</remarks>1044 protected void ValidateRemoteRunspacesSpecified()1045 {1046 Dbg.Assert(Session != null && Session.Length != 0,1047 "Remote Runspaces specified must not be null or empty");1048 1049 // Check if there are duplicates in the specified PSSession objects1050 if (RemotingCommandUtil.HasRepeatingRunspaces(Session))1051 {1052 ThrowTerminatingError(new ErrorRecord(new ArgumentException(1053 GetMessage(RemotingErrorIdStrings.RemoteRunspaceInfoHasDuplicates)),1054 nameof(PSRemotingErrorId.RemoteRunspaceInfoHasDuplicates),1055 ErrorCategory.InvalidArgument, Session));1056 }1057 1058 // BUGBUG: The following is a bogus check1059 // Check if the number of PSSession objects specified is greater1060 // than the maximum allowable range1061 if (RemotingCommandUtil.ExceedMaximumAllowableRunspaces(Session))1062 {1063 ThrowTerminatingError(new ErrorRecord(new ArgumentException(1064 GetMessage(RemotingErrorIdStrings.RemoteRunspaceInfoLimitExceeded)),1065 nameof(PSRemotingErrorId.RemoteRunspaceInfoLimitExceeded),1066 ErrorCategory.InvalidArgument, Session));1067 }1068 }1069 1070 /// <summary>1071 /// Updates connection info with the data read from cmdlet's parameters and1072 /// sessions variables.1073 /// The following data is updated:1074 /// 1. MaxURIRedirectionCount1075 /// 2. MaxRecvdDataSizePerSession1076 /// 3. MaxRecvdDataSizePerCommand1077 /// 4. MaxRecvdObjectSize.1078 /// </summary>1079 /// <param name="connectionInfo"></param>1080 internal void UpdateConnectionInfo(WSManConnectionInfo connectionInfo)1081 {1082 Dbg.Assert(connectionInfo != null, "connectionInfo cannot be null.");1083 1084 connectionInfo.SetSessionOptions(this.SessionOption);1085 1086 if (!ParameterSetName.Equals(PSRemotingBaseCmdlet.UriParameterSet, StringComparison.OrdinalIgnoreCase))1087 {1088 // uri redirection is supported only with URI parameter set1089 connectionInfo.MaximumConnectionRedirectionCount = 0;1090 }1091 1092 if (!_allowRedirection)1093 {1094 // uri redirection required explicit user consent1095 connectionInfo.MaximumConnectionRedirectionCount = 0;1096 }1097 }1098 1099 /// <summary>1100 /// Uri parameter set.1101 /// </summary>1102 protected const string UriParameterSet = "Uri";1103 1104 /// <summary>1105 /// Validates computer names to check if none of them1106 /// happen to be a Uri. If so this throws an error.1107 /// </summary>1108 /// <param name="computerNames">collection of computer1109 /// names to validate</param>1110 protected void ValidateComputerName(string[] computerNames)1111 {1112 foreach (string computerName in computerNames)1113 {1114 UriHostNameType nametype = Uri.CheckHostName(computerName);1115 if (!(nametype == UriHostNameType.Dns || nametype == UriHostNameType.IPv4 ||1116 nametype == UriHostNameType.IPv6))1117 {1118 ThrowTerminatingError(new ErrorRecord(1119 new ArgumentException(PSRemotingErrorInvariants.FormatResourceString(1120 RemotingErrorIdStrings.InvalidComputerName)), "PSSessionInvalidComputerName",1121 ErrorCategory.InvalidArgument, computerNames));1122 }1123 }1124 }1125 1126 /// <summary>1127 /// Validates parameter value and returns as string.1128 /// </summary>1129 /// <param name="param">Parameter value to be validated.</param>1130 /// <returns>Parameter value as string.</returns>1131 private static string GetSSHConnectionStringParameter(object param)1132 {1133 string paramValue;1134 try1135 {1136 paramValue = LanguagePrimitives.ConvertTo<string>(param);1137 }1138 catch (PSInvalidCastException e)1139 {1140 throw new PSArgumentException(e.Message, e);1141 }1142 1143 if (!string.IsNullOrEmpty(paramValue))1144 {1145 return paramValue;1146 }1147 1148 throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter);1149 }1150 1151 /// <summary>1152 /// Validates parameter value and returns as integer.1153 /// </summary>1154 /// <param name="param">Parameter value to be validated.</param>1155 /// <returns>Parameter value as integer.</returns>1156 private static int GetSSHConnectionIntParameter(object param)1157 {1158 if (param == null)1159 {1160 throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter);1161 }1162 1163 try1164 {1165 return LanguagePrimitives.ConvertTo<int>(param);1166 }1167 catch (PSInvalidCastException e)1168 {1169 throw new PSArgumentException(e.Message, e);1170 }1171 }1172 1173 #endregion Private Methods1174 1175 #region Overrides1176 1177 /// <summary>1178 /// Resolves shellname and appname.1179 /// </summary>1180 protected override void BeginProcessing()1181 {1182 base.BeginProcessing();1183 1184 // Validate KeyFilePath parameter.1185 if ((ParameterSetName == PSRemotingBaseCmdlet.SSHHostParameterSet) &&1186 (this.KeyFilePath != null))1187 {1188 // Resolve the key file path when set.1189 this.KeyFilePath = PathResolver.ResolveProviderAndPath(this.KeyFilePath, true, this, false, RemotingErrorIdStrings.FilePathNotFromFileSystemProvider);1190 }1191 1192 // Validate IdleTimeout parameter.1193 int idleTimeout = (int)SessionOption.IdleTimeout.TotalMilliseconds;1194 if (idleTimeout != BaseTransportManager.UseServerDefaultIdleTimeout &&1195 idleTimeout < BaseTransportManager.MinimumIdleTimeout)1196 {1197 throw new PSArgumentException(1198 StringUtil.Format(RemotingErrorIdStrings.InvalidIdleTimeoutOption,1199 idleTimeout / 1000, BaseTransportManager.MinimumIdleTimeout / 1000));1200 }