Archived Forum Post

Index of archived forum posts


Understanding SSH Automation

Oct 11 '13 at 08:15

I use Chilkat SSH to start a remote shell by calling ssh.SendReqShell and then run a command by calling ssh.ChannelSendString. I call ChannelReadAndPoll to receive the output, but it never receives the full output. When I test with PuTTY I always receive the full output? Why?


It's important to get a fundamental understanding of what's going on. I'm going to explain it in 3 steps:

1) Interactively typing commands at a Unix/Linux/etc. shell command prompt.
2) The same thing, but using the PuTTY application using an SSH connection.
3) Using a programming API (such as Chilkat)

1) Interactively Typing Commands at a Unix shell command prompt:

When you type a command and press RETURN, the shell runs the command and sends standard output (and stderr) to the terminal. When the command exits, the shell displays the next command prompt (which is also output going to stdout). For example, if you type "ls -al", the shell starts the ls program and it's output is displayed on the terminal. When ls exits, the shell prints the next command prompt. The command prompt always follows after the total amount of output has been received because the shell does not emit the command prompt until the program (ls in this case) exits.

It is important to understand that the terminal program simply provides a bidirectional channel -- it forwards interactively typed keystrokes to the shell program, and it displays any shell/command output on the screen. It does not interpret the input or output character stream -- they are simply characters to be relayed, either from the keyboard to the shell program, or from the shell/command output to the screen. The terminal program does not understand what output belongs to which commands. For example, if you type a shell command and run it as a background process, such as by typing "ls -al &", then the shell program does not wait for the command to complete and immediately emits the next command prompt. Meanwhile, the ls command is running in the background and its output is also being sent to the terminal.

Notice that the terminal program does not know when it has received all of the output from a command. It has no knowledge of commands. For as long as the terminal program is running, it will forward keystrokes to the shell program, and display incoming characters (output from the shell or commands) to its screen.

2) The same thing, but using the PuTTY application using an SSH connection.

PuTTy is a interactive terminal program where the human typed keystrokes are forwarded over a secure SSH channel to a remote shell session. Output from the remote shell commands (as well as the command prompt itself) is sent back to PuTTY, where it is displayed. It's the exact same situation as I've described above:

A) PuTTY has no knowledge of commands -- it's a terminal program providing a bi-directional channel to a remote shell session. It forwards keystrokes to the remote shell session, and displays whatever output it receives.

B) You'll always get the full output w/ PuTTY because of the simple fact that you're sitting in your chair waiting for it. You as a human understand when the full output has been received, but PuTTY doesn't. It's just displaying whatever it receives. It doesn't know about commands or what output is from which command, or anything. To PuTTY, it's just a bi-directional stream of characters.

3) Using a Programming API (such as Chilkat)

The Chilkat SSH API provides the functionality to start a remote shell session, send commands, and receive output. When a command is sent via a method such as ChannelSendString, the characters are seen by the remote shell as keystrokes typed at a keyboard. The remote shell has no way of knowing that it's not a human typing the commands. This is why you cannot simply pass the string "ls" to ChannelSendString. It would be like a human typing "ls", but never pressing RETURN. The CRLF (or bare LF) chars must be sent so that the shell executes the command.

Once the remote shell begins executing a command, the behavior is exactly the same as described above. Output is sent back (through the SSH channel in this case) to the terminal -- but there is no terminal program in this case. Instead it's your application using Chilkat SSH. The various methods available for reading the SSH channel, such as ChannelReadAndPoll, provide a means for reading the incoming characters. However, there is no way to associate incoming characters with commands, or to know when the full command output has been received. Let me say this another way: There is nothing in the SSH protocol, or in the Chilkat API, that can inherently add structure to the incoming character stream. This must be handled by the application. An application would need to invent some sort of scheme to identify when the full command output has been received. For example:

A) Use a remote shell account where the command prompt is an easy to identify marker, such as "---- READY ----". If your app does not send backgrounded commands (i.e. commands that end with "&", such as "ls -al &") Then your program can send a command and then read the SSH channel until it receives the next command prompt. The full output of a command is assured to have been received.

B) Send a remote command that also echoes a Done marker. For example:

ls -al; echo "--- DONE ---"

Your program can then read the SSH channel until it receives the "--- DONE ---".

Finally, a note about Chilkat SSH methods that receive remote command output. They differ only in conditions that cause the method to stop reading and return whatever it may have received. Remember: there is no way to know when all of the output has been received -- this is something your app must do. If not all of the command output is received, then your app must continue reading the channel by making additional calls to Chilkat receiving methods. Here is a short summary of the methods and conditions for return:

1) ChannelPoll -- Provides a means to quickly check for available output. The pollTimeoutMs is passed as an argument. The method will stop trying to read after pollTimeoutMs milliseconds.

2) ChannelRead -- Same as ChannelPoll, but the timeout value used is the IdleTimeoutMs property setting.

3) ChannelReadAndPoll -- A combination of the two above. The idea is that you're running a remote command where there may be some delay before output begins, and then once it begins you expect it to be steady. The IdleTimeoutMs property setting is used for the timeout until the first output appears, and then the pollTimeoutMs argument is used as output continues to be received.

4) ChannelReadAndPoll2 -- Same as ChannelReadAndPoll, but returns after a max number of bytes has been received.

5) ChannelReceiveToClose -- Receives channel data until the channel is closed by the server.

6) ChannelReceiveUntilMatch -- Receives channel data until a match string appears.

7) ChannelReceiveUntilMatchN -- Receives channel data until any of N match strings appear.