SSH from PowerShell using the SSH.NET library

From Svendsen Tech Powershell Wiki
Jump to: navigation, search

For various reasons you might want to execute commands via SSH, using PowerShell. Presented here is a module with functions (that work like cmdlets or commands) for running commands via SSH on remote hosts such as Linux or Unix computers, VMware ESX(i) hosts or network equipment such as routers and switches that support SSH. It seems to work very well against OpenSSH-type servers.

The module uses the SSH.NET library which you can find on CodePlex. It's still in beta (June 2012). Also see the downloads section below where I've bundled the DLLs I've tested with.

Starting with the good news about VMware: ESX(i) 4.0 and 4.1 seem to be supported.

However, I've found that when I try to connect to VMware ESXi 5.x using this module/library, using a password, I get the error "Unable to connect to 192.168.1.103: Exception calling "Connect" with "0" argument(s): "No suitable authentication method found to complete authentication."". So it appears the authentication method ESXi implements isn't supported by the SSH.NET library, or maybe the other way around... The developers suggest an approach here. I have tested this, but I'm getting an unexpected error related to the event handler.

The code I'm using and error I'm getting is what the poster "Jaykul" describes somewhere near the bottom of this thread.

Using a key will, however, work. See the bottom of the article for more information on that. Given that, I suppose using this module to add keys to hosts running 4.x might be a good idea before upgrading them to 5.x to avoid manual labour.

This was written in a couple of days and must be considered pretty alpha/beta - but as of July 2014 this module has been downloaded about 10,000 times, and I've not heard anything about bugs beyond wanting to be able to use key files in the current directory without using a full path (this is now supported in the "SCP add-on version").

I should also mention that importing private keys created with Putty will not work, as the Putty developer apparently uses a different key format standard than (most of) the rest of the world. The OpenSSH key format is currently supported, but the Putty key format might be supported later if the developers implement it. There's a thread about it in the discussion forums on their CodePlex page. Oh, and PuttyGen.exe can save/export keys in OpenSSH format if you tell it to.

Enjoy!

Contents






Proof of Concept

SSH-from-PowerShell-example.png



Module Functions

The help text for the module functions, and examples.

To list the cmdlets to get help for from the command line, use something like this, after you've loaded the module:

PS E:\> get-help *ssh*

Name                    Category  Synopsis
----                    --------  --------
Get-SshSession          Function  Shows all, or the specified, SSH sessions in the global $SshSessions var...
Remove-SshSession       Function  Removes opened SSH connections. Use the parameter -RemoveAll to remove a...
New-SshSession          Function  Creates SSH sessions to remote SSH-compatible hosts, such as Linux...
Invoke-SshCommand       Function  Invoke/run commands via SSH on target hosts to which you have already op...
Enter-SshSession        Function  Enter a primitive interactive SSH session against a target host....

New-SshSession

The first thing you do when you want to interact with hosts via SSH using this module, is to create SSH sessions to the target host or hosts.

The global $SshSessions variable will be populated with SSH client objects from the SSH.NET library (Renci).

You can connect to multiple hosts at the same time using the same credentials, and to other hosts with other credentials by running the command again with different credentials. They will be added to the SSH client pool maintained in $SshSessions.

New-SshSession Help Text

<#
.SYNOPSIS
Creates SSH sessions to remote SSH-compatible hosts, such as Linux
or Unix computers or network equipment. You can later issue commands
to be executed on one or more of these hosts.

.DESCRIPTION
Once you've created a session, you can use Invoke-SshCommand or Enter-SshSession
to send commands to the remote host or hosts.

The authentication is done here. If you specify -KeyFile, that will be used.
If you specify a password and no key, that will be used. If you do not specify
a key nor a password, you will be prompted for a password, and you can enter
it securely with asterisks displayed in place of the characters you type in.

.PARAMETER ComputerName
Required. DNS names or IP addresses for target hosts to establish
a connection to using the provided username and key/password.
.PARAMETER Username
Required. The username used for connecting.
.PARAMETER KeyFile
Optional. Specify the path to a private key file for authenticating.
Overrides a specified password.
.PARAMETER Password
Optional. You can specify a key, or leave out the password to be prompted
for a password which is typed in interactively and will not be displayed.
.PARAMETER Port
Optional. Default 22. Target port the SSH server uses.
#>


New-SshSession Example

PS E:\> New-SshSession -ComputerName ubuntu64esxi,192.168.1.153 -Username joakimbs
No key provided. Enter SSH password for joakimbs: ****************
Successfully connected to ubuntu64esxi
Successfully connected to 192.168.1.153

# This global variable is set by the module and contains the sessions.
PS E:\> $SshSessions

Name                           Value
----                           -----
ubuntu64esxi                   Renci.SshNet.SshClient
192.168.1.153                  Renci.SshNet.SshClient


PS E:\> New-SshSession -ComputerName 192.168.1.1 -Username root
No key provided. Enter SSH password for root: *****************
Successfully connected to 192.168.1.1

PS E:\> $SshSessions.Keys
ubuntu64esxi
192.168.1.1
192.168.1.153

Invoke-SshCommand

This lets you invoke/execute/run commands via SSH on remote hosts to which you are connected.

Invoke-SshCommand Help Text

<#
.SYNOPSIS
Invoke/run commands via SSH on target hosts for which you have already opened
connections using New-SshSession. See Get-Help New-SshSession.

.DESCRIPTION
Execute/run/invoke commands via SSH.

You are already authenticated and simply specify the target(s) and the command.

Output is emitted to the pipeline, so you collect results by using:
$Result = Invoke-SshCommand [...]

$Result there would be either a System.String if you target a single host or a
System.Array containing strings if you target multiple hosts.

If you do not specify -Quiet, you will also get colored Write-Host output - mostly
for the sake of displaying progress.

Use -InvokeOnAll to invoke on all computers to which you have opened connections.
The hosts will be processed in alphabetically sorted order.

.PARAMETER ComputerName
Target hosts to invoke command on.
.PARAMETER Command
Required. The Linux command to run on specified target computers.
.PARAMETER Quiet
Causes no colored output to be written by Write-Host. If you assign results to a
variable, no progress indication will be shown.
.PARAMETER InvokeOnAll
Invoke the specified command on all computers for which you have an open connection.
Overrides -ComputerName, but you will be asked politely if you want to continue,
if you specify both parameters.
#>

Invoke-SshCommand Example

The output that shows the progress is colored and contains the remote host name. It can be suppressed with the parameter -Quiet, but then no progress will be shown. The results are always emitted to the pipeline so you need to assign the results to a variable. If you do not, and do not specify -Quiet, you will get the same output twice. I did it like this to be able to collect results while still showing progression.

PS E:\temp> $Results = Invoke-SshCommand -InvokeOnAll -Command 'uname -a'
ubuntu64esxi: Linux ubuntu64esxi 2.6.32-40-generic #87-Ubuntu SMP Tue Mar 6 00:56:56 UTC 2012 x86_64 GNU/Linux
192.168.1.1: Linux linksys 2.4.20 #1 Sun Nov 29 06:53:09 PST 2009 mips GNU/Linux
192.168.1.153: Linux localhost.localdomain 2.6.32-220.7.1.el6.x86_64 #1 SMP Wed Mar 7 00:52:02 GMT 2012 x86_64 x86_64 x86_64 GNU/Linux

PS E:\temp> $Results
Linux ubuntu64esxi 2.6.32-40-generic #87-Ubuntu SMP Tue Mar 6 00:56:56 UTC 2012 x86_64 GNU/Linux
Linux linksys 2.4.20 #1 Sun Nov 29 06:53:09 PST 2009 mips GNU/Linux
Linux localhost.localdomain 2.6.32-220.7.1.el6.x86_64 #1 SMP Wed Mar 7 00:52:02 GMT 2012 x86_64 x86_64 x86_64 GNU/Linux

You might find yourself wanting to do something not supported by the module. You can run a command like this:

PS E:\> $SshSessions.'debian64esxi'.RunCommand('pwd')


CommandText          : pwd
CommandTimeout       : -00:00:00.0010000
ExitStatus           : 0
OutputStream         : Renci.SshNet.Common.PipeStream
ExtendedOutputStream : Renci.SshNet.Common.PipeStream
Result               : /home/joakimbs

Error                :



PS E:\> $SshSessions.'debian64esxi'.RunCommand('pwd').Result
/home/joakimbs

# Remove the trailing newline/whitespace (and any leading whitespace if it's there)
PS E:\> $SshSessions.'debian64esxi'.RunCommand('pwd').Result.Trim()
/home/joakimbs
PS E:\>

Most of the technicalities are taken care of for you if you use the pre-made Invoke-SshCommand, but I figure it might be useful to know this. Before output is returned by Invoke-SshCommand, any trailing carriage returns and newlines are removed from the output before it's sent to the pipeline.

Get-SshSession

Get-SshSession lists all connections you've created with New-SshSession. They will be listed alphabetically. The function can be piped to Format-Table -AutoSize for a more condensed representation on the console (some other stuff too, but not in this case, I would think).

You can specify the optional parameter -ComputerName. If you specify a host for which there is no key in the hash, you will get back the string "NULL" as the value for the "Connected" column.

Get-SshSession Help Text

<#
.SYNOPSIS
Shows all, or the specified, SSH sessions in the global $SshSessions variable,
along with the connection status.

.DESCRIPTION
It checks if they're still reported as connected and reports that too. However,
they can have a status of "connected" even if the remote computer has rebooted.
Seems like an issue with the SSH.NET library and how it maintains this status.

If you specify hosts with -ComputerName, which don't exist in the $SshSessions
variable, the "Connected" value will be "NULL" for these hosts.

Also be aware that with the version of the SSH.NET library at the time of writing,
the host will be reported as connected even if you use the .Disconnect() method
on it. When you invoke the .Dispose() method, it does report the connection status
as false.

.PARAMETER ComputerName
Optional. The default behavior is to list all hosts alphabetically, but this
lets you specify hosts to target specifically. NULL is returned as the connection
status if a non-existing host name/IP is passed in.
#>

Get-SshSession Example

PS E:\> Get-SshSession | ft -auto

ComputerName  Connected
------------  ---------
192.168.1.1        True
192.168.1.153      True
debian64esxi       True
ubuntu64esxi       True

To store all the hosts, sorted alphabetically by Get-SshSession, in an array, do something like this (by the way, it's important not to use Format-Table in this one-liner...):

PS E:\> $Hosts = Get-SshSession | select -ExpandProperty ComputerName

PS E:\> $Hosts
192.168.1.1
192.168.1.153
debian64esxi
ubuntu64esxi

PS E:\> $Hosts.Count
4

Enter-SshSession

This enters an interactive session where the commands you issue are executed directly and output returned. Exit with the command "exit".

Enter-SshSession Help Text

<#
.SYNOPSIS
Enter a primitive interactive SSH session against a target host.
Commands are executed on the remote host as you type them and you are
presented with a Linux-like prompt.

.DESCRIPTION
Enter commands that will be executed by the host you specify and have already
opened a connection to with New-SshSession.

You can not change the current working directory on the remote host.

.PARAMETER ComputerName
Required. Target host to connect with.
.PARAMETER NoPwd
Optional. Do not try to include the default remote working directory in the prompt.
#>

Enter-SshSession Example

PS E:\temp> Enter-SshSession -ComputerName ubuntu64esxi
[ubuntu64esxi]: /home/joakimbs # : ifconfig eth1
eth1      Link encap:Ethernet  HWaddr 00:0d:29:24:07:c9
          inet addr:192.168.1.102  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe25:7d9/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:184107 errors:0 dropped:0 overruns:0 frame:0
          TX packets:29856 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:47733722 (47.7 MB)  TX bytes:4641823 (4.6 MB)

[ubuntu64esxi]: /home/joakimbs # : uname -a
Linux ubuntu64esxi 2.6.32-40-generic #87-Ubuntu SMP Tue Mar 6 00:56:56 UTC 2012 x86_64 GNU/Linux

[ubuntu64esxi]: /home/joakimbs # : cd ..; ls -l
total 20
drwxr-xr-x 51 joakimbs joakimbs  4096 2012-04-18 04:42 joakimbs
drwx------  2 root     root     16384 2010-10-21 14:56 lost+found

[ubuntu64esxi]: /home/joakimbs # : exit

PS E:\temp>

Remove-SshSession

Disconnects and disposes SSH client objects and removes them from the global $SshSessions hashtable.

Remove-SshSession Help Text

<#
.SYNOPSIS
Removes opened SSH connections. Use the parameter -RemoveAll to remove all connections.

.DESCRIPTION
Performs disconnect (if connected) and dispose on the SSH client object, then
sets the $global:SshSessions hashtable value to $null and then removes it from
the hashtable.

.PARAMETER ComputerName
The names of the computers for which you want to remove connections.
.PARAMETER RemoveAll
Removes all open connections and effectively empties the hash table.
Overrides -ComputerName, but you will be asked politely if you are sure,
if you specify both.
#>

Remove-SshSession Example

PS E:\temp> Remove-SshSession -RemoveAll
ubuntu64esxi should now be disconnected and disposed.
192.168.1.1 should now be disconnected and disposed.
192.168.1.153 should now be disconnected and disposed.
PS E:\temp>

SCP Add-on Feature

If you're looking for SCP/SFTP features for copying/uploading/downloading files, check this article.

Downloads

I've included the copies of Renci.SshNet.dll that I tested with in the zip archives below. If you want to download them directly from CodePlex and possibly get an updated version, go to the SSH.NET CodePlex page, click the download page and get the appropriate binary. It's still in beta, so there might be changes that break my wrapper functions, but I use only a very limited subset of the library.

Currently this is a direct link to the version I'm testing with for .NET 3.5 and this for .NET 4.0 (newer). You might want to ensure that you have the latest version by visiting the project page and checking the downloads. The module using the .NET 3.5 DLL expects it to have the name Renci.SshNet35.dll - and the .NET 4.0 version expects Renci.SshNet.dll.



A Few Quick Tips About How To Use The Module

Download it and place it in one of your PowerShell modules folders. Modules require PowerShell version 2 or later. The DLL file requires .NET 3.5 or later. The PowerShell module folders should be listed in the variable $env:PSModulePath. This is from my environment:

PS E:\> $env:PSModulePath -split ';'

C:\Users\joakim\Documents\WindowsPowerShell\Modules
C:\Windows\system32\WindowsPowerShell\v1.0\Modules\

If the folders do not exist, you need to create them. So to use this SSH-Sessions module, unzip the files into a folder called SSH-Sessions inside one of the module folders (Microsoft says "never put anything in the modules folder in C:\Windows, because it's ours", but... ;-). The full path in my case is C:\Users\joakim\Documents\WindowsPowerShell\Modules\SSH-Sessions (this is for my profile/user only).

Then you use the cmdlet Import-Module to load the module after it's copied to the proper path:

PS E:\> Import-Module SSH-Sessions
PS E:\>

To load the module automatically when you start PowerShell, put this Import-Module command in your PowerShell profile (external Microsoft Technet site link).

From PowerShell version 3 and on, modules are auto-loaded when you type the name of a cmdlet/parameter in a module (I'm a bit impressed by this).

You can also use Import-Module on the (relative) path to the module, like so:

PS E:\> Import-Module .\MyPSModules\SSH-Sessions

A Few Notes About Creating a Key for ESXi 5.x

I got a request for help connecting to ESXi 5.x, so I decided to document how to set up a public/private key pair using Windows and puttygen.exe.

  • Download puttygen.exe, open it and click Generate to generate a private/public key pair. I set the number of key bits to 4096.

SSH-from-PowerShell-ESXi-key-generation.png

  • Then export the private key for use with the SSH-Sessions module (has to be in OpenSSH format - the Putty key format will not work as of 2013-09-14).
    • Use the menu choice Conversions -> Export OpenSSH Key.

SSH-from-PowerShell-ESXi-key-generation-putty.png

# echo 'YOUR KEY HERE' >> /etc/ssh/keys-root/authorized_keys

Then connect specifying the key you exported from puttygen.exe earlier:

PS E:\temp> New-SshSession -Computer 192.168.1.103 -KeyFile E:\temp\esxi-key-openssh.key
            -Username root
Key file specified. Will override password. Trying to read key file...
Successfully connected to 192.168.1.103
PS E:\temp> Invoke-SshCommand -Computer 192.168.1.103 -Command 'uname -a' -q
VMkernel esxi 5.1.0 #1 SMP Release build-799733 Aug  1 2012 20:03:00 x86_64 GNU/Linux
PS E:\temp>

SSH-from-PowerShell-ESXi-key-generation-successful-connection.png

Personal tools