/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System.Net; using System.Net.Sockets; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Pipes; using System.ComponentModel; // Win32Exception 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 System.Security.AccessControl; using Microsoft.Win32.SafeHandles; 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; set; } = 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; set; } = 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; set; } = 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; internal set; } = 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; } _openTimeout = TimeSpanToTimeOutMs(options.OpenTimeout); CancelTimeout = TimeSpanToTimeOutMs(options.CancelTimeout); 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. 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 (WSManAuthenticationMechanism) { 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 (AllowImplicitCredentialForNegotiate) { 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: WSManAuthenticationMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_DEFAULT_AUTHENTICATION; break; case AuthenticationMechanism.Basic: WSManAuthenticationMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_BASIC; break; case AuthenticationMechanism.Negotiate: WSManAuthenticationMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_NEGOTIATE; break; case AuthenticationMechanism.NegotiateWithImplicitCredential: WSManAuthenticationMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_NEGOTIATE; AllowImplicitCredentialForNegotiate = true; break; case AuthenticationMechanism.Credssp: WSManAuthenticationMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_CREDSSP; break; case AuthenticationMechanism.Digest: WSManAuthenticationMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_DIGEST; break; case AuthenticationMechanism.Kerberos: WSManAuthenticationMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_KERBEROS; break; default: throw new PSNotSupportedException(); } ValidateSpecifiedAuthentication(); } } /// /// AuthenticationMechanism converted to WSManAuthenticationMechanism type. /// This is internal. /// internal WSManAuthenticationMechanism WSManAuthenticationMechanism { get; private set; } = WSManAuthenticationMechanism.WSMAN_FLAG_DEFAULT_AUTHENTICATION; /// /// Allow default credentials for Negotiate /// internal bool AllowImplicitCredentialForNegotiate { get; private set; } /// /// Returns the actual port property value and not the ConnectionUri port. /// Internal only. /// internal int PortSetting { get; private set; } = -1; /// /// 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; set; } 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; set; } /// /// 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; set; } /// /// 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; set; } = true; /// /// 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; set; } // 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; set; } = ProxyAccessType.None; /// /// 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.ProxyAmbiguousAuthentication, 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 (ProxyAccessType == 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; set; } /// /// 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; set; } /// /// 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; set; } /// /// 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 /// public bool NoEncryption { get; set; } /// /// 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; set; } // END: Session Options /// /// Determines how server in disconnected state deals with cached output /// data when the cache becomes filled. /// public OutputBufferingMode OutputBufferingMode { get; set; } = DefaultOutputBufferingMode; /// /// Uses Service Principal Name (SPN) along with the Port number during authentication. /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SPN")] public bool IncludePortInSPN { get; set; } /// /// 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; set; } /// /// Specifies the maximum number of connection retries if previous connection attempts fail /// due to network issues. /// public int MaxConnectionRetryCount { get; set; } = DefaultMaxConnectionRetryCount; #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, s_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 runspace /// 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; ProxyAccessType = options.ProxyAccessType; _proxyAuthentication = options.ProxyAuthentication; _proxyCredential = options.ProxyCredential; SkipCACheck = options.SkipCACheck; SkipCNCheck = options.SkipCNCheck; SkipRevocationCheck = options.SkipRevocationCheck; NoEncryption = options.NoEncryption; UseUTF16 = options.UseUTF16; IncludePortInSPN = options.IncludePortInSPN; OutputBufferingMode = options.OutputBufferingMode; 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.PortSetting = PortSetting; result._appName = _appName; result._shellUri = _shellUri; result._credential = _credential; result.UseDefaultWSManPort = UseDefaultWSManPort; result.WSManAuthenticationMechanism = WSManAuthenticationMechanism; result.MaximumConnectionRedirectionCount = MaximumConnectionRedirectionCount; result.MaximumReceivedDataSizePerCommand = MaximumReceivedDataSizePerCommand; result.MaximumReceivedObjectSize = MaximumReceivedObjectSize; 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 = _thumbPrint; result.AllowImplicitCredentialForNegotiate = AllowImplicitCredentialForNegotiate; result.UseCompression = UseCompression; result.NoMachineProfile = NoMachineProfile; result.ProxyAccessType = 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 = 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 property..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 PortSetting = -1; UseDefaultWSManPort = true; } else if (port.Value == DefaultPortHttp || port.Value == DefaultPortHttps) { PortSetting = 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 { PortSetting = port.Value; UseDefaultWSManPort = false; } } // default appname is WSMan _appName = appName; if (String.IsNullOrEmpty(_appName)) { _appName = s_defaultAppName; } // construct Uri UriBuilder uriBuilder = new UriBuilder(_scheme, _computerName, PortSetting, _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. CertificateThumbPrint /// /// All the above are mutually exclusive. /// /// /// If there is ambiguity as specified above. /// private void ValidateSpecifiedAuthentication() { if ((WSManAuthenticationMechanism != WSManAuthenticationMechanism.WSMAN_FLAG_DEFAULT_AUTHENTICATION) && (_thumbPrint != null)) { throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.NewRunspaceAmbiguousAuthentication, "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 = s_defaultAppName; ConstructUri(uri.Scheme, uri.Host, uri.Port, appname); } else { _connectionUri = uri; _scheme = uri.Scheme; _appName = uri.AbsolutePath; PortSetting = uri.Port; _computerName = uri.Host; UseDefaultWSManPort = false; } } #endregion Private Methods #region Private Members private string _scheme = HttpScheme; private string _computerName = DefaultComputerName; private string _appName = s_defaultAppName; 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 string _thumbPrint; private AuthenticationMechanism _proxyAuthentication; private PSCredential _proxyCredential; #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 s_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; set; } #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; #endregion #region Properties /// /// Script to run while starting the background process. /// public ScriptBlock InitializationScript { get; set; } /// /// On a 64bit machine, specifying true for this will launch a 32 bit process /// for the background process. /// public bool RunAs32 { get; set; } /// /// Powershell version to execute the job in /// public Version PSVersion { get; set; } internal PowerShellProcessInstance Process { get; set; } #endregion #region Overrides /// /// Name of the computer. Will always be "localhost" to signify local machine. /// public override string ComputerName { 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 signify that this is not supported. /// public override string CertificateThumbprint { get { return string.Empty; } set { throw new NotImplementedException(); } } public NewProcessConnectionInfo Copy() { NewProcessConnectionInfo result = new NewProcessConnectionInfo(_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) { _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 = _appDomainName; newCopy.OpenTimeout = this.OpenTimeout; return newCopy; } internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { return new NamedPipeClientSessionTransportManager( this, instanceId, cryptoHelper); } #endregion } /// /// Class used to create a connection through an SSH.exe client to a remote host machine. /// Connection information includes SSH target (user name and host machine) along with /// client key used for key based user authorization. /// public sealed class SSHConnectionInfo : RunspaceConnectionInfo { #region Properties /// /// User Name /// public string UserName { get; private set; } /// /// Key File Path /// private string KeyFilePath { get; set; } #endregion #region Constructors /// /// Constructor /// private SSHConnectionInfo() { } /// /// Constructor /// /// User Name /// Computer Name /// Key File Path public SSHConnectionInfo( string userName, string computerName, string keyFilePath) { if (userName == null) { throw new PSArgumentNullException("userName"); } if (computerName == null) { throw new PSArgumentNullException("computerName"); } this.UserName = userName; this.ComputerName = computerName; this.KeyFilePath = keyFilePath; } #endregion #region Overrides /// /// Computer is always localhost. /// public override string ComputerName { get; set; } /// /// Credential /// public override PSCredential Credential { get { return null; } set { throw new NotImplementedException(); } } /// /// Authentication /// public override AuthenticationMechanism AuthenticationMechanism { get { return AuthenticationMechanism.Default; } set { throw new NotImplementedException(); } } /// /// CertificateThumbprint /// public override string CertificateThumbprint { get { return string.Empty; } set { throw new NotImplementedException(); } } /// /// Shallow copy of current instance. /// /// NamedPipeConnectionInfo internal override RunspaceConnectionInfo InternalCopy() { SSHConnectionInfo newCopy = new SSHConnectionInfo(); newCopy.ComputerName = this.ComputerName; newCopy.UserName = this.UserName; newCopy.KeyFilePath = this.KeyFilePath; return newCopy; } /// /// CreateClientSessionTransportManager /// /// /// /// /// internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { return new SSHClientSessionTransportManager( this, instanceId, cryptoHelper); } #endregion #region Internal Methods /// /// StartSSHProcess /// /// internal System.Diagnostics.Process StartSSHProcess( out StreamWriter stdInWriterVar, out StreamReader stdOutReaderVar, out StreamReader stdErrReaderVar) { string filePath = string.Empty; #if !UNIX var context = Runspaces.LocalPipeline.GetExecutionContextFromTLS(); if (context != null) { var cmdInfo = context.CommandDiscovery.LookupCommandInfo("ssh.exe", CommandOrigin.Internal) as ApplicationInfo; if (cmdInfo != null) { filePath = cmdInfo.Path; } } #else filePath = @"ssh"; #endif // Extract an optional domain name if provided. string domainName = null; string userName = this.UserName; #if !UNIX var parts = this.UserName.Split(Utils.Separators.Backslash); if (parts.Length == 2) { domainName = parts[0]; userName = parts[1]; } #endif // Create client ssh process that hosts powershell.exe as a subsystem and is configured // to be in server mode for PSRP over SSHD: // powershell -Version 5.1 -sshs -NoLogo -NoProfile // See sshd_configuration file, subsystems section and it will have this entry: // Subsystem powershell C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Version 5.1 -sshs -NoLogo -NoProfile string arguments; if (!string.IsNullOrEmpty(this.KeyFilePath)) { if (!System.IO.File.Exists(this.KeyFilePath)) { throw new FileNotFoundException( StringUtil.Format(RemotingErrorIdStrings.KeyFileNotFound, this.KeyFilePath)); } arguments = (string.IsNullOrEmpty(domainName)) ? string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" {1}@{2} -s powershell", this.KeyFilePath, userName, this.ComputerName) : string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" -l {1}@{2} {3} -s powershell", this.KeyFilePath, userName, domainName, this.ComputerName); } else { arguments = (string.IsNullOrEmpty(domainName)) ? string.Format(CultureInfo.InvariantCulture, @"{0}@{1} -s powershell", userName, this.ComputerName) : string.Format(CultureInfo.InvariantCulture, @"-l {0}@{1} {2} -s powershell", userName, domainName, this.ComputerName); } System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo( filePath, arguments); startInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(filePath); startInfo.CreateNoWindow = true; startInfo.UseShellExecute = false; return StartSSHProcessImpl(startInfo, out stdInWriterVar, out stdOutReaderVar, out stdErrReaderVar); } #endregion #region SSH Process Creation #if UNIX /// /// Create a process through managed APIs and return StdIn, StdOut, StdError reader/writers /// This works for non-Windows platforms and is simpler. /// private static System.Diagnostics.Process StartSSHProcessImpl( System.Diagnostics.ProcessStartInfo startInfo, out StreamWriter stdInWriterVar, out StreamReader stdOutReaderVar, out StreamReader stdErrReaderVar) { startInfo.RedirectStandardInput = true; startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; System.Diagnostics.Process process = new Process(); process.StartInfo = startInfo; process.Start(); stdInWriterVar = process.StandardInput; stdOutReaderVar = process.StandardOutput; stdErrReaderVar = process.StandardError; return process; } #else /// /// Create a process through native Win32 APIs and return StdIn, StdOut, StdError reader/writers /// This needs to be done via Win32 APIs because managed code creates anonymous synchronous pipes /// for redirected StdIn/Out and SSH (and PSRP) require asynchronous (overlapped) pipes, which must /// be through named pipes. Managed code for named pipes is unreliable and so this is done via /// P-Invoking native APIs. /// private static System.Diagnostics.Process StartSSHProcessImpl( System.Diagnostics.ProcessStartInfo startInfo, out StreamWriter stdInWriterVar, out StreamReader stdOutReaderVar, out StreamReader stdErrReaderVar) { Exception ex = null; System.Diagnostics.Process sshProcess = null; // // These std pipe handles are bound to managed Reader/Writer objects and returned to the transport // manager object, which uses them for PSRP communication. The lifetime of these handles are then // tied to the reader/writer objects which the transport is responsible for disposing (see // SSHClientSessionTransportManger and the CloseConnection() method. // SafePipeHandle stdInPipeServer = null; SafePipeHandle stdOutPipeServer = null; SafePipeHandle stdErrPipeServer = null; try { sshProcess = CreateProcessWithRedirectedStd( startInfo, out stdInPipeServer, out stdOutPipeServer, out stdErrPipeServer); } catch (InvalidOperationException e) { ex = e; } catch (ArgumentException e) { ex = e; } catch (FileNotFoundException e) { ex = e; } catch (System.ComponentModel.Win32Exception e) { ex = e; } if ((ex != null) || (sshProcess == null) || (sshProcess.HasExited == true)) { throw new InvalidOperationException( StringUtil.Format(RemotingErrorIdStrings.CannotStartSSHClient, (ex != null) ? ex.Message : string.Empty), ex); } // Create the std in writer/readers needed for communication with ssh.exe. stdInWriterVar = null; stdOutReaderVar = null; stdErrReaderVar = null; try { stdInWriterVar = new StreamWriter(new NamedPipeServerStream(PipeDirection.Out, true, true, stdInPipeServer)); stdOutReaderVar = new StreamReader(new NamedPipeServerStream(PipeDirection.In, true, true, stdOutPipeServer)); stdErrReaderVar = new StreamReader(new NamedPipeServerStream(PipeDirection.In, true, true, stdErrPipeServer)); } catch (Exception e) { CommandProcessorBase.CheckForSevereException(e); if (stdInWriterVar != null) { stdInWriterVar.Dispose(); } else { stdInPipeServer.Dispose(); } if (stdOutReaderVar != null) { stdInWriterVar.Dispose(); } else { stdOutPipeServer.Dispose(); } if (stdErrReaderVar != null) { stdInWriterVar.Dispose(); } else { stdErrPipeServer.Dispose(); } throw; } return sshProcess; } /// /// CreateProcessWithRedirectedStd /// private static Process CreateProcessWithRedirectedStd( ProcessStartInfo startInfo, out SafePipeHandle stdInPipeServer, out SafePipeHandle stdOutPipeServer, out SafePipeHandle stdErrPipeServer) { // // Create named (async) pipes for reading/writing to std. // stdInPipeServer = null; stdOutPipeServer = null; stdErrPipeServer = null; SafePipeHandle stdInPipeClient = null; SafePipeHandle stdOutPipeClient = null; SafePipeHandle stdErrPipeClient = null; string randomName = System.IO.Path.GetFileNameWithoutExtension(System.IO.Path.GetRandomFileName()); try { // Get default pipe security (Admin and current user access) var securityDesc = RemoteSessionNamedPipeServer.GetServerPipeSecurity(); var stdInPipeName = @"\\.\pipe\StdIn" + randomName; stdInPipeServer = CreateNamedPipe(stdInPipeName, securityDesc); stdInPipeClient = GetNamedPipeHandle(stdInPipeName); var stdOutPipeName = @"\\.\pipe\StdOut" + randomName; stdOutPipeServer = CreateNamedPipe(stdOutPipeName, securityDesc); stdOutPipeClient = GetNamedPipeHandle(stdOutPipeName); var stdErrPipeName = @"\\.\pipe\StdErr" + randomName; stdErrPipeServer = CreateNamedPipe(stdErrPipeName, securityDesc); stdErrPipeClient = GetNamedPipeHandle(stdErrPipeName); } catch (Exception e) { CommandProcessorBase.CheckForSevereException(e); if (stdInPipeServer != null) { stdInPipeServer.Dispose(); } if (stdInPipeClient != null) { stdInPipeClient.Dispose(); } if (stdOutPipeServer != null) { stdOutPipeServer.Dispose(); } if (stdOutPipeClient != null) { stdOutPipeClient.Dispose(); } if (stdErrPipeServer != null) { stdErrPipeServer.Dispose(); } if (stdErrPipeClient != null) { stdErrPipeClient.Dispose(); } throw; } // Create process PlatformInvokes.STARTUPINFO lpStartupInfo = new PlatformInvokes.STARTUPINFO(); PlatformInvokes.PROCESS_INFORMATION lpProcessInformation = new PlatformInvokes.PROCESS_INFORMATION(); int creationFlags = 0; try { var cmdLine = String.Format(CultureInfo.InvariantCulture, @"""{0}"" {1}", startInfo.FileName, startInfo.Arguments); lpStartupInfo.hStdInput = new SafeFileHandle(stdInPipeClient.DangerousGetHandle(), false); lpStartupInfo.hStdOutput = new SafeFileHandle(stdOutPipeClient.DangerousGetHandle(), false); lpStartupInfo.hStdError = new SafeFileHandle(stdErrPipeClient.DangerousGetHandle(), false); lpStartupInfo.dwFlags = 0x100; // No new window: Inherit the parent process's console window creationFlags = 0x00000000; // Create the new process suspended so we have a chance to get a corresponding Process object in case it terminates quickly. creationFlags |= 0x00000004; PlatformInvokes.SECURITY_ATTRIBUTES lpProcessAttributes = new PlatformInvokes.SECURITY_ATTRIBUTES(); PlatformInvokes.SECURITY_ATTRIBUTES lpThreadAttributes = new PlatformInvokes.SECURITY_ATTRIBUTES(); bool success = PlatformInvokes.CreateProcess( null, cmdLine, lpProcessAttributes, lpThreadAttributes, true, creationFlags, IntPtr.Zero, startInfo.WorkingDirectory, lpStartupInfo, lpProcessInformation); if (!success) { throw new Win32Exception(Marshal.GetLastWin32Error()); } // At this point, we should have a suspended process. Get the .Net Process object, resume the process, and return. Process result = Process.GetProcessById(lpProcessInformation.dwProcessId); PlatformInvokes.ResumeThread(lpProcessInformation.hThread); return result; } catch (Exception e) { CommandProcessorBase.CheckForSevereException(e); if (stdInPipeServer != null) { stdInPipeServer.Dispose(); } if (stdInPipeClient != null) { stdInPipeClient.Dispose(); } if (stdOutPipeServer != null) { stdOutPipeServer.Dispose(); } if (stdOutPipeClient != null) { stdOutPipeClient.Dispose(); } if (stdErrPipeServer != null) { stdErrPipeServer.Dispose(); } if (stdErrPipeClient != null) { stdErrPipeClient.Dispose(); } throw; } finally { lpProcessInformation.Dispose(); } } private static SafePipeHandle GetNamedPipeHandle(string pipeName) { // Create pipe flags for asynchronous pipes. uint pipeFlags = NamedPipeNative.FILE_FLAG_OVERLAPPED; // We want an inheritable handle. PlatformInvokes.SECURITY_ATTRIBUTES securityAttributes = new PlatformInvokes.SECURITY_ATTRIBUTES(); // Get handle to pipe. var fileHandle = PlatformInvokes.CreateFileW( pipeName, NamedPipeNative.GENERIC_READ | NamedPipeNative.GENERIC_WRITE, 0, securityAttributes, NamedPipeNative.OPEN_EXISTING, pipeFlags, IntPtr.Zero); int lastError = Marshal.GetLastWin32Error(); if (fileHandle == PlatformInvokes.INVALID_HANDLE_VALUE) { throw new System.ComponentModel.Win32Exception(lastError); } return new SafePipeHandle(fileHandle, true); } private static SafePipeHandle CreateNamedPipe( string pipeName, CommonSecurityDescriptor securityDesc) { // Create optional security attributes based on provided PipeSecurity. NamedPipeNative.SECURITY_ATTRIBUTES securityAttributes = null; GCHandle? securityDescHandle = null; if (securityDesc != null) { byte[] securityDescBuffer = new byte[securityDesc.BinaryLength]; securityDesc.GetBinaryForm(securityDescBuffer, 0); securityDescHandle = GCHandle.Alloc(securityDescBuffer, GCHandleType.Pinned); securityAttributes = NamedPipeNative.GetSecurityAttributes(securityDescHandle.Value, true); ; } // Create async named pipe. SafePipeHandle pipeHandle = NamedPipeNative.CreateNamedPipe( pipeName, NamedPipeNative.PIPE_ACCESS_DUPLEX | NamedPipeNative.FILE_FLAG_FIRST_PIPE_INSTANCE | NamedPipeNative.FILE_FLAG_OVERLAPPED, NamedPipeNative.PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_READMODE_MESSAGE, 1, 32768, 32768, 0, securityAttributes); int lastError = Marshal.GetLastWin32Error(); if (securityDescHandle != null) { securityDescHandle.Value.Free(); } if (pipeHandle.IsInvalid) { throw new Win32Exception(lastError); } return pipeHandle; } #endif #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. */ #endregion #region Properties /// /// GUID of the target VM. /// public Guid VMGuid { get; set; } /// /// Configuration name of the VM session. /// public string ConfigurationName { get; set; } #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 signify 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; set; } 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 PSCredential _credential; private const int _defaultOpenTimeout = 20000; /* 20 seconds. */ #endregion #region Properties /// /// ContainerProcess class instance. /// internal ContainerProcess ContainerProc { get; set; } #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 signify 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.ContainerId; } 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 CreateContainerConnectionInfo( string containerId, bool runAsAdmin, string configurationName) { ContainerProcess containerProc = new ContainerProcess(containerId, null, 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 const uint NoError = 0; private const uint InvalidContainerId = 1; private const uint ContainersFeatureNotEnabled = 2; 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; set; } /// /// OB root of the container. /// public string ContainerObRoot { get; set; } /// /// ID of the container. /// public string ContainerId { get; set; } /// /// Process ID of the process created in container. /// internal int ProcessId { get; set; } /// /// 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; set; } = false; /// /// The configuration name of the container session. /// internal string ConfigurationName { get; set; } /// /// Whether the process in container has terminated. /// internal bool ProcessTerminated { get; set; } = false; /// /// The error code. /// internal uint ErrorCode { get; set; } = 0; /// /// The error message for other errors. /// internal string ErrorMessage { get; set; } = string.Empty; #endregion #region Native HCS (i.e., host compute service) methods [StructLayout(LayoutKind.Sequential)] internal struct HCS_PROCESS_INFORMATION { /// /// The process id. /// public uint ProcessId; /// /// Reserved. /// public uint Reserved; /// /// If created, standard input handle of the process. /// public IntPtr StdInput; /// /// If created, standard output handle of the process. /// public IntPtr StdOutput; /// /// If created, standard error handle of the process. /// public IntPtr StdError; } [DllImport(PinvokeDllNames.CreateProcessInComputeSystemDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern uint HcsOpenComputeSystem( string id, ref IntPtr computeSystem, ref string result); [DllImport(PinvokeDllNames.CreateProcessInComputeSystemDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern uint HcsGetComputeSystemProperties( IntPtr computeSystem, string propertyQuery, ref string properties, ref string result); [DllImport(PinvokeDllNames.CreateProcessInComputeSystemDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern uint HcsCreateProcess( IntPtr computeSystem, string processParameters, ref HCS_PROCESS_INFORMATION processInformation, ref IntPtr process, ref string result); [DllImport(PinvokeDllNames.CreateProcessInComputeSystemDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern uint HcsOpenProcess( IntPtr computeSystem, int processId, ref IntPtr process, ref string result); [DllImport(PinvokeDllNames.CreateProcessInComputeSystemDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern uint HcsTerminateProcess( IntPtr process, ref string result); #endregion #region Constructors /// /// Creates an instance used for PowerShell Direct for container. /// public ContainerProcess(string containerId, string containerObRoot, int processId, bool runAsAdmin, string configurationName) { this.ContainerId = containerId; this.ContainerObRoot = containerObRoot; this.ProcessId = processId; this.RunAsAdmin = runAsAdmin; this.ConfigurationName = configurationName; Dbg.Assert(!String.IsNullOrEmpty(containerId), "containerId input cannot be empty."); GetContainerProperties(); } #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 object root based on given container id. /// public void GetContainerProperties() { RunOnMTAThread(GetContainerPropertiesInternal); // // Report error. // switch (ErrorCode) { case NoError: break; case ContainersFeatureNotEnabled: throw new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.ContainersFeatureNotEnabled)); 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 { IntPtr ComputeSystem = IntPtr.Zero; string resultString = String.Empty; result = HcsOpenComputeSystem(ContainerId, ref ComputeSystem, ref resultString); if (result != 0) { 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"); HCS_PROCESS_INFORMATION ProcessInformation = new HCS_PROCESS_INFORMATION(); IntPtr Process = IntPtr.Zero; // // Create PowerShell process inside the container. // result = HcsCreateProcess(ComputeSystem, cmd, ref ProcessInformation, ref Process, ref resultString); if (result != 0) { processId = 0; error = result; } else { processId = Convert.ToInt32(ProcessInformation.ProcessId); } } } 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() { IntPtr ComputeSystem = IntPtr.Zero; string resultString = String.Empty; IntPtr process = IntPtr.Zero; ProcessTerminated = false; if (HcsOpenComputeSystem(ContainerId, ref ComputeSystem, ref resultString) == 0) { if (HcsOpenProcess(ComputeSystem, ProcessId, ref process, ref resultString) == 0) { if (HcsTerminateProcess(process, ref resultString) == 0) { ProcessTerminated = true; } } } } /// /// Get object root based on given container id. /// private void GetContainerPropertiesInternal() { try { IntPtr ComputeSystem = IntPtr.Zero; string resultString = String.Empty; if (HcsOpenComputeSystem(ContainerId, ref ComputeSystem, ref resultString) == 0) { Type computeSystemPropertiesType; Type hostComputeInteropType; GetHostComputeInteropTypes(out computeSystemPropertiesType, out hostComputeInteropType); MethodInfo getComputeSystemPropertiesInfo = hostComputeInteropType.GetMethod("HcsGetComputeSystemProperties"); var computeSystemPropertiesHandle = getComputeSystemPropertiesInfo.Invoke(null, new object[] { ComputeSystem }); RuntimeId = (Guid)computeSystemPropertiesType.GetProperty("RuntimeId").GetValue(computeSystemPropertiesHandle); // // Get container object root for Windows Server container. // if (RuntimeId == Guid.Empty) { ContainerObRoot = (string)computeSystemPropertiesType.GetProperty("ObRoot").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); } } } /// /// 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 } }