/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System.Collections; using System.Net; using System.Net.Sockets; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Management.Automation.Tracing; using System.Management.Automation.Remoting; using System.Management.Automation.Internal; using System.Management.Automation.Remoting.Client; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using Dbg = System.Management.Automation.Diagnostics; using WSManAuthenticationMechanism = System.Management.Automation.Remoting.Client.WSManNativeApi.WSManAuthenticationMechanism; // ReSharper disable CheckNamespace namespace System.Management.Automation.Runspaces // ReSharper restore CheckNamespace { /// /// Different Authentication Mechanisms supported by New-Runspace command to connect /// to remote server. /// [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")] public enum AuthenticationMechanism { /// /// Use the default authentication (as defined by the underlying protocol) /// for establishing a remote connection. /// Default = 0x0, /// /// Use Basic authentication for establishing a remote connection. /// Basic = 0x1, /// /// Use Negotiate authentication for establishing a remote connection. /// Negotiate = 0x2, /// /// Use Negotiate authentication for establishing a remote connection. /// Allow implicit credentials for Negotiate. /// NegotiateWithImplicitCredential = 0x3, /// /// Use CredSSP authentication for establishing a remote connection. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Credssp")] Credssp = 0x4, /// /// Use Digest authentication mechanism. Digest authentication operates much /// like Basic authentication. However, unlike Basic authentication, Digest authentication /// transmits credentials across the network as a hash value, also known as a message digest. /// The user name and password cannot be deciphered from the hash value. Conversely, Basic /// authentication sends a Base 64 encoded password, essentially in clear text, across the /// network. /// Digest = 0x5, /// /// Use Kerberos authentication for establishing a remote connection. /// Kerberos = 0x6, } /// /// Specifies the type of session configuration that /// should be used for creating a connection info /// public enum PSSessionType { /// /// Default PowerShell remoting /// endpoint /// DefaultRemoteShell = 0, /// /// Default Workflow endpoint /// Workflow = 1, } /// /// Specify the type of access mode that should be /// used when creating a session configuration /// public enum PSSessionConfigurationAccessMode { /// /// Disable the configuration /// Disabled = 0, /// /// Allow local access /// Local = 1, /// /// Default allow remote access /// Remote = 2, } /// /// WSManTransportManager supports disconnected PowerShell sessions. /// When a remote PS session server is in disconnected state, output /// from the running command pipeline is cached on the server. This /// enum determines what the server does when the cache is full. /// public enum OutputBufferingMode { /// /// No output buffering mode specified. Output buffering mode on server will /// default to Block if a new session is created, or will retain its current /// mode for non-creation scenarios (e.g., disconnect/connect operations). /// None = 0, /// /// Command pipeline execution continues, excess output is dropped in FIFO manner. /// Drop = 1, /// /// Command pipeline execution on server is blocked until session is reconnected. /// Block = 2 } /// /// Class which defines connection path to a remote runspace /// that needs to be created. Transport specific connection /// paths will be derived from this /// public abstract class RunspaceConnectionInfo { #region Public Properties /// /// Name of the computer /// public abstract String ComputerName { get; set; } /// /// Credential used for the connection /// public abstract PSCredential Credential { get; set; } /// /// Authentication mechanism to use while connecting to the server /// public abstract AuthenticationMechanism AuthenticationMechanism { get; set; } /// /// ThumbPrint of a certificate used for connecting to a remote machine. /// When this is specified, you dont need to supply credential and authentication /// mechanism. /// public abstract string CertificateThumbprint { get; set; } /// /// Culture that the remote session should use /// public CultureInfo Culture { get { return culture; } set { if (value == null) { throw new ArgumentNullException("value"); } culture = value; } } private CultureInfo culture = CultureInfo.CurrentCulture; /// /// UI culture that the remote session should use /// public CultureInfo UICulture { get { return uiCulture; } set { if (value == null) { throw new ArgumentNullException("value"); } uiCulture = value; } } private CultureInfo uiCulture = CultureInfo.CurrentUICulture; /// /// The duration (in ms) 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 administrator 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. /// public int OpenTimeout { get { return openTimeout; } set { openTimeout = value; if (this is WSManConnectionInfo && openTimeout == DefaultTimeout) { openTimeout = DefaultOpenTimeout; } else if (this is WSManConnectionInfo && openTimeout == InfiniteTimeout) { // this timeout value gets passed to a // timer associated with the session // data structure handler state machine. // The timer constructor will throw an exception // for any value greater than Int32.MaxValue // hence this is the maximum possible limit openTimeout = Int32.MaxValue; } } } private int openTimeout = DefaultOpenTimeout; internal const int DefaultOpenTimeout = 3 * 60 * 1000; // 3 minutes internal const int DefaultTimeout = -1; internal const int InfiniteTimeout = 0; /// /// The duration (in ms) 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 administrator 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 administrator a feeling that the PowerShell client has hung. /// public int CancelTimeout { get { return cancelTimeout; } set { cancelTimeout = value; } } private int cancelTimeout = defaultCancelTimeout; internal const int defaultCancelTimeout = BaseTransportManager.ClientCloseTimeoutMs; /// /// 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 /// public int OperationTimeout { get { return operationTimeout; } set { operationTimeout = value; } } private int operationTimeout = BaseTransportManager.ClientDefaultOperationTimeoutMs; /// /// The duration (in ms) 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. /// public int IdleTimeout { get { return idleTimeout; } set { idleTimeout = value; } } private int idleTimeout = DefaultIdleTimeout; internal const int DefaultIdleTimeout = BaseTransportManager.UseServerDefaultIdleTimeout; /// /// The maximum allowed idle timeout duration (in ms) that can be set on a Runspace. This is a read-only property /// that is set once the Runspace is successfully created and opened. /// public int MaxIdleTimeout { get { return maxIdleTimeout; } internal set { maxIdleTimeout = value; } } private int maxIdleTimeout = Int32.MaxValue; /// /// Populates session options from a PSSessionOption instance. /// /// public virtual void SetSessionOptions(PSSessionOption options) { if (options == null) { throw new ArgumentNullException("options"); } if (options.Culture != null) { this.Culture = options.Culture; } if (options.UICulture != null) { this.UICulture = options.UICulture; } this.openTimeout = TimeSpanToTimeOutMs(options.OpenTimeout); this.cancelTimeout = TimeSpanToTimeOutMs(options.CancelTimeout); this.operationTimeout = TimeSpanToTimeOutMs(options.OperationTimeout); // Special case for idle timeout. A value of milliseconds == -1 // (BaseTransportManager.UseServerDefaultIdleTimeout) is allowed for // specifying the default value on the server. this.idleTimeout = (options.IdleTimeout.TotalMilliseconds >= BaseTransportManager.UseServerDefaultIdleTimeout && options.IdleTimeout.TotalMilliseconds < int.MaxValue) ? (int)(options.IdleTimeout.TotalMilliseconds) : int.MaxValue; } #endregion Public Properties #region Internal Methods internal int TimeSpanToTimeOutMs(TimeSpan t) { if ((t.TotalMilliseconds > int.MaxValue) || (t == TimeSpan.MaxValue) || (t.TotalMilliseconds < 0)) { return int.MaxValue; } else { return (int)(t.TotalMilliseconds); } } /// /// Creates the appropriate client session transportmanager. /// /// Runspace/Pool instance Id /// Session name /// PSRemotingCryptoHelper internal virtual BaseClientSessionTransportManager CreateClientSessionTransportManager( Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { throw new PSNotImplementedException(); } /// /// Create a copy of the connection info object. /// /// Copy of the connection info object. internal virtual RunspaceConnectionInfo InternalCopy() { throw new PSNotImplementedException(); } #endregion } /// /// Class which defines path to a remote runspace that /// need to be created /// public sealed class WSManConnectionInfo : RunspaceConnectionInfo { #region Public Properties /// /// Uri associated with this connection path /// public Uri ConnectionUri { get { return _connectionUri; } set { if (value == null) { throw PSTraceSource.NewArgumentNullException("value"); } UpdateUri(value); } } /// /// Name of the computer /// public override String ComputerName { get { return _computerName; } set { // null or empty value allowed ConstructUri(_scheme, value, null, _appName); } } /// /// Scheme used for connection /// public String Scheme { get { return _scheme; } set { // null or empty value allowed ConstructUri(value, _computerName, null, _appName); } } /// /// Port in which to connect /// public Int32 Port { get { return ConnectionUri.Port; } set { ConstructUri(_scheme, _computerName, value, _appName); } } /// /// AppName which identifies the connection /// end point in the machine /// public String AppName { get { return _appName; } set { //null or empty value allowed ConstructUri(_scheme, _computerName, null, value); } } /// /// Credential used for the connection /// public override PSCredential Credential { get { return _credential; } set { // null or empty value allowed _credential = value; } } /// /// /// [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Scope = "member", Target = "System.Management.Automation.Runspaces.WSManConnectionInfo.#ShellUri")] public string ShellUri { get { return _shellUri; } set { _shellUri = ResolveShellUri(value); } } /// /// Authentication mechanism to use while connecting to the server /// public override AuthenticationMechanism AuthenticationMechanism { get { switch (_authMechanism) { case WSManAuthenticationMechanism.WSMAN_FLAG_DEFAULT_AUTHENTICATION: return AuthenticationMechanism.Default; case WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_BASIC: return AuthenticationMechanism.Basic; case WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_CREDSSP: return AuthenticationMechanism.Credssp; case WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_NEGOTIATE: if (_allowImplicitCredForNegotiate) { return AuthenticationMechanism.NegotiateWithImplicitCredential; } return AuthenticationMechanism.Negotiate; case WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_DIGEST: return AuthenticationMechanism.Digest; case WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_KERBEROS: return AuthenticationMechanism.Kerberos; default: Dbg.Assert(false, "Invalid authentication mechanism detected."); return AuthenticationMechanism.Default; } } set { switch (value) { case AuthenticationMechanism.Default: _authMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_DEFAULT_AUTHENTICATION; break; case AuthenticationMechanism.Basic: _authMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_BASIC; break; case AuthenticationMechanism.Negotiate: _authMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_NEGOTIATE; break; case AuthenticationMechanism.NegotiateWithImplicitCredential: _authMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_NEGOTIATE; _allowImplicitCredForNegotiate = true; break; case AuthenticationMechanism.Credssp: _authMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_CREDSSP; break; case AuthenticationMechanism.Digest: _authMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_DIGEST; break; case AuthenticationMechanism.Kerberos: _authMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_KERBEROS; break; default: throw new PSNotSupportedException(); } ValidateSpecifiedAuthentication(); } } /// /// AuthenticationMaechanism converted to WSManAuthenticationMechanism type. /// This is internal. /// internal WSManAuthenticationMechanism WSManAuthenticationMechanism { get { return _authMechanism; } } /// /// Allow default credentials for Negotiate /// internal bool AllowImplicitCredentialForNegotiate { get { return _allowImplicitCredForNegotiate; } } /// /// Returns the actual port property value and not the ConnectionUri port. /// Internal only. /// internal int PortSetting { get { return _port; } } /// /// ThumbPrint of a certificate used for connecting to a remote machine. /// When this is specified, you dont need to supply credential and authentication /// mechanism. /// public override string CertificateThumbprint { get { return _thumbPrint; } set { if (value == null) { throw PSTraceSource.NewArgumentNullException("value"); } _thumbPrint = value; } } /// /// Maximum uri redirection count /// public int MaximumConnectionRedirectionCount { get { return _maxUriRedirectionCount; } set { _maxUriRedirectionCount = value; } } internal const int defaultMaximumConnectionRedirectionCount = 5; /// /// 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. /// public Nullable MaximumReceivedDataSizePerCommand { get { return _maxRecvdDataSizePerCommand; } set { _maxRecvdDataSizePerCommand = value; } } /// /// Maximum size (in bytes) of a deserialized object received from a remote machine. /// If null, then the size is unlimited. Default is unlimited object size. /// public Nullable MaximumReceivedObjectSize { get { return _maxRecvdObjectSize; } set { _maxRecvdObjectSize = value; } } /// /// If true, underlying WSMan infrastructure will compress data sent on the network. /// If false, 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 false. /// By default the value of this property is "true". /// public bool UseCompression { get { return _useCompression; } set { _useCompression = value; } } /// /// If 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). /// public bool NoMachineProfile { get { return _noMachineProfile; } set { _noMachineProfile = value; } } // BEGIN: Session Options /// /// By default, wsman uses IEConfig - 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 then the interactive user, the client /// application has to explicitly load the user profile prior to using this option. /// /// IMPORTANT: proxy configuration is supported for HTTPS only; for HTTP, the direct /// connection to the server is used /// public ProxyAccessType ProxyAccessType { get { return _proxyAcessType; } set { _proxyAcessType = value; } } /// /// 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 (ad 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 /// 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.ProxyAmbiguosAuthentication, value, AuthenticationMechanism.Basic.ToString(), AuthenticationMechanism.Negotiate.ToString(), AuthenticationMechanism.Digest.ToString()); throw new ArgumentException(message); } } } /// /// The following is the definition of the input parameter "ProxyCredential". /// public PSCredential ProxyCredential { get { return _proxyCredential; } set { if (_proxyAcessType == ProxyAccessType.None) { string message = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.ProxyCredentialWithoutAccess, ProxyAccessType.None); throw new ArgumentException(message); } _proxyCredential = value; } } /// /// 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 /// public bool SkipCACheck { get { return _skipCaCheck; } set { _skipCaCheck = value; } } /// /// 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. /// public bool SkipCNCheck { get { return _skipCnCheck; } set { _skipCnCheck = value; } } /// /// 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 /// public bool SkipRevocationCheck { get { return _skipRevocationCheck; } set { _skipRevocationCheck = value; } } /// /// Specifies that no encryption will be used when doing remote operations over /// http. Unencrypted traffix is not allowed by default and must be enabled in /// the local configuration /// public bool NoEncryption { get { return _noEncryption; } set { _noEncryption = value; } } /// /// Indicates the request is encoded in UTF16 format rather than UTF8 format; /// UTF8 is the default. /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "UTF")] public bool UseUTF16 { get { return _useUtf16; } set { _useUtf16 = value; } } // END: Session Options /// /// Determines how server in disconnected state deals with cached output /// data when the cache becomes filled. /// public OutputBufferingMode OutputBufferingMode { get { return _outputBufferingMode; } set { _outputBufferingMode = value; } } /// /// Uses Service Principal Name (SPN) along with the Port number during authentication. /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SPN")] public bool IncludePortInSPN { get { return _includePortInSPN; } set { _includePortInSPN = value; } } /// /// When true and in loopback scenario (localhost) this enables creation of WSMan /// host process with the user interactive token, allowing PowerShell script network access, /// i.e., allows going off box. When this property is true and a PSSession is disconnected, /// reconnection is allowed only if reconnecting from a PowerShell session on the same box. /// public bool EnableNetworkAccess { get { return _enableNetworkAccess; } set { _enableNetworkAccess = value; } } /// /// Specifies the maximum number of connection retries if previous connection attempts fail /// due to network issues. /// public int MaxConnectionRetryCount { get { return _maxConnectionRetryCount; } set { _maxConnectionRetryCount = value; } } #endregion Public Properties #region Constructors /// /// Constructor used to create a WSManConnectionInfo /// /// computer to connect to /// scheme to be used for connection /// port to connect to /// application end point to connect to /// remote shell to launch /// on connection /// credential to be used /// for connection /// Timeout in milliseconds for open /// call on Runspace to finish /// Invalid /// scheme or invalid port is specified [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Scope = "member", Target = "System.Management.Automation.Runspaces.WSManConnectionInfo.#.ctor(System.String,System.String,System.Int32,System.String,System.String,System.Management.Automation.PSCredential,System.Int64,System.Int64)", MessageId = "4#")] public WSManConnectionInfo(String scheme, String computerName, Int32 port, String appName, String shellUri, PSCredential credential, int openTimeout) { Scheme = scheme; ComputerName = computerName; Port = port; AppName = appName; ShellUri = shellUri; Credential = credential; OpenTimeout = openTimeout; } /// /// Constructor used to create a WSManConnectionInfo /// /// computer to connect to /// Scheme to be used for connection. /// port to connect to /// application end point to connect to /// remote shell to launch /// on connection /// credential to be used /// for connection /// Invalid /// scheme or invalid port is specified /// max server life timeout and open timeout are /// default in this case [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Scope = "member", Target = "System.Management.Automation.Runspaces.WSManConnectionInfo.#.ctor(System.String,System.String,System.Int32,System.String,System.String,System.Management.Automation.PSCredential)", MessageId = "4#")] public WSManConnectionInfo(String scheme, String computerName, Int32 port, String appName, String shellUri, PSCredential credential): this(scheme, computerName, port, appName, shellUri, credential, DefaultOpenTimeout) { } /// /// Constructor used to create a WSManConnectionInfo /// /// /// /// /// /// /// [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "4#")] public WSManConnectionInfo(bool useSsl, string computerName, Int32 port, string appName, string shellUri, PSCredential credential) : this(useSsl ? DefaultSslScheme : DefaultScheme, computerName, port, appName, shellUri, credential) { } /// /// /// /// /// /// /// /// /// /// [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "4#")] public WSManConnectionInfo(bool useSsl, String computerName, Int32 port, String appName, String shellUri, PSCredential credential, int openTimeout) : this(useSsl ? DefaultSslScheme : DefaultScheme, computerName, port, appName, shellUri, credential, openTimeout) { } /// /// Creates a WSManConnectionInfo for the following URI /// and with the default credentials, default server /// life time and default open timeout /// http://localhost/ /// The default shellname Microsoft.PowerShell will be /// used /// public WSManConnectionInfo() { //ConstructUri(DefaultScheme, DefaultComputerName, DefaultPort, DefaultAppName); UseDefaultWSManPort = true; } /// /// Constructor to create a WSManConnectionInfo with a uri /// and explicit credentials - server life time is /// default and open timeout is default /// /// uri of remote runspace /// /// credentials to use to /// connect to the remote runspace /// When an /// uri representing an invalid path is specified [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Scope = "member", Target = "System.Management.Automation.Runspaces.WSManConnectionInfo.#.ctor(System.Uri,System.String,System.Management.Automation.PSCredential)", MessageId = "1#")] public WSManConnectionInfo(Uri uri, String shellUri, PSCredential credential) { if (uri == null) { // if the uri is null..apply wsman default logic for port // resolution..BUG 542726 ShellUri = shellUri; Credential = credential; UseDefaultWSManPort = true; return; } if (!uri.IsAbsoluteUri) { throw new NotSupportedException(PSRemotingErrorInvariants.FormatResourceString (RemotingErrorIdStrings.RelativeUriForRunspacePathNotSupported)); } // This check is needed to make sure we connect to WSMan app in the // default case (when user did not specify any appname) like // http://localhost , http://127.0.0.1 etc. if (uri.AbsolutePath.Equals("/", StringComparison.OrdinalIgnoreCase) && string.IsNullOrEmpty(uri.Query) && string.IsNullOrEmpty(uri.Fragment)) { ConstructUri(uri.Scheme, uri.Host, uri.Port, DefaultAppName); } else { ConnectionUri = uri; } ShellUri = shellUri; Credential = credential; } /// /// Constructor used to create a WSManConnectionInfo. This constructor supports a certificate thumbprint to /// be used while connecting to a remote machine instead of credential. /// /// uri of remote runspace /// /// /// A thumb print of the certificate to use while connecting to the remote machine. /// [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#")] public WSManConnectionInfo(Uri uri, String shellUri, String certificateThumbprint) :this(uri, shellUri, (PSCredential)null) { _thumbPrint = certificateThumbprint; } /// /// constructor to create a WSManConnectionInfo with a /// uri specified and the default credentials, /// default server life time and default open /// timeout /// /// uri of remote runspae /// When an /// uri representing an invalid path is specified public WSManConnectionInfo(Uri uri) : this(uri, DefaultShellUri, DefaultCredential) { } #endregion Constructors #region Public Methods /// /// Populates session options from a PSSessionOption instance. /// /// /// /// 1. Proxy credential cannot be specified when proxy accesstype is None. /// Either specify a valid proxy accesstype other than None or do not specify proxy credential. /// public override void SetSessionOptions(PSSessionOption options) { if (options == null) { throw new ArgumentNullException("options"); } if ((options.ProxyAccessType == ProxyAccessType.None) && (options.ProxyCredential != null)) { string message = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.ProxyCredentialWithoutAccess, ProxyAccessType.None); throw new ArgumentException(message); } base.SetSessionOptions(options); this.MaximumConnectionRedirectionCount = options.MaximumConnectionRedirectionCount >= 0 ? options.MaximumConnectionRedirectionCount : int.MaxValue; this.MaximumReceivedDataSizePerCommand = options.MaximumReceivedDataSizePerCommand; this.MaximumReceivedObjectSize = options.MaximumReceivedObjectSize; this.UseCompression = !(options.NoCompression); this.NoMachineProfile = options.NoMachineProfile; this._proxyAcessType = options.ProxyAccessType; this._proxyAuthentication = options.ProxyAuthentication; this._proxyCredential = options.ProxyCredential; this._skipCaCheck = options.SkipCACheck; this._skipCnCheck = options.SkipCNCheck; this._skipRevocationCheck = options.SkipRevocationCheck; this._noEncryption = options.NoEncryption; this._useUtf16 = options.UseUTF16; this._includePortInSPN = options.IncludePortInSPN; this._outputBufferingMode = options.OutputBufferingMode; this._maxConnectionRetryCount = options.MaxConnectionRetryCount; } /// /// Shallow copy of the current instance. /// /// RunspaceConnectionInfo internal override RunspaceConnectionInfo InternalCopy() { return Copy(); } /// /// Does a shallow copy of the current instance /// /// public WSManConnectionInfo Copy() { WSManConnectionInfo result = new WSManConnectionInfo(); result._connectionUri = _connectionUri; result._computerName = _computerName; result._scheme = _scheme; result._port = _port; result._appName = _appName; result._shellUri = _shellUri; result._credential = _credential; result.UseDefaultWSManPort = UseDefaultWSManPort; result._authMechanism = this._authMechanism; result._maxUriRedirectionCount = this._maxUriRedirectionCount; result._maxRecvdDataSizePerCommand = this._maxRecvdDataSizePerCommand; result._maxRecvdObjectSize = this._maxRecvdObjectSize; result.OpenTimeout = this.OpenTimeout; result.IdleTimeout = this.IdleTimeout; result.MaxIdleTimeout = this.MaxIdleTimeout; result.CancelTimeout = this.CancelTimeout; result.OperationTimeout = base.OperationTimeout; result.Culture = this.Culture; result.UICulture = this.UICulture; result._thumbPrint = this._thumbPrint; result._allowImplicitCredForNegotiate = this._allowImplicitCredForNegotiate; result.UseCompression = this._useCompression; result.NoMachineProfile = this._noMachineProfile; result._proxyAcessType = this.ProxyAccessType; result._proxyAuthentication = this.ProxyAuthentication; result._proxyCredential = this.ProxyCredential; result._skipCaCheck = this.SkipCACheck; result._skipCnCheck = this.SkipCNCheck; result._skipRevocationCheck = this.SkipRevocationCheck; result._noEncryption = this.NoEncryption; result._useUtf16 = this.UseUTF16; result._includePortInSPN = this.IncludePortInSPN; result._enableNetworkAccess = this.EnableNetworkAccess; result.UseDefaultWSManPort = this.UseDefaultWSManPort; result._outputBufferingMode = this._outputBufferingMode; result.DisconnectedOn = this.DisconnectedOn; result.ExpiresOn = this.ExpiresOn; result.MaxConnectionRetryCount = this.MaxConnectionRetryCount; return result; } /// /// string for http scheme /// public const string HttpScheme = "http"; /// /// string for https scheme /// public const string HttpsScheme = "https"; #endregion #region Internal Methods internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { return new WSManClientSessionTransportManager( instanceId, this, cryptoHelper, sessionName); } #endregion #region Private Methods private String ResolveShellUri(string shell) { string resolvedShellUri = shell; if (String.IsNullOrEmpty(resolvedShellUri)) { resolvedShellUri = DefaultShellUri; } if (resolvedShellUri.IndexOf( System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix, StringComparison.OrdinalIgnoreCase) == -1) { resolvedShellUri = System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix + resolvedShellUri; } return resolvedShellUri; } /// /// Converts to a WSManConnectionInfo. If conversion succeeds extracts /// the propery..otherwise returns default value /// /// /// /// /// internal static T ExtractPropertyAsWsManConnectionInfo(RunspaceConnectionInfo rsCI, string property, T defaultValue) { WSManConnectionInfo wsCI = rsCI as WSManConnectionInfo; if (null == wsCI) { return defaultValue; } return (T) typeof(WSManConnectionInfo).GetProperty(property, typeof(T)).GetValue(wsCI, null); } internal void SetConnectionUri(Uri newUri) { Dbg.Assert(null != newUri, "newUri cannot be null."); _connectionUri = newUri; } /// /// Constructs a Uri from the supplied parameters. /// /// /// /// /// Making the port nullable to make sure the UseDefaultWSManPort variable is protected and updated /// only when Port is updated. Usages that dont update port, should use null for this parameter. /// /// /// internal void ConstructUri(String scheme, String computerName, Nullable port, String appName) { // Default scheme is http _scheme = scheme; if (string.IsNullOrEmpty(_scheme)) { _scheme = DefaultScheme; } // Valid values for scheme are "http" and "https" if (!(_scheme.Equals(HttpScheme, StringComparison.OrdinalIgnoreCase) || _scheme.Equals(HttpsScheme, StringComparison.OrdinalIgnoreCase) || _scheme.Equals(DefaultScheme, StringComparison.OrdinalIgnoreCase))) { String message = PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.InvalidSchemeValue, _scheme); ArgumentException e = new ArgumentException(message); throw e; } //default host is localhost if (String.IsNullOrEmpty(computerName) || String.Equals(computerName, ".", StringComparison.OrdinalIgnoreCase)) { _computerName = DefaultComputerName; } else { _computerName = computerName.Trim(); // According to RFC3513, an Ipv6 address in URI needs to be bracketed. IPAddress ipAddress = null; bool isIPAddress = IPAddress.TryParse(_computerName, out ipAddress); if (isIPAddress && ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { if ((_computerName.Length == 0) || (_computerName[0] != '[')) { _computerName = @"[" + _computerName + @"]"; } } } PSEtwLog.LogAnalyticVerbose(PSEventId.ComputerName, PSOpcode.Method, PSTask.CreateRunspace, PSKeyword.Runspace | PSKeyword.UseAlwaysAnalytic, _computerName); if (port.HasValue) { // resolve to default ports if required if (port.Value == DefaultPort) { // this is needed so that the OriginalString on // connection uri is fine _port = -1; UseDefaultWSManPort = true; } else if (port.Value == DefaultPortHttp || port.Value == DefaultPortHttps) { _port = port.Value; UseDefaultWSManPort = false; } else if ((port.Value < MinPort || port.Value > MaxPort)) { String message = PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.PortIsOutOfRange, port); ArgumentException e = new ArgumentException(message); throw e; } else { _port = port.Value; UseDefaultWSManPort = false; } } // default appname is WSMan _appName = appName; if (String.IsNullOrEmpty(_appName)) { _appName = DefaultAppName; } // construct Uri UriBuilder uriBuilder = new UriBuilder(_scheme, _computerName, _port, _appName); _connectionUri = uriBuilder.Uri; } /// /// returns connection string without the scheme portion. /// /// /// The uri from which the string will be extracted /// /// /// returns true if https scheme is specified /// /// /// returns connection string without the scheme portion. /// internal static string GetConnectionString(Uri connectionUri, out bool isSSLSpecified) { isSSLSpecified = connectionUri.Scheme.Equals(WSManConnectionInfo.HttpsScheme); string result = connectionUri.OriginalString.TrimStart(); if (isSSLSpecified) { return result.Substring(WSManConnectionInfo.HttpsScheme.Length + 3); } else { return result.Substring(WSManConnectionInfo.HttpScheme.Length + 3); } } /// /// Used to resolve authentication from the parameters chosen by the user. /// User has the following options: /// 1. AuthMechanism + Credential /// 2. CertiticateThumbPrint /// /// All the above are mutually exclusive. /// /// /// If there is ambiguity as specified above. /// private void ValidateSpecifiedAuthentication() { if ((_authMechanism != WSManAuthenticationMechanism.WSMAN_FLAG_DEFAULT_AUTHENTICATION) && (_thumbPrint != null)) { throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.NewRunspaceAmbiguosAuthentication, "CertificateThumbPrint", this.AuthenticationMechanism.ToString()); } } private void UpdateUri(Uri uri) { if (!uri.IsAbsoluteUri) { throw new NotSupportedException(PSRemotingErrorInvariants.FormatResourceString (RemotingErrorIdStrings.RelativeUriForRunspacePathNotSupported)); } if (uri.OriginalString.LastIndexOf(":", StringComparison.OrdinalIgnoreCase) > uri.AbsoluteUri.IndexOf("//", StringComparison.OrdinalIgnoreCase)) { _useDefaultWSManPort = false; } // This check is needed to make sure we connect to WSMan app in the // default case (when user did not specify any appname) like // http://localhost , http://127.0.0.1 etc. string appname; if (uri.AbsolutePath.Equals("/", StringComparison.OrdinalIgnoreCase) && string.IsNullOrEmpty(uri.Query) && string.IsNullOrEmpty(uri.Fragment)) { appname = DefaultAppName; ConstructUri(uri.Scheme, uri.Host, uri.Port, appname); } else { _connectionUri = uri; _scheme = uri.Scheme; _appName = uri.AbsolutePath; _port = uri.Port; _computerName = uri.Host; _useDefaultWSManPort = false; } } #endregion Private Methods #region Private Members private string _scheme = HttpScheme; private string _computerName = DefaultComputerName; private string _appName = DefaultAppName; private Int32 _port = -1; private Uri _connectionUri = new Uri(LocalHostUriString); // uri of this connection private PSCredential _credential; // credentials to be used for this connection private string _shellUri = DefaultShellUri; // shell thats specified by the user private WSManAuthenticationMechanism _authMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_DEFAULT_AUTHENTICATION; private bool _allowImplicitCredForNegotiate; private string _thumbPrint; private int _maxUriRedirectionCount; private int? _maxRecvdDataSizePerCommand; // Default unlimited private int? _maxRecvdObjectSize; // Default unlimited private bool _useCompression = true; private bool _noMachineProfile; private ProxyAccessType _proxyAcessType = ProxyAccessType.None; private AuthenticationMechanism _proxyAuthentication; private PSCredential _proxyCredential; private bool _skipCaCheck; private bool _skipCnCheck; private bool _skipRevocationCheck; private bool _noEncryption; private bool _useUtf16; private OutputBufferingMode _outputBufferingMode = DefaultOutputBufferingMode; private bool _includePortInSPN; private bool _enableNetworkAccess; private int _maxConnectionRetryCount = DefaultMaxConnectionRetryCount; #endregion Private Members #region constants /// /// Default disconnected server output mode is set to None. This mode allows the /// server to set the buffering mode to Block for new sessions and retain its /// current mode during disconnect/connect operations. /// internal const OutputBufferingMode DefaultOutputBufferingMode = OutputBufferingMode.None; /// /// Default maximum connection retry count. /// internal const int DefaultMaxConnectionRetryCount = 5; #if NOT_APPLY_PORT_DCR private static string DEFAULT_SCHEME = HTTP_SCHEME; internal static string DEFAULT_SSL_SCHEME = HTTPS_SCHEME; private static String DEFAULT_APP_NAME = "wsman"; /// /// See below for explanation. /// internal bool UseDefaultWSManPort { get { return false; } set { } } #else private const string DefaultScheme = HttpScheme; private const string DefaultSslScheme = HttpsScheme; /// /// Default appname. This is empty as WSMan configuration has support /// for this. Look at /// get-item WSMan:\localhost\Client\URLPrefix /// private static readonly String DefaultAppName = "/wsman"; /// /// Default scheme. /// As part of port DCR, WSMan changed the default ports /// from 80,443 to 5985,5986 respectively no-SSL,SSL /// connections. Since the standards say http,https use /// 80,443 as defaults..we came up with new mechanism /// to specify scheme as empty. For SSL, WSMan introduced /// a new SessionOption. In order to make scheme empty /// in the connection string passed to WSMan, we use /// this internal boolean. /// internal bool UseDefaultWSManPort { get { return _useDefaultWSManPort; } set { _useDefaultWSManPort = value; } } private bool _useDefaultWSManPort; #endif /// /// default port for http scheme /// private const int DefaultPortHttp = 80; /// /// default port for https scheme /// private const int DefaultPortHttps = 443; /// /// This is the default port value which when specified /// results in the default port for the scheme to be /// assumed /// private const int DefaultPort = 0; /// /// default remote host name /// private const string DefaultComputerName = "localhost"; /// /// Maximum value for port /// private const int MaxPort = 0xFFFF; /// /// Minimum value for port /// private const int MinPort = 0; /// /// String that represents the local host Uri /// private const String LocalHostUriString = "http://localhost/wsman"; /// /// Default value for shell /// private const String DefaultShellUri = System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix + RemotingConstants.DefaultShellName; /// /// Default credentials - null indicates credentials of /// current user /// private const PSCredential DefaultCredential = null; #endregion constants #region Internal members /// /// Helper property that returns true when the connection has EnableNetworkAccess set /// and the connection is localhost (loopback), i.e., not a network connection. /// /// internal bool IsLocalhostAndNetworkAccess { get { return (EnableNetworkAccess && // Interactive token requested (Credential == null && // No credential provided (ComputerName.Equals(DefaultComputerName, StringComparison.OrdinalIgnoreCase) || // Localhost computer name ComputerName.IndexOf('.') == -1))); // Not FQDN computer name } } /// /// DisconnectedOn property applies to disconnnected runspaces. /// This property is publicly exposed only through Runspace class. /// internal DateTime? DisconnectedOn { get; set; } /// /// ExpiresOn property applies to disconnnected runspaces. /// This property is publicly exposed only through Runspace class. /// internal DateTime? ExpiresOn { get; set; } /// /// Helper method to reset DisconnectedOn/ExpiresOn properties to null. /// internal void NullDisconnectedExpiresOn() { this.DisconnectedOn = null; this.ExpiresOn = null; } /// /// Helper method to set the DisconnectedOn/ExpiresOn properties based /// on current date/time and session idletimeout value. /// internal void SetDisconnectedExpiresOnToNow() { TimeSpan idleTimeoutTime = TimeSpan.FromSeconds(this.IdleTimeout / 1000); DateTime now = DateTime.Now; this.DisconnectedOn = now; this.ExpiresOn = now.Add(idleTimeoutTime); } #endregion Internal members #region V3 Extensions private const String DefaultM3PShellName = "Microsoft.PowerShell.Workflow"; private const String DefaultM3PEndpoint = Remoting.Client.WSManNativeApi.ResourceURIPrefix + DefaultM3PShellName; /// /// Constructor that constructs the configuration name from its type /// /// type of configuration to construct public WSManConnectionInfo(PSSessionType configurationType) : this() { ComputerName = string.Empty; switch (configurationType) { case PSSessionType.DefaultRemoteShell: { // it is already the default } break; case PSSessionType.Workflow: { ShellUri = DefaultM3PEndpoint; } break; default: { Diagnostics.Assert(false, "Unknown value for PSSessionType"); } break; } } #endregion V3 Extensions } /// /// Class which is used to create an Out-Of-Process Runspace/RunspacePool. /// This does not have a dependency on WSMan. *-Job cmdlets use Out-Of-Proc /// Runspaces to support background jobs. /// internal sealed class NewProcessConnectionInfo : RunspaceConnectionInfo { #region Private Data private PSCredential credential; private AuthenticationMechanism authMechanism; private ScriptBlock initScript; private bool shouldRunsAs32; private Version psVersion; private PowerShellProcessInstance process; #endregion #region Properties /// /// Script to run while starting the background process. /// public ScriptBlock InitializationScript { get { return initScript; } set { initScript = value; } } /// /// On a 64bit machine, specifying true for this will launch a 32 bit process /// for the background process. /// public bool RunAs32 { get { return shouldRunsAs32; } set { shouldRunsAs32 = value; } } /// /// Powershell version to execute the job in /// public Version PSVersion { get { return psVersion; } set { psVersion = value; } } internal PowerShellProcessInstance Process { get { return process; } set { process = value; } } #endregion #region Overrides /// /// Name of the computer. Will always be "localhost" to signify local machine. /// public override string ComputerName { // TODO: should this be different get { return "localhost"; } set { throw new NotImplementedException(); } } /// /// Credential used for the connection /// public override PSCredential Credential { get { return credential; } set { credential = value; authMechanism = AuthenticationMechanism.Default; } } /// /// Authentication mechanism to use while connecting to the server. /// Only Default is supported. /// public override AuthenticationMechanism AuthenticationMechanism { get { return authMechanism; } set { if (value != AuthenticationMechanism.Default) { throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.IPCSupportsOnlyDefaultAuth, value.ToString(), AuthenticationMechanism.Default.ToString()); } authMechanism = value; } } /// /// ThumbPrint of a certificate used for connecting to a remote machine. /// When this is specified, you dont need to supply credential and authentication /// mechanism. /// Will always be empty to signfy that this is not supported. /// public override string CertificateThumbprint { get { return string.Empty; } set { throw new NotImplementedException(); } } public NewProcessConnectionInfo Copy() { NewProcessConnectionInfo result = new NewProcessConnectionInfo(this.credential); result.AuthenticationMechanism = this.AuthenticationMechanism; result.InitializationScript = this.InitializationScript; result.RunAs32 = this.RunAs32; result.PSVersion = this.PSVersion; result.Process = Process; return result; } internal override RunspaceConnectionInfo InternalCopy() { return Copy(); } internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { return new OutOfProcessClientSessionTransportManager( instanceId, this, cryptoHelper); } #endregion #region Constructors /// /// Creates a connection info instance used to create a runspace on a different /// process on the local machine /// internal NewProcessConnectionInfo(PSCredential credential) { this.credential = credential; authMechanism = AuthenticationMechanism.Default; } #endregion } /// /// Class used to create an Out-Of-Process Runspace/RunspacePool between /// two local processes using a named pipe for IPC. /// This class does not have a dependency on WSMan and is used to implement /// the PowerShell attach-to-process feature. /// public sealed class NamedPipeConnectionInfo : RunspaceConnectionInfo { #region Private Data private PSCredential _credential; private AuthenticationMechanism _authMechanism; private string _appDomainName = string.Empty; private const int _defaultOpenTimeout = 60000; /* 60 seconds. */ #endregion #region Properties /// /// Process Id of process to attach to. /// public int ProcessId { get; set; } /// /// Optional application domain name. If not specified then the /// default application domain is used. /// public string AppDomainName { get { return _appDomainName; } set { _appDomainName = value ?? string.Empty; } } #endregion #region Constructors /// /// Constructor /// public NamedPipeConnectionInfo() { OpenTimeout = _defaultOpenTimeout; } /// /// Constructor /// /// Process Id to connect to. public NamedPipeConnectionInfo( int processId) : this(processId, string.Empty, _defaultOpenTimeout) { } /// /// Constructor /// /// Process Id to connect to /// Application domain name to connect to, or default AppDomain if blank. public NamedPipeConnectionInfo( int processId, string appDomainName) : this(processId, appDomainName, _defaultOpenTimeout) { } /// /// Constructor /// /// Process Id to connect to /// Name of application domain to connect to. Connection is to default application domain if blank. /// Open time out in Milliseconds public NamedPipeConnectionInfo( int processId, string appDomainName, int openTimeout) { ProcessId = processId; AppDomainName = appDomainName; OpenTimeout = openTimeout; } #endregion #region Overrides /// /// Computer is always localhost. /// public override string ComputerName { get { return "localhost"; } set { throw new NotImplementedException(); } } /// /// Credential /// public override PSCredential Credential { get { return _credential; } set { _credential = value; _authMechanism = Runspaces.AuthenticationMechanism.Default; } } /// /// Authentication /// public override AuthenticationMechanism AuthenticationMechanism { get { return _authMechanism; } set { if (value != Runspaces.AuthenticationMechanism.Default) { throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.IPCSupportsOnlyDefaultAuth, value.ToString(), AuthenticationMechanism.Default.ToString()); } _authMechanism = value; } } /// /// CertificateThumbprint /// public override string CertificateThumbprint { get { return string.Empty; } set { throw new NotImplementedException(); } } /// /// Shallow copy of current instance. /// /// NamedPipeConnectionInfo internal override RunspaceConnectionInfo InternalCopy() { NamedPipeConnectionInfo newCopy = new NamedPipeConnectionInfo(); newCopy._authMechanism = this.AuthenticationMechanism; newCopy._credential = this.Credential; newCopy.ProcessId = this.ProcessId; newCopy._appDomainName = this._appDomainName; newCopy.OpenTimeout = this.OpenTimeout; return newCopy; } internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { return new NamedPipeClientSessionTransportManager( this, instanceId, cryptoHelper); } #endregion } /// /// The class that contains connection information for a remote session between a local host /// and VM. The local host can be a VM in nested scenario. /// public sealed class VMConnectionInfo : RunspaceConnectionInfo { #region Private Data private AuthenticationMechanism _authMechanism; private PSCredential _credential; private const int _defaultOpenTimeout = 20000; /* 20 seconds. */ private Guid _vmGuid; private string _vmName; private string _configurationName; #endregion #region Properties /// /// GUID of the target VM. /// public Guid VMGuid { get { return _vmGuid; } set { _vmGuid = value; } } /// /// Configuration name of the VM session. /// public string ConfigurationName { get { return _configurationName; } set { _configurationName = value; } } #endregion #region Overrides /// /// Authentication mechanism to use while connecting to the server. /// Only Default is supported. /// public override AuthenticationMechanism AuthenticationMechanism { get { return _authMechanism; } set { if (value != AuthenticationMechanism.Default) { throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.IPCSupportsOnlyDefaultAuth, value.ToString(), AuthenticationMechanism.Default.ToString()); } _authMechanism = value; } } /// /// ThumbPrint of a certificate used for connecting to a remote machine. /// When this is specified, you dont need to supply credential and authentication /// mechanism. /// Will always be null to signfy that this is not supported. /// public override string CertificateThumbprint { get { return null; } set { throw new NotImplementedException(); } } /// /// Credential used for the connection. /// public override PSCredential Credential { get { return _credential; } set { _credential = value; _authMechanism = AuthenticationMechanism.Default; } } /// /// Name of the target VM. /// public override string ComputerName { get { return _vmName; } set { _vmName = value; } } internal override RunspaceConnectionInfo InternalCopy() { VMConnectionInfo result = new VMConnectionInfo(Credential, VMGuid, ComputerName, ConfigurationName); return result; } internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { return new VMHyperVSocketClientSessionTransportManager( this, instanceId, cryptoHelper, VMGuid, ConfigurationName); } #endregion #region Constructors /// /// Creates a connection info instance used to create a runspace on target VM. /// internal VMConnectionInfo( PSCredential credential, Guid vmGuid, string vmName, string configurationName) : base() { Credential = credential; VMGuid = vmGuid; ComputerName = vmName; ConfigurationName = configurationName; AuthenticationMechanism = AuthenticationMechanism.Default; OpenTimeout = _defaultOpenTimeout; } #endregion } /// /// The class that contains connection information for a remote session between a local /// container host and container. /// For Windows Server container, the transport is based on named pipe for now. /// For Hyper-V container, the transport is based on Hyper-V socket. /// public sealed class ContainerConnectionInfo : RunspaceConnectionInfo { #region Private Data private AuthenticationMechanism _authMechanism; private ContainerProcess _containerProc; private PSCredential _credential; private const int _defaultOpenTimeout = 20000; /* 20 seconds. */ #endregion #region Properties /// /// ContainerProcess class instance. /// internal ContainerProcess ContainerProc { get { return _containerProc; } set { _containerProc = value; } } #endregion #region Overrides /// /// Authentication mechanism to use while connecting to the server. /// Only Default is supported. /// public override AuthenticationMechanism AuthenticationMechanism { get { return _authMechanism; } set { if (value != AuthenticationMechanism.Default) { throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.IPCSupportsOnlyDefaultAuth, value.ToString(), AuthenticationMechanism.Default.ToString()); } _authMechanism = value; } } /// /// ThumbPrint of a certificate used for connecting to a remote machine. /// When this is specified, you dont need to supply credential and authentication /// mechanism. /// Will always be null to signfy that this is not supported. /// public override string CertificateThumbprint { get { return null; } set { throw new NotImplementedException(); } } /// /// Credential used for the connection. /// public override PSCredential Credential { get { return _credential; } set { _credential = value; _authMechanism = Runspaces.AuthenticationMechanism.Default; } } /// /// Name of the target container. /// public override string ComputerName { get { return _containerProc.ContainerName; } set { throw new PSNotSupportedException(); } } internal override RunspaceConnectionInfo InternalCopy() { ContainerConnectionInfo newCopy = new ContainerConnectionInfo(ContainerProc); return newCopy; } internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { if (ContainerProc.RuntimeId != Guid.Empty) { return new ContainerHyperVSocketClientSessionTransportManager( this, instanceId, cryptoHelper, ContainerProc.RuntimeId); } else { return new ContainerNamedPipeClientSessionTransportManager( this, instanceId, cryptoHelper); } } #endregion #region Constructors /// /// Creates a connection info instance used to create a runspace on target container. /// internal ContainerConnectionInfo( ContainerProcess containerProc) : base() { ContainerProc = containerProc; AuthenticationMechanism = AuthenticationMechanism.Default; Credential = null; OpenTimeout = _defaultOpenTimeout; } #endregion #region Public methods /// /// Create ContainerConnectionInfo object based on container id. /// public static ContainerConnectionInfo CreateContainerConnectionInfoById( string containerId, bool runAsAdmin, string configurationName) { ContainerProcess containerProc = new ContainerProcess(containerId, null, Guid.Empty, 0, runAsAdmin, configurationName); return new ContainerConnectionInfo(containerProc); } /// /// Create ContainerConnectionInfo object based on container name. /// public static ContainerConnectionInfo CreateContainerConnectionInfoByName( string containerName, bool runAsAdmin, string configurationName) { ContainerProcess containerProc = new ContainerProcess(null, containerName, Guid.Empty, 0, runAsAdmin, configurationName); return new ContainerConnectionInfo(containerProc); } /// /// Create process inside container. /// public void CreateContainerProcess() { ContainerProc.CreateContainerProcess(); } /// /// Terminate process inside container. /// public bool TerminateContainerProcess() { return ContainerProc.TerminateContainerProcess(); } #endregion } /// /// Class used to create/terminate process inside container, which can be either /// Windows Server Container or Hyper-V container. /// - Windows Server Container does not require Hyper-V. /// - Hyper-V container requires Hyper-V and utility VM, which is different from normal VM. /// internal class ContainerProcess { #region Private Data private Guid runtimeId; private Guid containerGuid = Guid.Empty; private string containerId; private string containerName; private int processId; private bool runAsAdmin = false; private string configurationName; private bool processTerminated = false; private uint errorCode = 0; private string errorMessage = string.Empty; private const uint NoError = 0; private const uint InvalidContainerId = 1; private const uint ContainersFeatureNotEnabled = 2; private const uint InvalidContainerNameMultiple = 3; private const uint InvalidContainerNameNotExist = 4; private const uint OtherError = 9999; #endregion #region Properties /// /// For Hyper-V container, it is Guid of utility VM hosting Hyper-V container. /// For Windows Server Container, it is empty. /// public Guid RuntimeId { get { return runtimeId; } set { runtimeId = value; } } /// /// Guid of the container. /// public Guid ContainerGuid { get { return containerGuid; } set { containerGuid = value; } } /// /// ID of the container. /// public string ContainerId { get { return containerId; } set { containerId = value; } } /// /// Name of the container. /// public string ContainerName { get { return containerName; } set { containerName = value; } } /// /// Process ID of the process created in container. /// internal int ProcessId { get { return processId; } set { processId = value; } } /// /// Whether the process in container should be launched as high privileged account /// (RunAsAdmin being true) or low privileged account (RunAsAdmin being false). /// internal bool RunAsAdmin { get { return runAsAdmin; } set { runAsAdmin = value; } } /// /// The configuration name of the container session. /// internal string ConfigurationName { get { return configurationName; } set { configurationName = value; } } /// /// Whether the process in container has terminated. /// internal bool ProcessTerminated { get { return processTerminated; } set { processTerminated = value; } } /// /// The error code. /// internal uint ErrorCode { get { return errorCode; } set { errorCode = value; } } /// /// The error message for other errors. /// internal string ErrorMessage { get { return errorMessage; } set { errorMessage = value; } } #endregion #region Native HCS (i.e., host compute service) methods [DllImport(PinvokeDllNames.CreateProcessInComputeSystemDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern uint CreateProcessInComputeSystem( string id, string ProcessParameters, ref int processId); [DllImport(PinvokeDllNames.CreateProcessInComputeSystemDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern uint TerminateProcessInComputeSystem( string id, int processId); #endregion #region Constructors /// /// Creates an instance used for PowerShell Direct for container. /// public ContainerProcess(string containerId, string containerName, Guid containerGuid, int processId, bool runAsAdmin, string configurationName) { this.ContainerId = containerId; this.ContainerName = containerName; this.ContainerGuid = containerGuid; this.ProcessId = processId; this.RunAsAdmin = runAsAdmin; this.ConfigurationName = configurationName; if (!String.IsNullOrEmpty(containerId)) { if (String.IsNullOrEmpty(containerName)) { GetContainerPropertiesFromContainerId(); } } else if (!String.IsNullOrEmpty(containerName)) { GetContainerPropertiesFromContainerName(); } else { Dbg.Assert(false, "containerId and containerName inputs cannot be empty at the same time."); } } #endregion #region Public methods /// /// Create process inside container. /// public void CreateContainerProcess() { RunOnMTAThread(CreateContainerProcessInternal); // // Report error. More error reporting will come later. // switch (ErrorCode) { case NoError: break; case InvalidContainerId: throw new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.InvalidContainerId, ContainerId)); case ContainersFeatureNotEnabled: throw new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.ContainersFeatureNotEnabled)); // other errors caught with exception case OtherError: throw new PSInvalidOperationException(ErrorMessage); // other errors caught without exception default: throw new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.CannotCreateProcessInContainer, ContainerId)); } } /// /// Terminate process inside container. /// public bool TerminateContainerProcess() { RunOnMTAThread(TerminateContainerProcessInternal); return ProcessTerminated; } /// /// Get container name and Guid based on given container id. /// public void GetContainerPropertiesFromContainerId() { RunOnMTAThread(GetContainerPropertiesFromContainerIdInternal); // // Report error. // switch (ErrorCode) { case NoError: break; case ContainersFeatureNotEnabled: throw new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.ContainersFeatureNotEnabled)); case OtherError: throw new PSInvalidOperationException(ErrorMessage); } } /// /// Get container id and Guid based on given container name. /// public void GetContainerPropertiesFromContainerName() { RunOnMTAThread(GetContainerPropertiesFromContainerNameInternal); // // Report error. // switch (ErrorCode) { case NoError: break; case ContainersFeatureNotEnabled: throw new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.ContainersFeatureNotEnabled)); case InvalidContainerNameMultiple: throw new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.InvalidContainerNameMultiple, ContainerName)); case InvalidContainerNameNotExist: throw new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.InvalidContainerNameNotExist, ContainerName)); case OtherError: throw new PSInvalidOperationException(ErrorMessage); } } #endregion #region Private methods /// /// Dynamically load the Host Compute interop assemblies and return useful types. /// /// The Microsoft.HyperV.Schema.Compute.System.Properties type. /// The Microsoft.HostCompute.Interop.HostComputeInterop type. private static void GetHostComputeInteropTypes(out Type computeSystemPropertiesType, out Type hostComputeInteropType) { Assembly schemaAssembly = Assembly.Load(new AssemblyName("Microsoft.HyperV.Schema, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")); computeSystemPropertiesType = schemaAssembly.GetType("Microsoft.HyperV.Schema.Compute.System.Properties"); Assembly hostComputeInteropAssembly = Assembly.Load(new AssemblyName("Microsoft.HostCompute.Interop, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")); hostComputeInteropType = hostComputeInteropAssembly.GetType("Microsoft.HostCompute.Interop.HostComputeInterop"); } /// /// Create process inside container. /// private void CreateContainerProcessInternal() { uint result; string cmd; int processId = 0; uint error = 0; // // Check whether the given container id exists. // try { Type computeSystemPropertiesType; Type hostComputeInteropType; GetHostComputeInteropTypes(out computeSystemPropertiesType, out hostComputeInteropType); MethodInfo computeSystemExistsInfo = hostComputeInteropType.GetMethod("ComputeSystemExists"); if (!(bool)computeSystemExistsInfo.Invoke(null, new object[]{ ContainerId })) { processId = 0; error = InvalidContainerId; } else { // // Hyper-V container (i.e., RuntimeId is not empty) uses Hyper-V socket transport. // Windows Server container (i.e., RuntimeId is empty) uses named pipe transport for now. // cmd = string.Format(System.Globalization.CultureInfo.InvariantCulture, @"{{""CommandLine"": ""powershell.exe {0} -NoLogo {1}"",""RestrictedToken"": {2}}}", (RuntimeId != Guid.Empty) ? "-so -NoProfile" : "-NamedPipeServerMode", String.IsNullOrEmpty(ConfigurationName) ? String.Empty : String.Concat("-Config ", ConfigurationName), (RunAsAdmin) ? "false" : "true"); // // Create PowerShell process inside the container. // result = CreateProcessInComputeSystem( ContainerId, cmd, ref processId); if (result != 0) { processId = 0; error = result; } } } catch (Exception e) { CommandProcessorBase.CheckForSevereException(e); if (e is FileNotFoundException || e is FileLoadException) { // // The ComputeSystemExists call depends on the existence of microsoft.hostcompute.interop.dll, // which requires Containers feature to be enabled. In case Containers feature is // not enabled, we need to output a corresponding error message to inform user. // ProcessId = 0; ErrorCode = ContainersFeatureNotEnabled; return; } else { ProcessId = 0; ErrorCode = OtherError; ErrorMessage = GetErrorMessageFromException(e); return; } } ProcessId = processId; ErrorCode = error; } /// /// Terminate the process inside container. /// private void TerminateContainerProcessInternal() { ProcessTerminated = (TerminateProcessInComputeSystem(ContainerId, ProcessId) == 0); } /// /// Get container name and Guid based on given container id. /// private void GetContainerPropertiesFromContainerIdInternal() { try { Type computeSystemPropertiesType; Type hostComputeInteropType; GetHostComputeInteropTypes(out computeSystemPropertiesType, out hostComputeInteropType); MethodInfo getComputeSystemPropertiesInfo = hostComputeInteropType.GetMethod("GetComputeSystemProperties"); var computeSystemPropertiesHandle = getComputeSystemPropertiesInfo.Invoke(null, new object[]{ ContainerId }); ContainerName = (string)computeSystemPropertiesType.GetProperty("Name").GetValue(computeSystemPropertiesHandle); RuntimeId = (Guid)computeSystemPropertiesType.GetProperty("RuntimeId").GetValue(computeSystemPropertiesHandle); // // Get container Guid for Windows Server container. // if (RuntimeId == Guid.Empty) { ContainerGuid = new Guid((string)computeSystemPropertiesType.GetProperty("SiloGuid").GetValue(computeSystemPropertiesHandle)); } } catch (FileNotFoundException) { ErrorCode = ContainersFeatureNotEnabled; } catch (FileLoadException) { ErrorCode = ContainersFeatureNotEnabled; } catch (Exception e) { CommandProcessorBase.CheckForSevereException(e); if (e.InnerException != null && StringComparer.Ordinal.Equals( e.InnerException.GetType().FullName, "Microsoft.HostCompute.Interop.ObjectNotFoundException")) { ErrorCode = InvalidContainerId; } else { ErrorCode = OtherError; ErrorMessage = GetErrorMessageFromException(e); } } } /// /// Get container id and Guid based on given container name. /// private void GetContainerPropertiesFromContainerNameInternal() { try { Type computeSystemPropertiesType; Type hostComputeInteropType; object computeSystemPropertiesHandle = null; int computeSystemPropertiesHandlesCount = 0; var wildCardPattern = WildcardPattern.Get(ContainerName, WildcardOptions.IgnoreCase); GetHostComputeInteropTypes(out computeSystemPropertiesType, out hostComputeInteropType); MethodInfo enumerateComputeSystemsInfo = hostComputeInteropType.GetMethod("EnumerateComputeSystems"); var computeSystemPropertiesHandles = (IEnumerable)enumerateComputeSystemsInfo.Invoke(null, new object[] { null, null, null, null }); foreach (var currentComputeSystemPropertiesHandle in computeSystemPropertiesHandles) { string name = (string)computeSystemPropertiesType.GetProperty("Name").GetValue(currentComputeSystemPropertiesHandle); if (wildCardPattern.IsMatch(name)) { computeSystemPropertiesHandle = currentComputeSystemPropertiesHandle; computeSystemPropertiesHandlesCount++; if (computeSystemPropertiesHandlesCount > 1) { ErrorCode = InvalidContainerNameMultiple; return; } } } if (computeSystemPropertiesHandle == null) { ErrorCode = InvalidContainerNameNotExist; return; } ContainerId = (string)computeSystemPropertiesType.GetProperty("Id").GetValue(computeSystemPropertiesHandle); ContainerName = (string)computeSystemPropertiesType.GetProperty("Name").GetValue(computeSystemPropertiesHandle); RuntimeId = (Guid)computeSystemPropertiesType.GetProperty("RuntimeId").GetValue(computeSystemPropertiesHandle); // // Get container Guid for Windows Server container. // if (RuntimeId == Guid.Empty) { ContainerGuid = new Guid((string)computeSystemPropertiesType.GetProperty("SiloGuid").GetValue(computeSystemPropertiesHandle)); } } catch (FileNotFoundException) { ErrorCode = ContainersFeatureNotEnabled; } catch (FileLoadException) { ErrorCode = ContainersFeatureNotEnabled; } catch (Exception e) { CommandProcessorBase.CheckForSevereException(e); ErrorCode = OtherError; ErrorMessage = GetErrorMessageFromException(e); } } /// /// Run some tasks on MTA thread if needed. /// private void RunOnMTAThread(ThreadStart threadProc) { // // By default, non-OneCore PowerShell is launched with ApartmentState being STA. // In this case, we need to create a separate thread, set its ApartmentState to MTA, // and do the work. // // For OneCore PowerShell, its ApartmentState is always MTA. // #if CORECLR threadProc(); #else if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) { threadProc(); } else { Thread executionThread = new Thread(new ThreadStart(threadProc)); executionThread.SetApartmentState(ApartmentState.MTA); executionThread.Start(); executionThread.Join(); } #endif } /// /// Get error message from the thrown exception. /// private string GetErrorMessageFromException(Exception e) { string errorMessage = e.Message; if (e.InnerException != null) { errorMessage += " " + e.InnerException.Message; } return errorMessage; } #endregion } }