Merge pull request #1787 from PaulHigin/WorkingA
PowerShell remoting over SSH.
This commit is contained in:
commit
ef5bf5ce08
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -436,7 +436,12 @@ namespace System.Management.Automation.Runspaces
|
|||
/// <summary>
|
||||
/// Runspace is based on a VM socket transport.
|
||||
/// </summary>
|
||||
VMSocketTransport = 0x4
|
||||
VMSocketTransport = 0x4,
|
||||
|
||||
/// <summary>
|
||||
/// Runspace is based on SSH transport.
|
||||
/// </summary>
|
||||
SSHTransport = 0x8
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -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)))
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
/// </summary>
|
||||
/// <returns>auto generated name</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -360,7 +395,7 @@ namespace System.Management.Automation.Runspaces
|
|||
/// <returns>Runspace name</returns>
|
||||
internal static string ComposeRunspaceName(int id)
|
||||
{
|
||||
return "Session" + id.ToString(System.Globalization.NumberFormatInfo.InvariantInfo);
|
||||
return "WinRM" + id.ToString(System.Globalization.NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -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
|
||||
|
||||
/// <summary>
|
||||
/// Host Name
|
||||
/// </summary>
|
||||
[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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// User Name
|
||||
/// </summary>
|
||||
[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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Key Path
|
||||
/// </summary>
|
||||
[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:
|
||||
{
|
||||
|
|
|
@ -176,6 +176,11 @@ namespace Microsoft.PowerShell.Commands
|
|||
/// </summary>
|
||||
protected const string VMNameParameterSet = "VMName";
|
||||
|
||||
/// <summary>
|
||||
/// SSH host parameter set
|
||||
/// </summary>
|
||||
protected const string SSHHostParameterSet = "SSHHost";
|
||||
|
||||
/// <summary>
|
||||
/// runspace parameter set
|
||||
/// </summary>
|
||||
|
@ -673,6 +678,43 @@ namespace Microsoft.PowerShell.Commands
|
|||
}
|
||||
private string _thumbPrint = null;
|
||||
|
||||
#region SSHHostParameters
|
||||
|
||||
/// <summary>
|
||||
/// SSH Target Host Name
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
public virtual string HostName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SSH User Name
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
public virtual string UserName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SSH Key Path
|
||||
/// </summary>
|
||||
[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
|
|||
/// </summary>
|
||||
protected const string FilePathContainerIdParameterSet = "FilePathContainerId";
|
||||
|
||||
/// <summary>
|
||||
/// SSH Host file path parameter set.
|
||||
/// </summary>
|
||||
protected const string FilePathSSHHostParameterSet = "FilePathSSHHost";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Parameters
|
||||
|
@ -1113,6 +1160,21 @@ namespace Microsoft.PowerShell.Commands
|
|||
}
|
||||
}// CreateHelpersForSpecifiedComputerNames
|
||||
|
||||
/// <summary>
|
||||
/// Creates helper objects for host names for PSRP over SSH
|
||||
/// remoting.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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:
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create remote runspace for SSH session
|
||||
/// </summary>
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
||||
/// <summary>
|
||||
/// CreateRunspacesForSSHHostParameterSet
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private List<RemoteRunspace> CreateRunspacesForSSHHostParameterSet()
|
||||
{
|
||||
var remoteRunspaces = new List<RemoteRunspace>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to either get a user supplied runspace/session name
|
||||
/// or to generate one along with a unique Id.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public sealed class SSHConnectionInfo : RunspaceConnectionInfo
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// User Name
|
||||
/// </summary>
|
||||
public string UserName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Key Path
|
||||
/// </summary>
|
||||
private string KeyPath
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
private SSHConnectionInfo()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="userName">User Name</param>
|
||||
/// <param name="computerName">Computer Name</param>
|
||||
/// <param name="keyPath">Key Path</param>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Computer is always localhost.
|
||||
/// </summary>
|
||||
public override string ComputerName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Credential
|
||||
/// </summary>
|
||||
public override PSCredential Credential
|
||||
{
|
||||
get { return null; }
|
||||
set { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authentication
|
||||
/// </summary>
|
||||
public override AuthenticationMechanism AuthenticationMechanism
|
||||
{
|
||||
get { return AuthenticationMechanism.Default; }
|
||||
set { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CertificateThumbprint
|
||||
/// </summary>
|
||||
public override string CertificateThumbprint
|
||||
{
|
||||
get { return string.Empty; }
|
||||
set { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shallow copy of current instance.
|
||||
/// </summary>
|
||||
/// <returns>NamedPipeConnectionInfo</returns>
|
||||
internal override RunspaceConnectionInfo InternalCopy()
|
||||
{
|
||||
SSHConnectionInfo newCopy = new SSHConnectionInfo();
|
||||
newCopy.ComputerName = this.ComputerName;
|
||||
newCopy.UserName = this.UserName;
|
||||
newCopy.KeyPath = this.KeyPath;
|
||||
|
||||
return newCopy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CreateClientSessionTransportManager
|
||||
/// </summary>
|
||||
/// <param name="instanceId"></param>
|
||||
/// <param name="sessionName"></param>
|
||||
/// <param name="cryptoHelper"></param>
|
||||
/// <returns></returns>
|
||||
internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper)
|
||||
{
|
||||
return new SSHClientSessionTransportManager(
|
||||
this,
|
||||
instanceId,
|
||||
cryptoHelper);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
/// <summary>
|
||||
/// StartSSHProcess
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Create a process through managed APIs and return StdIn, StdOut, StdError reader/writers
|
||||
/// This works for non-Windows platforms and is simpler.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CreateProcessWithRedirectedStd
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an SSH connection to the target host and set up
|
||||
/// transport reader/writer.
|
||||
/// </summary>
|
||||
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.");
|
||||
|
|
|
@ -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
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="initialCommand"></param>
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
||||
/// <summary>
|
||||
/// Creates a server remote session for the supplied <paramref name="configuratioinProviderId"/>
|
||||
|
@ -240,9 +241,9 @@ namespace System.Management.Automation.Remoting
|
|||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
#region Overrides
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
|
||||
/// <summary>
|
||||
/// This property returns the ServerRemoteSessionContext object created inside
|
||||
|
@ -473,9 +474,9 @@ namespace System.Management.Automation.Remoting
|
|||
/// </summary>
|
||||
internal ServerRemoteSessionDataStructureHandler SessionDataStructureHandler { get; }
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Private/Internal Methods
|
||||
#region Private/Internal Methods
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1609,4 +1609,7 @@ All WinRM sessions connected to Windows PowerShell session configurations, such
|
|||
<value> Other Possible Cause:
|
||||
-The domain or computer name was not included with the specified credential, for example: DOMAIN\UserName or COMPUTER\UserName.</value>
|
||||
</data>
|
||||
<data name="CannotStartSSHClient" xml:space="preserve">
|
||||
<value>An error occurred when starting the SSH.exe client needed for the remoting connection.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -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); }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue