Xceed .NET Libraries Documentation
Remote Command Execution

Welcome to Xceed .NET, .NET Standard and Xamarin Libraries! > Basic Concepts > SFTP Capabilities > Remote Command Execution

Description

The SSH protocol allows for the remote execution of commands on the server. This is done using a SSH logical channel. Many SSH features like SFtp and remote command execution are implemented over such channels. Every channel is multiplexed into a single encrypted SSH connection.

As such, a remote command execution can be issued while a SFtp session is performing work since a new channel will be created to issue the command.

Remote command execution works by the client sending a message that requests the server start the execution of a given command string. The 'command' string may contain a path and arguments. It is also possible to pass environment variables to the server before the command is executed.

It should be noted that servers will take normal precautions to prevent the execution of unauthorized commands. At the very least, this means that the right to execute a command, and which commands are allowed will depend on the user that is authenticated on the SSH connection. These rights are determined by the server's administrator and cannot be computed on the client side.

If a user doesn't have the rights to execute a command, an exception will be thrown but the SSH connection will not be closed. Other channels (like SFtp sessions) will continue to proceed without issue.

Usage

A remote command is executed using the Xceed.SSH.Client.ExecuteCommandSession Class. Its constructor needs to be supplied a SSHClient object that is connected and authenticated. Then, the Connect() method can be called, specifying the command to execute and, optionally, environment variables to set on the server before the command is executed.

The ExecuteCommandSession class is ony available with the Xceed SFtp for .NET that is compiled for .NET 4.0 and later. The class is available with Xceed SFtp for Xamarin, all versions.

The Connect() method returns immediately if the server accepts the command. If not, a SSHChannelRequestFailedException is thrown. The command is run on the server while control continues normall on the client side. The server might send standard output and/or standard error text output to the client. It is possible to catch that data with a Stream object using the GetOutputStream() and GetErrorStream() methods. It is also possible to send text to the command's standard input using a stream with the GetInputStream() method. With all these streams, it is often useful to wrap them around a System.Text.StreamReader or System.Text.StreamWriter object to read and write text lines. See the examples below for details.

The ExecuteCommandSession class contains a CommandCompleted Event event that will be triggered when the command has completed on the server. At that point, the channel will have been closed but the possible exit status or exit signal (not all servers supply these values) will still be available to be read.

Another way to watch for the completion of the command is to wait on the SessionCompletedWaitHandle.

The ExecuteCommandSession class implements the IDisposable interface. It is good practice to dispose of an instance of the class once it is no longer needed.

Examples

 Execute and wait for result

The simplest way to use the ExecuteCommandSession class is to start the command and wait for the command to complete.

using System.IO;

using Xceed.SSH.Client;
using Xceed.SSH.Core;
using Xceed.SSH.Protocols;
using Xceed.FileSystem;

namespace DocumentationExamples.SSH
{
  public class ExecuteCommandSession3
  {
    public void Example()
    {
      string host = "localhost";
      string username = "normal1";
      string password = "normal1";

      SSHClient ssh = new SSHClient();

      // Connect to the host
      ssh.Connect( host );

      try
      {
        // Log in
        ssh.Authenticate( username, password );

        // The remote command to execute
        string command = "dir";

        // Create a session that will execute a remote command
        using( ExecuteCommandSession executeCommandSession = new ExecuteCommandSession( ssh ) )
        {
          // Execute the command on the remote server, waiting until it completes
          Nullable<int> exitStatus = executeCommandSession.ExecuteCommand( command );

          /* Some servers will return exit information like the return code and the 'signal'
             if the command fails. */

          Console.WriteLine( "Command exited with status {0}", exitStatus.HasValue ? exitStatus.ToString() : "(null)" );

          SSHChannelExitSignal exitSignal = executeCommandSession.ExitSignal;
          if( exitSignal != null )
          {
            Console.WriteLine( "Command exited with signal {0}: {1}", exitSignal.SignalName, exitSignal.ErrorMessage );
          }
        }
      }
      catch( SSHChannelRequestFailedException )
      {
        /* This exception is thrown by ExecuteCommandSession.Connect(). The most common
         * cause is the authenticated user does not have the rights to execute commands
         * on the server. */
      }
      finally
      {
        // Always make sure to disconnect from the server when the connection is no longer needed
        ssh.Disconnect();
      }
    }
  }
}
Imports System.IO

Imports Xceed.SSH.Client
Imports Xceed.SSH.Core
Imports Xceed.SSH.Protocols
Imports Xceed.FileSystem

Namespace DocumentationExamples.SSH
  Public Class ExecuteCommandSession3
    Public Sub Example()
      Dim host As String = "localhost"
      Dim username As String = "normal1"
      Dim password As String = "normal1"

      Dim ssh As New SSHClient()

      ' Connect to the host
      ssh.Connect(host)

      Try
        ' Log in
        ssh.Authenticate(username, password)

        ' The remote command to execute
        Dim command As String = "dir"

        ' Create a session that will execute a remote command
        Using executeCommandSession As New ExecuteCommandSession(ssh)
          ' Execute the command on the remote server, waiting until it completes
          Dim exitStatus As Nullable(Of Integer) = executeCommandSession.ExecuteCommand(command)

'           Some servers will return exit information like the return code and the 'signal'
'             if the command fails. 

          Console.WriteLine("Command exited with status {0}",If(exitStatus.HasValue, exitStatus.ToString(), "(null)"))

          Dim exitSignal As SSHChannelExitSignal = executeCommandSession.ExitSignal
          If exitSignal IsNot Nothing Then
            Console.WriteLine("Command exited with signal {0}: {1}", exitSignal.SignalName, exitSignal.ErrorMessage)
          End If
        End Using
      Catch e1 As SSHChannelRequestFailedException
'         This exception is thrown by ExecuteCommandSession.Connect(). The most common
'         * cause is the authenticated user does not have the rights to execute commands
'         * on the server. 
      Finally
        ' Always make sure to disconnect from the server when the connection is no longer needed
        ssh.Disconnect()
      End Try
    End Sub
  End Class
End Namespace
 Fire-and-forget

The ExecuteCommandSession class can also be used in a fire-and-forget manner.

using System.IO;
using System.Threading.Tasks;

using Xceed.SSH.Client;
using Xceed.SSH.Core;
using Xceed.SSH.Protocols;
using Xceed.FileSystem;

namespace DocumentationExamples.SSH
{
  public class ExecuteCommandSession4
  {
    public void Example()
    {
      string host = "localhost";
      string username = "normal1";
      string password = "normal1";

      // The remote command to execute
      string command = "dir";

      SSHClient ssh = new SSHClient();

      // Connect to the host
      ssh.Connect( host );

      try
      {
        // Log in
        ssh.Authenticate( username, password );

        Task<Nullable<int>> task = this.ExecuteRemoteCommandAsync( ssh, command );

        /* TODO: Perform other business that does not require the result of the remote command */

        /* Now that we need the result from the remote command, we will wait for it */
        Nullable<int> exitStatus = task.Result;
        Console.WriteLine( "Command exited with status {0}", exitStatus.HasValue ? exitStatus.ToString() : "(null)" );
      }
      finally
      {
        // Always make sure to disconnect from the server when the connection is no longer needed
        ssh.Disconnect();
      }
    }

    private async Task<Nullable<int>> ExecuteRemoteCommandAsync( SSHClient ssh, string command )
    {
      Task<Nullable<int>> executeCommandTask;
      Nullable<int> exitStatus;

      ExecuteCommandSession executeCommandSession = null;

      // Create a session that will execute a remote command
      executeCommandSession = new ExecuteCommandSession( ssh );

      try
      {
        // Start executing the specified command on the remote server
        executeCommandTask = executeCommandSession.ExecuteCommandAsync( command );
      }
      catch( SSHChannelRequestFailedException )
      {
        /* This exception is thrown by ExecuteCommandSession.Connect(). The most common
          * cause is the authenticated user does not have the rights to execute commands
          * on the server. */

        /* The 'CommandCompleted' event is not triggered when the request to launch the command fails */

        // Dispose of the session to free resources
        executeCommandSession.Dispose();

        // Rethrow the exception
        throw;
      }

      // Wait for the remote command to complete while allowing the calling method to continue execution
      exitStatus = await executeCommandTask;

      /* This code will be executed in a background thread from ThreadPool */

      // Dispose of the session to free resources
      executeCommandSession.Dispose();

      return exitStatus;
    }
  }
}
Imports System.IO
Imports System.Threading.Tasks

Imports Xceed.SSH.Client
Imports Xceed.SSH.Core
Imports Xceed.SSH.Protocols
Imports Xceed.FileSystem

Namespace DocumentationExamples.SSH
  Public Class ExecuteCommandSession4
    Public Sub Example()
      Dim host As String = "localhost"
      Dim username As String = "normal1"
      Dim password As String = "normal1"

      ' The remote command to execute
      Dim command As String = "dir"

      Dim ssh As New SSHClient()

      ' Connect to the host
      ssh.Connect(host)

      Try
        ' Log in
        ssh.Authenticate(username, password)

        Dim task As Task(Of Nullable(Of Integer)) = Me.ExecuteRemoteCommandAsync(ssh, command)

        ' TODO: Perform other business that does not require the result of the remote command 

        ' Now that we need the result from the remote command, we will wait for it 
        Dim exitStatus As Nullable(Of Integer) = task.Result
        Console.WriteLine("Command exited with status {0}",If(exitStatus.HasValue, exitStatus.ToString(), "(null)"))
      Finally
        ' Always make sure to disconnect from the server when the connection is no longer needed
        ssh.Disconnect()
      End Try
    End Sub

    Private async Function ExecuteRemoteCommandAsync(ByVal ssh As SSHClient, ByVal command As String) As Task(Of Nullable(Of Integer))
      Dim executeCommandTask As Task(Of Nullable(Of Integer))
      Dim exitStatus As Nullable(Of Integer)

      Dim executeCommandSession As ExecuteCommandSession = Nothing

      ' Create a session that will execute a remote command
      executeCommandSession = New ExecuteCommandSession(ssh)

      Try
        ' Start executing the specified command on the remote server
        executeCommandTask = executeCommandSession.ExecuteCommandAsync(command)
      Catch e1 As SSHChannelRequestFailedException
'         This exception is thrown by ExecuteCommandSession.Connect(). The most common
'          * cause is the authenticated user does not have the rights to execute commands
'          * on the server. 

        ' The 'CommandCompleted' event is not triggered when the request to launch the command fails 

        ' Dispose of the session to free resources
        executeCommandSession.Dispose()

        ' Rethrow the exception
        Throw
      End Try

      ' Wait for the remote command to complete while allowing the calling method to continue execution
      exitStatus = await executeCommandTask

      ' This code will be executed in a background thread from ThreadPool 

      ' Dispose of the session to free resources
      executeCommandSession.Dispose()

      Return exitStatus
    End Function
  End Class
End Namespace
 Capture command output

The output of the command can be captured. Here a technique using asynchronous reading is used in order to read output and error text at the same time.

Also shown in this example is how to pass environment variables to the server.

using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

using Xceed.SSH.Client;
using Xceed.SSH.Core;
using Xceed.SSH.Protocols;
using Xceed.FileSystem;

namespace DocumentationExamples.SSH
{
  public class ExecuteCommandSession1
  {
    public void Example()
    {
      string host = "localhost";
      string username = "normal1";
      string password = "normal1";

      SSHClient ssh = new SSHClient();

      // Connect to the host
      ssh.Connect( host );

      try
      {
        // Log in
        ssh.Authenticate( username, password );

        // The remote command to execute
        string command = "dir *.*";

        // Some environment variables
        KeyValuePair<string, string> environmentVariable = new KeyValuePair<string, string>( "MYVARIABLE", "MYVALUE" );

        // Create a session that will execute a remote command
        using( ExecuteCommandSession executeCommandSession = new ExecuteCommandSession( ssh ) )
        {
          Stream commandOutputStream = null;
          Stream commandErrorStream = null;

          try
          {
            /* We obtain the output and error streams BEFORE starting the command so that we don't
               miss any output. */

            /* If the CommandOutputStream or the CommandErrorStream object is not taken,
               the component silently discards the incoming data. This might be desirable if
               the output and/or error text is not needed. */

            // Get the command's output stream (standard output)
            commandOutputStream = executeCommandSession.GetOutputStream();

            // Get the command's error stream (standard error)
            commandErrorStream = executeCommandSession.GetErrorStream();

            /* Now that we are setup the way we want, we can safely start the remote command */

            // Start executing the specified command on the remote server passing the supplied environment variables to the server
            executeCommandSession.Connect( command, environmentVariable );

            /* Notice how we wrap the streams with StreamReader objects only after starting the command.
               This guards against the possibility of the StreamReader object calling Stream.Read() before
               we've started the command. */

            // Wrap a stream reader around the output stream
            System.IO.StreamReader outputReader = new System.IO.StreamReader( commandOutputStream );

            // Wrap a stream reader around the error stream
            System.IO.StreamReader errorReader = new System.IO.StreamReader( commandErrorStream );

            // Read a line from the output and error at the same time
            Task<string> readOutputLineTask = outputReader.ReadLineAsync();
            Task<string> readErrorLineTask = errorReader.ReadLineAsync();

            string line;

            // While either the output or error streams haven't reached end-of-file
            while( readOutputLineTask != null || readErrorLineTask != null )
            {
              // If reading an output line has completed
              if( readOutputLineTask != null && readOutputLineTask.IsCompleted )
              {
                // Get the line
                line = readOutputLineTask.Result;

                // If we have a line
                if( line != null )
                {
                  // Output it to the console
                  Console.WriteLine( line );

                  // Read another line
                  readOutputLineTask = outputReader.ReadLineAsync();
                }
                else
                {
                  /* We've reached end-of-file. We don't want to read again */
                  readOutputLineTask = null;
                }
              }

              // If reading an error line has completed
              if( readErrorLineTask != null && readErrorLineTask.IsCompleted )
              {
                // Get the line
                line = readErrorLineTask.Result;

                // If we have a line
                if( line != null )
                {
                  // Output it to the console
                  Console.WriteLine( line );

                  // Read another line
                  readErrorLineTask = errorReader.ReadLineAsync();
                }
                else
                {
                  /* We've reached end-of-file. We don't want to read again */
                  readErrorLineTask = null;
                }
              }
            }

            /* Because we've processed the output stream until end-of-file, we are sure that
              the command has completed. No need to wait. 

               However, if accessing the exit status or possible exit signal of the command
               is important to the application, it is best to wait until the command has signaled it
               has completed. At that point, the exit information will have been received. */

            // Wait until the command has completed
            executeCommandSession.SessionCompletedWaitHandle.WaitOne();

            /* Some servers will return exit information like the return code and the 'signal'
              if the command fails. */

            Nullable<int> exitStatus = executeCommandSession.ExitStatus;
            Console.WriteLine( "Command exited with status {0}", exitStatus.HasValue ? exitStatus.ToString() : "(null)" );

            SSHChannelExitSignal exitSignal = executeCommandSession.ExitSignal;
            if( exitSignal != null )
            {
              Console.WriteLine( "Command exited with signal {0}: {1}", exitSignal.SignalName, exitSignal.ErrorMessage );
            }
          }
          catch( SSHChannelRequestFailedException )
          {
            /* This exception is thrown by ExecuteCommandSession.Connect(). The most common
             * cause is the authenticated user does not have the rights to execute commands
             * on the server. */
          }
          finally
          {
            if( commandOutputStream != null )
            {
              commandOutputStream.Close();
              commandOutputStream = null;
            }

            if( commandErrorStream != null )
            {
              commandErrorStream.Close();
              commandErrorStream = null;
            }
          }
        }

      }
      finally
      {
        // Always make sure to disconnect from the server when the connection is no longer needed
        ssh.Disconnect();
      }
    }
  }
}
Imports System.Collections.Generic
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks

Imports Xceed.SSH.Client
Imports Xceed.SSH.Core
Imports Xceed.SSH.Protocols
Imports Xceed.FileSystem

Namespace DocumentationExamples.SSH
  Public Class ExecuteCommandSession1
    Public Sub Example()
      Dim host As String = "localhost"
      Dim username As String = "normal1"
      Dim password As String = "normal1"

      Dim ssh As New SSHClient()

      ' Connect to the host
      ssh.Connect(host)

      Try
        ' Log in
        ssh.Authenticate(username, password)

        ' The remote command to execute
        Dim command As String = "dir *.*"

        ' Some environment variables
        Dim environmentVariable As KeyValuePair(Of String, String) = New KeyValuePair(Of String, String)("MYVARIABLE", "MYVALUE")

        ' Create a session that will execute a remote command
        Using executeCommandSession As New ExecuteCommandSession(ssh)
          Dim commandOutputStream As Stream = Nothing
          Dim commandErrorStream As Stream = Nothing

          Try
'             We obtain the output and error streams BEFORE starting the command so that we don't
'               miss any output. 

'             If the CommandOutputStream or the CommandErrorStream object is not taken,
'               the component silently discards the incoming data. This might be desirable if
'               the output and/or error text is not needed. 

            ' Get the command's output stream (standard output)
            commandOutputStream = executeCommandSession.GetOutputStream()

            ' Get the command's error stream (standard error)
            commandErrorStream = executeCommandSession.GetErrorStream()

            ' Now that we are setup the way we want, we can safely start the remote command 

            ' Start executing the specified command on the remote server passing the supplied environment variables to the server
            executeCommandSession.Connect(command, environmentVariable)

'             Notice how we wrap the streams with StreamReader objects only after starting the command.
'               This guards against the possibility of the StreamReader object calling Stream.Read() before
'               we've started the command. 

            ' Wrap a stream reader around the output stream
            Dim outputReader As New System.IO.StreamReader(commandOutputStream)

            ' Wrap a stream reader around the error stream
            Dim errorReader As New System.IO.StreamReader(commandErrorStream)

            ' Read a line from the output and error at the same time
            Dim readOutputLineTask As Task(Of String) = outputReader.ReadLineAsync()
            Dim readErrorLineTask As Task(Of String) = errorReader.ReadLineAsync()

            Dim line As String

            ' While either the output or error streams haven't reached end-of-file
            Do While readOutputLineTask IsNot Nothing OrElse readErrorLineTask IsNot Nothing
              ' If reading an output line has completed
              If readOutputLineTask IsNot Nothing AndAlso readOutputLineTask.IsCompleted Then
                ' Get the line
                line = readOutputLineTask.Result

                ' If we have a line
                If line IsNot Nothing Then
                  ' Output it to the console
                  Console.WriteLine(line)

                  ' Read another line
                  readOutputLineTask = outputReader.ReadLineAsync()
                Else
                  ' We've reached end-of-file. We don't want to read again 
                  readOutputLineTask = Nothing
                End If
              End If

              ' If reading an error line has completed
              If readErrorLineTask IsNot Nothing AndAlso readErrorLineTask.IsCompleted Then
                ' Get the line
                line = readErrorLineTask.Result

                ' If we have a line
                If line IsNot Nothing Then
                  ' Output it to the console
                  Console.WriteLine(line)

                  ' Read another line
                  readErrorLineTask = errorReader.ReadLineAsync()
                Else
                  ' We've reached end-of-file. We don't want to read again 
                  readErrorLineTask = Nothing
                End If
              End If
            Loop

'             Because we've processed the output stream until end-of-file, we are sure that
'              the command has completed. No need to wait. 
'
'               However, if accessing the exit status or possible exit signal of the command
'               is important to the application, it is best to wait until the command has signaled it
'               has completed. At that point, the exit information will have been received. 

            ' Wait until the command has completed
            executeCommandSession.SessionCompletedWaitHandle.WaitOne()

'             Some servers will return exit information like the return code and the 'signal'
'              if the command fails. 

            Dim exitStatus As Nullable(Of Integer) = executeCommandSession.ExitStatus
            Console.WriteLine("Command exited with status {0}",If(exitStatus.HasValue, exitStatus.ToString(), "(null)"))

            Dim exitSignal As SSHChannelExitSignal = executeCommandSession.ExitSignal
            If exitSignal IsNot Nothing Then
              Console.WriteLine("Command exited with signal {0}: {1}", exitSignal.SignalName, exitSignal.ErrorMessage)
            End If
          Catch e1 As SSHChannelRequestFailedException
'             This exception is thrown by ExecuteCommandSession.Connect(). The most common
'             * cause is the authenticated user does not have the rights to execute commands
'             * on the server. 
          Finally
            If commandOutputStream IsNot Nothing Then
              commandOutputStream.Close()
              commandOutputStream = Nothing
            End If

            If commandErrorStream IsNot Nothing Then
              commandErrorStream.Close()
              commandErrorStream = Nothing
            End If
          End Try
        End Using

      Finally
        ' Always make sure to disconnect from the server when the connection is no longer needed
        ssh.Disconnect()
      End Try
    End Sub
  End Class
End Namespace
 Send data to standard input

The output of the command can be captured. Here a technique using asynchronous reading is used in order to read output and error text at the same time.

using System.IO;

using Xceed.SSH.Client;
using Xceed.SSH.Core;
using Xceed.SSH.Protocols;
using Xceed.FileSystem;

namespace DocumentationExamples.SSH
{
  public class ExecuteCommandSession2
  {
    public void Example()
    {
      string host = "localhost";
      string username = "normal1";
      string password = "normal1";

      SSHClient ssh = new SSHClient();

      // Connect to the host
      ssh.Connect( host );

      try
      {
        // Log in
        ssh.Authenticate( username, password );

        // The remote command to execute
        string command = "copy Test.txt BackTest.txt /-Y";

        // Create a session that will execute a remote command
        using( ExecuteCommandSession executeCommandSession = new ExecuteCommandSession( ssh ) )
        {
          // Get the command's output stream (standard output/stdout)
          Stream commandOutputStream = executeCommandSession.GetOutputStream();

          // Start executing the specified command on the remote server
          executeCommandSession.Connect( command );

          // Wrap the output stream into a stream reader
          System.IO.StreamReader reader = new System.IO.StreamReader( commandOutputStream );

          string line;

          /* We expect to receive the text

             Overwrite BackTest.txt? (Yes/No/All):

             and then the remote console will wait for input. We can't call ReadLine() to get
             the question since it does not contain a newline, we would wait forever.

             Since we know what we're going to receive, we'll just go-ahead and send the response immediately. */

          // Get the command's input stream (standard input/stdin)
          Stream commandInputStream = executeCommandSession.GetInputStream();

          // Wrap the input stream into a stream writer
          StreamWriter writer = new StreamWriter( commandInputStream );

          // Answer the overwrite query
          writer.WriteLine( "y" );
          writer.Flush();

          // Read the next lines until we reach end-of-file
          while( ( line = reader.ReadLine() ) != null )
          {
            // Output the line
            Console.WriteLine( line );
          }
        }
      }
      catch( SSHChannelRequestFailedException )
      {
        /* This exception is thrown by ExecuteCommandSession.Connect(). The most common
         * cause is the authenticated user does not have the rights to execute commands
         * on the server. */
        }
      finally
      {
        // Always make sure to disconnect from the server when the connection is no longer needed
        ssh.Disconnect();
      }
    }
  }
}
Imports System.IO

Imports Xceed.SSH.Client
Imports Xceed.SSH.Core
Imports Xceed.SSH.Protocols
Imports Xceed.FileSystem

Namespace DocumentationExamples.SSH
  Public Class ExecuteCommandSession2
    Public Sub Example()
      Dim host As String = "localhost"
      Dim username As String = "normal1"
      Dim password As String = "normal1"

      Dim ssh As New SSHClient()

      ' Connect to the host
      ssh.Connect(host)

      Try
        ' Log in
        ssh.Authenticate(username, password)

        ' The remote command to execute
        Dim command As String = "copy Test.txt BackTest.txt /-Y"

        ' Create a session that will execute a remote command
        Using executeCommandSession As New ExecuteCommandSession(ssh)
          ' Get the command's output stream (standard output/stdout)
          Dim commandOutputStream As Stream = executeCommandSession.GetOutputStream()

          ' Start executing the specified command on the remote server
          executeCommandSession.Connect(command)

          ' Wrap the output stream into a stream reader
          Dim reader As New System.IO.StreamReader(commandOutputStream)

          Dim line As String

'           We expect to receive the text
'
'             Overwrite BackTest.txt? (Yes/No/All):
'
'             and then the remote console will wait for input. We can't call ReadLine() to get
'             the question since it does not contain a newline, we would wait forever.
'
'             Since we know what we're going to receive, we'll just go-ahead and send the response immediately. 

          ' Get the command's input stream (standard input/stdin)
          Dim commandInputStream As Stream = executeCommandSession.GetInputStream()

          ' Wrap the input stream into a stream writer
          Dim writer As New StreamWriter(commandInputStream)

          ' Answer the overwrite query
          writer.WriteLine("y")
          writer.Flush()

          ' Read the next lines until we reach end-of-file
          line = reader.ReadLine()
          Do While line IsNot Nothing
            ' Output the line
            Console.WriteLine(line)
            line = reader.ReadLine()
          Loop
        End Using
      Catch e1 As SSHChannelRequestFailedException
'         This exception is thrown by ExecuteCommandSession.Connect(). The most common
'         * cause is the authenticated user does not have the rights to execute commands
'         * on the server. 
      Finally
        ' Always make sure to disconnect from the server when the connection is no longer needed
        ssh.Disconnect()
      End Try
    End Sub
  End Class
End Namespace