diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
index 02b0a2c03..d5c9e843b 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
@@ -155,6 +155,11 @@ namespace Microsoft.PowerShell
get { return _namedPipeServerMode; }
}
+ internal bool SSHServerMode
+ {
+ get { return _sshServerMode; }
+ }
+
internal bool ServerMode
{
get
@@ -394,6 +399,10 @@ namespace Microsoft.PowerShell
{
_namedPipeServerMode = true;
}
+ else if (MatchSwitch(switchKey, "sshservermode", "sshs"))
+ {
+ _sshServerMode = true;
+ }
else if (MatchSwitch(switchKey, "configurationname", "config"))
{
++i;
@@ -935,6 +944,7 @@ namespace Microsoft.PowerShell
private bool _socketServerMode;
private bool _serverMode;
private bool _namedPipeServerMode;
+ private bool _sshServerMode;
private string _configurationName;
private ConsoleHost _parent;
private ConsoleHostUserInterface _ui;
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
index da0df1159..4c4d8f57f 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
@@ -249,6 +249,12 @@ namespace Microsoft.PowerShell
s_cpp.ConfigurationName);
exitCode = 0;
}
+ else if (s_cpp.SSHServerMode)
+ {
+ ClrFacade.StartProfileOptimization("StartupProfileData-SSHServerMode");
+ System.Management.Automation.Remoting.Server.SSHProcessMediator.Run(s_cpp.InitialCommand);
+ exitCode = 0;
+ }
else if (s_cpp.SocketServerMode)
{
ClrFacade.StartProfileOptimization("StartupProfileData-SocketServerMode");
diff --git a/src/System.Management.Automation/engine/hostifaces/Connection.cs b/src/System.Management.Automation/engine/hostifaces/Connection.cs
index 15dbd3fce..5a972e73b 100644
--- a/src/System.Management.Automation/engine/hostifaces/Connection.cs
+++ b/src/System.Management.Automation/engine/hostifaces/Connection.cs
@@ -436,7 +436,12 @@ namespace System.Management.Automation.Runspaces
///
/// Runspace is based on a VM socket transport.
///
- VMSocketTransport = 0x4
+ VMSocketTransport = 0x4,
+
+ ///
+ /// Runspace is based on SSH transport.
+ ///
+ SSHTransport = 0x8
}
#endregion
diff --git a/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs b/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs
index d2672445b..c40b6c674 100644
--- a/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs
+++ b/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs
@@ -663,6 +663,7 @@ namespace System.Management.Automation.Runspaces
if ((!(connectionInfo is WSManConnectionInfo)) &&
(!(connectionInfo is NewProcessConnectionInfo)) &&
(!(connectionInfo is NamedPipeConnectionInfo)) &&
+ (!(connectionInfo is SSHConnectionInfo)) &&
(!(connectionInfo is VMConnectionInfo)) &&
(!(connectionInfo is ContainerConnectionInfo)))
{
diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs
index 5a35801ce..715c216e3 100644
--- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs
+++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs
@@ -974,6 +974,10 @@ namespace System.Management.Automation
{
returnCaps |= RunspaceCapability.VMSocketTransport;
}
+ else if (_connectionInfo is SSHConnectionInfo)
+ {
+ returnCaps |= RunspaceCapability.SSHTransport;
+ }
else
{
ContainerConnectionInfo containerConnectionInfo = _connectionInfo as ContainerConnectionInfo;
diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs
index d81dcd65e..688f370d0 100644
--- a/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs
+++ b/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs
@@ -263,7 +263,7 @@ namespace System.Management.Automation.Runspaces
}
else
{
- Name = AutoGenerateRunspaceName();
+ Name = AutoGenerateRunspaceName(Id);
remoteRunspace.PSSessionName = Name;
}
@@ -298,6 +298,15 @@ namespace System.Management.Automation.Runspaces
return;
}
+ // SSH session
+ SSHConnectionInfo sshConnectionInfo = remoteRunspace.ConnectionInfo as SSHConnectionInfo;
+ if (sshConnectionInfo != null)
+ {
+ ComputerType = TargetMachineType.RemoteMachine;
+ ConfigurationName = "DefaultShell";
+ return;
+ }
+
// We only support WSMan/VM/Container sessions now.
Dbg.Assert(false, "Invalid Runspace");
}
@@ -310,9 +319,35 @@ namespace System.Management.Automation.Runspaces
/// Generates and returns the runspace name
///
/// auto generated name
- private String AutoGenerateRunspaceName()
+ private string AutoGenerateRunspaceName(int id)
{
- return "Session" + Id.ToString(System.Globalization.NumberFormatInfo.InvariantInfo);
+ string sessionIdStr = id.ToString(System.Globalization.NumberFormatInfo.InvariantInfo);
+
+ if (_remoteRunspace.ConnectionInfo is WSManConnectionInfo)
+ {
+ return "WinRM" + sessionIdStr;
+ }
+ else if (_remoteRunspace.ConnectionInfo is SSHConnectionInfo)
+ {
+ return "SSH" + sessionIdStr;
+ }
+ else if ((_remoteRunspace.ConnectionInfo is NamedPipeConnectionInfo) ||
+ (_remoteRunspace.ConnectionInfo is ContainerConnectionInfo))
+ {
+ return "NamedPipe" + sessionIdStr;
+ }
+ else if (_remoteRunspace.ConnectionInfo is NewProcessConnectionInfo)
+ {
+ return "Process" + sessionIdStr;
+ }
+ else if (_remoteRunspace.ConnectionInfo is VMConnectionInfo)
+ {
+ return "Socket" + sessionIdStr;
+ }
+ else
+ {
+ return "Session" + sessionIdStr;
+ }
}
///
@@ -360,7 +395,7 @@ namespace System.Management.Automation.Runspaces
/// Runspace name
internal static string ComposeRunspaceName(int id)
{
- return "Session" + id.ToString(System.Globalization.NumberFormatInfo.InvariantInfo);
+ return "WinRM" + id.ToString(System.Globalization.NumberFormatInfo.InvariantInfo);
}
#endregion
diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs
index 7bd3f13af..01f8ad113 100644
--- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs
+++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs
@@ -386,6 +386,8 @@ namespace Microsoft.PowerShell.Commands
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)]
+ [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]
+ [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)]
public SwitchParameter AsJob
{
get
@@ -443,6 +445,8 @@ namespace Microsoft.PowerShell.Commands
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)]
+ [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]
+ [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)]
[Alias("HCN")]
public SwitchParameter HideComputerName
{
@@ -505,6 +509,9 @@ namespace Microsoft.PowerShell.Commands
[Parameter(Position = 1,
Mandatory = true,
ParameterSetName = InvokeCommandCommand.ContainerIdParameterSet)]
+ [Parameter(Position = 1,
+ Mandatory = true,
+ ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]
[ValidateNotNull]
[Alias("Command")]
public override ScriptBlock ScriptBlock
@@ -548,6 +555,9 @@ namespace Microsoft.PowerShell.Commands
[Parameter(Position = 1,
Mandatory = true,
ParameterSetName = FilePathContainerIdParameterSet)]
+ [Parameter(Position = 1,
+ Mandatory = true,
+ ParameterSetName = FilePathSSHHostParameterSet)]
[ValidateNotNull]
[Alias("PSPath")]
public override string FilePath
@@ -649,6 +659,49 @@ namespace Microsoft.PowerShell.Commands
set { base.RunAsAdministrator = value; }
}
+ #region SSH Parameters
+
+ ///
+ /// Host Name
+ ///
+ [ValidateNotNullOrEmpty()]
+ [Parameter(Position = 0, Mandatory = true, ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]
+ [Parameter(Position = 0, Mandatory = true, ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)]
+ public override string HostName
+ {
+ get { return base.HostName; }
+
+ set { base.HostName = value; }
+ }
+
+ ///
+ /// User Name
+ ///
+ [Parameter(Mandatory = true, ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]
+ [Parameter(Mandatory = true, ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)]
+ [ValidateNotNullOrEmpty()]
+ public override string UserName
+ {
+ get { return base.UserName; }
+
+ set { base.UserName = value; }
+ }
+
+ ///
+ /// Key Path
+ ///
+ [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]
+ [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)]
+ [ValidateNotNullOrEmpty()]
+ public override string KeyPath
+ {
+ get { return base.KeyPath; }
+
+ set { base.KeyPath = value; }
+ }
+
+ #endregion
+
#endregion Parameters
#region Overrides
@@ -929,6 +982,18 @@ namespace Microsoft.PowerShell.Commands
}
break;
+ case InvokeCommandCommand.SSHHostParameterSet:
+ case InvokeCommandCommand.FilePathSSHHostParameterSet:
+ {
+ var job = new PSRemotingJob(new string[] { this.HostName }, Operations,
+ ScriptBlock.ToString(), ThrottleLimit, _name);
+ job.PSJobTypeName = RemoteJobType;
+ job.HideComputerName = _hideComputerName;
+ this.JobRepository.Add(job);
+ WriteObject(job);
+ }
+ break;
+
case InvokeCommandCommand.SessionParameterSet:
case InvokeCommandCommand.FilePathSessionParameterSet:
{
diff --git a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs
index 51434b42c..166ed55fb 100644
--- a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs
+++ b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs
@@ -176,6 +176,11 @@ namespace Microsoft.PowerShell.Commands
///
protected const string VMNameParameterSet = "VMName";
+ ///
+ /// SSH host parameter set
+ ///
+ protected const string SSHHostParameterSet = "SSHHost";
+
///
/// runspace parameter set
///
@@ -673,6 +678,43 @@ namespace Microsoft.PowerShell.Commands
}
private string _thumbPrint = null;
+ #region SSHHostParameters
+
+ ///
+ /// SSH Target Host Name
+ ///
+ [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
+ [ValidateNotNullOrEmpty()]
+ public virtual string HostName
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// SSH User Name
+ ///
+ [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
+ [ValidateNotNullOrEmpty()]
+ public virtual string UserName
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// SSH Key Path
+ ///
+ [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
+ [ValidateNotNullOrEmpty()]
+ public virtual string KeyPath
+ {
+ get;
+ set;
+ }
+
+ #endregion
+
#endregion Properties
#region Internal Static Methods
@@ -865,6 +907,11 @@ namespace Microsoft.PowerShell.Commands
///
protected const string FilePathContainerIdParameterSet = "FilePathContainerId";
+ ///
+ /// SSH Host file path parameter set.
+ ///
+ protected const string FilePathSSHHostParameterSet = "FilePathSSHHost";
+
#endregion
#region Parameters
@@ -1113,6 +1160,21 @@ namespace Microsoft.PowerShell.Commands
}
}// CreateHelpersForSpecifiedComputerNames
+ ///
+ /// Creates helper objects for host names for PSRP over SSH
+ /// remoting.
+ ///
+ protected void CreateHelpersForSpecifiedHostNames()
+ {
+ var sshConnectionInfo = new SSHConnectionInfo(this.UserName, this.HostName, this.KeyPath);
+ var typeTable = TypeTable.LoadDefaultTypeFiles();
+ var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace;
+ var pipeline = CreatePipeline(remoteRunspace);
+
+ var operation = new ExecutionCmdletHelperComputerName(remoteRunspace, pipeline);
+ Operations.Add(operation);
+ }
+
///
/// Creates helper objects with the specified command for
/// the specified remote runspaceinfo objects
@@ -1720,6 +1782,11 @@ namespace Microsoft.PowerShell.Commands
}
break;
+ case PSExecutionCmdlet.SSHHostParameterSet:
+ case PSExecutionCmdlet.FilePathSSHHostParameterSet:
+ CreateHelpersForSpecifiedHostNames();
+ break;
+
case PSExecutionCmdlet.FilePathSessionParameterSet:
case PSExecutionCmdlet.SessionParameterSet:
{
diff --git a/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs b/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs
index 3aa13ab9e..da9e7afcc 100644
--- a/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs
+++ b/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs
@@ -297,6 +297,10 @@ namespace Microsoft.PowerShell.Commands
case ContainerIdParameterSet:
remoteRunspace = GetRunspaceForContainerSession();
break;
+
+ case SSHHostParameterSet:
+ remoteRunspace = GetRunspaceForSSHSession();
+ break;
}
// If runspace is null then the error record has already been written and we can exit.
@@ -1239,6 +1243,20 @@ namespace Microsoft.PowerShell.Commands
return remoteRunspace;
}
+ ///
+ /// Create remote runspace for SSH session
+ ///
+ private RemoteRunspace GetRunspaceForSSHSession()
+ {
+ var sshConnectionInfo = new SSHConnectionInfo(this.UserName, this.HostName, this.KeyPath);
+ var typeTable = TypeTable.LoadDefaultTypeFiles();
+ var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace;
+ remoteRunspace.Open();
+ remoteRunspace.ShouldCloseOnPop = true;
+
+ return remoteRunspace;
+ }
+
#endregion
#region Internal Methods
diff --git a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs
index 9edd6956b..2abe99ef7 100644
--- a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs
+++ b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs
@@ -238,6 +238,12 @@ namespace Microsoft.PowerShell.Commands
}
break;
+ case NewPSSessionCommand.SSHHostParameterSet:
+ {
+ remoteRunspaces = CreateRunspacesForSSHHostParameterSet();
+ }
+ break;
+
default:
{
Dbg.Assert(false, "Missing paramenter set in switch statement");
@@ -1049,6 +1055,23 @@ namespace Microsoft.PowerShell.Commands
return remoteRunspaces;
}// CreateRunspacesWhenContainerParameterSpecified
+ ///
+ /// CreateRunspacesForSSHHostParameterSet
+ ///
+ ///
+ private List CreateRunspacesForSSHHostParameterSet()
+ {
+ var remoteRunspaces = new List();
+ var sshConnectionInfo = new SSHConnectionInfo(
+ this.UserName,
+ this.HostName,
+ this.KeyPath);
+ var typeTable = TypeTable.LoadDefaultTypeFiles();
+ remoteRunspaces.Add(RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace);
+
+ return remoteRunspaces;
+ }
+
///
/// Helper method to either get a user supplied runspace/session name
/// or to generate one along with a unique Id.
diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs
index 142017f98..88c606f31 100644
--- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs
+++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs
@@ -222,10 +222,10 @@ namespace System.Management.Automation.Remoting
uint nDefaultTimeOut,
SECURITY_ATTRIBUTES securityAttributes);
- internal static SECURITY_ATTRIBUTES GetSecurityAttributes(GCHandle securityDescriptorPinnedHandle)
+ internal static SECURITY_ATTRIBUTES GetSecurityAttributes(GCHandle securityDescriptorPinnedHandle, bool inheritHandle = false)
{
SECURITY_ATTRIBUTES securityAttributes = new NamedPipeNative.SECURITY_ATTRIBUTES();
- securityAttributes.InheritHandle = false;
+ securityAttributes.InheritHandle = inheritHandle;
securityAttributes.NLength = (int)Marshal.SizeOf(securityAttributes);
securityAttributes.LPSecurityDescriptor = securityDescriptorPinnedHandle.AddrOfPinnedObject();
return securityAttributes;
@@ -590,7 +590,7 @@ namespace System.Management.Automation.Remoting
#region Private Methods
- private static CommonSecurityDescriptor GetServerPipeSecurity()
+ internal static CommonSecurityDescriptor GetServerPipeSecurity()
{
// Built-in Admin SID
SecurityIdentifier adminSID = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
diff --git a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs
index ace6255ee..8c3ce5ad5 100644
--- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs
+++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs
@@ -4,9 +4,12 @@ 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;
@@ -14,6 +17,8 @@ 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;
@@ -1555,7 +1560,6 @@ namespace System.Management.Automation.Runspaces
///
public override string ComputerName
{
- // TODO: should this be different
get { return "localhost"; }
set { throw new NotImplementedException(); }
}
@@ -1821,6 +1825,487 @@ namespace System.Management.Automation.Runspaces
#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 Path
+ ///
+ private string KeyPath
+ {
+ get;
+ set;
+ }
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Constructor
+ ///
+ private SSHConnectionInfo()
+ { }
+
+ ///
+ /// Constructor
+ ///
+ /// User Name
+ /// Computer Name
+ /// Key Path
+ public SSHConnectionInfo(
+ string userName,
+ string computerName,
+ string keyPath)
+ {
+ if (userName == null) { throw new PSArgumentNullException("userName"); }
+ if (computerName == null) { throw new PSArgumentNullException("computerName"); }
+
+ this.UserName = userName;
+ this.ComputerName = computerName;
+ this.KeyPath = keyPath;
+ }
+
+ #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.KeyPath = this.KeyPath;
+
+ 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.KeyPath))
+ {
+ arguments = (string.IsNullOrEmpty(domainName)) ?
+ string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" {1}@{2} -s powershell", this.KeyPath, userName, this.ComputerName) :
+ string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" -l {1}@{2} {3} -s powershell", this.KeyPath, 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 asynchrous (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(RemotingErrorIdStrings.CannotStartSSHClient, 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.
diff --git a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs
index 9ce961d9b..cad472de9 100644
--- a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs
+++ b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs
@@ -424,6 +424,7 @@ namespace System.Management.Automation.Remoting
lock (_syncObject)
{
_writer.WriteLine(data);
+ _writer.Flush();
}
}
@@ -1403,6 +1404,174 @@ namespace System.Management.Automation.Remoting.Client
#endregion
}
+ internal sealed class SSHClientSessionTransportManager : OutOfProcessClientSessionTransportManagerBase
+ {
+ #region Data
+
+ private SSHConnectionInfo _connectionInfo;
+ private Process _sshProcess;
+ private StreamWriter _stdInWriter;
+ private StreamReader _stdOutReader;
+ private StreamReader _stdErrReader;
+ private const string _threadName = "SSHTransport Reader Thread";
+
+ #endregion
+
+ #region Constructors
+
+ internal SSHClientSessionTransportManager(
+ SSHConnectionInfo connectionInfo,
+ Guid runspaceId,
+ PSRemotingCryptoHelper cryptoHelper)
+ : base(runspaceId, cryptoHelper)
+ {
+ if (connectionInfo == null) { throw new PSArgumentException("connectionInfo"); }
+
+ _connectionInfo = connectionInfo;
+ }
+
+ #endregion
+
+ #region Overrides
+
+ internal override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (isDisposing)
+ {
+ CloseConnection();
+ }
+ }
+
+ protected override void CleanupConnection()
+ {
+ CloseConnection();
+ }
+
+ ///
+ /// Create an SSH connection to the target host and set up
+ /// transport reader/writer.
+ ///
+ internal override void CreateAsync()
+ {
+ // Create the ssh client process with connection to host target.
+ _sshProcess = _connectionInfo.StartSSHProcess(
+ out _stdInWriter,
+ out _stdOutReader,
+ out _stdErrReader);
+
+ _sshProcess.Exited += (sender, args) =>
+ {
+ CloseConnection();
+ };
+
+ // Create writer for named pipe.
+ stdInWriter = new OutOfProcessTextWriter(_stdInWriter);
+
+ // Create reader thread and send first PSRP message.
+ StartReaderThread(_stdOutReader);
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void CloseConnection()
+ {
+ var stdInWriter = _stdInWriter;
+ if (stdInWriter != null) { stdInWriter.Dispose(); }
+
+ var stdOutReader = _stdOutReader;
+ if (stdOutReader != null) { stdOutReader.Dispose(); }
+
+ var stdErrReader = _stdErrReader;
+ if (stdErrReader != null) { stdErrReader.Dispose(); }
+
+ var sshProcess = _sshProcess;
+ if ((sshProcess != null) && !sshProcess.HasExited)
+ {
+ _sshProcess = null;
+ try
+ {
+ sshProcess.Kill();
+ }
+ catch (InvalidOperationException) { }
+ catch (NotSupportedException) { }
+ catch (System.ComponentModel.Win32Exception) { }
+ }
+ }
+
+ private void StartReaderThread(
+ StreamReader reader)
+ {
+ Thread readerThread = new Thread(ProcessReaderThread);
+ readerThread.Name = _threadName;
+ readerThread.IsBackground = true;
+ readerThread.Start(reader);
+ }
+
+ private void ProcessReaderThread(object state)
+ {
+ try
+ {
+ StreamReader reader = state as StreamReader;
+ Dbg.Assert(reader != null, "Reader cannot be null.");
+
+ // Send one fragment.
+ SendOneItem();
+
+ // Start reader loop.
+ while (true)
+ {
+ string data = reader.ReadLine();
+ if (data == null)
+ {
+ // End of stream indicates the target process was lost.
+ // Raise transport exception to invalidate the client remote runspace.
+ PSRemotingTransportException psrte = new PSRemotingTransportException(
+ PSRemotingErrorId.IPCServerProcessReportedError,
+ RemotingErrorIdStrings.IPCServerProcessReportedError,
+ RemotingErrorIdStrings.NamedPipeTransportProcessEnded);
+ RaiseErrorHandler(new TransportErrorOccuredEventArgs(psrte, TransportMethodEnum.ReceiveShellOutputEx));
+ break;
+ }
+
+ if (data.StartsWith(System.Management.Automation.Remoting.Server.NamedPipeErrorTextWriter.ErrorPrepend, StringComparison.OrdinalIgnoreCase))
+ {
+ // Error message from the server.
+ string errorData = data.Substring(System.Management.Automation.Remoting.Server.NamedPipeErrorTextWriter.ErrorPrepend.Length);
+ HandleErrorDataReceived(errorData);
+ }
+ else
+ {
+ // Normal output data.
+ HandleOutputDataReceived(data);
+ }
+ }
+ }
+ catch (ObjectDisposedException)
+ {
+ // Normal reader thread end.
+ }
+ catch (Exception e)
+ {
+ CommandProcessorBase.CheckForSevereException(e);
+
+ if (e is ArgumentOutOfRangeException)
+ {
+ Dbg.Assert(false, "Need to adjust transport fragmentor to accomodate read buffer size.");
+ }
+
+ string errorMsg = (e.Message != null) ? e.Message : string.Empty;
+ _tracer.WriteMessage("SSHClientSessionTransportManager", "StartReaderThread", Guid.Empty,
+ "Transport manager reader thread ended with error: {0}", errorMsg);
+ }
+ }
+
+ #endregion
+ }
+
internal abstract class NamedPipeClientSessionTransportManagerBase : OutOfProcessClientSessionTransportManagerBase
{
#region Data
@@ -1979,8 +2148,8 @@ namespace System.Management.Automation.Remoting.Server
#region Constructors
- internal OutOfProcessServerSessionTransportManager(OutOfProcessTextWriter outWriter, OutOfProcessTextWriter errWriter)
- : base(BaseTransportManager.DefaultFragmentSize, new PSRemotingCryptoHelperServer())
+ internal OutOfProcessServerSessionTransportManager(OutOfProcessTextWriter outWriter, OutOfProcessTextWriter errWriter, PSRemotingCryptoHelperServer cryptoHelper)
+ : base(BaseTransportManager.DefaultFragmentSize, cryptoHelper)
{
Dbg.Assert(null != outWriter, "outWriter cannot be null.");
Dbg.Assert(null != errWriter, "errWriter cannot be null.");
diff --git a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs
index ac0a9fe94..1d19a2f1f 100644
--- a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs
+++ b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs
@@ -7,6 +7,7 @@ using System.IO;
using System.Threading;
using System.Security.Principal;
using System.Management.Automation.Internal;
+using Microsoft.Win32.SafeHandles;
using Dbg = System.Management.Automation.Diagnostics;
namespace System.Management.Automation.Remoting.Server
@@ -296,14 +297,21 @@ namespace System.Management.Automation.Remoting.Server
#region Methods
- protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager(string configurationName)
+ protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager(string configurationName, PSRemotingCryptoHelperServer cryptoHelper)
{
+ PSSenderInfo senderInfo;
+#if !UNIX
WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
PSPrincipal userPrincipal = new PSPrincipal(new PSIdentity("", true, currentIdentity.Name, null),
currentIdentity);
- PSSenderInfo senderInfo = new PSSenderInfo(userPrincipal, "http://localhost");
+ senderInfo = new PSSenderInfo(userPrincipal, "http://localhost");
+#else
+ PSPrincipal userPrincipal = new PSPrincipal(new PSIdentity("", true, "", null),
+ null);
+ senderInfo = new PSSenderInfo(userPrincipal, "http://localhost");
+#endif
- OutOfProcessServerSessionTransportManager tm = new OutOfProcessServerSessionTransportManager(originalStdOut, originalStdErr);
+ OutOfProcessServerSessionTransportManager tm = new OutOfProcessServerSessionTransportManager(originalStdOut, originalStdErr, cryptoHelper);
ServerRemoteSession srvrRemoteSession = ServerRemoteSession.CreateServerRemoteSession(senderInfo,
_initialCommand, tm, configurationName);
@@ -311,11 +319,11 @@ namespace System.Management.Automation.Remoting.Server
return tm;
}
- protected void Start(string initialCommand, string configurationName = null)
+ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoHelper, string configurationName = null)
{
_initialCommand = initialCommand;
- sessionTM = CreateSessionTransportManager(configurationName);
+ sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper);
try
{
@@ -326,7 +334,7 @@ namespace System.Management.Automation.Remoting.Server
{
if (sessionTM == null)
{
- sessionTM = CreateSessionTransportManager(configurationName);
+ sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper);
}
}
if (string.IsNullOrEmpty(data))
@@ -474,7 +482,76 @@ namespace System.Management.Automation.Remoting.Server
// Setup unhandled exception to log events
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException);
#endif
- s_singletonInstance.Start(initialCommand);
+ s_singletonInstance.Start(initialCommand, new PSRemotingCryptoHelperServer());
+ }
+
+ #endregion
+ }
+
+ internal sealed class SSHProcessMediator : OutOfProcessMediatorBase
+ {
+ #region Private Data
+
+ private static SSHProcessMediator s_singletonInstance;
+
+ #endregion
+
+ #region Constructors
+
+ private SSHProcessMediator() : base(true)
+ {
+#if !UNIX
+ var inputHandle = PlatformInvokes.GetStdHandle((uint)PlatformInvokes.StandardHandleId.Input);
+ originalStdIn = new StreamReader(
+ new FileStream(new SafeFileHandle(inputHandle, false), FileAccess.Read));
+
+ var outputHandle = PlatformInvokes.GetStdHandle((uint)PlatformInvokes.StandardHandleId.Output);
+ originalStdOut = new OutOfProcessTextWriter(
+ new StreamWriter(
+ new FileStream(new SafeFileHandle(outputHandle, false), FileAccess.Write)));
+
+ var errorHandle = PlatformInvokes.GetStdHandle((uint)PlatformInvokes.StandardHandleId.Error);
+ originalStdErr = new OutOfProcessTextWriter(
+ new StreamWriter(
+ new FileStream(new SafeFileHandle(errorHandle, false), FileAccess.Write)));
+#else
+ originalStdIn = new StreamReader(Console.OpenStandardInput(), true);
+ originalStdOut = new OutOfProcessTextWriter(
+ new StreamWriter(Console.OpenStandardOutput()));
+ originalStdErr = new OutOfProcessTextWriter(
+ new StreamWriter(Console.OpenStandardError()));
+#endif
+ }
+
+ #endregion
+
+ #region Static Methods
+
+ ///
+ ///
+ ///
+ ///
+ internal static void Run(string initialCommand)
+ {
+ lock (SyncObject)
+ {
+ if (s_singletonInstance != null)
+ {
+ Dbg.Assert(false, "Run should not be called multiple times");
+ return;
+ }
+
+ s_singletonInstance = new SSHProcessMediator();
+ }
+
+ PSRemotingCryptoHelperServer cryptoHelper;
+#if !UNIX
+ cryptoHelper = new PSRemotingCryptoHelperServer();
+#else
+ cryptoHelper = null;
+#endif
+
+ s_singletonInstance.Start(initialCommand, cryptoHelper);
}
#endregion
@@ -552,7 +629,7 @@ namespace System.Management.Automation.Remoting.Server
// AppDomain is not available in CoreCLR
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException);
#endif
- s_singletonInstance.Start(initialCommand, namedPipeServer.ConfigurationName);
+ s_singletonInstance.Start(initialCommand, new PSRemotingCryptoHelperServer(), namedPipeServer.ConfigurationName);
}
#endregion
@@ -643,7 +720,7 @@ namespace System.Management.Automation.Remoting.Server
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException);
#endif
- s_instance.Start(initialCommand, configurationName);
+ s_instance.Start(initialCommand, new PSRemotingCryptoHelperServer(), configurationName);
}
#endregion
diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs
index c7e070cc2..8b1c2c224 100644
--- a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs
+++ b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs
@@ -132,11 +132,12 @@ namespace System.Management.Automation.Remoting
_senderInfo = senderInfo;
_configProviderId = configurationProviderId;
_initParameters = initializationParameters;
- if (Platform.IsWindows)
- {
- _cryptoHelper = (PSRemotingCryptoHelperServer)transportManager.CryptoHelper;
- _cryptoHelper.Session = this;
- }
+#if !UNIX
+ _cryptoHelper = (PSRemotingCryptoHelperServer)transportManager.CryptoHelper;
+ _cryptoHelper.Session = this;
+#else
+ _cryptoHelper = null;
+#endif
Context = new ServerRemoteSessionContext();
SessionDataStructureHandler = new ServerRemoteSessionDSHandlerlImpl(this, transportManager);
@@ -162,9 +163,9 @@ namespace System.Management.Automation.Remoting
transportManager.ReceivedDataCollection.MaximumReceivedDataSize = null;
}
- #endregion Constructors
+#endregion Constructors
- #region Creation Factory
+#region Creation Factory
///
/// Creates a server remote session for the supplied
@@ -240,9 +241,9 @@ namespace System.Management.Automation.Remoting
return result;
}
- #endregion
+#endregion
- #region Overrides
+#region Overrides
///
/// This indicates the remote session object is Client, Server or Listener.
@@ -457,9 +458,9 @@ namespace System.Management.Automation.Remoting
SessionDataStructureHandler.StateMachine.RaiseEvent(args);
}
- #endregion Overrides
+#endregion Overrides
- #region Properties
+#region Properties
///
/// This property returns the ServerRemoteSessionContext object created inside
@@ -473,9 +474,9 @@ namespace System.Management.Automation.Remoting
///
internal ServerRemoteSessionDataStructureHandler SessionDataStructureHandler { get; }
- #endregion
+#endregion
- #region Private/Internal Methods
+#region Private/Internal Methods
///
/// Let the session clear its resources.
@@ -866,7 +867,11 @@ namespace System.Management.Automation.Remoting
_runspacePoolDriver.InstanceId);
}
+#if !UNIX
bool isAdministrator = _senderInfo.UserInfo.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
+#else
+ bool isAdministrator = false;
+#endif
ServerRunspacePoolDriver tmpDriver = new ServerRunspacePoolDriver(
clientRunspacePoolId,
@@ -1158,6 +1163,6 @@ namespace System.Management.Automation.Remoting
cmdTransportManager.ReceivedDataCollection.MaximumReceivedObjectSize = _maxRecvdObjectSize;
}
- #endregion
+#endregion
}
}
diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx
index d8148083f..ee890e003 100644
--- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx
+++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx
@@ -1609,4 +1609,7 @@ All WinRM sessions connected to Windows PowerShell session configurations, such
Other Possible Cause:
-The domain or computer name was not included with the specified credential, for example: DOMAIN\UserName or COMPUTER\UserName.
+
+ An error occurred when starting the SSH.exe client needed for the remoting connection.
+
\ No newline at end of file
diff --git a/src/System.Management.Automation/security/wldpNativeMethods.cs b/src/System.Management.Automation/security/wldpNativeMethods.cs
index d269c7ef3..17d423d55 100644
--- a/src/System.Management.Automation/security/wldpNativeMethods.cs
+++ b/src/System.Management.Automation/security/wldpNativeMethods.cs
@@ -258,6 +258,13 @@ namespace System.Management.Automation.Security
(System.Security.Principal.WindowsIdentity.GetCurrent().ImpersonationLevel == System.Security.Principal.TokenImpersonationLevel.Impersonation) ?
SaferPolicy.Allowed : SaferPolicy.Disallowed;
}
+ catch (ArgumentException)
+ {
+ // This is for IO.Path.GetTempPath() call when temp paths are not accessible.
+ result =
+ (System.Security.Principal.WindowsIdentity.GetCurrent().ImpersonationLevel == System.Security.Principal.TokenImpersonationLevel.Impersonation) ?
+ SaferPolicy.Allowed : SaferPolicy.Disallowed;
+ }
finally
{
if (IO.File.Exists(testPathScript)) { IO.File.Delete(testPathScript); }
diff --git a/src/System.Management.Automation/utils/PlatformInvokes.cs b/src/System.Management.Automation/utils/PlatformInvokes.cs
index 44fd3dd69..95abb623d 100644
--- a/src/System.Management.Automation/utils/PlatformInvokes.cs
+++ b/src/System.Management.Automation/utils/PlatformInvokes.cs
@@ -4,6 +4,7 @@ Copyright (c) Microsoft Corporation. All rights reserved.
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;
+using Microsoft.Win32.SafeHandles;
#if CORECLR
// Use stubs for SafeHandleZeroOrMinusOneIsInvalid, SecurityPermissionAttribute and ReliabilityContractAttribute
@@ -11,7 +12,6 @@ using Microsoft.PowerShell.CoreClr.Stubs;
#else
using System.Security.Permissions;
using System.Runtime.ConstrainedExecution;
-using Microsoft.Win32.SafeHandles;
#endif
namespace System.Management.Automation
@@ -552,5 +552,192 @@ namespace System.Management.Automation
internal const uint SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000;
internal const int ERROR_SUCCESS = 0x0;
+
+ #region CreateProcess for SSH Remoting
+
+#if !UNIX
+
+ // Fields
+ internal static readonly IntPtr INVALID_HANDLE_VALUE = IntPtr.Zero;
+ internal static UInt32 GENERIC_READ = 0x80000000;
+ internal static UInt32 GENERIC_WRITE = 0x40000000;
+ internal static UInt32 FILE_ATTRIBUTE_NORMAL = 0x80000000;
+ internal static UInt32 CREATE_ALWAYS = 2;
+ internal static UInt32 FILE_SHARE_WRITE = 0x00000002;
+ internal static UInt32 FILE_SHARE_READ = 0x00000001;
+ internal static UInt32 OF_READWRITE = 0x00000002;
+ internal static UInt32 OPEN_EXISTING = 3;
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal class PROCESS_INFORMATION
+ {
+ public IntPtr hProcess;
+ public IntPtr hThread;
+ public int dwProcessId;
+ public int dwThreadId;
+
+ public PROCESS_INFORMATION()
+ {
+ this.hProcess = IntPtr.Zero;
+ this.hThread = IntPtr.Zero;
+ }
+
+ ///
+ /// Dispose
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ ///
+ /// Dispose
+ ///
+ ///
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (this.hProcess != IntPtr.Zero)
+ {
+ CloseHandle(this.hProcess);
+ this.hProcess = IntPtr.Zero;
+ }
+
+ if (this.hThread != IntPtr.Zero)
+ {
+ CloseHandle(this.hThread);
+ this.hThread = IntPtr.Zero;
+ }
+ }
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal class STARTUPINFO
+ {
+ public int cb;
+ public IntPtr lpReserved;
+ public IntPtr lpDesktop;
+ public IntPtr lpTitle;
+ public int dwX;
+ public int dwY;
+ public int dwXSize;
+ public int dwYSize;
+ public int dwXCountChars;
+ public int dwYCountChars;
+ public int dwFillAttribute;
+ public int dwFlags;
+ public short wShowWindow;
+ public short cbReserved2;
+ public IntPtr lpReserved2;
+ public SafeFileHandle hStdInput;
+ public SafeFileHandle hStdOutput;
+ public SafeFileHandle hStdError;
+ public STARTUPINFO()
+ {
+ this.lpReserved = IntPtr.Zero;
+ this.lpDesktop = IntPtr.Zero;
+ this.lpTitle = IntPtr.Zero;
+ this.lpReserved2 = IntPtr.Zero;
+ this.hStdInput = new SafeFileHandle(IntPtr.Zero, false);
+ this.hStdOutput = new SafeFileHandle(IntPtr.Zero, false);
+ this.hStdError = new SafeFileHandle(IntPtr.Zero, false);
+ this.cb = Marshal.SizeOf(this);
+
+ }
+
+ public void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if ((this.hStdInput != null) && !this.hStdInput.IsInvalid)
+ {
+ this.hStdInput.Dispose();
+ this.hStdInput = null;
+ }
+ if ((this.hStdOutput != null) && !this.hStdOutput.IsInvalid)
+ {
+ this.hStdOutput.Dispose();
+ this.hStdOutput = null;
+ }
+ if ((this.hStdError != null) && !this.hStdError.IsInvalid)
+ {
+ this.hStdError.Dispose();
+ this.hStdError = null;
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal class SECURITY_ATTRIBUTES
+ {
+ public int nLength;
+ public SafeLocalMemHandle lpSecurityDescriptor;
+ public bool bInheritHandle;
+ public SECURITY_ATTRIBUTES()
+ {
+ this.nLength = 12;
+ this.bInheritHandle = true;
+ this.lpSecurityDescriptor = new SafeLocalMemHandle(IntPtr.Zero, true);
+ }
+ }
+
+ // Methods
+ //
+
+ [DllImport(PinvokeDllNames.CreateProcessDllName, CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern bool CreateProcess(
+ [MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
+ [MarshalAs(UnmanagedType.LPWStr)] string lpCommandLine,
+ SECURITY_ATTRIBUTES lpProcessAttributes,
+ SECURITY_ATTRIBUTES lpThreadAttributes,
+ bool bInheritHandles,
+ int dwCreationFlags,
+ IntPtr lpEnvironment,
+ [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
+ STARTUPINFO lpStartupInfo,
+ PROCESS_INFORMATION lpProcessInformation);
+
+ [DllImport(PinvokeDllNames.ResumeThreadDllName, CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern uint ResumeThread(IntPtr threadHandle);
+
+ [DllImport(PinvokeDllNames.CreateFileDllName, CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern System.IntPtr CreateFileW(
+ [In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
+ UInt32 dwDesiredAccess,
+ UInt32 dwShareMode,
+ SECURITY_ATTRIBUTES lpSecurityAttributes,
+ UInt32 dwCreationDisposition,
+ UInt32 dwFlagsAndAttributes,
+ System.IntPtr hTemplateFile);
+
+#endif
+
+ #endregion
+
+ #region GetStdHandle
+
+#if !UNIX
+
+ internal enum StandardHandleId : uint
+ {
+ Error = unchecked((uint)-12),
+ Output = unchecked((uint)-11),
+ Input = unchecked((uint)-10),
+ }
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern IntPtr GetStdHandle(uint handleId);
+
+#endif
+
+ #endregion
}
}
\ No newline at end of file