Programmatically Leveraging SSH with Powershell

Posted by vele1655 | Posted in Automation, Powershell | Posted on 11-01-2012

Tags:

0

When dealing with automation you tend to get into jams sometimes where either you need to quickly turn around some information that is readily available via SSH or you need to create a scripted workflow to accomplish a task.  I personally have thoroughly used this technique to take a concept to reality with minimal amounts of time and effort.

This post is one of those massive scalable tools that I have in my bag that seems to be well worth the time to look at so you have it as an option next time you get a crazy idea between Msft and SSH enabled hosts.

Now for the meat of it, I am looking for a couple of things with this post.  Firstly, I wanted to share my approach to SSH and Powershell as I haven’t found it clearly laid out online.  And second I am looking to start a discussion on this topic to get feedback around better or more cutting edge methods than “screen scraping” SSH (other than APIs).

 

Your First Powershell SSH Script

There are four simple steps that should get you cooking on this concept. 

  • Download the Scripts
  • Identify a unique string
  • Configure the script
  • Test and run the script

1) Download the Scripts

The following files include all of the necessary scripts and assemblies that allow us to create SSH connections via Powershell and .NET.  We will dissect the example use of these scripts (ps_ssh_example.ps1) to show how we leverage the SSH methods provided via the assemblies.  BTW.   If you have never loaded assemblies manually into Powershell, don’t worry we do this for you.  The real directions you need to get this working are provided below.

ps_ssh_example.zip (only download this file)

http://downloads.sourceforge.net/sharpssh/ (where SSH assemblies came from)

 

2) Identify a unique string to “expect” when the statement is completed

For now I am going to assume that you are trying to SSH to a Linux host.  Go ahead and run a “uname –a” command from the Linux host so you have it handy and continue the directions below.  Hopefully it will make sense when you get through the next section.

image

 

3) Configure the script

This script that we are using here is primarily an example of how to issue remote commands via SSH and return the output.  For this purpose we have broken the configurable portions into variables within the script to make it easy to understand.

The first two items shown below (in the beginning of ps_ssh_example.ps1) are critical to this whole script working and take some explaining.  Since SSH itself really has no concept of a command starting or finishing and when to stop listening we rely on “expects” to do this hard work for us.  The expects are configured in two parts.  The first part, $expectCmd is a command that we run with every SSH command.  So for example, if I issue a “ls”, we are actually going to issue a “ls;uname –a” based from the variables below.  This is important so that we know when to look for the end of the output.  But you may be asking, “how about just a line like STOP” to determine the end?  If we simply parse for STOP this is going to mean we see the first line as the end.  Now, you might be saying “Hey that’s a bit hokey.”  This is partly why I am writing this post.  There is no doubt a more streamlined approach from Powershell.

The $expect portion below is based on the output from the command.  Again, we issue a “ls;uname –a” and then we are able to screen scrape the output until we get to what we know that output should be “Linux irvine-celerra1 2”.  Important note here, if this is in any way incorrect then the script will hang the Powershell session.  So it is important that you think about the command running, in our case uname, and you make sure it has obsolescence built in.

$expectCmd = "uname -a"

$expect = "Linux irvine-celerra1 2"

4) Test and run the script

And now comes the best part, having Linux output show up in Powershell.  When you run the script what we are expecting is for the following output from iostat to show up in a repeating fashion every second.  The time to seeing the first output depends on the normal time to SSH to your host.  If you see the output as follows then all is good, you have your first working SSH Powershell script.  If you have a script that is hanging, go back to the previous section and check your expect variables to make sure that when you issue the command in $expectCmd, the output matches the $expect that you set.  Otherwise, the only thing stopping this from running successfully should be your authentication.

Output from SSH

image

Output from Putty (SSH)

image

 

Reviewing the script

Now as always I want to dig a bit deeper into what exactly is happening under the covers so you understand the script and what you can do with it.  I have pasted the complete example script at the very bottom of this post for review.

  • Cool Trick #1 – Getting the script’s path ($MyInvocation)
  • Cool Trick #2 – Intercepting Control-C’s (TreatControlCAsInput)
  • Cool Trick #3 – SSH commands without Expects
  • Cool Trick #4 – SSH commands with Expects
  • Cool Trick #5 – Interpreting Start and End lines (select-string –pattern)

 

Set your variables as described above.

$expectCmd = "uname -a"

$expect = "Linux irvine-celerra1 2"

$serverIp = "serverIp"

$username = "root"

$password = ""

 

Cool Trick #1 – Getting the script’s path

The below lines serve two purposes, 1) load the SSH functions and underlying assemblies 2) sets the path for loading to be based on where the script was executed from.  This helps if you call a script from c:\scripts\xx.ps1 and you aren’t in the same directory. 

$spath = (Split-Path -parent $MyInvocation.MyCommand.Definition) + "\ssh_function.ps1"

. $spath

 

An important thing to mention with this approach is that because we can press Ctrl-C we have the ability to end the SSH session without formally closing the session.  See the picture below for the output during Powershell if we stop the script with a Ctrl-C.

image

 

Cool Trick #2 – Intercepting Control-C’s

Instead of this, we employ a bit of a trick in Powershell.  We use the console namespace to set a method called “TreatControlCAsInput” to true.  This allows us to trap all of the Ctrl-C’s received during the scripts execution and do other things when these buttons are sensed versus just quitting.

[console]::TreatControlCAsInput = $true

The above will tell Powershell not to execute on that Ctrl-C command, which allows us to, during a certain portion of the script, detect these keys and then execute our own clean quit by formally disconnecting from the SSH session.

function cust_sleep {

param($sleep)

    1..($sleep) | %{

        sleep 1

        if ($Host.UI.RawUI.KeyAvailable -and (3 -eq [int]$Host.UI.RawUI.ReadKey("AllowCtrlC,IncludeKeyUp,NoEcho").Character)) {

            [console]::TreatControlCAsInput = $false

            Remove-SshSession

            write "Exiting cleanly and closing SSH session"

            exit

        } 

    }

}

With the TreatControlAsCAsInput set we end up outputting something similar to what you see below when we press Ctrl-C.

 image

Now let’s step into how we are executing the commands and doing some fancy parsing to gather from the SSH output the command output.

 

Below we are going to be creating a function that we will reuse with any SSH command.

function execp_ssh ($tmpCmd) {

    $tmpCmd=$tmpCmd.replace(‘"’,‘\"’)

Cool Trick #3 – SSH commands without Expects

Here comes the first important part, we are using a new SSH function “send-ssh” which sends command without expecting output.  In this case we are actually setting a Linux variable that will hold the command we are sending.

    send-ssh (‘cmd="’+$tmpCmd+‘"’)

Cool Trick #4 – SSH commands with Expects

The following SSH function “invoke-ssh” will invoke and output the results which we capture in $tmpOut.  Notice how we issue a “echo `$cmd | sh”, this tells bash to echo the command and then running it with “sh”.  We then follow this command with the $expectCmd command and then pass the SSH function the $expect variable so it knows when to stop looking for output and return the results.  You may be looking at the first portion and asking why we set the variables in Linux before executing?  It is something that I ran into a while back where the library at certain characters might issue a blank space in the command.  So I was able to limit the size of the commands by encapsulating them in variables.  A good troubleshooting move here is to look at the “history” command during a manual SSH to see what commands are making it over and in what form.

    [array]$tmpOut = invoke-ssh "echo `$cmd |sh;$expectCmd" $expect

Cool Trick #5 – Parsing for line numbers

The Next two lines take the output from the above command and apply pattern matching to find the appropriate line number start and end.  You can see with the pattern of “;$expectCmd” we are looking for the line that includes the command that we ran.  And the “$expect” pattern matches the line that includes the output that specified to match for the end.  Also notice, the “) – 2” which means take the line number that you found and remove 2 lines (1 since we count 0 in arrays and 1 in line numbers, and 1 meaning the previous line).  We finish it up with a $tmpOut[(… which outputs the specified line ranges.

    $tmpStartLine = $tmpOut | select-string -pattern ";$expectCmd" | %{ $_.linenumber } | select -last 1

    $tmpEndLine = ($tmpOut | select-string -pattern $expect | %{ $_.linenumber } | select -first 1) - 2

    if(!$tmpStartLine) { $tmpStartLine = 0 }

    if(!$tmpEndLine) { $tmpEndLine = $tmpOut.count-1 }

    $tmpOut[($tmpStartLine)..($tmpEndLine)]

}

 

Now that we have declared our function we are using for the command execution, the next step is to actually execute on it.  The following will login through SSH, and then repetitively execute the SSH command of “iostat” as well as return those results to the screen.

New-SshSession $username $password $serverIp

for($x) {

    [array]$arrOut = execp_ssh ("iostat")

    $arrOut

    cust_sleep -sleep $sleep

}

remove-sshsession

exit

And that does it for my typical approach to programmatically using SSH.  Where I go from here is the data cleansing steps where I would massage the $arrOut array into something like an array of PSObjects that can be passed in any number of ways.  I have found that if I can get Linux commands to output in CSV that this method can be very clean and reliable. 

I’m hoping that what you have seen above has opened a few doors.  This has helped me tremendously with making tough things and typically after thoughts a reality in automation and stats collection.

 

Where have I used this? ESX/i, vSCSIstats, Avamar, DART, Linux, VPLEX, VNXe, Isilon

EMC Community Network – ECN- Powershell scripts for VPlex

Write a comment


× one = three