Skip to content

Conversation

@0x4c6565
Copy link
Contributor

@0x4c6565 0x4c6565 commented Apr 6, 2017

Ref #3475

This pull request adds support for the Port parameter when establishing a new SSH PSSession.

Examples:

New-PSSession -HostName "localhost" -Port 8080
New-PSSession -SSHConnection @{"ComputerName"="localhost";"Port"="8080"}

}
catch
{
$_.FullyQualifiedErrorId | Should Match "PSArgumentNullException"
Copy link
Collaborator

Choose a reason for hiding this comment

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

catch
{
$_.FullyQualifiedErrorId | Should Be "PSArgumentNullException"
$_.FullyQualifiedErrorId | Should Match "PSArgumentNullException"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use ShouldBeErrorId

$expectedFileNotFoundExecption = $_.Exception.InnerException.InnerException
}

($expectedFileNotFoundExecption.GetType().FullName) | Should Be "System.IO.FileNotFoundException"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Your code may raise null reference exception.
Why do you not use BeOfType?

$expectedArgumentException = $_.Exception.InnerException
}

($expectedArgumentException.GetType().FullName) | Should Be "System.ArgumentException"
Copy link
Collaborator

Choose a reason for hiding this comment

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

The same about BeOfType.

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

throw "SSHConnectionInfo did not throw expected ArgumentException exception"
Copy link
Collaborator

Choose a reason for hiding this comment

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

The same about throw "No Exception!"

$rs.Open()

throw "No Exception!"
throw "SSHConnectionInfo did not throw expected FileNotFoundException exception"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use throw "No Exception!" - it is our documented template

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Merge fail on my behalf! Will fix.

@0x4c6565
Copy link
Contributor Author

0x4c6565 commented Apr 7, 2017

Fail merge from master on my behalf with tests, will push a fix later

@0x4c6565
Copy link
Contributor Author

0x4c6565 commented Apr 7, 2017

Reverted SSHRemotingAPI.Tests.ps1 to master, added back in new test, and made requested changes to existing tests

string computerName,
string keyFilePath)
string keyFilePath,
int port)
Copy link
Collaborator

Choose a reason for hiding this comment

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

We shouldn't change a public API.
Preferred way is to add new constructor.

$expectedFileNotFoundException = $_.Exception.InnerException.InnerException
}

$expectedFileNotFoundException | Should BeOfType "System.IO.FileNotFoundException"
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe that there is no need to examine the properties, we can get an undesirable exception only when a method is called (ex., $prop.GetType()).
Here we can make the test more graceful and use ShouldBeErrorId too:

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

                $rs = [runspacefactory]::CreateRunspace($sshConnectionInfo)
                $rs.Open()
} | ShouldBeErrorId "PSRemotingDataStructureException"

$exc.Exception.InnerException.InnerException | Should BeOfType System.IO.FileNotFoundException

Also the tests skip a cleanup code. Please add something like:

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It would appear that ShouldBeErrorId doesn't return the thrown exception, so we would be unable to process $exc further here (ShouldBeErrorId will return result of Should Be - bool).

Based on this, would you prefer for ShouldBeErrorId to be implemented here to check for PSRemotingDataStructureException, or continue to inspect the nested inner exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cleanup code added

@SteveL-MSFT SteveL-MSFT requested a review from PaulHigin April 9, 2017 05:44
@mirichmo mirichmo self-assigned this Apr 11, 2017
@PaulHigin
Copy link
Contributor

I haven't had a chance to look at this yet. But will review by EOD Wednesday (4/12).

/// 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.

{
connectionInfo.KeyFilePath = paramValue;
}
else if (paramName.Equals(PortParameter, StringComparison.OrdinalIgnoreCase))
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the Port parameter in the hash table should be an integer and not a string. It would be more natural than enclosing the port value in quotes: @{ HostName = 'targetMachine'; UserName = 'UserA'; Port = 8080 }. This would mean updating this parse method to special case the Port parameter.


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.

PSRemotingErrorInvariants.FormatResourceString(
RemotingErrorIdStrings.PortIsOutOfRange, port);
ArgumentException e = new ArgumentException(message);
throw e;
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is duplicate code from WSManConnectionInfo, can we put this in a helper method in the base RunspaceConnectionInfo class?

string userName,
string computerName,
string keyFilePath,
int port)
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use the original constructor so as not to duplicate code:
public SSHConnectionInfo(string userName, string computerName, string keyFilePath, int port) : this(userName, computerName, keyFilePath)
{
if ((port < MinPort) || (port > MaxPort))
{
...
}
this.Port = (port != 0) port : DefaultPort;
}

Copy link
Contributor

@PaulHigin PaulHigin left a comment

Choose a reason for hiding this comment

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

Please see my comments for requested changes.

@0x4c6565
Copy link
Contributor Author

I'll action this tomorrow evening :)

@0x4c6565
Copy link
Contributor Author

All requested changes made :)


string paramValue = item[paramName] as string;
if (string.IsNullOrEmpty(paramValue))
if (paramName.Equals(PortParameter, StringComparison.OrdinalIgnoreCase))
Copy link
Collaborator

Choose a reason for hiding this comment

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

I didn't like how it was done in code below. We should use the previous style of if-else if.

We could even use C# 7.0 patterns.
/cc @daxian-dbw Are we ready to use the patterns?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you referring to the ParseSSHConnectionHashTable() @iSazonov?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I means that you add extra if-else level with the port parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh, I did this as we now need to handle InvalidSSHConnectionParameter for value types (integer), wereas previously we would check whether paramValue (as string) was null.

The only reason I added the extra level of if-else was to handle the integer/string values differently, although I do agree that it isn't ideal :(

I'll take another look at this later.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with @iSazonov, it would be nice to keep the if-else-if blocks. You can create a helper method to get a string or int parameter and throw exception a needed.

private static string GetStringParameter(object param)
{
    var paramValue = param as string;
    if (string.IsNullOrEmpty(paramValue)) { throw new PSArgumentException("..."); }
    return paramValue;
}

private static int GetIntParameter(object param)
{
    int? paramValue = param as int?;
    if (paramValue == null) { throw new PSArgumentException("..."); }
    return paramValue.Value;
}

if (paramName.Equals(PortParameter, StringComparison.OridinalIgnoreCase))
{
    connectionInfo.Port = GetIntParameter(item[paramName]);
}
else if ...

We don't need the Convert.ToInt32() and I believe the integer is only un-boxed once.

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 just added a commit to address this - much cleaner :)


return paramValue.Value;
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

What about C# 7.0 patterns:

       private static string GetSSHConnectionStringParameter(object param)
        {
            if (param is string paramValue && string.IsNullOrEmpty(paramValue))
            {
                return paramValue;
            }

            throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter);
        }

       private static int GetSSHConnectionIntParameter(object param)
        {
            if (param is int paramValue)
            {
                return paramValue;
            }

            throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter);
        }

Copy link
Contributor

Choose a reason for hiding this comment

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

I like it, but is this supported in are targeted runtimes?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I can not still build locally after move to Core2.0. Maybe @daxian-dbw confirm.

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 able to build with Core 2.0 and this pattern builds successfully. So I think we are good to use it.

Copy link
Member

Choose a reason for hiding this comment

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

C#7 is supported by the latest dotnet-cli and VS2017, so we are good here.

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 added another commit to add C# 7 pattern goodness, sorry for the delay!

@mirichmo
Copy link
Member

@PaulHigin Have all of your concerns been addressed? If not, please call out any remaining issues.

Copy link
Contributor

@PaulHigin PaulHigin left a comment

Choose a reason for hiding this comment

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

LGTM

@mirichmo
Copy link
Member

@lee303 - Thanks for the contribution. I'll merge it once CI passes

@0x4c6565 0x4c6565 closed this Apr 24, 2017
@0x4c6565 0x4c6565 reopened this Apr 24, 2017
@mirichmo mirichmo merged commit 1d42862 into PowerShell:master Apr 26, 2017
@0x4c6565 0x4c6565 deleted the 3475-ssh-port-parameter branch May 15, 2017 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants