0 votes
by (120 points)
edited

Hi,

When using the RunCommand method of Rebex.Net.Ssh is it possible to get the exit code of the command I'm running, and when using RunCommand or StartCommand methods is it possible to read from stdout and stderr independently?

For example, I have a script that I'm running and if it fails it returns an exit code of 1 and writes the reason to stderr. If it succeeds the exit code is 0 and any details are written to stdout.

From my code I want to get the exit code and the text from stdout or stderr accordingly and then determine what to do next based on them.

As the RunCommand only returns a string - and it seems to be a combination of stdout & stderr - is the only option to change my script so it returns more consistently formatted output that I can parse and work out if it was all ok or if it was an error and if so, what the message was?

Using the StartCommand method instead I can get a Shell back and use the GetExitCode method once complete to get the exit code. That works fine for knowing if it failed or succeeded but the output text is still stdout and stderr combined so I need to change my script to output differently.

I've created my own version of RunCommand that uses StartCommand, ReadAll and GetExitCode internally to return the result & output and I'm not sure where I'd use the original RunCommand in preference to it.

Is there any benefit to using RunCommand over StartCommand? For virtually every command I run it's possible for it to fail so I'd always want to know the result and the output.

Thanks, Ian

3 Answers

+1 vote
by (144k points)
edited by
  1. It looks like what you need to achieve (independent access to stdout and stderr) would be possible using the low-level SshChannel class of Rebex SSH Shell, although a new release (due to be out tomorrow) would be needed. I will update this answer tomorrow when the new release is out to include the proposed solution. Please let us know whether you prefer C# or VB.NET!

  2. The Shell class (returned by StartCommand) doesn't support reading from stdout and stderr independently - it's a universal class shared with StartShell method (that doesn't always have access to independent stdout and stderr streams) and the Telnet component (that doesn't even support this functionality at all).

  3. RunCommand(cmd) is equivalent to StartCommand(cmd).ReadAll() - if you need to determine the exit code from the Shell object, use StartCommand.


Update: With Rebex SSH Shell 1.0.3854.0, it's possible to use the following helper class to read stdout and stderr independently:

public class SshCommandRunner
{
    private readonly Ssh _ssh;
    private readonly StringBuilder _stderr;
    private long _exitCode;

    public long ExitCode
    {
        get { return _exitCode; }
    }

    public SshCommandRunner(Ssh ssh)
    {
        _ssh = ssh;
        _stderr = new StringBuilder();
    }

    public string RunCommand(string command)
    {
        _exitCode = 0;
        _stderr.Length = 0;
        SshChannel channel = null;
        try
        {
            // start an exec session
            channel = _ssh.Session.OpenSession();
            channel.RequestExec(command);

            // get extended data (used for stderr)
            channel.ExtendedDataReceived += new EventHandler<SshExtendedDataReceivedEventArgs>(channel_ExtendedDataReceived);

            // receive all response
            StringBuilder response = new StringBuilder();
            byte[] buffer = new byte[4096];
            while (true)
            {
                // break if no data
                if (!channel.Poll(_ssh.Timeout * 1000, SocketSelectMode.SelectRead))
                    break;

                // break if closed
                int n = channel.Receive(buffer, 0, buffer.Length);
                if (n <= 0)
                    break;

                response.Append(_ssh.Encoding.GetString(buffer, 0, n));
            }

            return response.ToString().TrimEnd();
        }
        finally
        {
            if (channel != null)
            {
                channel.Close();

                // ExitCode has to be read after Close()
                SshChannelExitStatus exitStatus = channel.ExitStatus;
                if (exitStatus != null)
                    _exitCode = exitStatus.ExitCode;
            }
        }
    }

    public string Errors
    {
        get { return _stderr.ToString(); }
    }

    private void channel_ExtendedDataReceived(object sender, SshExtendedDataReceivedEventArgs e)
    {
        // get extended data
        byte[] data = e.GetData();

        // convert it to text
        string response = _ssh.Encoding.GetString(data, 0, data.Length);

        // add it to stderr StringBuilder
        _stderr.Append(response);
    }
}

This code demonstrates how to use this class:

// initialize an Ssh object
Ssh ssh = new Ssh();
ssh.Connect(...);
ssh.Login(...);

// create an instance of the helper class
SshCommandRunner runner = new SshCommandRunner(ssh);

// run command and display its results
string result = runner.RunCommand("echo one ; echo two >&2");
Console.WriteLine("Exit code: {0}", runner.ExitCode);
Console.WriteLine("Stdout: {0}", result);
Console.WriteLine("Stderr: {0}", runner.Errors);
by (160 points)
Cool, thanks Lukas. Just what I was after
0 votes
by (160 points)
edited

That's great, thanks Lukas. I would prefer C# please

0 votes
by (140 points)
edited

Hi, I am evaluating Rebex SSH for .NET 2.0.4086. According to this post, Shell s = Ssh.StartCommand("some command"); int iExitCode = s.GetExitCode(); should return correct exit code from remote process. But I am still getting ExitCode = 0 even though remote process exit with 1. The post helper SshCommandRunner returns correct exit code with 1. Does 2.0.4086 not fix the problem? I am using WinSSHD 5.26 on Windows 7 Ultimate. Thanks.

by (70.2k points)
edited

Please note the whole response has to be red to determine the ExitCode. See the output of this code:

   Ssh client = new Ssh();
   client.Connect(…);
   client.Login(…);

   Shell s = client.StartCommand("ls non-existing-file");
   Console.WriteLine(s.GetExitCode());
   Console.WriteLine(s.ReadAll());
   Console.WriteLine(s.GetExitCode());

Program output:

   // 0
   // ls: non-existing-file: No such file or directory
   // 
   // 2
...