From d2bf6294ab930ff57b777ba0f003abf5cc2b37a9 Mon Sep 17 00:00:00 2001 From: Paul Higinbotham Date: Fri, 2 Dec 2016 14:01:08 -0800 Subject: [PATCH] SSH remoting cmdlet parameter set changes based on RFC 0010 (#2710) * RFC 0010 SSH remoting cmdlet updates. * Tweaks to SSH remoting cmdlets * SSHTransport parameter is now optional instead of mandatory. Aded fix for CoreCLR entry point when hosted in SSH as a subsystem. * Added tests * Code review feedback * Code review comment changes * Making SSHTransport switch parameter mandatory because otherwise existing Invoke-Command parameter set * The new SSH parameter set was causing legacy positional based parameters to no longer work. This fix separates the SSH parameter sets from the existing WinRM parameter sets. * Fixed region name spelling error. --- .../host/msh/ConsoleHost.cs | 7 + .../host/msh/ConsoleShell.cs | 11 - .../remoting/commands/InvokeCommandCommand.cs | 79 ++++--- .../remoting/commands/PSRemotingCmdlet.cs | 200 ++++++++++++++++-- .../remoting/commands/PushRunspaceCommand.cs | 28 ++- .../engine/remoting/commands/StartJob.cs | 37 ++++ .../remoting/commands/newrunspacecommand.cs | 43 +++- .../remoting/common/RunspaceConnectionInfo.cs | 18 +- .../fanin/OutOfProcTransportManager.cs | 7 +- .../resources/RemotingErrorIdStrings.resx | 15 ++ .../Remoting/SSHRemotingCmdlets.Tests.ps1 | 57 +++++ 11 files changed, 441 insertions(+), 61 deletions(-) create mode 100644 test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 1f8909bfc9..b54449a17e 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -240,6 +240,13 @@ namespace Microsoft.PowerShell throw hostException; } +#if !CORECLR + // The default font face used for Powershell Console is Lucida Console. + // However certain CJK locales dont support Lucida Console font. Hence for such + // locales the console font is updated to Raster dynamically. + ConsoleControl.UpdateLocaleSpecificFont(); +#endif + s_theConsoleHost.BindBreakHandler(); PSHost.IsStdOutputRedirected = Console.IsOutputRedirected; diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs index 49f6ec56a4..abce899add 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs @@ -82,17 +82,6 @@ namespace Microsoft.PowerShell throw PSTraceSource.NewArgumentNullException("args"); } - // The default font face used for Powershell Console is Lucida Console. - // However certain CJK locales dont support Lucida Console font. Hence for such - // locales the console font is updated to Raster dynamically. - - // For NanoServer: - // 1. There is no GetCurrentConsoleFontEx / SetCurrentConsoleFontEx on NanoServer; - // 2. We don't handle CJK locales on NanoServer due to lack of win32 API supports on NanoServer. -#if !CORECLR - ConsoleControl.UpdateLocaleSpecificFont(); -#endif - return ConsoleHost.Start(configuration, bannerText, helpText, preStartWarning, args); } } diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs index b8d665ead6..df4945c53a 100644 --- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs @@ -3,6 +3,7 @@ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; @@ -387,7 +388,9 @@ namespace Microsoft.PowerShell.Commands [Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostHashParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostHashParameterSet)] public SwitchParameter AsJob { get @@ -446,7 +449,9 @@ namespace Microsoft.PowerShell.Commands [Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostHashParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostHashParameterSet)] [Alias("HCN")] public SwitchParameter HideComputerName { @@ -509,9 +514,10 @@ namespace Microsoft.PowerShell.Commands [Parameter(Position = 1, Mandatory = true, ParameterSetName = InvokeCommandCommand.ContainerIdParameterSet)] - [Parameter(Position = 1, - Mandatory = true, + [Parameter(Mandatory = true, ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(Mandatory = true, + ParameterSetName = InvokeCommandCommand.SSHHostHashParameterSet)] [ValidateNotNull] [Alias("Command")] public override ScriptBlock ScriptBlock @@ -555,9 +561,10 @@ namespace Microsoft.PowerShell.Commands [Parameter(Position = 1, Mandatory = true, ParameterSetName = FilePathContainerIdParameterSet)] - [Parameter(Position = 1, - Mandatory = true, + [Parameter(Mandatory = true, ParameterSetName = FilePathSSHHostParameterSet)] + [Parameter(Mandatory = true, + ParameterSetName = FilePathSSHHostHashParameterSet)] [ValidateNotNull] [Alias("PSPath")] public override string FilePath @@ -662,28 +669,28 @@ namespace Microsoft.PowerShell.Commands #region SSH Parameters /// - /// Host Name + /// Host name for an SSH remote connection /// + [Parameter(Mandatory = true, + ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(Mandatory = true, + ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] [ValidateNotNullOrEmpty()] - [Parameter(Position = 0, Mandatory = true, ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] - [Parameter(Position = 0, Mandatory = true, ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] - public override string HostName + 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)] + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] [ValidateNotNullOrEmpty()] public override string UserName { get { return base.UserName; } - set { base.UserName = value; } } @@ -693,13 +700,43 @@ namespace Microsoft.PowerShell.Commands [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] [ValidateNotNullOrEmpty()] + [Alias("IdentityFilePath")] public override string KeyFilePath { get { return base.KeyFilePath; } - set { base.KeyFilePath = value; } } + /// + /// This parameter specifies that SSH is used to establish the remote + /// connection and act as the remoting transport. By default WinRM is used + /// as the remoting transport. Using the SSH transport requires that SSH is + /// installed and PowerShell remoting is enabled on both client and remote machines. + /// + [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] + [ValidateSet("true")] + public override SwitchParameter SSHTransport + { + get { return base.SSHTransport; } + set { base.SSHTransport = value; } + } + + /// + /// Hashtable array containing SSH connection parameters for each remote target + /// ComputerName (Alias: HostName) (required) + /// UserName (optional) + /// KeyFilePath (Alias: IdentityFilePath) (optional) + /// + [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostHashParameterSet, Mandatory = true)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostHashParameterSet, Mandatory = true)] + [ValidateNotNullOrEmpty()] + public override Hashtable[] SSHConnection + { + get; + set; + } + #endregion #endregion Parameters @@ -969,6 +1006,10 @@ namespace Microsoft.PowerShell.Commands case InvokeCommandCommand.FilePathVMIdParameterSet: case InvokeCommandCommand.FilePathVMNameParameterSet: case InvokeCommandCommand.FilePathContainerIdParameterSet: + case InvokeCommandCommand.SSHHostParameterSet: + case InvokeCommandCommand.FilePathSSHHostParameterSet: + case InvokeCommandCommand.SSHHostHashParameterSet: + case InvokeCommandCommand.FilePathSSHHostHashParameterSet: { if (ResolvedComputerNames.Length != 0 && Operations.Count > 0) { @@ -982,18 +1023,6 @@ 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 624ad25dbf..f8ec2b44aa 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs @@ -181,6 +181,11 @@ namespace Microsoft.PowerShell.Commands /// protected const string SSHHostParameterSet = "SSHHost"; + /// + /// SSH host parmeter set supporting hash connection parameters + /// + protected const string SSHHostHashParameterSet = "SSHHostHashParam"; + /// /// runspace parameter set /// @@ -262,6 +267,16 @@ namespace Microsoft.PowerShell.Commands #endregion } + /// + /// Contains SSH connection information + /// + internal struct SSHConnection + { + public string ComputerName; + public string UserName; + public string KeyFilePath; + } + /// /// Base class for any cmdlet which takes a -Session parameter /// or a -ComputerName parameter (along with its other associated @@ -273,6 +288,8 @@ namespace Microsoft.PowerShell.Commands /// public abstract partial class PSRemotingBaseCmdlet : PSRemotingCmdlet { + #region Enums + /// /// State of virtual machine. This is the same as VMState in /// \vm\ux\powershell\objects\common\Types.cs @@ -415,6 +432,8 @@ namespace Microsoft.PowerShell.Commands FastSavingCritical, } + #endregion + #region Tracer //PSETWTracer tracer = PSETWTracer.GetETWTracer(PSKeyword.Runspace); @@ -681,11 +700,12 @@ namespace Microsoft.PowerShell.Commands #region SSHHostParameters /// - /// SSH Target Host Name + /// Host name for an SSH remote connection. /// - [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet, Mandatory = true)] + [Parameter(Position = 0, Mandatory = true, + ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] [ValidateNotNullOrEmpty()] - public virtual string HostName + public virtual string[] HostName { get; set; @@ -694,7 +714,7 @@ namespace Microsoft.PowerShell.Commands /// /// SSH User Name /// - [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet, Mandatory = true)] + [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] [ValidateNotNullOrEmpty()] public virtual string UserName { @@ -707,12 +727,41 @@ namespace Microsoft.PowerShell.Commands /// [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] [ValidateNotNullOrEmpty()] + [Alias("IdentityFilePath")] public virtual string KeyFilePath { get; set; } + /// + /// This parameter specifies that SSH is used to establish the remote + /// connection and act as the remoting transport. By default WinRM is used + /// as the remoting transport. Using the SSH transport requires that SSH is + /// installed and PowerShell remoting is enabled on both client and remote machines. + /// + [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] + [ValidateSet("true")] + public virtual SwitchParameter SSHTransport + { + get; + set; + } + + /// + /// Hashtable array containing SSH connection parameters for each remote target + /// ComputerName (Alias: HostName) (required) + /// UserName (optional) + /// KeyFilePath (Alias: IdentityFilePath) (optional) + /// + [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostHashParameterSet, Mandatory = true)] + [ValidateNotNullOrEmpty()] + public virtual Hashtable[] SSHConnection + { + get; + set; + } + #endregion #endregion Properties @@ -762,6 +811,86 @@ namespace Microsoft.PowerShell.Commands #endregion + #region Internal Methods + + #region SSH Connection Strings + + private const string ComputerNameParameter = "ComputerName"; + private const string HostNameAlias = "HostName"; + private const string UserNameParameter = "UserName"; + private const string KeyFilePathParameter = "KeyFilePath"; + private const string IdentityFilePathAlias = "IdentityFilePath"; + + #endregion + + /// + /// Parse the Connection parameter HashTable array. + /// + /// Array of SSHConnection objects + internal SSHConnection[] ParseSSHConnectionHashTable() + { + List connections = new List(); + foreach (var item in this.SSHConnection) + { + if (item.ContainsKey(ComputerNameParameter) && item.ContainsKey(HostNameAlias)) + { + throw new PSArgumentException(RemotingErrorIdStrings.SSHConnectionDuplicateHostName); + } + + if (item.ContainsKey(KeyFilePathParameter) && item.ContainsKey(IdentityFilePathAlias)) + { + throw new PSArgumentException(RemotingErrorIdStrings.SSHConnectionDuplicateKeyPath); + } + + SSHConnection connectionInfo = new SSHConnection(); + foreach (var key in item.Keys) + { + string paramName = key as string; + if (string.IsNullOrEmpty(paramName)) + { + throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter); + } + + string paramValue = item[paramName] as string; + if (string.IsNullOrEmpty(paramValue)) + { + throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter); + } + + if (paramName.Equals(ComputerNameParameter, StringComparison.OrdinalIgnoreCase) || paramName.Equals(HostNameAlias, StringComparison.OrdinalIgnoreCase)) + { + var resolvedComputerName = ResolveComputerName(paramValue); + ValidateComputerName(new string[] { resolvedComputerName }); + connectionInfo.ComputerName = resolvedComputerName; + } + else if (paramName.Equals(UserNameParameter, StringComparison.OrdinalIgnoreCase)) + { + connectionInfo.UserName = paramValue; + } + else if (paramName.Equals(KeyFilePathParameter, StringComparison.OrdinalIgnoreCase) || paramName.Equals(IdentityFilePathAlias, StringComparison.OrdinalIgnoreCase)) + { + connectionInfo.KeyFilePath = paramValue; + } + else + { + throw new PSArgumentException( + StringUtil.Format(RemotingErrorIdStrings.UnknownSSHConnectionParameter, paramName)); + } + } + + if (string.IsNullOrEmpty(connectionInfo.ComputerName)) + { + throw new PSArgumentException(RemotingErrorIdStrings.MissingRequiredSSHParameter); + } + + connections.Add(connectionInfo); + } + + return connections.ToArray(); + } + + #endregion + #region Private Methods /// @@ -920,6 +1049,11 @@ namespace Microsoft.PowerShell.Commands /// protected const string FilePathSSHHostParameterSet = "FilePathSSHHost"; + /// + /// SSH Host file path parameter set with HashTable connection parameter + /// + protected const string FilePathSSHHostHashParameterSet = "FilePathSSHHostHash"; + #endregion #region Parameters @@ -1169,18 +1303,45 @@ namespace Microsoft.PowerShell.Commands }// CreateHelpersForSpecifiedComputerNames /// - /// Creates helper objects for host names for PSRP over SSH + /// Creates helper objects for SSH remoting computer names /// remoting. /// - protected void CreateHelpersForSpecifiedHostNames() + protected void CreateHelpersForSpecifiedSSHComputerNames() { - var sshConnectionInfo = new SSHConnectionInfo(this.UserName, this.HostName, this.KeyFilePath); - var typeTable = TypeTable.LoadDefaultTypeFiles(); - var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace; - var pipeline = CreatePipeline(remoteRunspace); + ValidateComputerName(ResolvedComputerNames); - var operation = new ExecutionCmdletHelperComputerName(remoteRunspace, pipeline); - Operations.Add(operation); + foreach (string computerName in ResolvedComputerNames) + { + var sshConnectionInfo = new SSHConnectionInfo(this.UserName, computerName, this.KeyFilePath); + 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 for SSH remoting from HashTable parameters. + /// + protected void CreateHelpersForSpecifiedSSHHashComputerNames() + { + var sshConnections = ParseSSHConnectionHashTable(); + var remoteRunspaces = new List(); + foreach (var sshConnection in sshConnections) + { + var sshConnectionInfo = new SSHConnectionInfo( + sshConnection.UserName, + sshConnection.ComputerName, + sshConnection.KeyFilePath); + 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); + } } /// @@ -1791,7 +1952,20 @@ namespace Microsoft.PowerShell.Commands case PSExecutionCmdlet.SSHHostParameterSet: case PSExecutionCmdlet.FilePathSSHHostParameterSet: - CreateHelpersForSpecifiedHostNames(); + { + String[] resolvedComputerNames = null; + ResolveComputerNames(HostName, out resolvedComputerNames); + ResolvedComputerNames = resolvedComputerNames; + + CreateHelpersForSpecifiedSSHComputerNames(); + } + break; + + case PSExecutionCmdlet.SSHHostHashParameterSet: + case PSExecutionCmdlet.FilePathSSHHostHashParameterSet: + { + CreateHelpersForSpecifiedSSHHashComputerNames(); + } break; case PSExecutionCmdlet.FilePathSessionParameterSet: diff --git a/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs b/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs index 66c9f20965..c1c81cea93 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs @@ -13,7 +13,7 @@ using System.Management.Automation.Remoting; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using Dbg = System.Management.Automation.Diagnostics; - +using System.Collections; namespace Microsoft.PowerShell.Commands { @@ -44,6 +44,18 @@ namespace Microsoft.PowerShell.Commands #region Parameters + #region SSH Parameter Set + + /// + /// Host name for an SSH remote connection + /// + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, + ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] + [ValidateNotNullOrEmpty()] + public new string HostName { get; set; } + + #endregion + /// /// Computer name parameter. /// @@ -172,6 +184,18 @@ namespace Microsoft.PowerShell.Commands ParameterSetName = EnterPSSessionCommand.VMNameParameterSet)] public String ConfigurationName { get; set; } + #region Suppress PSRemotingBaseCmdlet SSH hash parameter set + + /// + /// Suppress SSHConnection parameter set + /// + public override Hashtable[] SSHConnection + { + get { return null; } + } + + #endregion + #endregion #region Overrides @@ -1246,7 +1270,7 @@ namespace Microsoft.PowerShell.Commands /// private RemoteRunspace GetRunspaceForSSHSession() { - var sshConnectionInfo = new SSHConnectionInfo(this.UserName, this.HostName, this.KeyFilePath); + var sshConnectionInfo = new SSHConnectionInfo(this.UserName, ResolveComputerName(HostName), this.KeyFilePath); var typeTable = TypeTable.LoadDefaultTypeFiles(); var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace; remoteRunspace.Open(); diff --git a/src/System.Management.Automation/engine/remoting/commands/StartJob.cs b/src/System.Management.Automation/engine/remoting/commands/StartJob.cs index 7d46f8259e..fb52844b32 100644 --- a/src/System.Management.Automation/engine/remoting/commands/StartJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/StartJob.cs @@ -10,6 +10,7 @@ using System.Management.Automation.Runspaces; using System.Management.Automation.Security; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Collections; namespace Microsoft.PowerShell.Commands { @@ -119,6 +120,8 @@ namespace Microsoft.PowerShell.Commands } } + #region Suppress PSRemotingBaseCmdlet parameters + // suppress all the parameters from PSRemotingBaseCmdlet // which should not be part of Start-PSJob @@ -152,6 +155,40 @@ namespace Microsoft.PowerShell.Commands get { return false; } } + /// + /// Suppress SSHTransport + /// + public override SwitchParameter SSHTransport + { + get { return false; } + } + + /// + /// Suppress SSHConnection + /// + public override Hashtable[] SSHConnection + { + get { return null; } + } + + /// + /// Suppress UserName + /// + public override string UserName + { + get { return null; } + } + + /// + /// Suppress KeyFilePath + /// + public override string KeyFilePath + { + get { return null; } + } + + #endregion + /// /// Credential to use for this job /// diff --git a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs index b310787575..386e320541 100644 --- a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs @@ -244,6 +244,12 @@ namespace Microsoft.PowerShell.Commands } break; + case NewPSSessionCommand.SSHHostHashParameterSet: + { + remoteRunspaces = CreateRunspacesForSSHHostHashParameterSet(); + } + break; + default: { Dbg.Assert(false, "Missing parameter set in switch statement"); @@ -1061,13 +1067,38 @@ namespace Microsoft.PowerShell.Commands /// private List CreateRunspacesForSSHHostParameterSet() { + // Resolve all the machine names + String[] resolvedComputerNames; + ResolveComputerNames(HostName, out resolvedComputerNames); + ValidateComputerName(resolvedComputerNames); + var remoteRunspaces = new List(); - var sshConnectionInfo = new SSHConnectionInfo( - this.UserName, - this.HostName, - this.KeyFilePath); - var typeTable = TypeTable.LoadDefaultTypeFiles(); - remoteRunspaces.Add(RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace); + foreach (var computerName in resolvedComputerNames) + { + var sshConnectionInfo = new SSHConnectionInfo( + this.UserName, + computerName, + this.KeyFilePath); + var typeTable = TypeTable.LoadDefaultTypeFiles(); + remoteRunspaces.Add(RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace); + } + + return remoteRunspaces; + } + + private List CreateRunspacesForSSHHostHashParameterSet() + { + var sshConnections = ParseSSHConnectionHashTable(); + var remoteRunspaces = new List(); + foreach (var sshConnection in sshConnections) + { + var sshConnectionInfo = new SSHConnectionInfo( + sshConnection.UserName, + sshConnection.ComputerName, + sshConnection.KeyFilePath); + var typeTable = TypeTable.LoadDefaultTypeFiles(); + remoteRunspaces.Add(RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace); + } return remoteRunspaces; } diff --git a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs index 5b5a19f3cd..b50f21da22 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs @@ -1873,7 +1873,6 @@ namespace System.Management.Automation.Runspaces string computerName, string keyFilePath) { - if (userName == null) { throw new PSArgumentNullException("userName"); } if (computerName == null) { throw new PSArgumentNullException("computerName"); } this.UserName = userName; @@ -1980,9 +1979,9 @@ namespace System.Management.Automation.Runspaces // Extract an optional domain name if provided. string domainName = null; - string userName = this.UserName; + string userName = this.UserName ?? GetCurrentUserName(); #if !UNIX - var parts = this.UserName.Split(Utils.Separators.Backslash); + var parts = userName.Split(Utils.Separators.Backslash); if (parts.Length == 2) { domainName = parts[0]; @@ -2027,6 +2026,19 @@ namespace System.Management.Automation.Runspaces #endregion + #region Private Methods + + private string GetCurrentUserName() + { +#if UNIX + return System.Environment.GetEnvironmentVariable("USER") ?? string.Empty; +#else + return System.Security.Principal.WindowsIdentity.GetCurrent().Name; +#endif + } + +#endregion + #region SSH Process Creation #if UNIX diff --git a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs index 69eff7e11d..71174daffd 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs @@ -1516,7 +1516,12 @@ namespace System.Management.Automation.Remoting.Client while (true) { string error = reader.ReadLine(); - if (!string.IsNullOrEmpty(error) && (error.IndexOf("WARNING:", StringComparison.OrdinalIgnoreCase) > -1)) + if (string.IsNullOrEmpty(error)) + { + // Ignore blank error messages. + continue; + } + if (error.IndexOf("WARNING:", StringComparison.OrdinalIgnoreCase) > -1) { // Handle as interactive warning message. Console.WriteLine(error); diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx index d74de9dd3e..69c3a35660 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -1621,4 +1621,19 @@ All WinRM sessions connected to Windows PowerShell session configurations, such The SSH client session has ended with no error message. + + The provided SSHConnection hashtable is missing the required ComputerName or HostName parameter. + + + The provided SSHConnection hashtable parameter name or element is null or empty. + + + The provided SSHConnection hashtable parameter {0} is not supported. + + + The provided SSHConnection hashtable contains both a ComputerName and HostName parameter. Only one can be specified. + + + The provided SSHConnection hashtable contains both a KeyFilePath and IdentityFilePath parameter. Only one can be specified. + \ No newline at end of file diff --git a/test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 b/test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 new file mode 100644 index 0000000000..8c325a6663 --- /dev/null +++ b/test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 @@ -0,0 +1,57 @@ +## +## SSH Remoting cmdlet tests +## + +Describe "SSHTransport switch parameter value" -Tags 'Feature' { + + BeforeAll { + + $TestCasesSSHTransport = @( + @{scriptBlock = {New-PSSession -HostName localhost -UserName UserA -SSHTransport:$false}; testName = 'New-PSSession SSHTransport parameter cannot have false value'} + @{scriptBlock = {Enter-PSSession -HostName localhost -UserName UserA -SSHTransport:$false}; testName = 'Enter-PSSession SSHTransport parameter cannot have false value'} + @{scriptBlock = {Invoke-Command -ScriptBlock {"Hello"} -HostName localhost -UserName UserA -SSHTransport:$false}; testName = 'Invoke-Command SSHTransport parameter cannot have false value'} + ) + } + + It "" -TestCases $TestCasesSSHTransport { + + param($scriptBlock) + try + { + & $scriptBlock + throw "Parameter argument should not be valid" + } + catch + { + $_.FullyQualifiedErrorId | Should Match "ParameterArgumentValidationError" + } + } +} + +Describe "SSHConnection parameter hashtable error conditions" -Tags 'Feature' { + + BeforeAll { + + $TestCasesSSHConnection = @( + @{scriptBlock = {New-PSSession -SSHConnection @{ ComputerName = "localhost"; "" = "noParameter" }}; testName = 'SSHConnection parameter hashtable cannot contain empty parameter names'} + @{scriptBlock = {New-PSSession -SSHConnection @{ HostName = $null }}; testName = 'SSHConnection parameter hashtable cannot contain empty parameter values'} + @{scriptBlock = {New-PSSession -SSHConnection @{ ComputerName = "localhost"; UnknownParameter = "Hello" }}; testName = 'SSHConnection parameter hashtable cannot contain unknown parameter names'} + @{scriptBlock = {New-PSSession -SSHConnection @{ UserName = "UserName"; KeyFilePath = "path" }}; testName = 'SSHConnection parmeter hashtable must contain the ComputerName parameter'} + @{scriptBlock = {New-PSSession -SSHConnection @{ ComputerName = "computerA"; hostname = "computerB" }}; testName = 'SSHConnection parameter hashtable cannot contain both ComputerName and HostName parameters' } + @{scriptBlock = {New-PSSession -SSHConnection @{ keyfilepath = "pathA"; IdentityFilePath = "pathB" }}; testName = 'SSHConnection parameter hashtable cannot contain both KeyFilePath and IdentityFilePath parameters' } + ) + } + + It "" -TestCases $TestCasesSSHConnection { + param ($scriptBlock) + try + { + & $scriptBlock + throw "Parameter set should not be valid" + } + catch + { + $_.FullyQualifiedErrorId | Should Match "Argument,Microsoft.PowerShell.Commands.NewPSSessionCommand" + } + } +}