Testing using Plink.exe to connect to SSH in C#

Festivejelly picture Festivejelly · Feb 26, 2014 · Viewed 13.1k times · Source

Im trying to connect to a unix terminal via plink.exe. The goal is so that I can read the text back into a string.

My dilema is that the bank I work for uses an old as400 type system that we normally access through putty. I'm trying to develop an automation suite that will interface with the system and run jobs and analyse the outputs etc.

So I figured I'd use plink through C#. If I run the code via Command prompt I get (roughly) the text I need back. However im suffering a problem in my C# code in that it just hangs and I never get a reponse.

What I want is like this:

Connect to server Input command Read back screen //More Commands etc

Here is my code so far:

class Program
{
    static void Main(string[] args)
    {

        ProcessStartInfo psi = new ProcessStartInfo(@"C:\Windows\System32\cmd");
        psi.RedirectStandardInput = true;
        psi.RedirectStandardOutput = true;
        psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
        psi.UseShellExecute = false;
        psi.CreateNoWindow = false;

        Process process = Process.Start(psi);
        string cmdForTunnel = @"c:\putty\plink -ssh jonkers@bankhq -pw automationhero";
        process.StandardInput.WriteLine(cmdForTunnel);
       // process.WaitForExit();
        Thread.Sleep(30000);
        string output = process.StandardOutput.ReadToEnd();

        Console.WriteLine(output);

        //DoBusinessLogic();
        process.StandardInput.WriteLine("logout");
        Thread.Sleep(10000);

        if (process.HasExited)
        {
            process.Close();
            process.Dispose();
        } 
    }
}

Im not really sure where the issues lie because as I say ive tested in using plink through command line, but with my solution above it just hangs. Ive tried using other peoples solutions on stackoverflow but none of them seem to work for me as I keep getting this hang. And tips would be much appreciated.

EDIT

I've now decided to use the Renci Sharp SSH library and have build my own framework around this. It works much better.

Answer

MrPaulch picture MrPaulch · Feb 26, 2014

So I just wanted to test plink myself and well it's result were pretty pleasing.

As I said in the comments you can't use ReadToEnd before you send the exit command, unless you want to block your current Thread.

Actually you could just send a bunch of commands (including the exit or logout) before engaging the ReadToEnd, but I did suggest to do the Read asynchrounusly as it is more robust.

Now there are a few ways to do async reading of a stream.


The Process class actually provides Events that are raised on incoming data. You could create handlers for those Events. These Events are:

  • OutputDataReceived
  • ErrorDataReceived

Their event handlers provide strings containing the data.


You could use the BeginRead of the StreamReader instances of stdout/stdin.

But here I provide a code sample that does it in a more crude way using simple multi-threading:

  public string RequestInfo(string remoteHost, string userName, string password, string[] lstCommands) {
        m_szFeedback = "Feedback from: " + remoteHost + "\r\n";

        ProcessStartInfo psi = new ProcessStartInfo()
        {
            FileName = PLINK_PATH, // A const or a readonly string that points to the plink executable
            Arguments = String.Format("-ssh {0}@{1} -pw {2}", userName, remoteHost, password),
            RedirectStandardError = true,
            RedirectStandardOutput = true,
            RedirectStandardInput = true,
            UseShellExecute = false,
            CreateNoWindow = true
        };

        Process p = Process.Start(psi);

        m_objLock = new Object();
        m_blnDoRead = true;

        AsyncReadFeedback(p.StandardOutput); // start the async read of stdout
        AsyncReadFeedback(p.StandardError); // start the async read of stderr

        StreamWriter strw = p.StandardInput;

        foreach (string cmd in lstCommands)
        {
            strw.WriteLine(cmd); // send commands 
        }
        strw.WriteLine("exit"); // send exit command at the end

        p.WaitForExit(); // block thread until remote operations are done
        return m_szFeedback;
    }

    private String m_szFeedback; // hold feedback data
    private Object m_objLock; // lock object
    private Boolean m_blnDoRead; // boolean value keeping up the read (may be used to interrupt the reading process)

    public void AsyncReadFeedback(StreamReader strr)
    {
        Thread trdr = new Thread(new ParameterizedThreadStart(__ctReadFeedback));
        trdr.Start(strr);
    }
    private void __ctReadFeedback(Object objStreamReader)
    {
        StreamReader strr = (StreamReader)objStreamReader; 
        string line;          
        while (!strr.EndOfStream && m_blnDoRead) 
        {
            line = strr.ReadLine();
            // lock the feedback buffer (since we don't want some messy stdout/err mix string in the end)
            lock (m_objLock) { m_szFeedback += line + "\r\n"; }
        }
    }

So if you want to get the contents of the user directory of a remote host call:

String feedback = RequestInfo("remote.ssh.host.de", "user", "password", new string[] { "ls -la" });

Obviously you substitute your own address, credentials and command-list.

Also you might want to clean the output string. e.G. in my case the commands I send to the remotehost are echoed into the output and thus appear in the return string.