0 votes
by (380 points)
edited by

Hello, I have a problem with the "ReadUntilPrompt" function.

Test Environment.

Client libs:

  1. Rebex.Terminal 1.0.5605.1
  2. Rebex.SshShell 1.0.5605.1

Target hosts:

  1. Linux (CentOS, Red Hat, Ubuntu, Debian, OpenSUSE etc.) - any Linux with any version.
  2. VMware ESXi - version 6.0.0, other versions not tested.

Code Example #1:

var client = new Ssh();
client.Connect(host, port);
client.Login(user, password);

var shell = client.StartScripting();

shell.WaitFor(Rebex.TerminalEmulation.ScriptEvent.AnyText);
shell.DetectPrompt();

shell.SendCommand("echo abcdefghijklmnopqrstuvwxyz01234567890123456789abcdefghijklmnopqrstuvwxyz")    
var response = shell.ReadUntilPrompt();

shell.Close();

client.Disconnect();
client.Dispose();

Result:

Linux returns
"abcdefghijklmnopqrstuvwxyz01234567890123456789abcdefghijklmnopqrstuvwxyz\r\n".

It's correct.

But VMware ESXi returns "pqrstuvwxyz\r\nabcdefghijklmnopqrstuvwxyz01234567890123456789abcdefghijklmnopqrstuvwxyz\r\n".

VMware ESXi response has first 15 unnecessary chars - "pqrstuvwxyz\r\n".

That is the problem!

Modifying the example code.

Code Example #2:

var client = new Ssh();
client.Connect(host, port);
client.Login(user, password);

var shell = client.StartScripting();

shell.WaitFor(Rebex.TerminalEmulation.ScriptEvent.AnyText);
shell.DetectPrompt();

shell.Send("echo abcdefghijklmnopqrstuvwxyz01234567890123456789abcdefghijklmnopqrstuvwxyz");
shell.Send(FunctionKey.Enter);
var response = shell.ReadUntil(ScriptEvent.Duration(5000));

shell.Close();

client.Disconnect();
client.Dispose();

Result:

VMware ESXi returns "echo abcdefghijklmnopqrstuvwxyz01234567890123456789abcdefghijklmno\r\r\npqrstuvwxyz\r\nabcdefghijklmnopqrstuvwxyz01234567890123456789abcdefghijklmnopqrstuvwxyz\r\n[root@esxi:~] ", where:

  • "[root@esxi:~] " - first 14 chars are prompt, not present in response.
  • "echo abcdefghijklmnopqrstuvwxyz01234567890123456789abcdefghijklmno" - 66 chars, first part of the input command.

14 + 66 = 80 is equals to virtual terminal columns number default value.

  • "\r\r\n" - 3 chars, virtual terminal wrap line delimiter?
  • "pqrstuvwxyz" - 11 chars, last part of the input command.
  • the command output.

I suppose, that while response is contain the "\r\r\n", the function ReadUntilPrompt can't determine correctly where the command output is started.

I can increase the virtual terminal columns number value, but 1024 is a max value and is not enough for one line shell scripts. Also the sample code works perfectly on any Linux system without modyifing default value.

1 Answer

+1 vote
by (15.2k points)
selected by
 
Best answer

Hi,

thank you for the detailed description of the issue. You guessed it right that the "\r\r\n" in confirmation response of "echo ..." command is the problem.
Terminal works like this:
Everything a user sees on the terminal screen is what the server sent as a response for user actions. So, when a user type a command, e.g. "echo", the terminal does not show the keys pressed down, but the response of the server of those characters.
So on your ESXi server, response for your long command to show to a user (server does not know that the client side is a Scripting class) contains additional "\r\r\n" as a response for "typed" command. I would say that the server is trying to "help" its client side to format the text properly.
But all of your linux server are not "helping" theirs connected clients, so no line breaks are present in those "confirmations", so our Scripting class is not confused.
Now I try to describe how the Scripting class works:
When you use scripting.SendCommand(...) method, it do

scripting.Send("command" + options.NewLine); // adds new line after the command string
scripting.WaitFor(ScriptEvent.Line); // read until new line appears

So it reads the response for the command until first line appears in it. That is why your first example did not work properly when using scripting.SendCommand(...) with your ESXi server.

I would suggest you to write your own SendCommand(Scripting scripting, string command) method, which will be based on our approach, but will check, if the command confirmation response contains additional new lines and if it does, read further line of code, until it reads whole command. It can look similar to this code (feel free to modify it to your needs):

private void SendCommand(Scripting scripting, string command)
{
    // shortcut for the columns count
    int columns = scripting.Terminal.Screen.Columns;
    int confirmationCharacterCount = 0;

    // send a command
    scripting.Send(command);
    // confirm the end of the command (as a human user would do)
    scripting.Send(FunctionKey.Enter);

    // read response until all of the confirmation characters are read
    while (confirmationCharacterCount < command.Length)
    {
        confirmationCharacterCount += scripting.ReadLine().Trim().Length;
    }
    // NOTE: this solution can be a problem when a command contains
    // BACKSPACE character and the server decides 
    // to interpret it (result: send back one character less),
    // so this will consume one line of actual command output.


    // now the command is received and all remaining incoming data
    // are an output of the command
}

Please note that as the NOTE comment is saying, this is somewhat naive implementation of the send command, but without the actual server it is difficult to write it better.

by (380 points)
As there is unlikely, that my input commands will contain the backspace character or other special characters, the solution will work both on an ESXi and on a Linux systems in the most cases.

Thank You!
...