Connecting to SSH Servers in .NET

Wednesday Jan 14th 2015 by Peter Shaw
Share:

Need to monitor a Linux-based server from a Windows-based computer? It's easier than you might think.

Recently, I had a requirement to monitor a Linux-based server from a Windows-based computer that was being used as a management station. Normally with something like this, you'd often employ some software that interrogates systems using the SNMP (Simple Network Management Protocol) over a UDP connection.

In the case of my requirements, however, this wasn't an option. The only option I had available was to have the application log in to the remote server via SSH, issue a command or two to get the information it needed, and then log back out.

.NET Does SSH

One of the great things about .NET as a framework is all of the wonderful stuff it has under the hood for dealing with network connections. You can do peer to peer, FTP, HTTP, and direct socket connections, along with much more. Unfortunately, one bad thing is that there is no built-in support for is SSH.

For those of you who have never used SSH before, or indeed have any clue about it, SSH is the process of getting a remote command line login to the server in question, and that this remote command line shall be encrypted. Typically (on the Windows platform, at least), most people use a small free application called 'Putty'. If you run this with the appropriate setup and authentication, you'll end up with something not too dissimilar to the following image:

SSH1
Figure 1: A Putty application

Once I have this terminal open, apart from the information I get at login, I can run a number of commands to obtain information from the system; for example, the 'df' or Disk free command.

SSH2
Figure 2: With the terminal open

As you can see, the output is fairly uniform, and would be easy to split apart and make use of in a Windows-based application. At this point, you might be thinking, if .NET doesn't do SSH natively, perhaps we can automate the connection by using something like Putty and the .NET process manipulation functions.

We could do that very easily because Putty comes with a few other tools and applications that are designed specifically for automating things directly from other programs.  As way of an example of that, many of you who use Git for source control on the Windows platform might not actually realise that, behind the scenes, it uses tools from the Putty collection to help it do the tasks that it needs to.

For our purposes, however, there is a much easier way.

Enter SSH.NET

Many developers will tell you that there's gold to be found in those most unexpected of places, and this couldn't be more true than paying a visit to Codeplex. Codeplex was originally a Microsoft initiative to provide their own open source code and project sharing platform, which by and large they succeeded at, even today there are many excellent projects on there run by some very passionate people.

One of those projects is "SSH.NET".

Going the way of many projects these days, SSH.NET has recently been evolved to support many of the new language features and framework APIs available in .NET 4, and although a backwards compatible version is still available, it's recommended that you use the new .NET4 re-write for new projects.

Enough talk, though; let's take a look at how we might use it. As usual, for simplicity, I'll be creating this as a .NET Windows Forms project. I won't be doing anything WinForms specific, though, and you should easily be able to adapt this to your own projects.

I'm not going to describe the project creation step by step. Instead, I'm just going to show you what my resulting Windows Forms UI looks like in Visual Studio.

If you look back at Figure 2 and count the lines that begin with '/dev', you'll see that I have six drives that I'd like to monitor on the server (the others are special Linux OS ones), so we need a UI with six progress bars on.

My user interface looks like the following:

SSH3
Figure 3: A sample user interface

I've named the button 'btnUpdate' and the six progress bars 'prgDriveSpace1' through to 'prgDriveSpace2'.

Now, we need to add 'ssh.net' to the project. Initially, I thought that it was only available as a download from Codeplex. However, after typing 'ssh.net' into my NuGet package manager, I was very happy to find that it was available inside NuGet, too.

SSH4
Figure 4: Adding 'ssh.net' to the project

Clicking install, as usual, does all of the work for us. Now all we need to do is wire it up. For the purposes of this post, I'm just going to do a single update at a time, triggered from a button press.

There are, however, many ways of using the library. Specifically, if you're going to be using a long running task, that might require two-way communication, and regular output from the server (such as the top or mpstat monitoring programs) then you'll want to use ssh.net's 'ShellStream' object, and run this in some kind of background worker, or async task request.

There is a CHM available showing what's available in the library (This can be downloaded from the Codeplex page), but examples of code are mostly available as answers to questions in the Codeplex discussions page for the project. Unfortunately, this does mean that, to find examples of how to use the lib, you're going to need to do quite a bit of reading. When you do find the samples, however, you'll generally find them of good quality; it's just not something that will be a quick exercise to locate.

For our purposes, however, double-click the update button, and then enter the following code into the button handler.

 using(SshClient ssh = new SshClient("server_host_name",
    "server_user_name", "server_password"))
 {
    ssh.Connect();
    var result = ssh.RunCommand("df -h");
    ssh.Disconnect();
 }
 

If we now place a break point on the using clause in the code, run the application, and then trace through to the 'RunCommand' call, we'll see that we get back from that an 'SshCommand' object. This SshCommand object will contain a property called 'Result', a string that contains the text returned to you after calling the 'df' command on the server. If you inspect this returned data, you'll find that it contains the same output as seen just a moment ago using Putty to log in manually.

SSH5
Figure 5: Viewing the same output as with Putty

For this example, we're only interested in column number 5, and only for lines that begin with '/dev', so the first thing we need to do is grab the returned data, and split it into an array of lines. Because this is coming from a Linux server, the end of line marker will usually just be '\n' and not '\r\n' as many Windows developers might expect.

Once we have an array of lines, then we then use a little bit of Linq magic and pull out only the lines we want, into a string list. The final step, once we've isolated the lines we want, is to get the percentage used figure (from the 5th column), turn that into an integer, and assign it to the appropriate progress gauge.

I've chosen to also get the 'sdx' name from each line, then use this is in a switch to decide which progress bar to update. In a production application, you'd most likely want to generate each bar dynamically so that the application updated as appropriate for however many drives where available.

My final processing function looks like the following:

 private void ProcessResults(string resultData)
 {
    string[] lines = resultData.Split('\n');
 
    List<string> deviceLines = lines.ToList().Where(x =>
       x.StartsWith("/dev")).ToList();
 
    string matchPattern = @"^(/dev/.+)\s+([0-9].+)\s+([0-9].+)\
       s+([0-9].+)\s+([0-9].+)\s+/.*$";
 
    foreach (string deviceLine in deviceLines)
    {
       var res = Regex.Match(deviceLine, matchPattern);
 
       string deviceName = (res.Groups[1].Value.Replace("/dev/",
          string.Empty)).Trim();
       int percentUsed = Convert.ToInt32((res.Groups[5].Value.Replace
          ("%", string.Empty)).Trim());
 
    switch(deviceName)
    {
       case "sda1":
          prgDriveSpace1.Value = percentUsed;
          break;
 
       case "sdb2":
          prgDriveSpace2.Value = percentUsed;
          break;
 
       case "sdc2":
          prgDriveSpace3.Value = percentUsed;
          break;
 
       case "sdd1":
          prgDriveSpace4.Value = percentUsed;
          break;
 
       case "sdc3":
          prgDriveSpace5.Value = percentUsed;
          break;
 
       case "sdd2":
          prgDriveSpace6.Value = percentUsed;
          break;
 
       }
    }
 }
 

and this is called by using

ProcessResults(result.Result);

just after the 'ssh.RunCommand' line in the button handler. If all goes well, then running the program and clicking Update should give you something similar to the following:

SSH6
Figure 6: The output results

And that's all there is to it.

There are many ways this can be improved; for example, where you call the 'df' command, you could call that in a number of different ways, making the output easier to deal with. Doing this, in turn, means you don't need the Regular expression call that I used, making things easier to maintain.

As I mentioned, you'll most likely want to dynamically create the UI, so you always have the correct number of entries. In my case, I ended up having to write a Windows 32 service that not only grabbed disk space statistics, but a number of other key metrics from the server, too. This service was then put to work feeding an ASP.NET MVC application.

If you have anything you'd like to see in this column, or have an idea for a future post, please let me know your comments in the comment section below, or come hunt me down as @shawty_ds on Twitter and let me know your thoughts.

Share:
Home
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved