Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ public override PSCredential Credential
/// </remarks>
[Parameter(ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathComputerNameParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]
[ValidateRange((Int32)1, (Int32)UInt16.MaxValue)]
public override Int32 Port
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ internal struct SSHConnection
public string ComputerName;
public string UserName;
public string KeyFilePath;
public int Port;
}

/// <summary>
Expand Down Expand Up @@ -561,6 +562,7 @@ public virtual PSCredential Credential
/// to override the policy setting
/// </remarks>
[Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)]
[Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little worried about sharing parameters between WinRM and SSH parameter sets. My worry is that PowerShell parameter resolution might resolve to the wrong parameter set for positional parameters (e.g., New-PSSession localhost -Port 8080). Please verify that parameter set resolution does not change (i.e. causes a breaking change to existing behavior).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this turns out to be a problem we can think about adding a new SSH specific parameter set port parameter (SSHPort), but hopefully we won't have to.


In reply to: 111206290 [](ancestors = 111206290)

Copy link
Contributor Author

@0x4c6565 0x4c6565 Apr 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tested this, confirmed to default to ComputerNameParameterSet:

PS C:\Users\Lee\Documents\PowerShell> New-PSSession localhost -Port 5985

 Id Name            ComputerName    ComputerType    State         ConfigurationName     Availability
 -- ----            ------------    ------------    -----         -----------------     ------------
 11 WinRM11         localhost       RemoteMachine   Opened        Microsoft.PowerShell     Available

Not sure whether DefaultParameterSetName would help here to remove any uncertainty?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we are Ok. I assume that this works and doesn't result in a "cannot resolve parameter set" error?

New-PSSession -HostName localhost -Port 22

Also please check this again when you implement Invoke-Command SSH parameter sets

Invoke-Command -HostName localhost -Port 22 -ScriptBlock { "Hello" }
Invoke-Command localhost { "Hello" } -Port 5895

I think this will also be Ok but it does have more complex parameter sets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter set looks to be resolving correctly:

PS C:\Users\Lee\Projects\PowerShell> New-PSSession -HostName localhost -Port 22
Lee@DESKTOP-0I6KRV7@localhost's password:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've confirmed this to be resolving correctly following implementation for Invoke-Command:

PS C:\Users\Lee\Projects\PowerShell> Invoke-Command -HostName localhost -Port 22 -ScriptBlock { "Hello" }
Lee@DESKTOP-0I6KRV7@localhost's password:
PS C:\Users\Lee\Projects\PowerShell> Invoke-Command localhost { "Hello" } -Port 5985
Hello

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great news. I am looking forward to the next commit.

[ValidateRange((Int32)1, (Int32)UInt16.MaxValue)]
public virtual Int32 Port { get; set; }

Expand Down Expand Up @@ -820,6 +822,7 @@ internal static void ValidateSpecifiedAuthentication(PSCredential credential, st
private const string UserNameParameter = "UserName";
private const string KeyFilePathParameter = "KeyFilePath";
private const string IdentityFilePathAlias = "IdentityFilePath";
private const string PortParameter = "Port";

#endregion

Expand Down Expand Up @@ -851,25 +854,23 @@ internal SSHConnection[] ParseSSHConnectionHashTable()
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);
var resolvedComputerName = ResolveComputerName(GetSSHConnectionStringParameter(item[paramName]));
ValidateComputerName(new string[] { resolvedComputerName });
connectionInfo.ComputerName = resolvedComputerName;
}
else if (paramName.Equals(UserNameParameter, StringComparison.OrdinalIgnoreCase))
{
connectionInfo.UserName = paramValue;
connectionInfo.UserName = GetSSHConnectionStringParameter(item[paramName]);
}
else if (paramName.Equals(KeyFilePathParameter, StringComparison.OrdinalIgnoreCase) || paramName.Equals(IdentityFilePathAlias, StringComparison.OrdinalIgnoreCase))
{
connectionInfo.KeyFilePath = paramValue;
connectionInfo.KeyFilePath = GetSSHConnectionStringParameter(item[paramName]);
}
else if (paramName.Equals(PortParameter, StringComparison.OrdinalIgnoreCase))
{
connectionInfo.Port = GetSSHConnectionIntParameter(item[paramName]);
}
else
{
Expand Down Expand Up @@ -981,6 +982,37 @@ protected void ValidateComputerName(String[] computerNames)
}
}

/// <summary>
/// Validates parameter value and returns as string
/// </summary>
/// <param name="param">Parameter value to be validated</param>
/// <returns>Parameter value as string</returns>
private static string GetSSHConnectionStringParameter(object param)
{
if (param is string paramValue && !string.IsNullOrEmpty(paramValue))
{
return paramValue;
}

throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter);
}


/// <summary>
/// Validates parameter value and returns as integer
/// </summary>
/// <param name="param">Parameter value to be validated</param>
/// <returns>Parameter value as integer</returns>
private static int GetSSHConnectionIntParameter(object param)
{
if (param is int paramValue)
{
return paramValue;
}

throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter);
}

#endregion Private Methods

#region Overrides
Expand Down Expand Up @@ -1312,7 +1344,7 @@ protected void CreateHelpersForSpecifiedSSHComputerNames()

foreach (string computerName in ResolvedComputerNames)
{
var sshConnectionInfo = new SSHConnectionInfo(this.UserName, computerName, this.KeyFilePath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These creation helper functions are used by Invoke-Command, and this cmdlet will also have to be updated to support the new "Port" parameter. All cmdlets that currently support SSH parameter sets are New-PSSession, Enter-PSSession, Invoke-Command. You covered the first two but missed the last one.

var sshConnectionInfo = new SSHConnectionInfo(this.UserName, computerName, this.KeyFilePath, this.Port);
var typeTable = TypeTable.LoadDefaultTypeFiles();
var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace;
var pipeline = CreatePipeline(remoteRunspace);
Expand All @@ -1334,7 +1366,8 @@ protected void CreateHelpersForSpecifiedSSHHashComputerNames()
var sshConnectionInfo = new SSHConnectionInfo(
sshConnection.UserName,
sshConnection.ComputerName,
sshConnection.KeyFilePath);
sshConnection.KeyFilePath,
sshConnection.Port);
var typeTable = TypeTable.LoadDefaultTypeFiles();
var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace;
var pipeline = CreatePipeline(remoteRunspace);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1270,7 +1270,7 @@ private RemoteRunspace GetRunspaceForContainerSession()
/// </summary>
private RemoteRunspace GetRunspaceForSSHSession()
{
var sshConnectionInfo = new SSHConnectionInfo(this.UserName, ResolveComputerName(HostName), this.KeyFilePath);
var sshConnectionInfo = new SSHConnectionInfo(this.UserName, ResolveComputerName(HostName), this.KeyFilePath, this.Port);
var typeTable = TypeTable.LoadDefaultTypeFiles();
var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace;
remoteRunspace.Open();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,8 @@ private List<RemoteRunspace> CreateRunspacesForSSHHostParameterSet()
var sshConnectionInfo = new SSHConnectionInfo(
this.UserName,
computerName,
this.KeyFilePath);
this.KeyFilePath,
this.Port);
var typeTable = TypeTable.LoadDefaultTypeFiles();
remoteRunspaces.Add(RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace);
}
Expand All @@ -1095,7 +1096,8 @@ private List<RemoteRunspace> CreateRunspacesForSSHHostHashParameterSet()
var sshConnectionInfo = new SSHConnectionInfo(
sshConnection.UserName,
sshConnection.ComputerName,
sshConnection.KeyFilePath);
sshConnection.KeyFilePath,
sshConnection.Port);
var typeTable = TypeTable.LoadDefaultTypeFiles();
remoteRunspaces.Add(RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,36 @@ internal virtual RunspaceConnectionInfo InternalCopy()
throw new PSNotImplementedException();
}

/// <summary>
/// Validates port number is in range
/// </summary>
/// <param name="port">Port number to validate</param>
internal virtual void ValidatePortInRange(int port)
{
if ((port < MinPort || port > MaxPort))
{
String message =
PSRemotingErrorInvariants.FormatResourceString(
RemotingErrorIdStrings.PortIsOutOfRange, port);
ArgumentException e = new ArgumentException(message);
throw e;
}
}

#endregion

#region Constants

/// <summary>
/// Maximum value for port
/// </summary>
protected const int MaxPort = 0xFFFF;

/// <summary>
/// Minimum value for port
/// </summary>
protected const int MinPort = 0;

#endregion
}

Expand Down Expand Up @@ -1172,6 +1202,7 @@ internal void ConstructUri(String scheme, String computerName, Nullable<Int32> p

if (port.HasValue)
{
ValidatePortInRange(port.Value);
// resolve to default ports if required
if (port.Value == DefaultPort)
{
Expand All @@ -1185,14 +1216,6 @@ internal void ConstructUri(String scheme, String computerName, Nullable<Int32> p
PortSetting = port.Value;
UseDefaultWSManPort = false;
}
else if ((port.Value < MinPort || port.Value > MaxPort))
{
String message =
PSRemotingErrorInvariants.FormatResourceString(
RemotingErrorIdStrings.PortIsOutOfRange, port);
ArgumentException e = new ArgumentException(message);
throw e;
}
else
{
PortSetting = port.Value;
Expand Down Expand Up @@ -1391,16 +1414,6 @@ internal bool UseDefaultWSManPort
/// </summary>
private const string DefaultComputerName = "localhost";

/// <summary>
/// Maximum value for port
/// </summary>
private const int MaxPort = 0xFFFF;

/// <summary>
/// Minimum value for port
/// </summary>
private const int MinPort = 0;

/// <summary>
/// String that represents the local host Uri
/// </summary>
Expand Down Expand Up @@ -1852,6 +1865,15 @@ private string KeyFilePath
set;
}

/// <summary>
/// Port for connection
/// </summary>
private int Port
{
get;
set;
}

#endregion

#region Constructors
Expand All @@ -1878,6 +1900,25 @@ public SSHConnectionInfo(
this.UserName = userName;
this.ComputerName = computerName;
this.KeyFilePath = keyFilePath;
this.Port = DefaultPort;
}

/// <summary>
/// Constructor
/// </summary>
/// <param name="userName">User Name</param>
/// <param name="computerName">Computer Name</param>
/// <param name="keyFilePath">Key File Path</param>
/// <param name="port">Port number for connection (default 22)</param>
public SSHConnectionInfo(
string userName,
string computerName,
string keyFilePath,
int port) : this(userName, computerName, keyFilePath)
{
ValidatePortInRange(port);

this.Port = (port != 0) ? port : DefaultPort;
}

#endregion
Expand Down Expand Up @@ -1930,6 +1971,7 @@ internal override RunspaceConnectionInfo InternalCopy()
newCopy.ComputerName = this.ComputerName;
newCopy.UserName = this.UserName;
newCopy.KeyFilePath = this.KeyFilePath;
newCopy.Port = this.Port;

return newCopy;
}
Expand Down Expand Up @@ -2004,14 +2046,14 @@ internal System.Diagnostics.Process StartSSHProcess(
}

arguments = (string.IsNullOrEmpty(domainName)) ?
string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" {1}@{2} -s powershell", this.KeyFilePath, userName, this.ComputerName) :
string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" -l {1}@{2} {3} -s powershell", this.KeyFilePath, userName, domainName, this.ComputerName);
string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" {1}@{2} -p {3} -s powershell", this.KeyFilePath, userName, this.ComputerName, this.Port) :
string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" -l {1}@{2} {3} -p {4} -s powershell", this.KeyFilePath, userName, domainName, this.ComputerName, this.Port);
}
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);
string.Format(CultureInfo.InvariantCulture, @"{0}@{1} -p {2} -s powershell", userName, this.ComputerName, this.Port) :
string.Format(CultureInfo.InvariantCulture, @"-l {0}@{1} {2} -p {3} -s powershell", userName, domainName, this.ComputerName, this.Port);
}

System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo(
Expand All @@ -2037,7 +2079,16 @@ private string GetCurrentUserName()
#endif
}

#endregion
#endregion

#region Constants

/// <summary>
/// Default value for port
/// </summary>
private const int DefaultPort = 22;

#endregion

#region SSH Process Creation

Expand Down
47 changes: 37 additions & 10 deletions test/powershell/engine/Remoting/SSHRemotingAPI.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,41 +1,68 @@
Import-Module $PSScriptRoot\..\..\Common\Test.Helpers.psm1

Describe "SSH Remoting API Tests" -Tags "Feature" {

Context "SSHConnectionInfo Class Tests" {

AfterEach {
if ($rs -ne $null) {
$rs.Dispose()
}
}

It "SSHConnectionInfo constructor should throw null argument exception for null HostName parameter" {

{ [System.Management.Automation.Runspaces.SSHConnectionInfo]::new(
"UserName",
[System.Management.Automation.Internal.AutomationNull]::Value,
[System.Management.Automation.Internal.AutomationNull]::Value,
0) } | ShouldBeErrorId "PSArgumentNullException"
}

It "SSHConnectionInfo should throw file not found exception for invalid key file path" {

try
{
[System.Management.Automation.Runspaces.SSHConnectionInfo]::new(
$sshConnectionInfo = [System.Management.Automation.Runspaces.SSHConnectionInfo]::new(
"UserName",
[System.Management.Automation.Internal.AutomationNull]::Value,
[System.Management.Automation.Internal.AutomationNull]::Value)
"localhost",
"NoValidKeyFilePath",
22)

$rs = [runspacefactory]::CreateRunspace($sshConnectionInfo)
$rs.Open()

throw "No Exception!"
}
catch
{
$_.FullyQualifiedErrorId | Should Be "PSArgumentNullException"
$_.Exception.InnerException.InnerException | Should BeOfType "System.IO.FileNotFoundException"
}
}

It "SSHConnectionInfo should throw file not found exception for invalid key file path" {

try
It "SSHConnectionInfo should throw argument exception for invalid port (non 16bit uint)" {
try
{
$sshConnectionInfo = [System.Management.Automation.Runspaces.SSHConnectionInfo]::new(
"UserName",
"localhost",
"NoValidKeyFilePath")
"ValidKeyFilePath",
99999)

$rs = [runspacefactory]::CreateRunspace($sshConnectionInfo)
$rs.Open()

throw "No Exception!"
}
catch
{
$_.Exception.InnerException.InnerException | Should BeOfType System.IO.FileNotFoundException
$expectedArgumentException = $_.Exception
if ($_.Exception.InnerException -ne $null)
{
$expectedArgumentException = $_.Exception.InnerException
}

$expectedArgumentException | Should BeOfType "System.ArgumentException"
}
}
}
Expand Down