/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System; using System.Linq; using System.Management.Automation; using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Management.Automation.Internal; using Dbg = System.Management.Automation.Diagnostics; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Management.Automation.Language; namespace Microsoft.PowerShell.Commands { /// /// This class defines most of the common functionality used /// across remoting cmdlets. /// /// It contains tons of utility functions which are used all /// across the remoting cmdlets /// public abstract partial class PSRemotingCmdlet : PSCmdlet { #region Overrides /// /// Verifies if remoting cmdlets can be used /// protected override void BeginProcessing() { if (!_skipWinRMCheck) { RemotingCommandUtil.CheckRemotingCmdletPrerequisites(); } } #endregion Overrides #region Utility functions /// /// Handle the object obtained from an ObjectStream's reader /// based on its type /// internal void WriteStreamObject(Action action) { action(this); }// WriteStreamObject /// /// Resolve all the machine names provided. Basically, if a machine /// name is '.' assume localhost /// /// array of computer names to resolve /// resolved array of machine names protected void ResolveComputerNames(string[] computerNames, out string[] resolvedComputerNames) { if (computerNames == null) { resolvedComputerNames = new string[1]; resolvedComputerNames[0] = ResolveComputerName("."); } else if (computerNames.Length == 0) { resolvedComputerNames = Utils.EmptyArray(); } else { resolvedComputerNames = new string[computerNames.Length]; for (int i = 0; i < resolvedComputerNames.Length; i++) { resolvedComputerNames[i] = ResolveComputerName(computerNames[i]); } } }// ResolveComputerNames /// /// Resolves a computer name. If its null or empty /// its assumed to be localhost /// /// computer name to resolve /// resolved computer name protected string ResolveComputerName(string computerName) { Diagnostics.Assert(computerName != null, "Null ComputerName"); if (String.Equals(computerName, ".", StringComparison.OrdinalIgnoreCase)) { //tracer.WriteEvent(ref PSEventDescriptors.PS_EVENT_HOSTNAMERESOLVE); //tracer.Dispose(); //tracer.OperationalChannel.WriteVerbose(PSEventId.HostNameResolve, PSOpcode.Method, PSTask.CreateRunspace); return LOCALHOST; } else { return computerName; } } /// /// Load the resource corresponding to the specified errorId and /// return the message as a string /// /// resource String which holds the message /// /// Error message loaded from appropriate resouce cache internal String GetMessage(string resourceString) { String message = GetMessage(resourceString, null); return message; }// GetMessage /// /// /// /// /// /// internal String GetMessage(string resourceString, params object[] args) { String message; if (args != null) { message = StringUtil.Format(resourceString, args); } else { message = resourceString; } return message; } #endregion Utility functions #region Private Members private static string LOCALHOST = "localhost"; //private PSETWTracer tracer = PSETWTracer.GetETWTracer(PSKeyword.Cmdlets); #endregion Private Members #region Protected Members /// /// Computername parameter set /// protected const string ComputerNameParameterSet = "ComputerName"; /// /// Computername with session instance ID parameter set /// protected const string ComputerInstanceIdParameterSet = "ComputerInstanceId"; /// /// Container ID parameter set /// protected const string ContainerIdParameterSet = "ContainerId"; /// /// Container name parameter set /// protected const string ContainerNameParameterSet = "ContainerName"; /// /// VM guid parameter set /// protected const string VMIdParameterSet = "VMId"; /// /// VM name parameter set /// protected const string VMNameParameterSet = "VMName"; /// /// runspace parameter set /// protected const string SessionParameterSet = "Session"; /// /// Default shellname /// protected const string DefaultPowerShellRemoteShellName = System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix + "Microsoft.PowerShell"; /// /// default application name for the connection uri /// protected const string DefaultPowerShellRemoteShellAppName = "WSMan"; #endregion Protected Members #region Internal Members private bool _skipWinRMCheck = false; /// /// Skip checking for WinRM /// internal bool SkipWinRMCheck { get { return _skipWinRMCheck; } set { _skipWinRMCheck = value; } } #endregion Internal Members #region Protected Methods /// /// Determines the shellname to use based on the following order: /// 1. ShellName parameter specified /// 2. DEFAULTREMOTESHELLNAME variable set /// 3. PowerShell /// /// The shell to launch in the remote machine protected String ResolveShell(String shell) { String resolvedShell; if (!String.IsNullOrEmpty(shell)) { resolvedShell = shell; } else { resolvedShell = (string)SessionState.Internal.ExecutionContext.GetVariableValue( SpecialVariables.PSSessionConfigurationNameVarPath, DefaultPowerShellRemoteShellName); } return resolvedShell; } /// /// Determines the appname to be used based on the following order: /// 1. AppName parameter specified /// 2. DEFAULTREMOTEAPPNAME variable set /// 3. WSMan /// /// application name to resolve /// resolved appname protected String ResolveAppName(String appName) { String resolvedAppName; if (!String.IsNullOrEmpty(appName)) { resolvedAppName = appName; } else { resolvedAppName = (string)SessionState.Internal.ExecutionContext.GetVariableValue( SpecialVariables.PSSessionApplicationNameVarPath, DefaultPowerShellRemoteShellAppName); } return resolvedAppName; } #endregion } /// /// Base class for any cmdlet which takes a -Session parameter /// or a -ComputerName parameter (along with its other associated /// parameters). The following cmdlets currently fall under this /// category: /// 1. New-PSSession /// 2. Invoke-Expression /// 3. Start-PSJob /// public abstract partial class PSRemotingBaseCmdlet : PSRemotingCmdlet { /// /// State of virtual machine. This is the same as VMState in /// \vm\ux\powershell\objects\common\Types.cs /// internal enum VMState { /// /// Other. Corresponds to CIM_EnabledLogicalElement.EnabledState = Other. /// Other = 1, /// /// Running. Corresponds to CIM_EnabledLogicalElement.EnabledState = Enabled. /// Running = 2, /// /// Off. Corresponds to CIM_EnabledLogicalElement.EnabledState = Disabled. /// Off = 3, /// /// Stopping. Corresponds to CIM_EnabledLogicalElement.EnabledState = ShuttingDown. /// Stopping = 4, /// /// Saved. Corresponds to CIM_EnabledLogicalElement.EnabledState = Enabled but offline. /// Saved = 6, /// /// Paused. Corresponds to CIM_EnabledLogicalElement.EnabledState = Quiesce. /// Paused = 9, /// /// Starting. EnabledStateStarting. State transition from PowerOff or Saved to Running. /// Starting = 10, /// /// Reset. Corresponds to CIM_EnabledLogicalElement.EnabledState = Reset. /// Reset = 11, /// /// Saving. Corresponds to EnabledStateSaving. /// Saving = 32773, /// /// Pausing. Corresponds to EnabledStatePausing. /// Pausing = 32776, /// /// Resuming. Corresponds to EnabledStateResuming. /// Resuming = 32777, /// /// FastSaved. EnabledStateFastSuspend. /// FastSaved = 32779, /// /// FastSaving. EnabledStateFastSuspending. /// FastSaving = 32780, /// /// ForceShutdown. Used to force a graceful shutdown of the virtual machine. /// ForceShutdown = 32781, /// /// ForceReboot. Used to force a graceful reboot of the virtual machine. /// ForceReboot = 32782, /// /// RunningCritical. Critical states. /// RunningCritical, /// /// OffCritical. Critical states. /// OffCritical, /// /// StoppingCritical. Critical states. /// StoppingCritical, /// /// SavedCritical. Critical states. /// SavedCritical, /// /// PausedCritical. Critical states. /// PausedCritical, /// /// StartingCritical. Critical states. /// StartingCritical, /// /// ResetCritical. Critical states. /// ResetCritical, /// /// SavingCritical. Critical states. /// SavingCritical, /// /// PausingCritical. Critical states. /// PausingCritical, /// /// ResumingCritical. Critical states. /// ResumingCritical, /// /// FastSavedCritical. Critical states. /// FastSavedCritical, /// /// FastSavingCritical. Critical states. /// FastSavingCritical, } #region Tracer //PSETWTracer tracer = PSETWTracer.GetETWTracer(PSKeyword.Runspace); #endregion Tracer #region Properties /// /// The PSSession object describing the remote runspace /// using which the specified cmdlet operation will be performed /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.SessionParameterSet)] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public virtual PSSession[] Session { get { return remoteRunspaceInfos; } set { remoteRunspaceInfos = value; } } private PSSession[] remoteRunspaceInfos; /// /// This parameter represents the address(es) of the remote /// computer(s). The following formats are supported: /// (a) Computer name /// (b) IPv4 address : 132.3.4.5 /// (c) IPv6 address: 3ffe:8311:ffff:f70f:0:5efe:172.30.162.18 /// /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)] [Alias("Cn")] public virtual String[] ComputerName { get { return computerNames; } set { computerNames = value; } } private String[] computerNames; /// /// Computer names after they have been resolved /// (null, empty string, "." resolves to localhost) /// /// If Null or empty string is specified, then localhost is assumed. /// The ResolveComputerNames will include this. /// protected String[] ResolvedComputerNames { get { return resolvedComputerNames; } set { resolvedComputerNames = value; } } private String[] resolvedComputerNames; /// /// Guid of target virtual machine. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is by spec.")] [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.VMIdParameterSet)] [ValidateNotNullOrEmpty] [Alias("VMGuid")] public virtual Guid[] VMId { get { return vmId; } set { vmId = value; } } private Guid[] vmId; /// /// Name of target virtual machine. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is by spec.")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.VMNameParameterSet)] [ValidateNotNullOrEmpty] public virtual string[] VMName { get { return vmName; } set { vmName = value; } } private string[] vmName; /// /// 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(ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.VMIdParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.VMNameParameterSet)] [Credential()] public virtual PSCredential Credential { get { return pscredential; } set { pscredential = value; ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } private PSCredential pscredential; /// /// ID of target container. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is by spec.")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.ContainerIdParameterSet)] [ValidateNotNullOrEmpty] public virtual string[] ContainerId { get { return containerId; } set { containerId = value; } } private string[] containerId; /// /// Name of target container. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is by spec.")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.ContainerNameParameterSet)] [ValidateNotNullOrEmpty] public virtual string[] ContainerName { get { return containerName; } set { containerName = value; } } private string[] containerName; /// /// When set, PowerShell process inside container will be launched with /// high privileged account. /// Otherwise (default case), PowerShell process inside container will be launched /// with low privileged account. /// [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ContainerIdParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ContainerNameParameterSet)] public virtual SwitchParameter RunAsAdministrator { get { return runAsAdministrator; } set { runAsAdministrator = value; } } private SwitchParameter runAsAdministrator; /// /// 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 = PSRemotingBaseCmdlet.ComputerNameParameterSet)] [ValidateRange((Int32)1, (Int32)UInt16.MaxValue)] public virtual Int32 Port { get { return port; } set { port = value; } } private Int32 port; /// /// 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 = PSRemotingBaseCmdlet.ComputerNameParameterSet)] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SSL")] public virtual SwitchParameter UseSSL { get { return useSSL; } set { useSSL = value; } } private SwitchParameter useSSL; /// /// 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 = PSRemotingBaseCmdlet.ComputerNameParameterSet)] public virtual String ApplicationName { get { return appName; } set { appName = ResolveAppName(value); } } private String appName; /// /// 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 = PSRemotingBaseCmdlet.ComputerNameParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SessionParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ContainerIdParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ContainerNameParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.VMIdParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.VMNameParameterSet)] public virtual Int32 ThrottleLimit { set { throttleLimit = value; } get { return throttleLimit; } } private int throttleLimit = 0; /// /// A complete URI(s) specified for the remote computer and shell to /// connect to and create runspace for /// [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)] [ValidateNotNullOrEmpty] [Alias("URI", "CU")] public virtual Uri[] ConnectionUri { get { return uris; } set { uris = value; } } private Uri[] uris; /// /// The AllowRediraction parameter enables the implicit redirection functionality /// [Parameter(ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)] public virtual SwitchParameter AllowRedirection { get { return this.allowRedirection; } set { this.allowRedirection = value; } } private bool allowRedirection = false; /// /// Extended Session Options for controlling the session creation. Use /// "New-WSManSessionOption" cmdlet to supply value for this parameter. /// [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)] [ValidateNotNull] public virtual PSSessionOption SessionOption { get { if (this.sessionOption == null) { object tmp = this.SessionState.PSVariable.GetValue(DEFAULT_SESSION_OPTION); if (tmp == null || !LanguagePrimitives.TryConvertTo(tmp, out this.sessionOption)) { this.sessionOption = new PSSessionOption(); } } return this.sessionOption; } set { this.sessionOption = value; } } private PSSessionOption sessionOption; internal const string DEFAULT_SESSION_OPTION = "PSSessionOption"; // Quota related variables. /// /// Use basic authentication to authenticate the user. /// [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)] public virtual AuthenticationMechanism Authentication { get { return authMechanism; } set { authMechanism = value; // Validate if a user can specify this authentication. ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } private AuthenticationMechanism authMechanism = AuthenticationMechanism.Default; /// /// Specifies the certificate thumbprint to be used to impersonate the user on the /// remote machine. /// [Parameter(ParameterSetName = NewPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = NewPSSessionCommand.UriParameterSet)] public virtual string CertificateThumbprint { get { return thumbPrint; } set { thumbPrint = value; ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } private string thumbPrint = null; #endregion Properties #region Internal Static Methods /// /// Used to resolve authentication from the parameters chosen by the user. /// User has the following options: /// 1. AuthMechanism + Credential /// 2. CertiticateThumbPrint /// /// All the above are mutually exclusive. /// /// /// If there is ambiguity as specified above. /// internal static void ValidateSpecifiedAuthentication(PSCredential credential, string thumbprint, AuthenticationMechanism authentication) { if ((credential != null) && (thumbprint != null)) { String message = PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.NewRunspaceAmbiguosAuthentication, "CertificateThumbPrint", "Credential"); throw new InvalidOperationException(message); } if ((authentication != AuthenticationMechanism.Default) && (thumbprint != null)) { String message = PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.NewRunspaceAmbiguosAuthentication, "CertificateThumbPrint", authentication.ToString()); throw new InvalidOperationException(message); } if ((authentication == AuthenticationMechanism.NegotiateWithImplicitCredential) && (credential != null)) { string message = PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.NewRunspaceAmbiguosAuthentication, "Credential", authentication.ToString()); throw new InvalidOperationException(message); } } #endregion #region Private Methods /// /// Validate the PSSession objects specified and write /// appropriate error records. /// /// This function will lead in terminating errors when any of /// the validations fail protected void ValidateRemoteRunspacesSpecified() { Dbg.Assert(Session != null && Session.Length != 0, "Remote Runspaces specified must not be null or empty"); // Check if there are duplicates in the specified PSSession objects if (RemotingCommandUtil.HasRepeatingRunspaces(Session)) { ThrowTerminatingError(new ErrorRecord(new ArgumentException( GetMessage(RemotingErrorIdStrings.RemoteRunspaceInfoHasDuplicates)), PSRemotingErrorId.RemoteRunspaceInfoHasDuplicates.ToString(), ErrorCategory.InvalidArgument, Session)); } //BUGBUG: The following is a bogus check // Check if the number of PSSession objects specified is greater // than the maximum allowable range if (RemotingCommandUtil.ExceedMaximumAllowableRunspaces(Session)) { ThrowTerminatingError(new ErrorRecord(new ArgumentException( GetMessage(RemotingErrorIdStrings.RemoteRunspaceInfoLimitExceeded)), PSRemotingErrorId.RemoteRunspaceInfoLimitExceeded.ToString(), ErrorCategory.InvalidArgument, Session)); } } // ValidateRemoteRunspacesSpecified /// /// Updates connection info with the data read from cmdlet's parameters and /// sessions variables. /// The following data is updated: /// 1. MaxURIRedirectionCount /// 2. MaxRecvdDataSizePerSession /// 3. MaxRecvdDataSizePerCommand /// 4. MaxRecvdObjectSize /// /// internal void UpdateConnectionInfo(WSManConnectionInfo connectionInfo) { Dbg.Assert(connectionInfo != null, "connectionInfo cannot be null."); connectionInfo.SetSessionOptions(this.SessionOption); if (!ParameterSetName.Equals(PSRemotingBaseCmdlet.UriParameterSet, StringComparison.OrdinalIgnoreCase)) { // uri redirection is supported only with URI parmeter set connectionInfo.MaximumConnectionRedirectionCount = 0; } if (!this.allowRedirection) { // uri redirection required explicit user consent connectionInfo.MaximumConnectionRedirectionCount = 0; } } /// /// Uri parameter set /// protected const string UriParameterSet = "Uri"; /// /// Validates computer names to check if none of them /// happen to be a Uri. If so this throws an error /// /// collection of computer /// names to validate protected void ValidateComputerName(String[] computerNames) { foreach (String computerName in computerNames) { UriHostNameType nametype = Uri.CheckHostName(computerName); if (!(nametype == UriHostNameType.Dns || nametype == UriHostNameType.IPv4 || nametype == UriHostNameType.IPv6)) { ThrowTerminatingError(new ErrorRecord( new ArgumentException(PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.InvalidComputerName)), "PSSessionInvalidComputerName", ErrorCategory.InvalidArgument, computerNames)); } } } #endregion Private Methods #region Overrides /// /// Resolves shellname and appname /// protected override void BeginProcessing() { base.BeginProcessing(); // Validate IdleTimeout parameter. int idleTimeout = (int)SessionOption.IdleTimeout.TotalMilliseconds; if (idleTimeout != BaseTransportManager.UseServerDefaultIdleTimeout && idleTimeout < BaseTransportManager.MinimumIdleTimeout) { throw new PSArgumentException( StringUtil.Format(RemotingErrorIdStrings.InvalidIdleTimeoutOption, idleTimeout / 1000, BaseTransportManager.MinimumIdleTimeout / 1000)); } if (String.IsNullOrEmpty(appName)) { appName = ResolveAppName(null); } } #endregion Overrides } /// /// Base class for any cmdlet which has to execute a pipeline. The /// following cmdlets currently fall under this category: /// 1. Invoke-Expression /// 2. Start-PSJob /// public abstract partial class PSExecutionCmdlet : PSRemotingBaseCmdlet { #region Strings /// /// VM guid file path parameter set. /// protected const string FilePathVMIdParameterSet = "FilePathVMId"; /// /// VM name file path parameter set. /// protected const string FilePathVMNameParameterSet = "FilePathVMName"; /// /// Container ID file path parameter set. /// protected const string FilePathContainerIdParameterSet = "FilePathContainerId"; /// /// Container name file path parameter set. /// protected const string FilePathContainerNameParameterSet = "FilePathContainerName"; #endregion #region Parameters /// /// Input object which gets assigned to $input when executed /// on the remote machine. This is the only parameter in /// this cmdlet which will bind with a ValueFromPipeline=true /// [Parameter(ValueFromPipeline = true)] public virtual PSObject InputObject { get { return inputObject; } set { inputObject = value; } } private PSObject inputObject = AutomationNull.Value; /// /// Command to execute specified as a string. This can be a single /// cmdlet, an expression or anything that can be internally /// converted into a ScriptBlock /// public virtual ScriptBlock ScriptBlock { get { return scriptBlock; } set { scriptBlock = value; } } private ScriptBlock scriptBlock; /// /// The file containing the script that the user has specified in the /// cmdlet. This will be converted to a powershell before /// its actually sent to the remote end /// [Parameter(Position = 1, Mandatory = true, ParameterSetName = FilePathComputerNameParameterSet)] [Parameter(Position = 1, Mandatory = true, ParameterSetName = FilePathSessionParameterSet)] [Parameter(Position = 1, Mandatory = true, ParameterSetName = FilePathUriParameterSet)] [ValidateNotNull] public virtual string FilePath { get { return filePath; } set { filePath = value; } } private string filePath; /// /// True if FilePath should be processed as a literal path /// protected bool IsLiteralPath { get; set; } /// /// Arguments that are passed to this scriptblock /// [Parameter()] [Alias("Args")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public virtual Object[] ArgumentList { get { return args; } set { args = value; } } private Object[] args; /// /// Indicates that if a job/command is invoked remotely the connection should be severed /// right have invocation of job/command. /// /// protected bool InvokeAndDisconnect { get { return this.invokeAndDisconnect; } set { this.invokeAndDisconnect = value; } } private bool invokeAndDisconnect = false; /// /// Session names optionally provided for Disconnected parameter. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] protected string[] DisconnectedSessionName { get { return sessionName; } set { sessionName = value; } } private string[] sessionName; /// /// When set and in loopback scenario (localhost) this enables creation of WSMan /// host process with the user interactive token, allowing PowerShell script network access, /// i.e., allows going off box. When this property is true and a PSSession is disconnected, /// reconnection is allowed only if reconnecting from a PowerShell session on the same box. /// public virtual SwitchParameter EnableNetworkAccess { get { return enableNetworkAccess; } set { enableNetworkAccess = value; } } private SwitchParameter enableNetworkAccess; /// /// Guid of target virtual machine. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is by spec.")] [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.VMIdParameterSet)] [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)] [ValidateNotNullOrEmpty] [Alias("VMGuid")] public override Guid[] VMId { get { return vmId; } set { vmId = value; } } private Guid[] vmId; /// /// Name of target virtual machine. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is by spec.")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.VMNameParameterSet)] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)] [ValidateNotNullOrEmpty] public override string[] VMName { get { return vmName; } set { vmName = value; } } private string[] vmName; /// /// ID of target container. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is by spec.")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.ContainerIdParameterSet)] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)] [ValidateNotNullOrEmpty] public override string[] ContainerId { get { return containerId; } set { containerId = value; } } private string[] containerId; /// /// Name of target container. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is by spec.")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.ContainerNameParameterSet)] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathContainerNameParameterSet)] [ValidateNotNullOrEmpty] public override string[] ContainerName { get { return containerName; } set { containerName = value; } } private string[] containerName; /// /// For WSMan session: /// 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. /// /// For VM/Container sessions: /// If this parameter is not specified then no configuration is used. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.UriParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathComputerNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathUriParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.ContainerIdParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.ContainerNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.VMIdParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.VMNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathContainerNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)] public virtual String ConfigurationName { get { return shell; } set { // Only InvokeCommandCommand may enter here, // which will call ResolveShell() if needed. shell = value; } } private String shell; #endregion Parameters #region Private Methods /// /// Creates helper objects with the command for the specified /// remote computer names /// protected virtual void CreateHelpersForSpecifiedComputerNames() { ValidateComputerName(ResolvedComputerNames); // create helper objects for computer names RemoteRunspace remoteRunspace = null; string scheme = UseSSL.IsPresent ? WSManConnectionInfo.HttpsScheme : WSManConnectionInfo.HttpScheme; for (int i = 0; i < ResolvedComputerNames.Length; i++) { try { WSManConnectionInfo connectionInfo = new WSManConnectionInfo(); connectionInfo.Scheme = scheme; connectionInfo.ComputerName = ResolvedComputerNames[i]; connectionInfo.Port = Port; connectionInfo.AppName = ApplicationName; connectionInfo.ShellUri = ConfigurationName; if (CertificateThumbprint != null) { connectionInfo.CertificateThumbprint = CertificateThumbprint; } else { connectionInfo.Credential = Credential; } connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); connectionInfo.EnableNetworkAccess = EnableNetworkAccess; // Use the provided session name or create one for this remote runspace so that // it can be easily identified if it becomes disconnected and is queried on the server. int rsId = PSSession.GenerateRunspaceId(); string rsName = (DisconnectedSessionName != null && DisconnectedSessionName.Length > i) ? DisconnectedSessionName[i] : PSSession.ComposeRunspaceName(rsId); remoteRunspace = new RemoteRunspace(Utils.GetTypeTableFromExecutionContextTLS(), connectionInfo, this.Host, this.SessionOption.ApplicationArguments, rsName, rsId); remoteRunspace.Events.ReceivedEvents.PSEventReceived += OnRunspacePSEventReceived; } catch (UriFormatException uriException) { ErrorRecord errorRecord = new ErrorRecord(uriException, "CreateRemoteRunspaceFailed", ErrorCategory.InvalidArgument, ResolvedComputerNames[i]); WriteError(errorRecord); continue; } Pipeline pipeline = CreatePipeline(remoteRunspace); IThrottleOperation operation = new ExecutionCmdletHelperComputerName(remoteRunspace, pipeline, this.invokeAndDisconnect); Operations.Add(operation); } }// CreateHelpersForSpecifiedComputerNames /// /// Creates helper objects with the specified command for /// the specified remote runspaceinfo objects /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] protected void CreateHelpersForSpecifiedRunspaces() { RemoteRunspace[] remoteRunspaces; Pipeline[] pipelines; // extract RemoteRunspace out of the PSSession objects int length = Session.Length; remoteRunspaces = new RemoteRunspace[length]; for (int i = 0; i < length; i++) { remoteRunspaces[i] = (RemoteRunspace)Session[i].Runspace; } // create the set of pipelines from the RemoteRunspace objects and // create IREHelperRunspace helper class to create operations pipelines = new Pipeline[length]; for (int i = 0; i < length; i++) { pipelines[i] = CreatePipeline(remoteRunspaces[i]); // create the operation object IThrottleOperation operation = new ExecutionCmdletHelperRunspace(pipelines[i]); Operations.Add(operation); } } // CreateHelpersForSpecifiedRunspaces /// /// Creates helper objects with the command for the specified /// remote connection uris /// protected void CreateHelpersForSpecifiedUris() { // create helper objects for computer names RemoteRunspace remoteRunspace = null; for (int i = 0; i < ConnectionUri.Length; i++) { try { WSManConnectionInfo connectionInfo = new WSManConnectionInfo(); connectionInfo.ConnectionUri = ConnectionUri[i]; connectionInfo.ShellUri = ConfigurationName; if (CertificateThumbprint != null) { connectionInfo.CertificateThumbprint = CertificateThumbprint; } else { connectionInfo.Credential = Credential; } connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); connectionInfo.EnableNetworkAccess = EnableNetworkAccess; remoteRunspace = (RemoteRunspace)RunspaceFactory.CreateRunspace(connectionInfo, this.Host, Utils.GetTypeTableFromExecutionContextTLS(), this.SessionOption.ApplicationArguments); Dbg.Assert(remoteRunspace != null, "RemoteRunspace object created using URI is null"); remoteRunspace.Events.ReceivedEvents.PSEventReceived += OnRunspacePSEventReceived; } catch (UriFormatException e) { WriteErrorCreateRemoteRunspaceFailed(e, ConnectionUri[i]); continue; } catch (InvalidOperationException e) { WriteErrorCreateRemoteRunspaceFailed(e, ConnectionUri[i]); continue; } catch (ArgumentException e) { WriteErrorCreateRemoteRunspaceFailed(e, ConnectionUri[i]); continue; } Pipeline pipeline = CreatePipeline(remoteRunspace); IThrottleOperation operation = new ExecutionCmdletHelperComputerName(remoteRunspace, pipeline, this.invokeAndDisconnect); Operations.Add(operation); } } // CreateHelpersForSpecifiedUris /// /// Creates helper objects with the command for the specified /// VM GUIDs or VM names. /// protected virtual void CreateHelpersForSpecifiedVMSession() { int inputArraySize; int index; string command; bool[] vmIsRunning; Collection results; if ((ParameterSetName == PSExecutionCmdlet.VMIdParameterSet) || (ParameterSetName == PSExecutionCmdlet.FilePathVMIdParameterSet)) { inputArraySize = this.VMId.Length; this.VMName = new string[inputArraySize]; vmIsRunning = new bool[inputArraySize]; for (index = 0; index < inputArraySize; index++) { vmIsRunning[index] = false; command = "Get-VM -Id $args[0]"; try { results = this.InvokeCommand.InvokeScript( command, false, PipelineResultTypes.None, null, this.VMId[index]); } catch (CommandNotFoundException) { ThrowTerminatingError( new ErrorRecord( new ArgumentException(RemotingErrorIdStrings.HyperVModuleNotAvailable), PSRemotingErrorId.HyperVModuleNotAvailable.ToString(), ErrorCategory.NotInstalled, null)); return; } if (results.Count != 1) { this.VMName[index] = string.Empty; } else { this.VMName[index] = (string)results[0].Properties["VMName"].Value; if ((VMState)results[0].Properties["State"].Value == VMState.Running) { vmIsRunning[index] = true; } } } } else { Dbg.Assert((ParameterSetName == PSExecutionCmdlet.VMNameParameterSet) || (ParameterSetName == PSExecutionCmdlet.FilePathVMNameParameterSet), "Expected ParameterSetName == VMName or FilePathVMName"); inputArraySize = this.VMName.Length; this.VMId = new Guid[inputArraySize]; vmIsRunning = new bool[inputArraySize]; for (index = 0; index < inputArraySize; index++) { vmIsRunning[index] = false; command = "Get-VM -Name $args"; try { results = this.InvokeCommand.InvokeScript( command, false, PipelineResultTypes.None, null, this.VMName[index]); } catch (CommandNotFoundException) { ThrowTerminatingError( new ErrorRecord( new ArgumentException(RemotingErrorIdStrings.HyperVModuleNotAvailable), PSRemotingErrorId.HyperVModuleNotAvailable.ToString(), ErrorCategory.NotInstalled, null)); return; } if (results.Count != 1) { this.VMId[index] = Guid.Empty; } else { this.VMId[index] = (Guid)results[0].Properties["VMId"].Value; this.VMName[index] = (string)results[0].Properties["VMName"].Value; if ((VMState)results[0].Properties["State"].Value == VMState.Running) { vmIsRunning[index] = true; } } } } ResolvedComputerNames = this.VMName; for (index = 0; index < ResolvedComputerNames.Length; index++) { if ((this.VMId[index] == Guid.Empty) && ((ParameterSetName == PSExecutionCmdlet.VMNameParameterSet) || (ParameterSetName == PSExecutionCmdlet.FilePathVMNameParameterSet))) { WriteError( new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.InvalidVMNameNotSingle, this.VMName[index])), PSRemotingErrorId.InvalidVMNameNotSingle.ToString(), ErrorCategory.InvalidArgument, null)); continue; } else if ((this.VMName[index] == string.Empty) && ((ParameterSetName == PSExecutionCmdlet.VMIdParameterSet) || (ParameterSetName == PSExecutionCmdlet.FilePathVMIdParameterSet))) { WriteError( new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.InvalidVMIdNotSingle, this.VMId[index].ToString(null))), PSRemotingErrorId.InvalidVMIdNotSingle.ToString(), ErrorCategory.InvalidArgument, null)); continue; } else if (!vmIsRunning[index]) { WriteError( new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.InvalidVMState, this.VMName[index])), PSRemotingErrorId.InvalidVMState.ToString(), ErrorCategory.InvalidArgument, null)); continue; } // create helper objects for VM GUIDs or names RemoteRunspace remoteRunspace = null; VMConnectionInfo connectionInfo; try { connectionInfo = new VMConnectionInfo(this.Credential, this.VMId[index], this.VMName[index], this.ConfigurationName); remoteRunspace = new RemoteRunspace(Utils.GetTypeTableFromExecutionContextTLS(), connectionInfo, this.Host, null, null, -1); remoteRunspace.Events.ReceivedEvents.PSEventReceived += OnRunspacePSEventReceived; } catch (InvalidOperationException e) { ErrorRecord errorRecord = new ErrorRecord(e, "CreateRemoteRunspaceForVMFailed", ErrorCategory.InvalidOperation, null); WriteError(errorRecord); } catch (ArgumentException e) { ErrorRecord errorRecord = new ErrorRecord(e, "CreateRemoteRunspaceForVMFailed", ErrorCategory.InvalidArgument, null); WriteError(errorRecord); } Pipeline pipeline = CreatePipeline(remoteRunspace); IThrottleOperation operation = new ExecutionCmdletHelperComputerName(remoteRunspace, pipeline, false); Operations.Add(operation); } }// CreateHelpersForSpecifiedVMSession /// /// Creates helper objects with the command for the specified /// container IDs or names. /// protected virtual void CreateHelpersForSpecifiedContainerSession() { string[] inputArray; bool isContainerIdSet = false; List resolvedNameList = new List(); if ((ParameterSetName == PSExecutionCmdlet.ContainerIdParameterSet) || (ParameterSetName == PSExecutionCmdlet.FilePathContainerIdParameterSet)) { inputArray = ContainerId; isContainerIdSet = true; } else { Dbg.Assert((ParameterSetName == PSExecutionCmdlet.ContainerNameParameterSet) || (ParameterSetName == PSExecutionCmdlet.FilePathContainerNameParameterSet), "Expected ParameterSetName == ContainerId, ContainerName, FilePathContainerId or FilePathContainerName"); inputArray = ContainerName; } foreach (var input in inputArray) { // // Create helper objects for container ID or name. // RemoteRunspace remoteRunspace = null; ContainerConnectionInfo connectionInfo = null; try { // // Hyper-V container uses Hype-V socket as transport. // Windows Server container uses named pipe as transport. // if (isContainerIdSet) { connectionInfo = ContainerConnectionInfo.CreateContainerConnectionInfoById(input, RunAsAdministrator.IsPresent, this.ConfigurationName); } else { connectionInfo = ContainerConnectionInfo.CreateContainerConnectionInfoByName(input, RunAsAdministrator.IsPresent, this.ConfigurationName); } resolvedNameList.Add(connectionInfo.ComputerName); connectionInfo.CreateContainerProcess(); remoteRunspace = new RemoteRunspace(Utils.GetTypeTableFromExecutionContextTLS(), connectionInfo, this.Host, null, null, -1); remoteRunspace.Events.ReceivedEvents.PSEventReceived += OnRunspacePSEventReceived; } catch (InvalidOperationException e) { ErrorRecord errorRecord = new ErrorRecord(e, "CreateRemoteRunspaceForContainerFailed", ErrorCategory.InvalidOperation, null); WriteError(errorRecord); continue; } catch (ArgumentException e) { ErrorRecord errorRecord = new ErrorRecord(e, "CreateRemoteRunspaceForContainerFailed", ErrorCategory.InvalidArgument, null); WriteError(errorRecord); continue; } catch (Exception e) { ErrorRecord errorRecord = new ErrorRecord(e, "CreateRemoteRunspaceForContainerFailed", ErrorCategory.InvalidOperation, null); WriteError(errorRecord); continue; } Pipeline pipeline = CreatePipeline(remoteRunspace); IThrottleOperation operation = new ExecutionCmdletHelperComputerName(remoteRunspace, pipeline, false); Operations.Add(operation); } ResolvedComputerNames = resolvedNameList.ToArray(); }// CreateHelpersForSpecifiedContainerSession /// /// Creates a pipeline from the powershell /// /// runspace on which to create the pipeline /// a pipeline internal Pipeline CreatePipeline(RemoteRunspace remoteRunspace) { // The fix to WinBlue#475223 changed how UsingExpression is handled on the client/server sides, if the remote end is PSv5 // or later, we send the dictionary-form using values to the remote end. If the remote end is PSv3 or PSv4, then we send // the array-form using values if all UsingExpressions are in the same scope, otherwise, we handle the UsingExpression as // if the remote end is PSv2. string serverPsVersion = GetRemoteServerPsVersion(remoteRunspace); System.Management.Automation.PowerShell powershellToUse = (serverPsVersion == PSv2) ? GetPowerShellForPSv2() : GetPowerShellForPSv3OrLater(serverPsVersion); Pipeline pipeline = remoteRunspace.CreatePipeline(powershellToUse.Commands.Commands[0].CommandText, true); pipeline.Commands.Clear(); foreach (Command command in powershellToUse.Commands.Commands) { pipeline.Commands.Add(command); } pipeline.RedirectShellErrorOutputPipe = true; return pipeline; } /// /// Check the powershell version of the remote server /// private string GetRemoteServerPsVersion(RemoteRunspace remoteRunspace) { if (remoteRunspace.ConnectionInfo is NewProcessConnectionInfo) { // This is for Start-Job. The remote end is actually a child local powershell process, so it must be PSv5 or later return PSv5OrLater; } PSPrimitiveDictionary psApplicationPrivateData = remoteRunspace.GetApplicationPrivateData(); if (psApplicationPrivateData == null) { // The remote runspace is not opened yet, or it's disconnected before the private data is retrieved. // In this case we cannot validate if the remote server is running PSv5 or later, so for safety purpose, // we will handle the $using expressions as if the remote server is PSv2. return PSv2; } // Unfortunately, the PSVersion value in the private data from PSv3 and PSv4 server is always 2.0. // This got fixed in PSv5, so a PSv5 server will return 5.0. That means we need other way to tell // if the remote server is PSv2 or PSv3+. After PSv3, remote runspace supports connect/disconnect, // so we can use it to differentiate PSv2 from PSv3+. if (remoteRunspace.CanDisconnect) { Version serverPsVersion = null; PSPrimitiveDictionary.TryPathGet( psApplicationPrivateData, out serverPsVersion, PSVersionInfo.PSVersionTableName, PSVersionInfo.PSVersionName); if (serverPsVersion != null) { return serverPsVersion.Major >= 5 ? PSv5OrLater : PSv3Orv4; } // The private data is available but we failed to get the server powershell version. // This should never happen, but in case it happens, handle the $using expressions // as if the remote server is PSv2. Dbg.Assert(false, "Application private data is available but we failed to get the server powershell version. This should never happen."); } return PSv2; } /// /// Adds forwarded events to the local queue /// internal void OnRunspacePSEventReceived(object sender, PSEventArgs e) { if(this.Events != null) this.Events.AddForwardedEvent(e); } #endregion Private Methods #region Protected Members / Methods /// /// List of operations /// internal List Operations { get { return operations; } } private List operations = new List(); /// /// Closes the input streams on all the pipelines /// protected void CloseAllInputStreams() { foreach (IThrottleOperation operation in Operations) { ExecutionCmdletHelper helper = (ExecutionCmdletHelper)operation; helper.Pipeline.Input.Close(); } } // CloseAllInputStreams /// /// Writes an error record specifying that creation of remote runspace /// failed /// /// exception which is causing this error record /// to be written /// Uri which caused this exception private void WriteErrorCreateRemoteRunspaceFailed(Exception e, Uri uri) { Dbg.Assert(e is UriFormatException || e is InvalidOperationException || e is ArgumentException, "Exception has to be of type UriFormatException or InvalidOperationException or ArgumentException"); ErrorRecord errorRecord = new ErrorRecord(e, "CreateRemoteRunspaceFailed", ErrorCategory.InvalidArgument, uri); WriteError(errorRecord); } // WriteErrorCreateRemoteRunspaceFailed /// /// FilePathComputername parameter set /// protected const string FilePathComputerNameParameterSet = "FilePathComputerName"; /// /// LiteralFilePathComputername parameter set /// protected const string LiteralFilePathComputerNameParameterSet = "LiteralFilePathComputerName"; /// /// FilePathRunspace parameter set /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace")] protected const string FilePathSessionParameterSet = "FilePathRunspace"; /// /// FilePathUri parameter set /// protected const string FilePathUriParameterSet = "FilePathUri"; /// /// PS version of the remote server /// private const string PSv5OrLater = "PSv5OrLater"; private const string PSv3Orv4 = "PSv3Orv4"; private const string PSv2 = "PSv2"; private System.Management.Automation.PowerShell _powershellV2; private System.Management.Automation.PowerShell _powershellV3; /// /// Reads content of file and converts it to a scriptblock /// /// /// /// protected ScriptBlock GetScriptBlockFromFile(string filePath, bool isLiteralPath) { //Make sure filepath doesn't contain wildcards if ((! isLiteralPath) && WildcardPattern.ContainsWildcardCharacters(filePath)) { throw new ArgumentException(PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.WildCardErrorFilePathParameter), "filePath"); } if (!filePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException(PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.FilePathShouldPS1Extension), "filePath"); } //Resolve file path PathResolver resolver = new PathResolver(); string resolvedPath = resolver.ResolveProviderAndPath(filePath, isLiteralPath, this, false, RemotingErrorIdStrings.FilePathNotFromFileSystemProvider); //read content of file ExternalScriptInfo scriptInfo = new ExternalScriptInfo(filePath, resolvedPath, this.Context); // Skip ShouldRun check for .psd1 files. // Use ValidateScriptInfo() for explicitly validating the checkpolicy for psd1 file. // if (!filePath.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) { this.Context.AuthorizationManager.ShouldRunInternal(scriptInfo, CommandOrigin.Internal, this.Context.EngineHostInterface); } return scriptInfo.ScriptBlock; } #endregion Protected Members / Methods #region Overrides /// /// Creates the helper classes for the specified /// parameter set /// protected override void BeginProcessing() { if ((ParameterSetName == PSExecutionCmdlet.VMIdParameterSet) || (ParameterSetName == PSExecutionCmdlet.VMNameParameterSet) || (ParameterSetName == PSExecutionCmdlet.ContainerIdParameterSet) || (ParameterSetName == PSExecutionCmdlet.ContainerNameParameterSet) || (ParameterSetName == PSExecutionCmdlet.FilePathVMIdParameterSet) || (ParameterSetName == PSExecutionCmdlet.FilePathVMNameParameterSet) || (ParameterSetName == PSExecutionCmdlet.FilePathContainerIdParameterSet) || (ParameterSetName == PSExecutionCmdlet.FilePathContainerNameParameterSet)) { SkipWinRMCheck = true; } base.BeginProcessing(); if (filePath != null) { scriptBlock = GetScriptBlockFromFile(filePath, IsLiteralPath); } switch (ParameterSetName) { case PSExecutionCmdlet.FilePathComputerNameParameterSet: case PSExecutionCmdlet.LiteralFilePathComputerNameParameterSet: case PSExecutionCmdlet.ComputerNameParameterSet: { String[] resolvedComputerNames = null; ResolveComputerNames(ComputerName, out resolvedComputerNames); ResolvedComputerNames = resolvedComputerNames; CreateHelpersForSpecifiedComputerNames(); } break; case PSExecutionCmdlet.FilePathSessionParameterSet: case PSExecutionCmdlet.SessionParameterSet: { ValidateRemoteRunspacesSpecified(); CreateHelpersForSpecifiedRunspaces(); } break; case PSExecutionCmdlet.FilePathUriParameterSet: case PSExecutionCmdlet.UriParameterSet: { CreateHelpersForSpecifiedUris(); } break; case PSExecutionCmdlet.VMIdParameterSet: case PSExecutionCmdlet.VMNameParameterSet: case PSExecutionCmdlet.FilePathVMIdParameterSet: case PSExecutionCmdlet.FilePathVMNameParameterSet: { CreateHelpersForSpecifiedVMSession(); } break; case PSExecutionCmdlet.ContainerIdParameterSet: case PSExecutionCmdlet.ContainerNameParameterSet: case PSExecutionCmdlet.FilePathContainerIdParameterSet: case PSExecutionCmdlet.FilePathContainerNameParameterSet: { CreateHelpersForSpecifiedContainerSession(); } break; } } #endregion Overrides #region "Get PowerShell instance" /// /// Get the PowerShell instance for the PSv2 remote end /// Generate the PowerShell instance by using the text of the scriptblock /// /// /// PSv2 doesn't understand the '$using' prefix. To make UsingExpression work on PSv2 remote end, we will have to /// alter the script, and send the altered script to the remote end. Since the script is altered, when there is an /// error, the error message will show the altered script, and that could be confusing to the user. So if the remote /// server is PSv3 or later version, we will use a different approach to handle UsingExpression so that we can keep /// the script unchanged. /// /// However, on PSv3 and PSv4 remote server, it's not well supported if UsingExpressions are used in different scopes (fixed in PSv5). /// If the remote end is PSv3 or PSv4, and there are UsingExpressions in different scopes, then we have to revert back to the approach /// used for PSv2 remote server. /// /// private System.Management.Automation.PowerShell GetPowerShellForPSv2() { if (_powershellV2 != null) { return _powershellV2; } // Try to convert the scriptblock to powershell commands. _powershellV2 = ConvertToPowerShell(); if (_powershellV2 != null) { // Look for EndOfStatement tokens. foreach (var command in _powershellV2.Commands.Commands) { if (command.IsEndOfStatement) { // PSv2 cannot process this. Revert to sending script. _powershellV2 = null; break; } } if (_powershellV2 != null) { return _powershellV2; } } List newParameterNames; List newParameterValues; string scriptTextAdaptedForPSv2 = GetConvertedScript(out newParameterNames, out newParameterValues); _powershellV2 = System.Management.Automation.PowerShell.Create().AddScript(scriptTextAdaptedForPSv2); if (args != null) { foreach (object arg in args) { _powershellV2.AddArgument(arg); } } if (newParameterNames != null) { Dbg.Assert(newParameterValues != null && newParameterNames.Count == newParameterValues.Count, "We should get the value for each using variable"); for (int i = 0; i < newParameterNames.Count; i++) { _powershellV2.AddParameter(newParameterNames[i], newParameterValues[i]); } } return _powershellV2; } /// /// Get the PowerShell instance for the PSv3 (or later) remote end /// Generate the PowerShell instance by using the text of the scriptblock /// /// /// In PSv3 and PSv4, if the remote server is PSv3 or later, we generate an object array that contains the value of each using expression in /// the parsing order, and then pass the array to the remote end as a special argument. On the remote end, the using expressions will be indexed /// in the same parsing order during the variable analysis process, and the index is used to get the value of the corresponding using expression /// from the special array. There is a limitation in that approach -- $using cannot be used in different scopes with Invoke-Command/Start-Job /// (see WinBlue#475223), because the variable analysis process can only index using expressions within the same scope (this is by design), and a /// using expression from a different scope may be assigned with an index that conflicts with other using expressions. /// /// To fix the limitation described above, we changed to pass a dictionary with key/value pairs for the using expressions on the client side. The key /// is an unique base64 encoded string generated based on the text of the using expression. On the remote end, it can always get the unique key of a /// using expression because the text passed to the server side is the same, and thus the value of the using expression can be retrieved from the special /// dictionary. With this approach, $using in different scopes can be supported for Invoke-Command/Start-Job. /// /// This fix involved changes on the server side, so the fix will work only if the remote end is PSv5 or later. In order to avoid possible breaking /// change in 'PSv5 client - PSv3 server' and 'PSv5 client - PSv4 server' scenarios, we should keep sending the array-form using values if the remote /// end is PSv3 or PSv4 as long as no UsingExpression is in a different scope. If the remote end is PSv3 or PSv4 and we do have UsingExpressions /// in different scopes, then we will revert back to the approach we use to handle UsingExpression for PSv2 remote server. /// /// private System.Management.Automation.PowerShell GetPowerShellForPSv3OrLater(string serverPsVersion) { if (_powershellV3 != null) { return _powershellV3; } // Try to convert the scriptblock to powershell commands. _powershellV3 = ConvertToPowerShell(); if (_powershellV3 != null) { return _powershellV3; } // Using expressions can be a variable, as well as property and / or array references. E.g. // // icm { echo $using:a } // icm { echo $using:a[3] } // icm { echo $using:a.Length } // // Semantic checks on the using statement have already validated that there are no arbitrary expressions, // so we'll allow these expressions in everything but NoLanguage mode. bool allowUsingExpressions = (Context.SessionState.LanguageMode != PSLanguageMode.NoLanguage); object[] usingValuesInArray = null; IDictionary usingValuesInDict = null; // Value of 'serverPsVersion' should be either 'PSv3Orv4' or 'PSv5OrLater' if (serverPsVersion == PSv3Orv4) { usingValuesInArray = ScriptBlockToPowerShellConverter.GetUsingValuesAsArray(scriptBlock, allowUsingExpressions, Context, null); if (usingValuesInArray == null) { // 'usingValuesInArray' will be null only if there are UsingExpressions used in different scopes. // PSv3 and PSv4 remote server cannot handle this, so we revert back to the approach we use for PSv2 remote end. return GetPowerShellForPSv2(); } } else { // Remote server is PSv5 or later version usingValuesInDict = ScriptBlockToPowerShellConverter.GetUsingValuesAsDictionary(scriptBlock, allowUsingExpressions, Context, null); } string textOfScriptBlock = this.MyInvocation.ExpectingInput ? scriptBlock.GetWithInputHandlingForInvokeCommand() : scriptBlock.ToString(); _powershellV3 = System.Management.Automation.PowerShell.Create().AddScript(textOfScriptBlock); if (args != null) { foreach (object arg in args) { _powershellV3.AddArgument(arg); } } if (usingValuesInDict != null && usingValuesInDict.Count > 0) { _powershellV3.AddParameter(Parser.VERBATIM_ARGUMENT, usingValuesInDict); } else if (usingValuesInArray != null && usingValuesInArray.Length > 0) { _powershellV3.AddParameter(Parser.VERBATIM_ARGUMENT, usingValuesInArray); } return _powershellV3; } private System.Management.Automation.PowerShell ConvertToPowerShell() { System.Management.Automation.PowerShell powershell = null; try { // This is trusted input as long as we're in FullLanguage mode bool isTrustedInput = (Context.LanguageMode == PSLanguageMode.FullLanguage); powershell = scriptBlock.GetPowerShell(isTrustedInput, args); } catch (ScriptBlockToPowerShellNotSupportedException) { // conversion failed, we need to send the script to the remote end. // since the PowerShell instance would be different according to the PSVersion of the remote end, // we generate it when we know which version we are talking to. } return powershell; } #endregion "Get PowerShell instance" #region "UsingExpression Utilities" /// /// Get the converted script for a remote PSv2 end /// /// /// The new parameter names that we added to the param block /// /// /// The new parameter values that need to be added to the powershell instance /// /// private string GetConvertedScript(out List newParameterNames, out List newParameterValues) { newParameterNames = null; newParameterValues = null; string textOfScriptBlock = null; // Scan for the using variables List usingVariables = GetUsingVariables(scriptBlock); if (usingVariables == null || usingVariables.Count == 0) { // No using variable is used, then we don't change the script textOfScriptBlock = this.MyInvocation.ExpectingInput ? scriptBlock.GetWithInputHandlingForInvokeCommand() : scriptBlock.ToString(); } else { newParameterNames = new List(); var paramNamesWithDollarSign = new List(); var paramUsingVars = new List(); var nameHashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var varAst in usingVariables) { string varName = varAst.VariablePath.UserPath; string paramName = UsingExpressionAst.UsingPrefix + varName; string paramNameWithDollar = "$" + paramName; if (!nameHashSet.Contains(paramNameWithDollar)) { newParameterNames.Add(paramName); paramNamesWithDollarSign.Add(paramNameWithDollar); paramUsingVars.Add(varAst); nameHashSet.Add(paramNameWithDollar); } } // Retrieve the value for each using variable newParameterValues = GetUsingVariableValues(paramUsingVars); // Generate the wrapped script string additionalNewParams = string.Join(", ", paramNamesWithDollarSign); textOfScriptBlock = this.MyInvocation.ExpectingInput ? scriptBlock.GetWithInputHandlingForInvokeCommandWithUsingExpression(Tuple.Create(usingVariables, additionalNewParams)) : scriptBlock.ToStringWithDollarUsingHandling(Tuple.Create(usingVariables, additionalNewParams)); } return textOfScriptBlock; } /// /// Get the values for the using variables that are passed in. /// /// /// private List GetUsingVariableValues(List paramUsingVars) { var values = new List(paramUsingVars.Count); VariableExpressionAst currentVarAst = null; Version oldStrictVersion = Context.EngineSessionState.CurrentScope.StrictModeVersion; try { Context.EngineSessionState.CurrentScope.StrictModeVersion = PSVersionInfo.PSVersion; // GetExpressionValue ensures that it only does variable access when supplied a VariableExpressionAst. // So, this is still safe to use in ConstrainedLanguage and will not result in arbitrary code // execution. bool allowVariableAccess = (Context.SessionState.LanguageMode != PSLanguageMode.NoLanguage); foreach (var varAst in paramUsingVars) { currentVarAst = varAst; object value = Compiler.GetExpressionValue(varAst, allowVariableAccess, Context); values.Add(value); } } catch (RuntimeException rte) { if (rte.ErrorRecord.FullyQualifiedErrorId.Equals("VariableIsUndefined", StringComparison.Ordinal)) { throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException), currentVarAst.Extent, "UsingVariableIsUndefined", AutomationExceptions.UsingVariableIsUndefined, rte.ErrorRecord.TargetObject); } } finally { Context.EngineSessionState.CurrentScope.StrictModeVersion = oldStrictVersion; } return values; } /// /// Get all Using expressions that we care about /// /// /// a list of UsingExpressionAsts ordered by the StartOffset private List GetUsingVariables(ScriptBlock localScriptBlock) { if (localScriptBlock == null) { throw new ArgumentNullException("localScriptBlock", "Caller needs to make sure the parameter value is not null"); } var allUsingExprs = UsingExpressionAstSearcher.FindAllUsingExpressionExceptForWorkflow(localScriptBlock.Ast); return allUsingExprs.Select(usingExpr => UsingExpressionAst.ExtractUsingVariable((UsingExpressionAst) usingExpr)).ToList(); } #endregion "UsingExpression Utilities" } /// /// Base class for any cmdlet which operates on a runspace. The /// following cmdlets currently fall under this category: /// 1. Get-PSSession /// 2. Remove-PSSession /// 3. Disconnect-PSSession /// 4. Connect-PSSession /// public abstract partial class PSRunspaceCmdlet : PSRemotingCmdlet { #region Parameters /// /// ContainerIdInstanceId parameter set: container id + session instance id /// protected const string ContainerIdInstanceIdParameterSet = "ContainerIdInstanceId"; /// /// ContainerNameInstanceId parameter set: container name + session instance id /// protected const string ContainerNameInstanceIdParameterSet = "ContainerNameInstanceId"; /// /// VMIdInstanceId parameter set: vm id + session instance id /// protected const string VMIdInstanceIdParameterSet = "VMIdInstanceId"; /// /// VMNameInstanceId parameter set: vm name + session instance id /// protected const string VMNameInstanceIdParameterSet = "VMNameInstanceId"; /// /// RemoteRunspaceId to retrieve corresponding PSSession /// object /// [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.InstanceIdParameterSet)] [ValidateNotNull] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public virtual Guid[] InstanceId { get { return remoteRunspaceIds; } set { remoteRunspaceIds = value; } } private Guid[] remoteRunspaceIds; /// /// Session Id of the remoterunspace info object /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, Mandatory=true, ParameterSetName = PSRunspaceCmdlet.IdParameterSet)] [ValidateNotNull] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public int[] Id { get { return sessionIds; } set { sessionIds = value; } } private int[] sessionIds; /// /// Name of the remote runspaceinfo object /// [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.NameParameterSet)] [ValidateNotNullOrEmpty()] public virtual String[] Name { get { return names; } set { names = value; } } private String[] names; /// /// Name of the computer for which the runspace needs to be /// returned /// [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.ComputerNameParameterSet)] [ValidateNotNullOrEmpty] [Alias("Cn")] public virtual String[] ComputerName { get { return computerNames; } set { computerNames = value; } } private String[] computerNames; /// /// ID of target container. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is by spec.")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.ContainerIdParameterSet)] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.ContainerIdInstanceIdParameterSet)] [ValidateNotNullOrEmpty] public virtual string[] ContainerId { get { return containerId; } set { containerId = value; } } private string[] containerId; /// /// Name of target container. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is by spec.")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.ContainerNameParameterSet)] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.ContainerNameInstanceIdParameterSet)] [ValidateNotNullOrEmpty] public virtual string[] ContainerName { get { return containerName; } set { containerName = value; } } private string[] containerName; /// /// Guid of target virtual machine. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is by spec.")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.VMIdParameterSet)] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.VMIdInstanceIdParameterSet)] [ValidateNotNullOrEmpty] [Alias("VMGuid")] public virtual Guid[] VMId { get { return vmId; } set { vmId = value; } } private Guid[] vmId; /// /// Name of target virtual machine. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is by spec.")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.VMNameParameterSet)] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.VMNameInstanceIdParameterSet)] [ValidateNotNullOrEmpty] public virtual string[] VMName { get { return vmName; } set { vmName = value; } } private string[] vmName; #endregion Parameters #region Private / Protected Methods /// /// Gets the matching runspaces based on the parameterset /// /// write an error record when /// no matches are found /// if true write the object down /// the pipeline /// list of matching runspaces [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] protected Dictionary GetMatchingRunspaces(bool writeobject, bool writeErrorOnNoMatch) { return GetMatchingRunspaces(writeobject, writeErrorOnNoMatch, SessionFilterState.All, null); } /// /// Gets the matching runspaces based on the parameterset /// /// write an error record when /// no matches are found /// if true write the object down /// the pipeline /// Runspace state filter value. /// Runspace configuration name filter value. /// list of matching runspaces [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] protected Dictionary GetMatchingRunspaces(bool writeobject, bool writeErrorOnNoMatch, SessionFilterState filterState, String configurationName) { switch (ParameterSetName) { case PSRunspaceCmdlet.ComputerNameParameterSet: { return GetMatchingRunspacesByComputerName(writeobject, writeErrorOnNoMatch); } case PSRunspaceCmdlet.InstanceIdParameterSet: { return GetMatchingRunspacesByRunspaceId(writeobject, writeErrorOnNoMatch); } case PSRunspaceCmdlet.NameParameterSet: { return GetMatchingRunspacesByName(writeobject, writeErrorOnNoMatch); } case PSRunspaceCmdlet.IdParameterSet: { return GetMatchingRunspacesBySessionId(writeobject, writeErrorOnNoMatch); } // // writeErrorOnNoMatch should always be false for container/vm id/name inputs // in Get-PSSession/Remove-PSSession cmdlets // // container id + optional session name case PSRunspaceCmdlet.ContainerIdParameterSet: { return GetMatchingRunspacesByContainerVMNameContainerId(writeobject, filterState, configurationName, true, true); } // container id + session instanceid case PSRunspaceCmdlet.ContainerIdInstanceIdParameterSet: { return GetMatchingRunspacesByContainerVMNameContainerIdSessionInstanceId(writeobject, filterState, configurationName, true, true); } // container name + optional session name case PSRunspaceCmdlet.ContainerNameParameterSet: { return GetMatchingRunspacesByContainerVMNameContainerId(writeobject, filterState, configurationName, true, false); } // container name + session instanceid case PSRunspaceCmdlet.ContainerNameInstanceIdParameterSet: { return GetMatchingRunspacesByContainerVMNameContainerIdSessionInstanceId(writeobject, filterState, configurationName, true, false); } // vm Guid + optional session name case PSRunspaceCmdlet.VMIdParameterSet: { return GetMatchingRunspacesByVMId(writeobject, filterState, configurationName); } // vm Guid + session instanceid case PSRunspaceCmdlet.VMIdInstanceIdParameterSet: { return GetMatchingRunspacesByVMIdSessionInstanceId(writeobject, filterState, configurationName); } // vm name + optional session name case PSRunspaceCmdlet.VMNameParameterSet: { return GetMatchingRunspacesByContainerVMNameContainerId(writeobject, filterState, configurationName, false, false); } // vm name + session instanceid case PSRunspaceCmdlet.VMNameInstanceIdParameterSet: { return GetMatchingRunspacesByContainerVMNameContainerIdSessionInstanceId(writeobject, filterState, configurationName, false, false); } } return null; } internal Dictionary GetAllRunspaces(bool writeobject, bool writeErrorOnNoMatch) { Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; foreach (PSSession remoteRunspaceInfo in remoteRunspaceInfos) { // return all remote runspace info objects if (writeobject) { WriteObject(remoteRunspaceInfo); } else { matches.Add(remoteRunspaceInfo.InstanceId, remoteRunspaceInfo); } } return matches; } /// /// Gets the matching runspaces by computernames /// /// write an error record when /// no matches are found /// if true write the object down /// the pipeline /// list of matching runspaces private Dictionary GetMatchingRunspacesByComputerName(bool writeobject, bool writeErrorOnNoMatch) { if (computerNames == null || computerNames.Length == 0) { return GetAllRunspaces(writeobject, writeErrorOnNoMatch); } Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; // Loop through all computer-name patterns and runspaces to find matches. foreach (string computerName in computerNames) { WildcardPattern computerNamePattern = WildcardPattern.Get(computerName, WildcardOptions.IgnoreCase); // Match the computer-name patterns against all the runspaces and remember the matches. bool found = false; foreach (PSSession remoteRunspaceInfo in remoteRunspaceInfos) { if (computerNamePattern.IsMatch(remoteRunspaceInfo.ComputerName)) { found = true; if (writeobject) { WriteObject(remoteRunspaceInfo); } else { try { matches.Add(remoteRunspaceInfo.InstanceId, remoteRunspaceInfo); } catch (ArgumentException) { // if match already found ignore } } } } // If no match found write an error record. if (!found && writeErrorOnNoMatch) { WriteInvalidArgumentError(PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedComputer, RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedComputer, computerName); } } return matches; } /// /// Gets the matching runspaces based on name /// /// write an error record when /// no matches are found /// if true write the object down /// the pipeline /// list of matching runspaces [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "writeobject")] protected Dictionary GetMatchingRunspacesByName(bool writeobject, bool writeErrorOnNoMatch) { Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; // Loop through all computer-name patterns and runspaces to find matches. foreach (string name in names) { WildcardPattern namePattern = WildcardPattern.Get(name, WildcardOptions.IgnoreCase); // Match the computer-name patterns against all the runspaces and remember the matches. bool found = false; foreach (PSSession remoteRunspaceInfo in remoteRunspaceInfos) { if (namePattern.IsMatch(remoteRunspaceInfo.Name)) { found = true; if (writeobject) { WriteObject(remoteRunspaceInfo); } else { try { matches.Add(remoteRunspaceInfo.InstanceId, remoteRunspaceInfo); } catch (ArgumentException) { // if match already found ignore } } } } // If no match found write an error record. if (!found && writeErrorOnNoMatch && !WildcardPattern.ContainsWildcardCharacters(name)) { WriteInvalidArgumentError(PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedName, RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedName, name); } } return matches; } /// /// Gets the matching runspaces based on the runspaces instance id /// /// write an error record when /// no matches are found /// if true write the object down /// the pipeline /// list of matching runspaces [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "writeobject")] protected Dictionary GetMatchingRunspacesByRunspaceId(bool writeobject, bool writeErrorOnNoMatch) { Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; // Loop through all computer-name patterns and runspaces to find matches. foreach (Guid remoteRunspaceId in remoteRunspaceIds) { // Match the computer-name patterns against all the runspaces and remember the matches. bool found = false; foreach (PSSession remoteRunspaceInfo in remoteRunspaceInfos) { if (remoteRunspaceId.Equals(remoteRunspaceInfo.InstanceId)) { found = true; if (writeobject) { WriteObject(remoteRunspaceInfo); } else { try { matches.Add(remoteRunspaceInfo.InstanceId, remoteRunspaceInfo); } catch (ArgumentException) { // if match already found ignore } } } } // If no match found write an error record. if (!found && writeErrorOnNoMatch) { WriteInvalidArgumentError(PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedRunspaceId, RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedRunspaceId, remoteRunspaceId); } } return matches; } /// /// Gets the matching runspaces based on the session id (the /// short integer id which is unique for a runspace) /// /// write an error record when /// no matches are found /// if true write the object down /// the pipeline /// list of matching runspaces private Dictionary GetMatchingRunspacesBySessionId(bool writeobject, bool writeErrorOnNoMatch) { Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; // Loop through all computer-name patterns and runspaces to find matches. foreach (int sessionId in sessionIds) { // Match the computer-name patterns against all the runspaces and remember the matches. bool found = false; foreach (PSSession remoteRunspaceInfo in remoteRunspaceInfos) { if (sessionId == remoteRunspaceInfo.Id) { found = true; if (writeobject) { WriteObject(remoteRunspaceInfo); } else { try { matches.Add(remoteRunspaceInfo.InstanceId, remoteRunspaceInfo); } catch (ArgumentException) { // if match already found ignore } } } } // If no match found write an error record. if (!found && writeErrorOnNoMatch) { WriteInvalidArgumentError(PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedSessionId, RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedSessionId, sessionId); } } return matches; } /// /// Gets the matching runspaces by container name, vm name or container id with optional session name /// /// if true write the object down the pipeline /// Runspace state filter value. /// Runspace configuration name filter value. /// if true the target is a container instead of virtual machine /// if true the input is container id /// list of matching runspaces private Dictionary GetMatchingRunspacesByContainerVMNameContainerId(bool writeobject, SessionFilterState filterState, String configurationName, bool isContainer, bool useContainerId) { String[] inputNames; TargetMachineType computerType; bool supportWildChar = false; String[] sessionNames = { "*" }; WildcardPattern configurationNamePattern = String.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase); Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; // container name and vm name support wild characters, while container id does not. // vm id does not apply in this method, which does not support wild characters either. if (isContainer) { if (useContainerId) { inputNames = ContainerId; } else { inputNames = ContainerName; supportWildChar = true; } computerType = TargetMachineType.Container; } else { inputNames = VMName; computerType = TargetMachineType.VirtualMachine; supportWildChar = true; } // When "-name" is not set, we use "*" that means matching all. if (Name != null) { sessionNames = Name; } foreach (string inputName in inputNames) { WildcardPattern inputNamePattern = WildcardPattern.Get(inputName, WildcardOptions.IgnoreCase); foreach (string sessionName in sessionNames) { WildcardPattern sessionNamePattern = String.IsNullOrEmpty(sessionName) ? null : WildcardPattern.Get(sessionName, WildcardOptions.IgnoreCase); var matchingRunspaceInfos = remoteRunspaceInfos .Where(session => (supportWildChar ? inputNamePattern.IsMatch(isContainer ? session.ContainerName : session.VMName) : inputName.Equals(session.ContainerId)) && ((sessionNamePattern == null) ? true : sessionNamePattern.IsMatch(session.Name)) && QueryRunspaces.TestRunspaceState(session.Runspace, filterState) && ((configurationNamePattern == null) ? true : configurationNamePattern.IsMatch(session.ConfigurationName)) && (session.ComputerType == computerType)) .ToList(); WriteOrAddMatches(matchingRunspaceInfos, writeobject, ref matches); } } return matches; } /// /// Gets the matching runspaces by container name, vm name or container id with session instanceid /// /// if true write the object down the pipeline /// Runspace state filter value. /// Runspace configuration name filter value. /// if true the target is a container instead of virtual machine /// if true the input is container id /// list of matching runspaces private Dictionary GetMatchingRunspacesByContainerVMNameContainerIdSessionInstanceId(bool writeobject, SessionFilterState filterState, String configurationName, bool isContainer, bool useContainerId) { String[] inputNames; TargetMachineType computerType; bool supportWildChar = false; WildcardPattern configurationNamePattern = String.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase); Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; // container name and vm name support wild characters, while container id does not. // vm id does not apply in this method, which does not support wild characters either. if (isContainer) { if (useContainerId) { inputNames = ContainerId; } else { inputNames = ContainerName; supportWildChar = true; } computerType = TargetMachineType.Container; } else { inputNames = VMName; computerType = TargetMachineType.VirtualMachine; supportWildChar = true; } foreach (string inputName in inputNames) { WildcardPattern inputNamePattern = WildcardPattern.Get(inputName, WildcardOptions.IgnoreCase); foreach (Guid sessionInstanceId in InstanceId) { var matchingRunspaceInfos = remoteRunspaceInfos .Where(session => (supportWildChar ? inputNamePattern.IsMatch(isContainer ? session.ContainerName : session.VMName) : inputName.Equals(session.ContainerId)) && sessionInstanceId.Equals(session.InstanceId) && QueryRunspaces.TestRunspaceState(session.Runspace, filterState) && ((configurationNamePattern == null) ? true : configurationNamePattern.IsMatch(session.ConfigurationName)) && (session.ComputerType == computerType)) .ToList(); WriteOrAddMatches(matchingRunspaceInfos, writeobject, ref matches); } } return matches; } /// /// Gets the matching runspaces by vm guid and optional session name /// /// if true write the object down the pipeline /// Runspace state filter value. /// Runspace configuration name filter value. /// list of matching runspaces private Dictionary GetMatchingRunspacesByVMId(bool writeobject, SessionFilterState filterState, String configurationName) { String[] sessionNames = { "*" }; WildcardPattern configurationNamePattern = String.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase); Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; // When "-name" is not set, we use "*" that means matching all . if (Name != null) { sessionNames = Name; } foreach (Guid vmId in VMId) { foreach (string sessionName in sessionNames) { WildcardPattern sessionNamePattern = String.IsNullOrEmpty(sessionName) ? null : WildcardPattern.Get(sessionName, WildcardOptions.IgnoreCase); var matchingRunspaceInfos = remoteRunspaceInfos .Where(session => vmId.Equals(session.VMId) && ((sessionNamePattern == null) ? true : sessionNamePattern.IsMatch(session.Name)) && QueryRunspaces.TestRunspaceState(session.Runspace, filterState) && ((configurationNamePattern == null) ? true : configurationNamePattern.IsMatch(session.ConfigurationName)) && (session.ComputerType == TargetMachineType.VirtualMachine)) .ToList(); WriteOrAddMatches(matchingRunspaceInfos, writeobject, ref matches); } } return matches; } /// /// Gets the matching runspaces by vm guid and session instanceid /// /// if true write the object down the pipeline /// Runspace state filter value. /// Runspace configuration name filter value. /// list of matching runspaces private Dictionary GetMatchingRunspacesByVMIdSessionInstanceId(bool writeobject, SessionFilterState filterState, String configurationName) { WildcardPattern configurationNamePattern = String.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase); Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; foreach (Guid vmId in VMId) { foreach (Guid sessionInstanceId in InstanceId) { var matchingRunspaceInfos = remoteRunspaceInfos .Where(session => vmId.Equals(session.VMId) && sessionInstanceId.Equals(session.InstanceId) && QueryRunspaces.TestRunspaceState(session.Runspace, filterState) && ((configurationNamePattern == null) ? true : configurationNamePattern.IsMatch(session.ConfigurationName)) && (session.ComputerType == TargetMachineType.VirtualMachine)) .ToList(); WriteOrAddMatches(matchingRunspaceInfos, writeobject, ref matches); } } return matches; } /// /// Write the maching runspace objects down the pipeline, or add to the list. /// /// The maching runspaces /// if true write the object down the pipeline. Otherwise, add to the list /// The list we add the maching runspaces to private void WriteOrAddMatches(List matchingRunspaceInfos, bool writeobject, ref Dictionary matches) { foreach (PSSession remoteRunspaceInfo in matchingRunspaceInfos) { if (writeobject) { WriteObject(remoteRunspaceInfo); } else { try { matches.Add(remoteRunspaceInfo.InstanceId, remoteRunspaceInfo); } catch (ArgumentException) { // if match already found ignore } } } } /// /// Write invalid argument error. /// private void WriteInvalidArgumentError(PSRemotingErrorId errorId, string resourceString, object errorArgument) { String message = GetMessage(resourceString, errorArgument); WriteError(new ErrorRecord(new ArgumentException(message), errorId.ToString(), ErrorCategory.InvalidArgument, errorArgument)); } #endregion Private / Protected Methods #region Protected Members /// /// Runspace Id parameter set /// protected const string InstanceIdParameterSet = "InstanceId"; /// /// session id parameter set /// protected const string IdParameterSet = "Id"; /// /// name parameter set /// protected const string NameParameterSet = "Name"; #endregion Protected Members } #region Helper Classes /// /// Base class for both the helpers. This is an abstract class /// and the helpers need to derive from this /// internal abstract partial class ExecutionCmdletHelper : IThrottleOperation { /// /// Pipeline associated with this operation /// internal Pipeline Pipeline { get { return pipeline; } } protected Pipeline pipeline; /// /// Exception raised internally when any method of this class /// is executed /// internal Exception InternalException { get { return internalException; } } protected Exception internalException; /// /// Internal access to Runspace and Computer helper runspace. /// internal Runspace PipelineRunspace { set; get; } } // ExecutionCmdletHelper /// /// Contains a pipeline and calls InvokeAsync on the pipeline /// on StartOperation. On StopOperation it calls StopAsync. /// The handler sends a StopComplete message in OperationComplete /// for both the functions. This is because, there is only a /// single state of the pipeline which raises an event on /// a method call. There are no separate events raised as /// part of method calls /// internal class ExecutionCmdletHelperRunspace : ExecutionCmdletHelper { /// /// Indicates whether or not the server should be using the steppable pipeline /// internal bool ShouldUseSteppablePipelineOnServer; /// /// Internal constructor /// /// pipeline object associated with this operation internal ExecutionCmdletHelperRunspace(Pipeline pipeline) { this.pipeline = pipeline; PipelineRunspace = pipeline.Runspace; this.pipeline.StateChanged += new EventHandler(HandlePipelineStateChanged); } /// /// Invokes the pipeline asynchronously /// internal override void StartOperation() { try { if (ShouldUseSteppablePipelineOnServer) { RemotePipeline rPipeline = pipeline as RemotePipeline; rPipeline.SetIsNested(true); rPipeline.SetIsSteppable(true); } pipeline.InvokeAsync(); } catch (InvalidRunspaceStateException e) { internalException = e; RaiseOperationCompleteEvent(); } catch (InvalidPipelineStateException e) { internalException = e; RaiseOperationCompleteEvent(); } catch (InvalidOperationException e) { internalException = e; RaiseOperationCompleteEvent(); } } // StartOperation /// /// Closes the pipeline asynchronously /// internal override void StopOperation() { if (pipeline.PipelineStateInfo.State == PipelineState.Running || pipeline.PipelineStateInfo.State == PipelineState.Disconnected || pipeline.PipelineStateInfo.State == PipelineState.NotStarted) { // If the pipeline state has reached Complete/Failed/Stopped // by the time control reaches here, then this operation // becomes a no-op. However, an OperationComplete would have // already been raised from the handler pipeline.StopAsync(); } else { // will have to raise OpertionComplete from here, // else ThrottleManager will have RaiseOperationCompleteEvent(); } } // StopOperation internal override event EventHandler OperationComplete; /// /// Handles the state changed events for the pipeline. This is registered in both /// StartOperation and StopOperation. Here nothing more is done excepting raising /// the OperationComplete event appropriately which will be handled by the cmdlet /// /// source of this event /// object describing state information about the /// pipeline private void HandlePipelineStateChanged(object sender, PipelineStateEventArgs stateEventArgs) { PipelineStateInfo stateInfo = stateEventArgs.PipelineStateInfo; switch (stateInfo.State) { case PipelineState.Running: case PipelineState.NotStarted: case PipelineState.Stopping: case PipelineState.Disconnected: return; } RaiseOperationCompleteEvent(stateEventArgs); } // HandlePipelineStateChanged /// /// Raise an OperationComplete Event. The base event will be /// null in this case /// private void RaiseOperationCompleteEvent() { RaiseOperationCompleteEvent(null); } // RaiseOperationCompleteEvent /// /// Raise an operation complete event. /// /// The event args which actually /// raises this operation complete private void RaiseOperationCompleteEvent(EventArgs baseEventArgs) { if (pipeline != null) { // Dispose the pipeline object and release data and remoting resources. // Pipeline object remains to provide information on final state and any errors incurred. pipeline.StateChanged -= new EventHandler(HandlePipelineStateChanged); pipeline.Dispose(); } OperationStateEventArgs operationStateEventArgs = new OperationStateEventArgs(); operationStateEventArgs.OperationState = OperationState.StopComplete; operationStateEventArgs.BaseEvent = baseEventArgs; if (OperationComplete != null) { OperationComplete.SafeInvoke(this, operationStateEventArgs); } } // RaiseOperationCompleteEvent } // ExecutionCmdletHelperRunspace /// /// This helper class contains a runspace and /// an associated pipeline. On StartOperation it calls /// OpenAsync on the runspace. In the handler for runspace, /// when the runspace is successfully opened it calls /// InvokeAsync on the pipeline. StartOperation /// is assumed complete when both the operations complete. /// StopOperation will call StopAsync first on the pipeline /// and then close the associated runspace. StopOperation /// is considered complete when both these operations /// complete. The handler sends a StopComplete message in /// OperationComplete for both the calls. /// internal class ExecutionCmdletHelperComputerName : ExecutionCmdletHelper { /// /// Determines if the command should be invoked and then disconnect the /// remote runspace from the client. /// private bool invokeAndDisconnect; /// /// The remote runspace created using the computer name /// parameter set details /// internal RemoteRunspace RemoteRunspace { get { return remoteRunspace; } } private RemoteRunspace remoteRunspace; /// /// Constructor /// /// RemoteRunspace that is associated /// with this operation /// pipeline created from the remote runspace /// Indicates if pipeline should be disconnected after invoking command. internal ExecutionCmdletHelperComputerName(RemoteRunspace remoteRunspace, Pipeline pipeline, bool invokeAndDisconnect = false) { Dbg.Assert(remoteRunspace != null, "RemoteRunspace reference cannot be null"); PipelineRunspace = remoteRunspace; this.invokeAndDisconnect = invokeAndDisconnect; this.remoteRunspace = remoteRunspace; remoteRunspace.StateChanged += new EventHandler(HandleRunspaceStateChanged); Dbg.Assert(pipeline != null, "Pipeline cannot be null or empty"); this.pipeline = pipeline; pipeline.StateChanged += new EventHandler(HandlePipelineStateChanged); } // IREHelperComputerName /// /// Call OpenAsync() on the RemoteRunspace /// internal override void StartOperation() { try { remoteRunspace.OpenAsync(); } catch (PSRemotingTransportException e) { internalException = e; RaiseOperationCompleteEvent(); } } // StartOperation /// /// StopAsync on the pipeline /// internal override void StopOperation() { bool needToStop = false; // indicates whether to call StopAsync if (pipeline.PipelineStateInfo.State == PipelineState.Running || pipeline.PipelineStateInfo.State == PipelineState.NotStarted) { needToStop = true; } if (needToStop) { // If the pipeline state has reached Complete/Failed/Stopped // by the time control reaches here, then this operation // becomes a no-op. However, an OperationComplete would have // already been raised from the handler pipeline.StopAsync(); } else { // raise an OperationComplete event here. Else the // throttle manager will hang as it will be waiting for // this StopOperation to complete RaiseOperationCompleteEvent(); } } // StopOperation internal override event EventHandler OperationComplete; /// /// Handles the state changed event for runspace operations /// /// sender of this information /// object describing this event private void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs stateEventArgs) { RunspaceState state = stateEventArgs.RunspaceStateInfo.State; switch (state) { case RunspaceState.BeforeOpen: case RunspaceState.Opening: case RunspaceState.Closing: return; case RunspaceState.Opened: { // if successfully opened // Call InvokeAsync() on the pipeline try { if (this.invokeAndDisconnect) { pipeline.InvokeAsyncAndDisconnect(); } else { pipeline.InvokeAsync(); } } catch (InvalidPipelineStateException) { remoteRunspace.CloseAsync(); } catch (InvalidRunspaceStateException e) { internalException = e; remoteRunspace.CloseAsync(); } } break; case RunspaceState.Broken: { RaiseOperationCompleteEvent(stateEventArgs); } break; case RunspaceState.Closed: { // raise a OpertionComplete event with // StopComplete message if (stateEventArgs.RunspaceStateInfo.Reason != null) { RaiseOperationCompleteEvent(stateEventArgs); } else { RaiseOperationCompleteEvent(); } } break; } // switch (state... } // HandleRunspaceStateChanged /// /// Handles the state changed event for the pipeline. /// /// sender of this information /// object describing this event private void HandlePipelineStateChanged(object sender, PipelineStateEventArgs stateEventArgs) { PipelineState state = stateEventArgs.PipelineStateInfo.State; switch (state) { case PipelineState.Running: case PipelineState.NotStarted: case PipelineState.Stopping: return; case PipelineState.Completed: case PipelineState.Stopped: case PipelineState.Failed: if (remoteRunspace != null) { remoteRunspace.CloseAsync(); } break; } // switch(state... } // HandlePipelineStateChanged /// /// Raise an OperationComplete Event. The base event will be /// null in this case /// private void RaiseOperationCompleteEvent() { RaiseOperationCompleteEvent(null); } // RaiseOperationCompleteEvent /// /// Raise an operation complete event. /// /// The event args which actually /// raises this operation complete private void RaiseOperationCompleteEvent(EventArgs baseEventArgs) { if (pipeline != null) { // Dispose the pipeline object and release data and remoting resources. // Pipeline object remains to provide information on final state and any errors incurred. pipeline.StateChanged -= new EventHandler(HandlePipelineStateChanged); pipeline.Dispose(); } if (remoteRunspace != null) { // Dispose of the runspace object. remoteRunspace.Dispose(); remoteRunspace = null; } OperationStateEventArgs operationStateEventArgs = new OperationStateEventArgs(); operationStateEventArgs.OperationState = OperationState.StopComplete; operationStateEventArgs.BaseEvent = baseEventArgs; OperationComplete.SafeInvoke(this, operationStateEventArgs); } // RaiseOperationCompleteEvent } // ExecutionCmdletHelperComputerName /// /// A helper class to resolve the path /// internal class PathResolver { /// /// Resolves the specified path and verifies the path belongs to /// FileSystemProvider. /// /// Path to resolve /// True if wildcard expansion should be suppressed for this path. /// reference to calling cmdlet. This will be used for /// for writing errors /// /// resource string for error when path is not from filesystem provider /// A fully qualified string representing filename. internal string ResolveProviderAndPath(string path, bool isLiteralPath, PSCmdlet cmdlet, bool allowNonexistingPaths, string resourceString) { // First resolve path PathInfo resolvedPath = ResolvePath(path, isLiteralPath, allowNonexistingPaths, cmdlet); if (resolvedPath.Provider.ImplementingType == typeof(FileSystemProvider)) { return resolvedPath.ProviderPath; } throw PSTraceSource.NewInvalidOperationException(resourceString, resolvedPath.Provider.Name); } /// /// Resolves the specified path to PathInfo objects /// /// /// /// The path to be resolved. Each path may contain glob characters. /// /// /// /// True if wildcard expansion should be suppressed for pathToResolve. /// /// /// /// If true, resolves the path even if it doesn't exist. /// /// /// /// Calling cmdlet /// /// /// /// A string representing the resolved path. /// /// private PathInfo ResolvePath( string pathToResolve, bool isLiteralPath, bool allowNonexistingPaths, PSCmdlet cmdlet) { // Construct cmdletprovidercontext CmdletProviderContext cmdContext = new CmdletProviderContext(cmdlet); cmdContext.SuppressWildcardExpansion = isLiteralPath; Collection results = new Collection(); try { // First resolve path Collection pathInfos = cmdlet.SessionState.Path.GetResolvedPSPathFromPSPath( pathToResolve, cmdContext); foreach (PathInfo pathInfo in pathInfos) { results.Add(pathInfo); } } catch (PSNotSupportedException notSupported) { cmdlet.ThrowTerminatingError( new ErrorRecord( notSupported.ErrorRecord, notSupported)); } catch (System.Management.Automation.DriveNotFoundException driveNotFound) { cmdlet.ThrowTerminatingError( new ErrorRecord( driveNotFound.ErrorRecord, driveNotFound)); } catch (ProviderNotFoundException providerNotFound) { cmdlet.ThrowTerminatingError( new ErrorRecord( providerNotFound.ErrorRecord, providerNotFound)); } catch (ItemNotFoundException pathNotFound) { if (allowNonexistingPaths) { ProviderInfo provider = null; System.Management.Automation.PSDriveInfo drive = null; string unresolvedPath = cmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath( pathToResolve, cmdContext, out provider, out drive); PathInfo pathInfo = new PathInfo( drive, provider, unresolvedPath, cmdlet.SessionState); results.Add(pathInfo); } else { cmdlet.ThrowTerminatingError( new ErrorRecord( pathNotFound.ErrorRecord, pathNotFound)); } } if (results.Count == 1) { return results[0]; } else //if (results.Count > 1) { Exception e = PSTraceSource.NewNotSupportedException(); cmdlet.ThrowTerminatingError( new ErrorRecord(e, "NotSupported", ErrorCategory.NotImplemented, results)); return null; } } // ResolvePath } #endregion Helper Classes }