// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
using System.Management.Automation.Remoting;
using System.Management.Automation.Remoting.Client;
using System.Management.Automation.Runspaces;
using System.Threading;

using Dbg = System.Management.Automation.Diagnostics;

namespace Microsoft.PowerShell.Commands
{
    /// <summary>
    /// 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.
    /// </summary>
    public abstract class PSRemotingCmdlet : PSCmdlet
    {
        #region Overrides

        /// <summary>
        /// Verifies if remoting cmdlets can be used.
        /// </summary>
        protected override void BeginProcessing()
        {
            if (!SkipWinRMCheck)
            {
                RemotingCommandUtil.CheckRemotingCmdletPrerequisites();
            }
        }

        #endregion Overrides

        #region Utility functions

        /// <summary>
        /// Handle the object obtained from an ObjectStream's reader
        /// based on its type.
        /// </summary>
        internal void WriteStreamObject(Action<Cmdlet> action)
        {
            action(this);
        }

        /// <summary>
        /// Resolve all the machine names provided. Basically, if a machine
        /// name is '.' assume localhost.
        /// </summary>
        /// <param name="computerNames">Array of computer names to resolve.</param>
        /// <param name="resolvedComputerNames">Resolved array of machine names.</param>
        protected void ResolveComputerNames(string[] computerNames, out string[] resolvedComputerNames)
        {
            if (computerNames == null)
            {
                resolvedComputerNames = new string[1];

                resolvedComputerNames[0] = ResolveComputerName(".");
            }
            else if (computerNames.Length == 0)
            {
                resolvedComputerNames = Array.Empty<string>();
            }
            else
            {
                resolvedComputerNames = new string[computerNames.Length];

                for (int i = 0; i < resolvedComputerNames.Length; i++)
                {
                    resolvedComputerNames[i] = ResolveComputerName(computerNames[i]);
                }
            }
        }

        /// <summary>
        /// Resolves a computer name. If its null or empty
        /// its assumed to be localhost.
        /// </summary>
        /// <param name="computerName">Computer name to resolve.</param>
        /// <returns>Resolved computer name.</returns>
        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 s_LOCALHOST;
            }
            else
            {
                return computerName;
            }
        }

        /// <summary>
        /// Load the resource corresponding to the specified errorId and
        /// return the message as a string.
        /// </summary>
        /// <param name="resourceString">resource String which holds the message
        /// </param>
        /// <returns>Error message loaded from appropriate resource cache.</returns>
        internal string GetMessage(string resourceString)
        {
            string message = GetMessage(resourceString, null);

            return message;
        }

        /// <summary>
        /// </summary>
        /// <param name="resourceString"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        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 readonly string s_LOCALHOST = "localhost";

        // private PSETWTracer tracer = PSETWTracer.GetETWTracer(PSKeyword.Cmdlets);

        #endregion Private Members

        #region Protected Members

        /// <summary>
        /// Computername parameter set.
        /// </summary>
        protected const string ComputerNameParameterSet = "ComputerName";

        /// <summary>
        /// Computername with session instance ID parameter set.
        /// </summary>
        protected const string ComputerInstanceIdParameterSet = "ComputerInstanceId";

        /// <summary>
        /// Container ID parameter set.
        /// </summary>
        protected const string ContainerIdParameterSet = "ContainerId";

        /// <summary>
        /// VM guid parameter set.
        /// </summary>
        protected const string VMIdParameterSet = "VMId";

        /// <summary>
        /// VM name parameter set.
        /// </summary>
        protected const string VMNameParameterSet = "VMName";

        /// <summary>
        /// SSH host parameter set.
        /// </summary>
        protected const string SSHHostParameterSet = "SSHHost";

        /// <summary>
        /// SSH host parmeter set supporting hash connection parameters.
        /// </summary>
        protected const string SSHHostHashParameterSet = "SSHHostHashParam";

        /// <summary>
        /// Runspace parameter set.
        /// </summary>
        protected const string SessionParameterSet = "Session";

        /// <summary>
        /// Parameter set to use Windows PowerShell.
        /// </summary>
        protected const string UseWindowsPowerShellParameterSet = "UseWindowsPowerShellParameterSet";

        /// <summary>
        /// Default shellname.
        /// </summary>
        protected const string DefaultPowerShellRemoteShellName = WSManNativeApi.ResourceURIPrefix + "Microsoft.PowerShell";

        /// <summary>
        /// Default application name for the connection uri.
        /// </summary>
        protected const string DefaultPowerShellRemoteShellAppName = "WSMan";

        #endregion Protected Members

        #region Internal Members

        /// <summary>
        /// Skip checking for WinRM.
        /// </summary>
        internal bool SkipWinRMCheck { get; set; } = false;

        #endregion Internal Members

        #region Protected Methods

        /// <summary>
        /// Determines the shellname to use based on the following order:
        ///     1. ShellName parameter specified
        ///     2. DEFAULTREMOTESHELLNAME variable set
        ///     3. PowerShell.
        /// </summary>
        /// <returns>The shell to launch in the remote machine.</returns>
        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;
        }

        /// <summary>
        /// Determines the appname to be used based on the following order:
        ///     1. AppName parameter specified
        ///     2. DEFAULTREMOTEAPPNAME variable set
        ///     3. WSMan.
        /// </summary>
        /// <param name="appName">Application name to resolve.</param>
        /// <returns>Resolved appname.</returns>
        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
    }

    /// <summary>
    /// Contains SSH connection information.
    /// </summary>
    internal struct SSHConnection
    {
        public string ComputerName;
        public string UserName;
        public string KeyFilePath;
        public int Port;
        public string Subsystem;
        public int ConnectingTimeout;
        public Hashtable Options;
    }

    /// <summary>
    /// 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.
    /// </summary>
    public abstract class PSRemotingBaseCmdlet : PSRemotingCmdlet
    {
        #region Enums

        /// <summary>
        /// State of virtual machine. This is the same as VMState in
        /// \vm\ux\powershell\objects\common\Types.cs.
        /// </summary>
        internal enum VMState
        {
            /// <summary>
            /// Other. Corresponds to CIM_EnabledLogicalElement.EnabledState = Other.
            /// </summary>
            Other = 1,

            /// <summary>
            /// Running. Corresponds to CIM_EnabledLogicalElement.EnabledState = Enabled.
            /// </summary>
            Running = 2,

            /// <summary>
            /// Off. Corresponds to CIM_EnabledLogicalElement.EnabledState = Disabled.
            /// </summary>
            Off = 3,

            /// <summary>
            /// Stopping. Corresponds to CIM_EnabledLogicalElement.EnabledState = ShuttingDown.
            /// </summary>
            Stopping = 4,

            /// <summary>
            /// Saved. Corresponds to CIM_EnabledLogicalElement.EnabledState = Enabled but offline.
            /// </summary>
            Saved = 6,

            /// <summary>
            /// Paused. Corresponds to CIM_EnabledLogicalElement.EnabledState = Quiesce.
            /// </summary>
            Paused = 9,

            /// <summary>
            /// Starting. EnabledStateStarting. State transition from PowerOff or Saved to Running.
            /// </summary>
            Starting = 10,

            /// <summary>
            /// Reset. Corresponds to CIM_EnabledLogicalElement.EnabledState = Reset.
            /// </summary>
            Reset = 11,

            /// <summary>
            /// Saving. Corresponds to EnabledStateSaving.
            /// </summary>
            Saving = 32773,

            /// <summary>
            /// Pausing. Corresponds to EnabledStatePausing.
            /// </summary>
            Pausing = 32776,

            /// <summary>
            /// Resuming. Corresponds to EnabledStateResuming.
            /// </summary>
            Resuming = 32777,

            /// <summary>
            /// FastSaved. EnabledStateFastSuspend.
            /// </summary>
            FastSaved = 32779,

            /// <summary>
            /// FastSaving. EnabledStateFastSuspending.
            /// </summary>
            FastSaving = 32780,

            /// <summary>
            /// ForceShutdown. Used to force a graceful shutdown of the virtual machine.
            /// </summary>
            ForceShutdown = 32781,

            /// <summary>
            /// ForceReboot. Used to force a graceful reboot of the virtual machine.
            /// </summary>
            ForceReboot = 32782,

            /// <summary>
            /// RunningCritical. Critical states.
            /// </summary>
            RunningCritical,

            /// <summary>
            /// OffCritical. Critical states.
            /// </summary>
            OffCritical,

            /// <summary>
            /// StoppingCritical. Critical states.
            /// </summary>
            StoppingCritical,

            /// <summary>
            /// SavedCritical. Critical states.
            /// </summary>
            SavedCritical,

            /// <summary>
            /// PausedCritical. Critical states.
            /// </summary>
            PausedCritical,

            /// <summary>
            /// StartingCritical. Critical states.
            /// </summary>
            StartingCritical,

            /// <summary>
            /// ResetCritical. Critical states.
            /// </summary>
            ResetCritical,

            /// <summary>
            /// SavingCritical. Critical states.
            /// </summary>
            SavingCritical,

            /// <summary>
            /// PausingCritical. Critical states.
            /// </summary>
            PausingCritical,

            /// <summary>
            /// ResumingCritical. Critical states.
            /// </summary>
            ResumingCritical,

            /// <summary>
            /// FastSavedCritical. Critical states.
            /// </summary>
            FastSavedCritical,

            /// <summary>
            /// FastSavingCritical. Critical states.
            /// </summary>
            FastSavingCritical,
        }

        #endregion

        #region Tracer

        // PSETWTracer tracer = PSETWTracer.GetETWTracer(PSKeyword.Runspace);

        #endregion Tracer

        #region Properties

        /// <summary>
        /// The PSSession object describing the remote runspace
        /// using which the specified cmdlet operation will be performed.
        /// </summary>
        [Parameter(Position = 0,
                   ValueFromPipelineByPropertyName = true,
                   ParameterSetName = PSRemotingBaseCmdlet.SessionParameterSet)]
        [ValidateNotNullOrEmpty]
        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
        public virtual PSSession[] Session { get; set; }

        /// <summary>
        /// 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.
        /// </summary>
        [Parameter(Position = 0,
                   ValueFromPipelineByPropertyName = true,
                   ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]
        [Alias("Cn")]
        public virtual string[] ComputerName { get; set; }

        /// <summary>
        /// Computer names after they have been resolved
        /// (null, empty string, "." resolves to localhost)
        /// </summary>
        /// <remarks>If Null or empty string is specified, then localhost is assumed.
        /// The ResolveComputerNames will include this.
        /// </remarks>
        protected string[] ResolvedComputerNames { get; set; }

        /// <summary>
        /// Guid of target virtual machine.
        /// </summary>
        [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; set; }

        /// <summary>
        /// Name of target virtual machine.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays",
            Justification = "This is by spec.")]
        [Parameter(Mandatory = true,
                   ValueFromPipelineByPropertyName = true,
                   ParameterSetName = PSRemotingBaseCmdlet.VMNameParameterSet)]
        [ValidateNotNullOrEmpty]
        public virtual string[] VMName { get; set; }

        /// <summary>
        /// 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.
        /// </summary>
        [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;

        /// <summary>
        /// ID of target container.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays",
            Justification = "This is by spec.")]
        [Parameter(Mandatory = true,
                   ValueFromPipelineByPropertyName = true,
                   ParameterSetName = PSRemotingBaseCmdlet.ContainerIdParameterSet)]
        [ValidateNotNullOrEmpty]
        public virtual string[] ContainerId { get; set; }

        /// <summary>
        /// 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.
        /// </summary>
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ContainerIdParameterSet)]
        public virtual SwitchParameter RunAsAdministrator { get; set; }

        /// <summary>
        /// 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)
        /// </summary>
        /// <remarks>
        /// 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
        /// </remarks>
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
        [ValidateRange((int)1, (int)UInt16.MaxValue)]
        public virtual int Port { get; set; }

        /// <summary>
        /// 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.
        /// </summary>
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SSL")]
        public virtual SwitchParameter UseSSL { get; set; }

        /// <summary>
        /// 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 that's
        /// not specified as well, then "WSMAN" will be used.
        /// </summary>
        [Parameter(ValueFromPipelineByPropertyName = true,
                   ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]
        public virtual string ApplicationName
        {
            get
            {
                return _appName;
            }

            set
            {
                _appName = ResolveAppName(value);
            }
        }

        private string _appName;

        /// <summary>
        /// Allows the user of the cmdlet to specify a throttling value
        /// for throttling the number of remote operations that can
        /// be executed simultaneously.
        /// </summary>
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SessionParameterSet)]
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)]
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ContainerIdParameterSet)]
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.VMIdParameterSet)]
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.VMNameParameterSet)]
        public virtual int ThrottleLimit { get; set; } = 0;

        /// <summary>
        /// A complete URI(s) specified for the remote computer and shell to
        /// connect to and create runspace for.
        /// </summary>
        [Parameter(Position = 0, Mandatory = true,
                   ValueFromPipelineByPropertyName = true,
                   ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)]
        [ValidateNotNullOrEmpty]
        [Alias("URI", "CU")]
        public virtual Uri[] ConnectionUri { get; set; }

        /// <summary>
        /// The AllowRedirection parameter enables the implicit redirection functionality.
        /// </summary>
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)]
        public virtual SwitchParameter AllowRedirection
        {
            get { return _allowRedirection; }

            set { _allowRedirection = value; }
        }

        private bool _allowRedirection = false;

        /// <summary>
        /// Extended Session Options for controlling the session creation. Use
        /// "New-WSManSessionOption" cmdlet to supply value for this parameter.
        /// </summary>
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)]
        [ValidateNotNull]
        public virtual PSSessionOption SessionOption
        {
            get
            {
                if (_sessionOption == null)
                {
                    object tmp = this.SessionState.PSVariable.GetValue(DEFAULT_SESSION_OPTION);
                    if (tmp == null || !LanguagePrimitives.TryConvertTo<PSSessionOption>(tmp, out _sessionOption))
                    {
                        _sessionOption = new PSSessionOption();
                    }
                }

                return _sessionOption;
            }

            set
            {
                _sessionOption = value;
            }
        }

        private PSSessionOption _sessionOption;

        internal const string DEFAULT_SESSION_OPTION = "PSSessionOption";

        // Quota related variables.
        /// <summary>
        /// Use basic authentication to authenticate the user.
        /// </summary>
        [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;

        /// <summary>
        /// Specifies the certificate thumbprint to be used to impersonate the user on the
        /// remote machine.
        /// </summary>
        [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;

        #region SSHHostParameters

        /// <summary>
        /// Host name for an SSH remote connection.
        /// </summary>
        [Parameter(Position = 0, Mandatory = true,
            ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
        [ValidateNotNullOrEmpty()]
        public virtual string[] HostName
        {
            get;
            set;
        }

        /// <summary>
        /// SSH User Name.
        /// </summary>
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
        [ValidateNotNullOrEmpty()]
        public virtual string UserName
        {
            get;
            set;
        }

        /// <summary>
        /// SSH Key File Path.
        /// </summary>
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
        [ValidateNotNullOrEmpty()]
        [Alias("IdentityFilePath")]
        public virtual string KeyFilePath
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value for the SSH subsystem to use for the remote connection.
        /// </summary>
        [Parameter(ValueFromPipelineByPropertyName = true,
                   ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
        public virtual string Subsystem { get; set; }

        /// <summary>
        /// Gets or sets a value in milliseconds that limits the time allowed for an SSH connection to be established.
        /// Default timeout value is infinite.
        /// </summary>
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
        public virtual int ConnectingTimeout { get; set; } = Timeout.Infinite;

        /// <summary>
        /// This parameter specifies that SSH is used to establish the remote
        /// connection and act as the remoting transport.  By default WinRM is used
        /// as the remoting transport.  Using the SSH transport requires that SSH is
        /// installed and PowerShell remoting is enabled on both client and remote machines.
        /// </summary>
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
        [ValidateSet("true")]
        public virtual SwitchParameter SSHTransport
        {
            get;
            set;
        }

        /// <summary>
        /// Hashtable array containing SSH connection parameters for each remote target
        ///   ComputerName  (Alias: HostName)           (required)
        ///   UserName                                  (optional)
        ///   KeyFilePath   (Alias: IdentityFilePath)   (optional)
        /// </summary>
        [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostHashParameterSet, Mandatory = true)]
        [ValidateNotNullOrEmpty()]
        public virtual Hashtable[] SSHConnection
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the Hashtable containing options to be passed to OpenSSH.
        /// </summary>
        [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]
        [ValidateNotNullOrEmpty]
        public virtual Hashtable Options { get; set; }

        #endregion

        #endregion Properties

        #region Internal Static Methods

        /// <summary>
        /// Used to resolve authentication from the parameters chosen by the user.
        /// User has the following options:
        /// 1. AuthMechanism + Credential
        /// 2. CertificateThumbPrint
        ///
        /// All the above are mutually exclusive.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// If there is ambiguity as specified above.
        /// </exception>
        internal static void ValidateSpecifiedAuthentication(PSCredential credential, string thumbprint, AuthenticationMechanism authentication)
        {
            if ((credential != null) && (thumbprint != null))
            {
                string message = PSRemotingErrorInvariants.FormatResourceString(
                    RemotingErrorIdStrings.NewRunspaceAmbiguousAuthentication,
                        "CertificateThumbPrint", "Credential");

                throw new InvalidOperationException(message);
            }

            if ((authentication != AuthenticationMechanism.Default) && (thumbprint != null))
            {
                string message = PSRemotingErrorInvariants.FormatResourceString(
                    RemotingErrorIdStrings.NewRunspaceAmbiguousAuthentication,
                        "CertificateThumbPrint", authentication.ToString());

                throw new InvalidOperationException(message);
            }

            if ((authentication == AuthenticationMechanism.NegotiateWithImplicitCredential) &&
                (credential != null))
            {
                string message = PSRemotingErrorInvariants.FormatResourceString(
                    RemotingErrorIdStrings.NewRunspaceAmbiguousAuthentication,
                    "Credential", authentication.ToString());
                throw new InvalidOperationException(message);
            }
        }

        #endregion

        #region Internal Methods

        #region SSH Connection Strings

        private const string ComputerNameParameter = "ComputerName";
        private const string HostNameAlias = "HostName";
        private const string UserNameParameter = "UserName";
        private const string KeyFilePathParameter = "KeyFilePath";
        private const string IdentityFilePathAlias = "IdentityFilePath";
        private const string PortParameter = "Port";
        private const string SubsystemParameter = "Subsystem";
        private const string ConnectingTimeoutParameter = "ConnectingTimeout";
        private const string OptionsParameter = "Options";

        #endregion

        /// <summary>
        /// Parse a hostname used with SSH Transport to get embedded
        /// username and/or port.
        /// </summary>
        /// <param name="hostname">Host name to parse.</param>
        /// <param name="host">Resolved target host.</param>
        /// <param name="userName">Resolved target user name.</param>
        /// <param name="port">Resolved target port.</param>
        protected void ParseSshHostName(string hostname, out string host, out string userName, out int port)
        {
            host = hostname;
            userName = this.UserName;
            port = this.Port;
            try
            {
                Uri uri = new System.Uri("ssh://" + hostname);
                host = ResolveComputerName(uri.Host);
                ValidateComputerName(new string[] { host });
                if (uri.UserInfo != string.Empty)
                {
                    userName = uri.UserInfo;
                }

                if (uri.Port != -1)
                {
                    port = uri.Port;
                }
            }
            catch (UriFormatException)
            {
                ThrowTerminatingError(new ErrorRecord(
                    new ArgumentException(PSRemotingErrorInvariants.FormatResourceString(
                        RemotingErrorIdStrings.InvalidComputerName)), "PSSessionInvalidComputerName",
                            ErrorCategory.InvalidArgument, hostname));
            }
        }

        /// <summary>
        /// Parse the Connection parameter HashTable array.
        /// </summary>
        /// <returns>Array of SSHConnection objects.</returns>
        internal SSHConnection[] ParseSSHConnectionHashTable()
        {
            List<SSHConnection> connections = new();
            foreach (var item in this.SSHConnection)
            {
                if (item.ContainsKey(ComputerNameParameter) && item.ContainsKey(HostNameAlias))
                {
                    throw new PSArgumentException(RemotingErrorIdStrings.SSHConnectionDuplicateHostName);
                }

                if (item.ContainsKey(KeyFilePathParameter) && item.ContainsKey(IdentityFilePathAlias))
                {
                    throw new PSArgumentException(RemotingErrorIdStrings.SSHConnectionDuplicateKeyPath);
                }

                SSHConnection connectionInfo = new();
                foreach (var key in item.Keys)
                {
                    string paramName = key as string;
                    if (string.IsNullOrEmpty(paramName))
                    {
                        throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter);
                    }

                    if (paramName.Equals(ComputerNameParameter, StringComparison.OrdinalIgnoreCase) || paramName.Equals(HostNameAlias, StringComparison.OrdinalIgnoreCase))
                    {
                        var resolvedComputerName = ResolveComputerName(GetSSHConnectionStringParameter(item[paramName]));
                        ParseSshHostName(resolvedComputerName, out string host, out string userName, out int port);
                        connectionInfo.ComputerName = host;
                        if (userName != string.Empty)
                        {
                            connectionInfo.UserName = userName;
                        }

                        if (port != -1)
                        {
                            connectionInfo.Port = port;
                        }
                    }
                    else if (paramName.Equals(UserNameParameter, StringComparison.OrdinalIgnoreCase))
                    {
                        connectionInfo.UserName = GetSSHConnectionStringParameter(item[paramName]);
                    }
                    else if (paramName.Equals(KeyFilePathParameter, StringComparison.OrdinalIgnoreCase) || paramName.Equals(IdentityFilePathAlias, StringComparison.OrdinalIgnoreCase))
                    {
                        connectionInfo.KeyFilePath = GetSSHConnectionStringParameter(item[paramName]);
                    }
                    else if (paramName.Equals(PortParameter, StringComparison.OrdinalIgnoreCase))
                    {
                        connectionInfo.Port = GetSSHConnectionIntParameter(item[paramName]);
                    }
                    else if (paramName.Equals(SubsystemParameter, StringComparison.OrdinalIgnoreCase))
                    {
                        connectionInfo.Subsystem = GetSSHConnectionStringParameter(item[paramName]);
                    }
                    else if (paramName.Equals(ConnectingTimeoutParameter, StringComparison.OrdinalIgnoreCase))
                    {
                        connectionInfo.ConnectingTimeout = GetSSHConnectionIntParameter(item[paramName]);
                    }
                    else if (paramName.Equals(OptionsParameter, StringComparison.OrdinalIgnoreCase))
                    {
                        connectionInfo.Options = item[paramName] as Hashtable;
                    }
                    else
                    {
                        throw new PSArgumentException(
                            StringUtil.Format(RemotingErrorIdStrings.UnknownSSHConnectionParameter, paramName));
                    }
                }

                if (string.IsNullOrEmpty(connectionInfo.ComputerName))
                {
                    throw new PSArgumentException(RemotingErrorIdStrings.MissingRequiredSSHParameter);
                }

                connections.Add(connectionInfo);
            }

            return connections.ToArray();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Validate the PSSession objects specified and write
        /// appropriate error records.
        /// </summary>
        /// <remarks>This function will lead in terminating errors when any of
        /// the validations fail</remarks>
        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)),
                        nameof(PSRemotingErrorId.RemoteRunspaceInfoHasDuplicates),
                            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)),
                        nameof(PSRemotingErrorId.RemoteRunspaceInfoLimitExceeded),
                            ErrorCategory.InvalidArgument, Session));
            }
        }

        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="connectionInfo"></param>
        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 parameter set
                connectionInfo.MaximumConnectionRedirectionCount = 0;
            }

            if (!_allowRedirection)
            {
                // uri redirection required explicit user consent
                connectionInfo.MaximumConnectionRedirectionCount = 0;
            }
        }

        /// <summary>
        /// Uri parameter set.
        /// </summary>
        protected const string UriParameterSet = "Uri";

        /// <summary>
        /// Validates computer names to check if none of them
        /// happen to be a Uri. If so this throws an error.
        /// </summary>
        /// <param name="computerNames">collection of computer
        /// names to validate</param>
        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));
                }
            }
        }

        /// <summary>
        /// Validates parameter value and returns as string.
        /// </summary>
        /// <param name="param">Parameter value to be validated.</param>
        /// <returns>Parameter value as string.</returns>
        private static string GetSSHConnectionStringParameter(object param)
        {
            string paramValue;
            try
            {
                paramValue = LanguagePrimitives.ConvertTo<string>(param);
            }
            catch (PSInvalidCastException e)
            {
                throw new PSArgumentException(e.Message, e);
            }

            if (!string.IsNullOrEmpty(paramValue))
            {
                return paramValue;
            }

            throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter);
        }

        /// <summary>
        /// Validates parameter value and returns as integer.
        /// </summary>
        /// <param name="param">Parameter value to be validated.</param>
        /// <returns>Parameter value as integer.</returns>
        private static int GetSSHConnectionIntParameter(object param)
        {
            if (param == null)
            {
                throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter);
            }

            try
            {
                return LanguagePrimitives.ConvertTo<int>(param);
            }
            catch (PSInvalidCastException e)
            {
                throw new PSArgumentException(e.Message, e);
            }
        }

        #endregion Private Methods

        #region Overrides

        /// <summary>
        /// Resolves shellname and appname.
        /// </summary>
        protected override void BeginProcessing()
        {
            base.BeginProcessing();

            // Validate KeyFilePath parameter.
            if ((ParameterSetName == PSRemotingBaseCmdlet.SSHHostParameterSet) &&
                (this.KeyFilePath != null))
            {
                // Resolve the key file path when set.
                this.KeyFilePath = PathResolver.ResolveProviderAndPath(this.KeyFilePath, true, this, false, RemotingErrorIdStrings.FilePathNotFromFileSystemProvider);
            }

            // 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
    }

    /// <summary>
    /// 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.
    /// </summary>
    public abstract class PSExecutionCmdlet : PSRemotingBaseCmdlet
    {
        #region Strings

        /// <summary>
        /// VM guid file path parameter set.
        /// </summary>
        protected const string FilePathVMIdParameterSet = "FilePathVMId";

        /// <summary>
        /// VM name file path parameter set.
        /// </summary>
        protected const string FilePathVMNameParameterSet = "FilePathVMName";

        /// <summary>
        /// Container ID file path parameter set.
        /// </summary>
        protected const string FilePathContainerIdParameterSet = "FilePathContainerId";

        /// <summary>
        /// SSH Host file path parameter set.
        /// </summary>
        protected const string FilePathSSHHostParameterSet = "FilePathSSHHost";

        /// <summary>
        /// SSH Host file path parameter set with HashTable connection parameter.
        /// </summary>
        protected const string FilePathSSHHostHashParameterSet = "FilePathSSHHostHash";

        #endregion

        #region Parameters

        /// <summary>
        /// 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.
        /// </summary>
        [Parameter(ValueFromPipeline = true)]
        public virtual PSObject InputObject { get; set; } = AutomationNull.Value;

        /// <summary>
        /// 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.
        /// </summary>
        public virtual ScriptBlock ScriptBlock
        {
            get
            {
                return _scriptBlock;
            }

            set
            {
                _scriptBlock = value;
            }
        }

        private ScriptBlock _scriptBlock;

        /// <summary>
        /// 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.
        /// </summary>
        [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;

        /// <summary>
        /// True if FilePath should be processed as a literal path.
        /// </summary>
        protected bool IsLiteralPath { get; set; }

        /// <summary>
        /// Arguments that are passed to this scriptblock.
        /// </summary>
        [Parameter()]
        [Alias("Args")]
        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
        public virtual object[] ArgumentList
        {
            get
            {
                return _args;
            }

            set
            {
                _args = value;
            }
        }

        private object[] _args;

        /// <summary>
        /// Indicates that if a job/command is invoked remotely the connection should be severed
        /// right have invocation of job/command.
        /// </summary>
        protected bool InvokeAndDisconnect { get; set; } = false;

        /// <summary>
        /// Session names optionally provided for Disconnected parameter.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
        protected string[] DisconnectedSessionName { get; set; }

        /// <summary>
        /// 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.
        /// </summary>
        public virtual SwitchParameter EnableNetworkAccess { get; set; }

        /// <summary>
        /// Guid of target virtual machine.
        /// </summary>
        [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; set; }

        /// <summary>
        /// Name of target virtual machine.
        /// </summary>
        [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; set; }

        /// <summary>
        /// ID of target container.
        /// </summary>
        [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; set; }

        /// <summary>
        /// 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.
        /// </summary>
        [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.VMIdParameterSet)]
        [Parameter(ValueFromPipelineByPropertyName = true,
                   ParameterSetName = InvokeCommandCommand.VMNameParameterSet)]
        [Parameter(ValueFromPipelineByPropertyName = true,
                   ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)]
        [Parameter(ValueFromPipelineByPropertyName = true,
                   ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)]
        [Parameter(ValueFromPipelineByPropertyName = true,
                   ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)]
        public virtual string ConfigurationName { get; set; }

        #endregion Parameters

        #region Private Methods

        /// <summary>
        /// Creates helper objects with the command for the specified
        /// remote computer names.
        /// </summary>
        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.GenerateRunspaceName(out 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, InvokeAndDisconnect);

                Operations.Add(operation);
            }
        }

        /// <summary>
        /// Creates helper objects for SSH remoting computer names
        /// remoting.
        /// </summary>
        protected void CreateHelpersForSpecifiedSSHComputerNames()
        {
            foreach (string computerName in ResolvedComputerNames)
            {
                ParseSshHostName(computerName, out string host, out string userName, out int port);

                var sshConnectionInfo = new SSHConnectionInfo(userName, host, KeyFilePath, port, Subsystem, ConnectingTimeout, Options);
                var typeTable = TypeTable.LoadDefaultTypeFiles();
                var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, Host, typeTable) as RemoteRunspace;
                var pipeline = CreatePipeline(remoteRunspace);

                var operation = new ExecutionCmdletHelperComputerName(remoteRunspace, pipeline);
                Operations.Add(operation);
            }
        }

        /// <summary>
        /// Creates helper objects for SSH remoting from HashTable parameters.
        /// </summary>
        protected void CreateHelpersForSpecifiedSSHHashComputerNames()
        {
            var sshConnections = ParseSSHConnectionHashTable();
            foreach (var sshConnection in sshConnections)
            {
                var sshConnectionInfo = new SSHConnectionInfo(
                    sshConnection.UserName,
                    sshConnection.ComputerName,
                    sshConnection.KeyFilePath,
                    sshConnection.Port,
                    sshConnection.Subsystem,
                    sshConnection.ConnectingTimeout);
                var typeTable = TypeTable.LoadDefaultTypeFiles();
                var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace;
                var pipeline = CreatePipeline(remoteRunspace);

                var operation = new ExecutionCmdletHelperComputerName(remoteRunspace, pipeline);
                Operations.Add(operation);
            }
        }

        /// <summary>
        /// Creates helper objects with the specified command for
        /// the specified remote runspaceinfo objects.
        /// </summary>
        [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);
            }
        }

        /// <summary>
        /// Creates helper objects with the command for the specified
        /// remote connection uris.
        /// </summary>
        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, InvokeAndDisconnect);

                Operations.Add(operation);
            }
        }

        /// <summary>
        /// Creates helper objects with the command for the specified
        /// VM GUIDs or VM names.
        /// </summary>
        protected virtual void CreateHelpersForSpecifiedVMSession()
        {
            int inputArraySize;
            int index;
            string command;
            bool[] vmIsRunning;
            Collection<PSObject> 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),
                                nameof(PSRemotingErrorId.HyperVModuleNotAvailable),
                                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),
                                nameof(PSRemotingErrorId.HyperVModuleNotAvailable),
                                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])),
                            nameof(PSRemotingErrorId.InvalidVMNameNotSingle),
                            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))),
                            nameof(PSRemotingErrorId.InvalidVMIdNotSingle),
                            ErrorCategory.InvalidArgument,
                            null));

                    continue;
                }
                else if (!vmIsRunning[index])
                {
                    WriteError(
                        new ErrorRecord(
                            new ArgumentException(GetMessage(RemotingErrorIdStrings.InvalidVMState,
                                                             this.VMName[index])),
                            nameof(PSRemotingErrorId.InvalidVMState),
                            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);
            }
        }

        /// <summary>
        /// Creates helper objects with the command for the specified
        /// container IDs or names.
        /// </summary>
        protected virtual void CreateHelpersForSpecifiedContainerSession()
        {
            List<string> resolvedNameList = new List<string>();

            Dbg.Assert((ParameterSetName == PSExecutionCmdlet.ContainerIdParameterSet) ||
                       (ParameterSetName == PSExecutionCmdlet.FilePathContainerIdParameterSet),
                       "Expected ParameterSetName == ContainerId or FilePathContainerId");

            foreach (var input in ContainerId)
            {
                //
                // 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.
                    //
                    connectionInfo = ContainerConnectionInfo.CreateContainerConnectionInfo(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();
        }

        /// <summary>
        /// Creates a pipeline from the powershell.
        /// </summary>
        /// <param name="remoteRunspace">Runspace on which to create the pipeline.</param>
        /// <returns>A pipeline.</returns>
        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;
        }

        /// <summary>
        /// Check the powershell version of the remote server.
        /// </summary>
        private static 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;
        }

        /// <summary>
        /// Adds forwarded events to the local queue.
        /// </summary>
        internal void OnRunspacePSEventReceived(object sender, PSEventArgs e) => this.Events?.AddForwardedEvent(e);

        #endregion Private Methods

        #region Protected Members / Methods

        /// <summary>
        /// List of operations.
        /// </summary>
        internal List<IThrottleOperation> Operations { get; } = new List<IThrottleOperation>();

        /// <summary>
        /// Closes the input streams on all the pipelines.
        /// </summary>
        protected void CloseAllInputStreams()
        {
            foreach (IThrottleOperation operation in Operations)
            {
                ExecutionCmdletHelper helper = (ExecutionCmdletHelper)operation;
                helper.Pipeline.Input.Close();
            }
        }

        /// <summary>
        /// Writes an error record specifying that creation of remote runspace
        /// failed.
        /// </summary>
        /// <param name="e">exception which is causing this error record
        /// to be written</param>
        /// <param name="uri">Uri which caused this exception.</param>
        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);
        }

        /// <summary>
        /// FilePathComputername parameter set.
        /// </summary>
        protected const string FilePathComputerNameParameterSet = "FilePathComputerName";

        /// <summary>
        /// LiteralFilePathComputername parameter set.
        /// </summary>
        protected const string LiteralFilePathComputerNameParameterSet = "LiteralFilePathComputerName";

        /// <summary>
        /// FilePathRunspace parameter set.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace")]
        protected const string FilePathSessionParameterSet = "FilePathRunspace";

        /// <summary>
        /// FilePathUri parameter set.
        /// </summary>
        protected const string FilePathUriParameterSet = "FilePathUri";

        /// <summary>
        /// PS version of the remote server.
        /// </summary>
        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;

        /// <summary>
        /// Reads content of file and converts it to a scriptblock.
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="isLiteralPath"></param>
        /// <returns></returns>
        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), nameof(filePath));
            }

            if (!filePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException(PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.FilePathShouldPS1Extension), nameof(filePath));
            }

            // Resolve file path
            string resolvedPath = PathResolver.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

        /// <summary>
        /// Creates the helper classes for the specified
        /// parameter set.
        /// </summary>
        protected override void BeginProcessing()
        {
            if ((ParameterSetName == PSExecutionCmdlet.VMIdParameterSet) ||
                (ParameterSetName == PSExecutionCmdlet.VMNameParameterSet) ||
                (ParameterSetName == PSExecutionCmdlet.ContainerIdParameterSet) ||
                (ParameterSetName == PSExecutionCmdlet.FilePathVMIdParameterSet) ||
                (ParameterSetName == PSExecutionCmdlet.FilePathVMNameParameterSet) ||
                (ParameterSetName == PSExecutionCmdlet.FilePathContainerIdParameterSet))
            {
                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.SSHHostParameterSet:
                case PSExecutionCmdlet.FilePathSSHHostParameterSet:
                    {
                        string[] resolvedComputerNames = null;
                        ResolveComputerNames(HostName, out resolvedComputerNames);
                        ResolvedComputerNames = resolvedComputerNames;

                        CreateHelpersForSpecifiedSSHComputerNames();
                    }

                    break;

                case PSExecutionCmdlet.SSHHostHashParameterSet:
                case PSExecutionCmdlet.FilePathSSHHostHashParameterSet:
                    {
                        CreateHelpersForSpecifiedSSHHashComputerNames();
                    }

                    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.FilePathContainerIdParameterSet:
                    {
                        CreateHelpersForSpecifiedContainerSession();
                    }

                    break;
            }
        }

        #endregion Overrides

        #region "Get PowerShell instance"

        /// <summary>
        /// Get the PowerShell instance for the PSv2 remote end
        /// Generate the PowerShell instance by using the text of the scriptblock.
        /// </summary>
        /// <remarks>
        /// 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.
        /// </remarks>
        /// <returns></returns>
        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<string> newParameterNames;
            List<object> 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;
        }

        /// <summary>
        /// Get the PowerShell instance for the PSv3 (or later) remote end
        /// Generate the PowerShell instance by using the text of the scriptblock.
        /// </summary>
        /// <remarks>
        /// 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.
        /// </remarks>
        /// <returns></returns>
        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"

        /// <summary>
        /// Get the converted script for a remote PSv2 end.
        /// </summary>
        /// <param name="newParameterNames">
        /// The new parameter names that we added to the param block
        /// </param>
        /// <param name="newParameterValues">
        /// The new parameter values that need to be added to the powershell instance
        /// </param>
        /// <returns></returns>
        private string GetConvertedScript(out List<string> newParameterNames, out List<object> newParameterValues)
        {
            newParameterNames = null; newParameterValues = null;
            string textOfScriptBlock = null;

            // Scan for the using variables
            List<VariableExpressionAst> 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<string>();
                var paramNamesWithDollarSign = new List<string>();
                var paramUsingVars = new List<VariableExpressionAst>();
                var nameHashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

                foreach (var varAst in usingVariables)
                {
                    VariablePath varPath = varAst.VariablePath;
                    string varName = varPath.IsDriveQualified ? $"{varPath.DriveName}_{varPath.UnqualifiedPath}" : $"{varPath.UnqualifiedPath}";
                    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;
        }

        /// <summary>
        /// Get the values for the using variables that are passed in.
        /// </summary>
        /// <param name="paramUsingVars"></param>
        /// <returns></returns>
        private List<object> GetUsingVariableValues(List<VariableExpressionAst> paramUsingVars)
        {
            var values = new List<object>(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;
        }

        /// <summary>
        /// Get all Using expressions that we care about.
        /// </summary>
        /// <param name="localScriptBlock"></param>
        /// <returns>A list of UsingExpressionAsts ordered by the StartOffset.</returns>
        private static List<VariableExpressionAst> GetUsingVariables(ScriptBlock localScriptBlock)
        {
            ArgumentNullException.ThrowIfNull(localScriptBlock, "Caller needs to make sure the parameter value is not null");

            var allUsingExprs = UsingExpressionAstSearcher.FindAllUsingExpressions(localScriptBlock.Ast);
            return allUsingExprs.Select(static usingExpr => UsingExpressionAst.ExtractUsingVariable((UsingExpressionAst)usingExpr)).ToList();
        }

        #endregion "UsingExpression Utilities"
    }

    /// <summary>
    /// 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.
    /// </summary>
    public abstract class PSRunspaceCmdlet : PSRemotingCmdlet
    {
        #region Parameters

        /// <summary>
        /// ContainerIdInstanceId parameter set: container id + session instance id.
        /// </summary>
        protected const string ContainerIdInstanceIdParameterSet = "ContainerIdInstanceId";

        /// <summary>
        /// VMIdInstanceId parameter set: vm id + session instance id.
        /// </summary>
        protected const string VMIdInstanceIdParameterSet = "VMIdInstanceId";

        /// <summary>
        /// VMNameInstanceId parameter set: vm name + session instance id.
        /// </summary>
        protected const string VMNameInstanceIdParameterSet = "VMNameInstanceId";

        /// <summary>
        /// RemoteRunspaceId to retrieve corresponding PSSession
        /// object.
        /// </summary>
        [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;

        /// <summary>
        /// Session Id of the remoterunspace info object.
        /// </summary>
        [Parameter(Position = 0,
                   ValueFromPipelineByPropertyName = true,
                   Mandatory = true,
                   ParameterSetName = PSRunspaceCmdlet.IdParameterSet)]
        [ValidateNotNull]
        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
        public int[] Id { get; set; }

        /// <summary>
        /// Name of the remote runspaceinfo object.
        /// </summary>
        [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true,
                   ParameterSetName = PSRunspaceCmdlet.NameParameterSet)]
        [ValidateNotNullOrEmpty()]
        public virtual string[] Name
        {
            get
            {
                return _names;
            }

            set
            {
                _names = value;
            }
        }

        private string[] _names;

        /// <summary>
        /// Name of the computer for which the runspace needs to be
        /// returned.
        /// </summary>
        [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;

        /// <summary>
        /// ID of target container.
        /// </summary>
        [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; set; }

        /// <summary>
        /// Guid of target virtual machine.
        /// </summary>
        [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; set; }

        /// <summary>
        /// Name of target virtual machine.
        /// </summary>
        [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; set; }

        #endregion Parameters

        #region Private / Protected Methods

        /// <summary>
        /// Gets the matching runspaces based on the parameterset.
        /// </summary>
        /// <param name="writeErrorOnNoMatch">write an error record when
        /// no matches are found</param>
        /// <param name="writeobject">if true write the object down
        /// the pipeline</param>
        /// <returns>List of matching runspaces.</returns>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")]
        protected Dictionary<Guid, PSSession> GetMatchingRunspaces(bool writeobject,
            bool writeErrorOnNoMatch)
        {
            return GetMatchingRunspaces(writeobject, writeErrorOnNoMatch, SessionFilterState.All, null);
        }

        /// <summary>
        /// Gets the matching runspaces based on the parameterset.
        /// </summary>
        /// <param name="writeErrorOnNoMatch">write an error record when
        /// no matches are found</param>
        /// <param name="writeobject">if true write the object down
        /// the pipeline</param>
        /// <param name="filterState">Runspace state filter value.</param>
        /// <param name="configurationName">Runspace configuration name filter value.</param>
        /// <returns>List of matching runspaces.</returns>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")]
        protected Dictionary<Guid, PSSession> 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 GetMatchingRunspacesByVMNameContainerId(writeobject, filterState, configurationName, true);
                    }

                // container id + session instanceid
                case PSRunspaceCmdlet.ContainerIdInstanceIdParameterSet:
                    {
                        return GetMatchingRunspacesByVMNameContainerIdSessionInstanceId(writeobject, filterState, configurationName, true);
                    }

                // 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 GetMatchingRunspacesByVMNameContainerId(writeobject, filterState, configurationName, false);
                    }

                // vm name + session instanceid
                case PSRunspaceCmdlet.VMNameInstanceIdParameterSet:
                    {
                        return GetMatchingRunspacesByVMNameContainerIdSessionInstanceId(writeobject, filterState, configurationName, false);
                    }
            }

            return null;
        }

        internal Dictionary<Guid, PSSession> GetAllRunspaces(bool writeobject,
            bool writeErrorOnNoMatch)
        {
            Dictionary<Guid, PSSession> matches = new Dictionary<Guid, PSSession>();
            List<PSSession> 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;
        }

        /// <summary>
        /// Gets the matching runspaces by computernames.
        /// </summary>
        /// <param name="writeErrorOnNoMatch">write an error record when
        /// no matches are found</param>
        /// <param name="writeobject">if true write the object down
        /// the pipeline</param>
        /// <returns>List of matching runspaces.</returns>
        private Dictionary<Guid, PSSession> GetMatchingRunspacesByComputerName(bool writeobject,
            bool writeErrorOnNoMatch)
        {
            if (_computerNames == null || _computerNames.Length == 0)
            {
                return GetAllRunspaces(writeobject, writeErrorOnNoMatch);
            }

            Dictionary<Guid, PSSession> matches = new Dictionary<Guid, PSSession>();

            List<PSSession> 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;
        }

        /// <summary>
        /// Gets the matching runspaces based on name.
        /// </summary>
        /// <param name="writeErrorOnNoMatch">write an error record when
        /// no matches are found</param>
        /// <param name="writeobject">if true write the object down
        /// the pipeline</param>
        /// <returns>List of matching runspaces.</returns>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")]
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "writeobject")]
        protected Dictionary<Guid, PSSession> GetMatchingRunspacesByName(bool writeobject,
            bool writeErrorOnNoMatch)
        {
            Dictionary<Guid, PSSession> matches = new Dictionary<Guid, PSSession>();

            List<PSSession> 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;
        }

        /// <summary>
        /// Gets the matching runspaces based on the runspaces instance id.
        /// </summary>
        /// <param name="writeErrorOnNoMatch">write an error record when
        /// no matches are found</param>
        /// <param name="writeobject">if true write the object down
        /// the pipeline</param>
        /// <returns>List of matching runspaces.</returns>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")]
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "writeobject")]
        protected Dictionary<Guid, PSSession> GetMatchingRunspacesByRunspaceId(bool writeobject,
            bool writeErrorOnNoMatch)
        {
            Dictionary<Guid, PSSession> matches = new Dictionary<Guid, PSSession>();

            List<PSSession> 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;
        }

        /// <summary>
        /// Gets the matching runspaces based on the session id (the
        /// short integer id which is unique for a runspace)
        /// </summary>
        /// <param name="writeErrorOnNoMatch">write an error record when
        /// no matches are found</param>
        /// <param name="writeobject">if true write the object down
        /// the pipeline</param>
        /// <returns>List of matching runspaces.</returns>
        private Dictionary<Guid, PSSession> GetMatchingRunspacesBySessionId(bool writeobject,
            bool writeErrorOnNoMatch)
        {
            Dictionary<Guid, PSSession> matches = new Dictionary<Guid, PSSession>();

            List<PSSession> remoteRunspaceInfos = this.RunspaceRepository.Runspaces;

            // Loop through all computer-name patterns and runspaces to find matches.
            foreach (int sessionId in Id)
            {
                // 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;
        }

        /// <summary>
        /// Gets the matching runspaces by vm name or container id with optional session name.
        /// </summary>
        /// <param name="writeobject">If true write the object down the pipeline.</param>
        /// <param name="filterState">Runspace state filter value.</param>
        /// <param name="configurationName">Runspace configuration name filter value.</param>
        /// <param name="isContainer">If true the target is a container instead of virtual machine.</param>
        /// <returns>List of matching runspaces.</returns>
        private Dictionary<Guid, PSSession> GetMatchingRunspacesByVMNameContainerId(bool writeobject,
            SessionFilterState filterState,
            string configurationName,
            bool isContainer)
        {
            string[] inputNames;
            TargetMachineType computerType;
            bool supportWildChar;
            string[] sessionNames = { "*" };
            WildcardPattern configurationNamePattern =
                string.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase);
            Dictionary<Guid, PSSession> matches = new Dictionary<Guid, PSSession>();
            List<PSSession> remoteRunspaceInfos = this.RunspaceRepository.Runspaces;

            // 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)
            {
                inputNames = ContainerId;
                computerType = TargetMachineType.Container;
                supportWildChar = false;
            }
            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<PSSession>(session => (supportWildChar ? inputNamePattern.IsMatch(session.VMName)
                                                                      : inputName.Equals(session.ContainerId)) &&
                                                     (sessionNamePattern == null || sessionNamePattern.IsMatch(session.Name)) &&
                                                     QueryRunspaces.TestRunspaceState(session.Runspace, filterState) &&
                                                     (configurationNamePattern == null || configurationNamePattern.IsMatch(session.ConfigurationName)) &&
                                                     (session.ComputerType == computerType))
                        .ToList<PSSession>();

                    WriteOrAddMatches(matchingRunspaceInfos, writeobject, ref matches);
                }
            }

            return matches;
        }

        /// <summary>
        /// Gets the matching runspaces by vm name or container id with session instanceid.
        /// </summary>
        /// <param name="writeobject">If true write the object down the pipeline.</param>
        /// <param name="filterState">Runspace state filter value.</param>
        /// <param name="configurationName">Runspace configuration name filter value.</param>
        /// <param name="isContainer">If true the target is a container instead of virtual machine.</param>
        /// <returns>List of matching runspaces.</returns>
        private Dictionary<Guid, PSSession> GetMatchingRunspacesByVMNameContainerIdSessionInstanceId(bool writeobject,
            SessionFilterState filterState,
            string configurationName,
            bool isContainer)
        {
            string[] inputNames;
            TargetMachineType computerType;
            bool supportWildChar;
            WildcardPattern configurationNamePattern =
                string.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase);
            Dictionary<Guid, PSSession> matches = new Dictionary<Guid, PSSession>();
            List<PSSession> remoteRunspaceInfos = this.RunspaceRepository.Runspaces;

            // 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)
            {
                inputNames = ContainerId;
                computerType = TargetMachineType.Container;
                supportWildChar = false;
            }
            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<PSSession>(session => (supportWildChar ? inputNamePattern.IsMatch(session.VMName)
                                                                      : inputName.Equals(session.ContainerId)) &&
                                                     sessionInstanceId.Equals(session.InstanceId) &&
                                                     QueryRunspaces.TestRunspaceState(session.Runspace, filterState) &&
                                                     (configurationNamePattern == null || configurationNamePattern.IsMatch(session.ConfigurationName)) &&
                                                     (session.ComputerType == computerType))
                        .ToList<PSSession>();

                    WriteOrAddMatches(matchingRunspaceInfos, writeobject, ref matches);
                }
            }

            return matches;
        }

        /// <summary>
        /// Gets the matching runspaces by vm guid and optional session name.
        /// </summary>
        /// <param name="writeobject">If true write the object down the pipeline.</param>
        /// <param name="filterState">Runspace state filter value.</param>
        /// <param name="configurationName">Runspace configuration name filter value.</param>
        /// <returns>List of matching runspaces.</returns>
        private Dictionary<Guid, PSSession> GetMatchingRunspacesByVMId(bool writeobject,
            SessionFilterState filterState,
            string configurationName)
        {
            string[] sessionNames = { "*" };
            WildcardPattern configurationNamePattern =
                string.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase);
            Dictionary<Guid, PSSession> matches = new Dictionary<Guid, PSSession>();
            List<PSSession> 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<PSSession>(session => vmId.Equals(session.VMId) &&
                                                     (sessionNamePattern == null || sessionNamePattern.IsMatch(session.Name)) &&
                                                     QueryRunspaces.TestRunspaceState(session.Runspace, filterState) &&
                                                     (configurationNamePattern == null || configurationNamePattern.IsMatch(session.ConfigurationName)) &&
                                                     (session.ComputerType == TargetMachineType.VirtualMachine))
                        .ToList<PSSession>();

                    WriteOrAddMatches(matchingRunspaceInfos, writeobject, ref matches);
                }
            }

            return matches;
        }

        /// <summary>
        /// Gets the matching runspaces by vm guid and session instanceid.
        /// </summary>
        /// <param name="writeobject">If true write the object down the pipeline.</param>
        /// <param name="filterState">Runspace state filter value.</param>
        /// <param name="configurationName">Runspace configuration name filter value.</param>
        /// <returns>List of matching runspaces.</returns>
        private Dictionary<Guid, PSSession> GetMatchingRunspacesByVMIdSessionInstanceId(bool writeobject,
            SessionFilterState filterState,
            string configurationName)
        {
            WildcardPattern configurationNamePattern =
                string.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase);
            Dictionary<Guid, PSSession> matches = new Dictionary<Guid, PSSession>();
            List<PSSession> remoteRunspaceInfos = this.RunspaceRepository.Runspaces;

            foreach (Guid vmId in VMId)
            {
                foreach (Guid sessionInstanceId in InstanceId)
                {
                    var matchingRunspaceInfos = remoteRunspaceInfos
                        .Where<PSSession>(session => vmId.Equals(session.VMId) &&
                                                     sessionInstanceId.Equals(session.InstanceId) &&
                                                     QueryRunspaces.TestRunspaceState(session.Runspace, filterState) &&
                                                     (configurationNamePattern == null || configurationNamePattern.IsMatch(session.ConfigurationName)) &&
                                                     (session.ComputerType == TargetMachineType.VirtualMachine))
                        .ToList<PSSession>();

                    WriteOrAddMatches(matchingRunspaceInfos, writeobject, ref matches);
                }
            }

            return matches;
        }

        /// <summary>
        /// Write the matching runspace objects down the pipeline, or add to the list.
        /// </summary>
        /// <param name="matchingRunspaceInfos">The matching runspaces.</param>
        /// <param name="writeobject">If true write the object down the pipeline. Otherwise, add to the list.</param>
        /// <param name="matches">The list we add the matching runspaces to.</param>
        private void WriteOrAddMatches(List<PSSession> matchingRunspaceInfos,
            bool writeobject,
            ref Dictionary<Guid, PSSession> matches)
        {
            foreach (PSSession remoteRunspaceInfo in matchingRunspaceInfos)
            {
                if (writeobject)
                {
                    WriteObject(remoteRunspaceInfo);
                }
                else
                {
                    try
                    {
                        matches.Add(remoteRunspaceInfo.InstanceId, remoteRunspaceInfo);
                    }
                    catch (ArgumentException)
                    {
                        // if match already found ignore
                    }
                }
            }
        }

        /// <summary>
        /// Write invalid argument error.
        /// </summary>
        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

        /// <summary>
        /// Runspace Id parameter set.
        /// </summary>
        protected const string InstanceIdParameterSet = "InstanceId";

        /// <summary>
        /// Session id parameter set.
        /// </summary>
        protected const string IdParameterSet = "Id";

        /// <summary>
        /// Name parameter set.
        /// </summary>
        protected const string NameParameterSet = "Name";

        #endregion Protected Members
    }

    #region Helper Classes

    /// <summary>
    /// Base class for both the helpers. This is an abstract class
    /// and the helpers need to derive from this.
    /// </summary>
    internal abstract class ExecutionCmdletHelper : IThrottleOperation
    {
        /// <summary>
        /// Pipeline associated with this operation.
        /// </summary>
        internal Pipeline Pipeline
        {
            get
            {
                return pipeline;
            }
        }

        protected Pipeline pipeline;

        /// <summary>
        /// Exception raised internally when any method of this class
        /// is executed.
        /// </summary>
        internal Exception InternalException
        {
            get
            {
                return internalException;
            }
        }

        protected Exception internalException;

        /// <summary>
        /// Internal access to Runspace and Computer helper runspace.
        /// </summary>
        internal Runspace PipelineRunspace
        {
            get;
            set;
        }

        #region Runspace Debug

        internal void ConfigureRunspaceDebugging(Runspace runspace)
        {
            if (!RunspaceDebuggingEnabled || (runspace == null) || (runspace.Debugger == null)) { return; }

            runspace.Debugger.DebuggerStop += HandleDebuggerStop;

            // Configure runspace debugger to preserve unhandled stops (wait for debugger attach)
            runspace.Debugger.UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Wait;

            if (RunspaceDebugStepInEnabled)
            {
                // Configure runspace debugger to run script in step mode
                try
                {
                    runspace.Debugger.SetDebuggerStepMode(true);
                }
                catch (PSInvalidOperationException) { }
            }
        }

        internal void CleanupRunspaceDebugging(Runspace runspace)
        {
            if ((runspace == null) || (runspace.Debugger == null)) { return; }

            runspace.Debugger.DebuggerStop -= HandleDebuggerStop;
        }

        private void HandleDebuggerStop(object sender, DebuggerStopEventArgs args)
        {
            PipelineRunspace.Debugger.DebuggerStop -= HandleDebuggerStop;

            // Forward event
            RaiseRunspaceDebugStopEvent(PipelineRunspace);

            // Signal remote session to remain stopped in debuger
            args.SuspendRemote = true;
        }

        #endregion
    }

    /// <summary>
    /// 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.
    /// </summary>
    internal class ExecutionCmdletHelperRunspace : ExecutionCmdletHelper
    {
        /// <summary>
        /// Indicates whether or not the server should be using the steppable pipeline.
        /// </summary>
        internal bool ShouldUseSteppablePipelineOnServer;

        /// <summary>
        /// Internal constructor.
        /// </summary>
        /// <param name="pipeline">Pipeline object associated with this operation.</param>
        internal ExecutionCmdletHelperRunspace(Pipeline pipeline)
        {
            this.pipeline = pipeline;
            PipelineRunspace = pipeline.Runspace;
            this.pipeline.StateChanged += HandlePipelineStateChanged;
        }

        /// <summary>
        /// Invokes the pipeline asynchronously.
        /// </summary>
        internal override void StartOperation()
        {
            ConfigureRunspaceDebugging(PipelineRunspace);

            try
            {
                if (ShouldUseSteppablePipelineOnServer && pipeline is RemotePipeline rPipeline)
                {
                    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();
            }
        }

        /// <summary>
        /// Closes the pipeline asynchronously.
        /// </summary>
        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 OperationComplete from here,
                // else ThrottleManager will have
                RaiseOperationCompleteEvent();
            }
        }

        internal override event EventHandler<OperationStateEventArgs> OperationComplete;

        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="sender">Source of this event.</param>
        /// <param name="stateEventArgs">object describing state information about the
        /// pipeline</param>
        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);
        }

        /// <summary>
        /// Raise an OperationComplete Event. The base event will be
        /// null in this case.
        /// </summary>
        private void RaiseOperationCompleteEvent()
        {
            RaiseOperationCompleteEvent(null);
        }

        /// <summary>
        /// Raise an operation complete event.
        /// </summary>
        /// <param name="baseEventArgs">The event args which actually
        /// raises this operation complete</param>
        private void RaiseOperationCompleteEvent(EventArgs baseEventArgs)
        {
            CleanupRunspaceDebugging(PipelineRunspace);

            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 -= HandlePipelineStateChanged;
                pipeline.Dispose();
            }

            OperationStateEventArgs operationStateEventArgs =
                    new OperationStateEventArgs();
            operationStateEventArgs.OperationState =
                    OperationState.StopComplete;
            operationStateEventArgs.BaseEvent = baseEventArgs;

            OperationComplete?.SafeInvoke(this, operationStateEventArgs);
        }
    }

    /// <summary>
    /// 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.
    /// </summary>
    internal class ExecutionCmdletHelperComputerName : ExecutionCmdletHelper
    {
        /// <summary>
        /// Determines if the command should be invoked and then disconnect the
        /// remote runspace from the client.
        /// </summary>
        private readonly bool _invokeAndDisconnect;

        /// <summary>
        /// The remote runspace created using the computer name
        /// parameter set details.
        /// </summary>
        internal RemoteRunspace RemoteRunspace { get; private set; }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="remoteRunspace">RemoteRunspace that is associated
        /// with this operation</param>
        /// <param name="pipeline">Pipeline created from the remote runspace.</param>
        /// <param name="invokeAndDisconnect">Indicates if pipeline should be disconnected after invoking command.</param>
        internal ExecutionCmdletHelperComputerName(RemoteRunspace remoteRunspace, Pipeline pipeline, bool invokeAndDisconnect = false)
        {
            Dbg.Assert(remoteRunspace != null,
                    "RemoteRunspace reference cannot be null");
            PipelineRunspace = remoteRunspace;

            _invokeAndDisconnect = invokeAndDisconnect;

            RemoteRunspace = remoteRunspace;
            remoteRunspace.StateChanged += HandleRunspaceStateChanged;

            Dbg.Assert(pipeline != null,
                    "Pipeline cannot be null or empty");

            this.pipeline = pipeline;
            pipeline.StateChanged += HandlePipelineStateChanged;
        }

        /// <summary>
        /// Call OpenAsync() on the RemoteRunspace.
        /// </summary>
        internal override void StartOperation()
        {
            try
            {
                RemoteRunspace.OpenAsync();
            }
            catch (PSRemotingTransportException e)
            {
                internalException = e;
                RaiseOperationCompleteEvent();
            }
        }

        /// <summary>
        /// StopAsync on the pipeline.
        /// </summary>
        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 not respond as it will be waiting for
                // this StopOperation to complete
                RaiseOperationCompleteEvent();
            }
        }

        internal override event EventHandler<OperationStateEventArgs> OperationComplete;

        /// <summary>
        /// Handles the state changed event for runspace operations.
        /// </summary>
        /// <param name="sender">Sender of this information.</param>
        /// <param name="stateEventArgs">Object describing this event.</param>
        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:
                    {
                        ConfigureRunspaceDebugging(RemoteRunspace);

                        // if successfully opened
                        // Call InvokeAsync() on the pipeline
                        try
                        {
                            if (_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 OperationComplete event with
                        // StopComplete message
                        if (stateEventArgs.RunspaceStateInfo.Reason != null)
                        {
                            RaiseOperationCompleteEvent(stateEventArgs);
                        }
                        else
                        {
                            RaiseOperationCompleteEvent();
                        }
                    }

                    break;
            }
        }

        /// <summary>
        /// Handles the state changed event for the pipeline.
        /// </summary>
        /// <param name="sender">Sender of this information.</param>
        /// <param name="stateEventArgs">Object describing this event.</param>
        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:
                    RemoteRunspace?.CloseAsync();
                    break;
            }
        }

        /// <summary>
        /// Raise an OperationComplete Event. The base event will be
        /// null in this case.
        /// </summary>
        private void RaiseOperationCompleteEvent()
        {
            RaiseOperationCompleteEvent(null);
        }

        /// <summary>
        /// Raise an operation complete event.
        /// </summary>
        /// <param name="baseEventArgs">The event args which actually
        /// raises this operation complete</param>
        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 -= 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);
        }
    }

    #region Path Resolver

    /// <summary>
    /// A helper class to resolve the path.
    /// </summary>
    internal static class PathResolver
    {
        /// <summary>
        /// Resolves the specified path and verifies the path belongs to
        /// FileSystemProvider.
        /// </summary>
        /// <param name="path">Path to resolve.</param>
        /// <param name="isLiteralPath">True if wildcard expansion should be suppressed for this path.</param>
        /// <param name="cmdlet">reference to calling cmdlet. This will be used for
        /// for writing errors</param>
        /// <param name="allowNonexistingPaths"></param>
        /// <param name="resourceString">Resource string for error when path is not from filesystem provider.</param>
        /// <returns>A fully qualified string representing filename.</returns>
        internal static 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);
        }

        /// <summary>
        /// Resolves the specified path to PathInfo objects.
        /// </summary>
        /// <param name="pathToResolve">
        /// The path to be resolved. Each path may contain glob characters.
        /// </param>
        /// <param name="isLiteralPath">
        /// True if wildcard expansion should be suppressed for pathToResolve.
        /// </param>
        /// <param name="allowNonexistingPaths">
        /// If true, resolves the path even if it doesn't exist.
        /// </param>
        /// <param name="cmdlet">
        /// Calling cmdlet
        /// </param>
        /// <returns>
        /// A string representing the resolved path.
        /// </returns>
        private static PathInfo ResolvePath(
            string pathToResolve,
            bool isLiteralPath,
            bool allowNonexistingPaths,
            PSCmdlet cmdlet)
        {
            // Construct cmdletprovidercontext
            CmdletProviderContext cmdContext = new CmdletProviderContext(cmdlet);
            cmdContext.SuppressWildcardExpansion = isLiteralPath;

            Collection<PathInfo> results = new Collection<PathInfo>();

            try
            {
                // First resolve path
                Collection<PathInfo> 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;
            }
        }
    }

    #endregion

    #region QueryRunspaces

    internal class QueryRunspaces
    {
        #region Constructor

        internal QueryRunspaces()
        {
            _stopProcessing = false;
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// Queries all remote computers specified in collection of WSManConnectionInfo objects
        /// and returns disconnected PSSession objects ready for connection to server.
        /// Returned sessions can be matched to Guids or Names.
        /// </summary>
        /// <param name="connectionInfos">Collection of WSManConnectionInfo objects.</param>
        /// <param name="host">Host for PSSession objects.</param>
        /// <param name="stream">Out stream object.</param>
        /// <param name="runspaceRepository">Runspace repository.</param>
        /// <param name="throttleLimit">Throttle limit.</param>
        /// <param name="filterState">Runspace state filter value.</param>
        /// <param name="matchIds">Array of session Guids to match to.</param>
        /// <param name="matchNames">Array of session Names to match to.</param>
        /// <param name="configurationName">Configuration name to match to.</param>
        /// <returns>Collection of disconnected PSSession objects.</returns>
        internal Collection<PSSession> GetDisconnectedSessions(Collection<WSManConnectionInfo> connectionInfos, PSHost host,
                                                               ObjectStream stream, RunspaceRepository runspaceRepository,
                                                               int throttleLimit, SessionFilterState filterState,
                                                               Guid[] matchIds, string[] matchNames, string configurationName)
        {
            Collection<PSSession> filteredPSSessions = new Collection<PSSession>();

            // Create a query operation for each connection information object.
            foreach (WSManConnectionInfo connectionInfo in connectionInfos)
            {
                Runspace[] runspaces = null;

                try
                {
                    runspaces = Runspace.GetRunspaces(connectionInfo, host, BuiltInTypesTable);
                }
                catch (System.Management.Automation.RuntimeException e)
                {
                    if (e.InnerException is InvalidOperationException)
                    {
                        // The Get-WSManInstance cmdlet used to query remote computers for runspaces will throw
                        // an Invalid Operation (inner) exception if the connectInfo object is invalid, including
                        // invalid computer names.
                        // We don't want to propagate the exception so just write error here.
                        if (stream.ObjectWriter != null && stream.ObjectWriter.IsOpen)
                        {
                            int errorCode;
                            string msg = StringUtil.Format(RemotingErrorIdStrings.QueryForRunspacesFailed, connectionInfo.ComputerName, ExtractMessage(e.InnerException, out errorCode));
                            string FQEID = WSManTransportManagerUtils.GetFQEIDFromTransportError(errorCode, "RemotePSSessionQueryFailed");
                            Exception reason = new RuntimeException(msg, e.InnerException);
                            ErrorRecord errorRecord = new ErrorRecord(reason, FQEID, ErrorCategory.InvalidOperation, connectionInfo);
                            stream.ObjectWriter.Write((Action<Cmdlet>)(cmdlet => cmdlet.WriteError(errorRecord)));
                        }
                    }
                    else
                    {
                        throw;
                    }
                }

                if (_stopProcessing)
                {
                    break;
                }

                // Add all runspaces meeting filter criteria to collection.
                if (runspaces != null)
                {
                    // Convert configuration name into shell Uri for comparison.
                    string shellUri = null;
                    if (!string.IsNullOrEmpty(configurationName))
                    {
                        shellUri = configurationName.Contains(WSManNativeApi.ResourceURIPrefix, StringComparison.OrdinalIgnoreCase)
                            ? configurationName
                            : WSManNativeApi.ResourceURIPrefix + configurationName;
                    }

                    foreach (Runspace runspace in runspaces)
                    {
                        // Filter returned runspaces by ConfigurationName if provided.
                        if (shellUri != null)
                        {
                            // Compare with returned shell Uri in connection info.
                            WSManConnectionInfo wsmanConnectionInfo = runspace.ConnectionInfo as WSManConnectionInfo;
                            if (wsmanConnectionInfo != null &&
                                !shellUri.Equals(wsmanConnectionInfo.ShellUri, StringComparison.OrdinalIgnoreCase))
                            {
                                continue;
                            }
                        }

                        // Check the repository for an existing viable PSSession for
                        // this runspace (based on instanceId).  Use the existing
                        // local runspace instead of the one returned from the server
                        // query.
                        PSSession existingPSSession = null;
                        if (runspaceRepository != null)
                        {
                            existingPSSession = runspaceRepository.GetItem(runspace.InstanceId);
                        }

                        if (existingPSSession != null &&
                            UseExistingRunspace(existingPSSession.Runspace, runspace))
                        {
                            if (TestRunspaceState(existingPSSession.Runspace, filterState))
                            {
                                filteredPSSessions.Add(existingPSSession);
                            }
                        }
                        else if (TestRunspaceState(runspace, filterState))
                        {
                            filteredPSSessions.Add(new PSSession(runspace as RemoteRunspace));
                        }
                    }
                }
            }

            // Return only PSSessions that match provided Ids or Names.
            if ((matchIds != null) && (filteredPSSessions.Count > 0))
            {
                Collection<PSSession> matchIdsSessions = new Collection<PSSession>();
                foreach (Guid id in matchIds)
                {
                    bool matchFound = false;
                    foreach (PSSession psSession in filteredPSSessions)
                    {
                        if (_stopProcessing)
                        {
                            break;
                        }

                        if (psSession.Runspace.InstanceId.Equals(id))
                        {
                            matchFound = true;
                            matchIdsSessions.Add(psSession);
                            break;
                        }
                    }

                    if (!matchFound && stream.ObjectWriter != null && stream.ObjectWriter.IsOpen)
                    {
                        string msg = StringUtil.Format(RemotingErrorIdStrings.SessionIdMatchFailed, id);
                        Exception reason = new RuntimeException(msg);
                        ErrorRecord errorRecord = new ErrorRecord(reason, "PSSessionIdMatchFail", ErrorCategory.InvalidOperation, id);
                        stream.ObjectWriter.Write((Action<Cmdlet>)(cmdlet => cmdlet.WriteError(errorRecord)));
                    }
                }

                // Return all found sessions.
                return matchIdsSessions;
            }
            else if ((matchNames != null) && (filteredPSSessions.Count > 0))
            {
                Collection<PSSession> matchNamesSessions = new Collection<PSSession>();
                foreach (string name in matchNames)
                {
                    WildcardPattern namePattern = WildcardPattern.Get(name, WildcardOptions.IgnoreCase);
                    bool matchFound = false;
                    foreach (PSSession psSession in filteredPSSessions)
                    {
                        if (_stopProcessing)
                        {
                            break;
                        }

                        if (namePattern.IsMatch(((RemoteRunspace)psSession.Runspace).RunspacePool.RemoteRunspacePoolInternal.Name))
                        {
                            matchFound = true;
                            matchNamesSessions.Add(psSession);
                        }
                    }

                    if (!matchFound && stream.ObjectWriter != null && stream.ObjectWriter.IsOpen)
                    {
                        string msg = StringUtil.Format(RemotingErrorIdStrings.SessionNameMatchFailed, name);
                        Exception reason = new RuntimeException(msg);
                        ErrorRecord errorRecord = new ErrorRecord(reason, "PSSessionNameMatchFail", ErrorCategory.InvalidOperation, name);
                        stream.ObjectWriter.Write((Action<Cmdlet>)(cmdlet => cmdlet.WriteError(errorRecord)));
                    }
                }

                return matchNamesSessions;
            }
            else
            {
                // Return all collected sessions.
                return filteredPSSessions;
            }
        }

        /// <summary>
        /// Returns true if the existing runspace should be returned to the user
        /// a.  If the existing runspace is not broken
        /// b.  If the queried runspace is not connected to a different user.
        /// </summary>
        /// <param name="existingRunspace"></param>
        /// <param name="queriedrunspace"></param>
        /// <returns></returns>
        private static bool UseExistingRunspace(
            Runspace existingRunspace,
            Runspace queriedrunspace)
        {
            Dbg.Assert(existingRunspace != null, "Invalid parameter.");
            Dbg.Assert(queriedrunspace != null, "Invalid parameter.");

            if (existingRunspace.RunspaceStateInfo.State == RunspaceState.Broken)
            {
                return false;
            }

            if (existingRunspace.RunspaceStateInfo.State == RunspaceState.Disconnected &&
                queriedrunspace.RunspaceAvailability == RunspaceAvailability.Busy)
            {
                return false;
            }

            // Update existing runspace to have latest DisconnectedOn/ExpiresOn data.
            existingRunspace.DisconnectedOn = queriedrunspace.DisconnectedOn;
            existingRunspace.ExpiresOn = queriedrunspace.ExpiresOn;

            return true;
        }

        /// <summary>
        /// Returns Exception message.  If message is WSMan Xml then
        /// the WSMan message and error code is extracted and returned.
        /// </summary>
        /// <param name="e">Exception.</param>
        /// <param name="errorCode">Returned WSMan error code.</param>
        /// <returns>WSMan message.</returns>
        internal static string ExtractMessage(
            Exception e,
            out int errorCode)
        {
            errorCode = 0;

            if (e == null ||
                e.Message == null)
            {
                return string.Empty;
            }

            string rtnMsg = null;
            try
            {
                System.Xml.XmlReaderSettings xmlReaderSettings = InternalDeserializer.XmlReaderSettingsForUntrustedXmlDocument.Clone();
                xmlReaderSettings.MaxCharactersInDocument = 4096;
                xmlReaderSettings.MaxCharactersFromEntities = 1024;
                xmlReaderSettings.DtdProcessing = System.Xml.DtdProcessing.Prohibit;

                using (System.Xml.XmlReader reader = System.Xml.XmlReader.Create(
                        new System.IO.StringReader(e.Message), xmlReaderSettings))
                {
                    while (reader.Read())
                    {
                        if (reader.NodeType == System.Xml.XmlNodeType.Element)
                        {
                            if (reader.LocalName.Equals("Message", StringComparison.OrdinalIgnoreCase))
                            {
                                rtnMsg = reader.ReadElementContentAsString();
                            }
                            else if (reader.LocalName.Equals("WSManFault", StringComparison.OrdinalIgnoreCase))
                            {
                                string errorCodeString = reader.GetAttribute("Code");
                                if (errorCodeString != null)
                                {
                                    try
                                    {
                                        // WinRM returns both signed and unsigned 32 bit string values.  Convert to signed 32 bit integer.
                                        Int64 eCode = Convert.ToInt64(errorCodeString, System.Globalization.NumberFormatInfo.InvariantInfo);
                                        unchecked
                                        {
                                            errorCode = (int)eCode;
                                        }
                                    }
                                    catch (FormatException)
                                    { }
                                    catch (OverflowException)
                                    { }
                                }
                            }
                        }
                    }
                }
            }
            catch (System.Xml.XmlException)
            { }

            return rtnMsg ?? e.Message;
        }

        /// <summary>
        /// Discontinue all remote server query operations.
        /// </summary>
        internal void StopAllOperations()
        {
            _stopProcessing = true;
        }

        /// <summary>
        /// Compares the runspace filter state with the runspace state.
        /// </summary>
        /// <param name="runspace">Runspace object to test.</param>
        /// <param name="filterState">Filter state to compare.</param>
        /// <returns>Result of test.</returns>
        public static bool TestRunspaceState(Runspace runspace, SessionFilterState filterState)
        {
            bool result;

            switch (filterState)
            {
                case SessionFilterState.All:
                    result = true;
                    break;

                case SessionFilterState.Opened:
                    result = (runspace.RunspaceStateInfo.State == RunspaceState.Opened);
                    break;

                case SessionFilterState.Closed:
                    result = (runspace.RunspaceStateInfo.State == RunspaceState.Closed);
                    break;

                case SessionFilterState.Disconnected:
                    result = (runspace.RunspaceStateInfo.State == RunspaceState.Disconnected);
                    break;

                case SessionFilterState.Broken:
                    result = (runspace.RunspaceStateInfo.State == RunspaceState.Broken);
                    break;

                default:
                    Dbg.Assert(false, "Invalid SessionFilterState value.");
                    result = false;
                    break;
            }

            return result;
        }

        /// <summary>
        /// Returns the default type table for built-in PowerShell types.
        /// </summary>
        internal static TypeTable BuiltInTypesTable
        {
            get
            {
                if (s_TypeTable == null)
                {
                    lock (s_SyncObject)
                    {
                        s_TypeTable ??= TypeTable.LoadDefaultTypeFiles();
                    }
                }

                return s_TypeTable;
            }
        }

        #endregion

        #region Private Members

        private bool _stopProcessing;

        private static readonly object s_SyncObject = new object();
        private static TypeTable s_TypeTable;

        #endregion
    }

    #endregion

    #region SessionFilterState Enum

    /// <summary>
    /// Runspace states that can be used as filters for querying remote runspaces.
    /// </summary>
    public enum SessionFilterState
    {
        /// <summary>
        /// Return runspaces in any state.
        /// </summary>
        All = 0,

        /// <summary>
        /// Return runspaces in Opened state.
        /// </summary>
        Opened = 1,

        /// <summary>
        /// Return runspaces in Disconnected state.
        /// </summary>
        Disconnected = 2,

        /// <summary>
        /// Return runspaces in Closed state.
        /// </summary>
        Closed = 3,

        /// <summary>
        /// Return runspaces in Broken state.
        /// </summary>
        Broken = 4
    }

    #endregion

    #endregion Helper Classes
}

namespace System.Management.Automation.Remoting
{
    /// <summary>
    /// IMPORTANT: proxy configuration is supported for HTTPS only; for HTTP, the direct
    /// connection to the server is used.
    /// </summary>
    [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
    public enum ProxyAccessType
    {
        /// <summary>
        /// ProxyAccessType is not specified. That means Proxy information (ProxyAccessType, ProxyAuthenticationMechanism
        /// and ProxyCredential)is not passed to WSMan at all.
        /// </summary>
        None = 0,
        /// <summary>
        /// Use the Internet Explorer proxy configuration for the current user.
        ///  Internet Explorer proxy settings for the current active network connection.
        ///  This option requires the user profile to be loaded, so the option can
        ///  be directly used when called within a process that is running under
        ///  an interactive user account identity; if the client application is running
        ///  under a user context different than the interactive user, the client
        ///  application has to explicitly load the user profile prior to using this option.
        /// </summary>
        IEConfig = 1,
        /// <summary>
        /// Proxy settings configured for WinHTTP, using the ProxyCfg.exe utility.
        /// </summary>
        WinHttpConfig = 2,
        /// <summary>
        /// Force autodetection of proxy.
        /// </summary>
        AutoDetect = 4,
        /// <summary>
        /// Do not use a proxy server - resolves all host names locally.
        /// </summary>
        NoProxyServer = 8
    }
    /// <summary>
    /// Options for a remote PSSession.
    /// </summary>
    public sealed class PSSessionOption
    {
        /// <summary>
        /// Creates a new instance of <see cref="PSSessionOption"/>
        /// </summary>
        public PSSessionOption()
        {
        }

        /// <summary>
        /// The MaximumConnectionRedirectionCount parameter enables the implicit redirection functionality.
        /// -1 = no limit
        ///  0 = no redirection.
        /// </summary>
        public int MaximumConnectionRedirectionCount { get; set; } = WSManConnectionInfo.defaultMaximumConnectionRedirectionCount;

        /// <summary>
        /// If false, underlying WSMan infrastructure will compress data sent on the network.
        /// If true, data will not be compressed. Compression improves performance by
        /// reducing the amount of data sent on the network. Compression my require extra
        /// memory consumption and CPU usage. In cases where available memory / CPU is less,
        /// set this property to "true".
        /// By default the value of this property is "false".
        /// </summary>
        public bool NoCompression { get; set; } = false;

        /// <summary>
        /// If <see langword="true"/> then Operating System won't load the user profile (i.e. registry keys under HKCU) on the remote server
        /// which can result in a faster session creation time.  This option won't have any effect if the remote machine has
        /// already loaded the profile (i.e. in another session).
        /// </summary>
        public bool NoMachineProfile { get; set; } = false;

        /// <summary>
        /// By default, ProxyAccessType is None, that means Proxy information (ProxyAccessType,
        /// ProxyAuthenticationMechanism and ProxyCredential)is not passed to WSMan at all.
        /// </summary>
        public ProxyAccessType ProxyAccessType { get; set; } = ProxyAccessType.None;

        /// <summary>
        /// The following is the definition of the input parameter "ProxyAuthentication".
        /// This parameter takes a set of authentication methods the user can select
        /// from.  The available options should be as follows:
        /// - Negotiate: Use the default authentication (as defined by the underlying
        /// protocol) for establishing a remote connection.
        /// - Basic:  Use basic authentication for establishing a remote connection
        /// - Digest: Use Digest authentication for establishing a remote connection
        ///
        /// Default is Negotiate.
        /// </summary>
        public AuthenticationMechanism ProxyAuthentication
        {
            get
            {
                return _proxyAuthentication;
            }

            set
            {
                switch (value)
                {
                    case AuthenticationMechanism.Basic:
                    case AuthenticationMechanism.Negotiate:
                    case AuthenticationMechanism.Digest:
                        _proxyAuthentication = value;
                        break;
                    default:
                        string message = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.ProxyAmbiguousAuthentication,
                            value,
                            nameof(AuthenticationMechanism.Basic),
                            nameof(AuthenticationMechanism.Negotiate),
                            nameof(AuthenticationMechanism.Digest));
                        throw new ArgumentException(message);
                }
            }
        }

        private AuthenticationMechanism _proxyAuthentication = AuthenticationMechanism.Negotiate;

        /// <summary>
        /// The following is the definition of the input parameter "ProxyCredential".
        /// </summary>
        public PSCredential ProxyCredential { get; set; }

        /// <summary>
        /// When connecting over HTTPS, the client does not validate that the server
        /// certificate is signed by a trusted certificate authority (CA). Use only when
        /// the remote computer is trusted by other means, for example, if the remote
        /// computer is part of a network that is physically secure and isolated or the
        /// remote computer is listed as a trusted host in WinRM configuration.
        /// </summary>
        public bool SkipCACheck { get; set; }

        /// <summary>
        /// Indicates that certificate common name (CN) of the server need not match the
        /// hostname of the server. Used only in remote operations using https. This
        /// option should only be used for trusted machines.
        /// </summary>
        public bool SkipCNCheck { get; set; }

        /// <summary>
        /// Indicates that certificate common name (CN) of the server need not match the
        /// hostname of the server. Used only in remote operations using https. This
        /// option should only be used for trusted machines.
        /// </summary>
        public bool SkipRevocationCheck { get; set; }

        /// <summary>
        /// The duration for which PowerShell remoting waits before timing out
        /// for any operation. The user would like to tweak this timeout
        /// depending on whether he/she is connecting to a machine in the data
        /// center or across a slow WAN.
        ///
        /// Default: 3*60*1000 == 3minutes.
        /// </summary>
        public TimeSpan OperationTimeout { get; set; } = TimeSpan.FromMilliseconds(BaseTransportManager.ClientDefaultOperationTimeoutMs);

        /// <summary>
        /// Specifies that no encryption will be used when doing remote operations over
        /// http. Unencrypted traffic is not allowed by default and must be enabled in
        /// the local configuration.
        /// </summary>
        public bool NoEncryption { get; set; }

        /// <summary>
        /// Indicates the request is encoded in UTF16 format rather than UTF8 format;
        /// UTF8 is the default.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "UTF")]
        public bool UseUTF16 { get; set; }

        /// <summary>
        /// Uses Service Principal Name (SPN) along with the Port number during authentication.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SPN")]
        public bool IncludePortInSPN { get; set; }

        /// <summary>
        /// Determines how server in disconnected state deals with cached output
        /// data when the cache becomes filled.
        /// Default value is 'block mode' where command execution is blocked after
        /// the server side data cache becomes filled.
        /// </summary>
        public OutputBufferingMode OutputBufferingMode { get; set; } = WSManConnectionInfo.DefaultOutputBufferingMode;

        /// <summary>
        /// Number of times a connection will be re-attempted when a connection fails due to network
        /// issues.
        /// </summary>
        public int MaxConnectionRetryCount { get; set; } = WSManConnectionInfo.DefaultMaxConnectionRetryCount;

        /// <summary>
        /// Culture that the remote session should use.
        /// </summary>
        public CultureInfo Culture { get; set; }

        /// <summary>
        /// UI culture that the remote session should use.
        /// </summary>
        public CultureInfo UICulture { get; set; }

        /// <summary>
        /// Total data (in bytes) that can be received from a remote machine
        /// targeted towards a command. If null, then the size is unlimited.
        /// Default is unlimited data.
        /// </summary>
        public int? MaximumReceivedDataSizePerCommand { get; set; }

        /// <summary>
        /// Maximum size (in bytes) of a deserialized object received from a remote machine.
        /// If null, then the size is unlimited. Default is 200MB object size.
        /// </summary>
        public int? MaximumReceivedObjectSize { get; set; } = 200 << 20;

        /// <summary>
        /// Application arguments the server can see in <see cref="System.Management.Automation.Remoting.PSSenderInfo.ApplicationArguments"/>
        /// </summary>
        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public PSPrimitiveDictionary ApplicationArguments { get; set; }

        /// <summary>
        /// The duration for which PowerShell remoting waits before timing out on a connection to a remote machine.
        /// Simply put, the timeout for a remote runspace creation.
        /// The user would like to tweak this timeout depending on whether
        /// he/she is connecting to a machine in the data center or across a slow WAN.
        ///
        /// Default: 3 * 60 * 1000 = 3 minutes.
        /// </summary>
        public TimeSpan OpenTimeout { get; set; } = TimeSpan.FromMilliseconds(RunspaceConnectionInfo.DefaultOpenTimeout);

        /// <summary>
        /// The duration for which PowerShell should wait before it times out on cancel operations
        /// (close runspace or stop powershell). For instance, when the user hits ctrl-C,
        /// New-PSSession cmdlet tries to call a stop on all remote runspaces which are in the Opening state.
        /// The user wouldn't mind waiting for 15 seconds, but this should be time bound and of a shorter duration.
        /// A high timeout here like 3 minutes will give the user a feeling that the PowerShell client is not responding.
        ///
        /// Default: 60 * 1000 = 1 minute.
        /// </summary>
        public TimeSpan CancelTimeout { get; set; } = TimeSpan.FromMilliseconds(RunspaceConnectionInfo.defaultCancelTimeout);

        /// <summary>
        /// The duration for which a Runspace on server needs to wait before it declares the client dead and closes itself down.
        /// This is especially important as these values may have to be configured differently for enterprise administration
        /// and exchange scenarios.
        ///
        /// Default: -1 -> Use current server value for IdleTimeout.
        /// </summary>
        public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMilliseconds(RunspaceConnectionInfo.DefaultIdleTimeout);
    }
}
