Shell functionlity

Coordinator
Feb 10, 2012 at 1:34 AM

Hi all,

 

I saw quite a few problems and questions regarding the Shell functionality. I didn't touch this feature pretty much from the day I created it so this might explain why its so buggy. I also created it cause I wanted to try and see what shell request does and, well, pretty much left it like that.

 

I see that now its time to review it and make it more workable and bug free.

It seems what I need to do is to create some kind of stream that works and can be used to read and write data into the shell stream.

 

I want you to use this discussion to report any feature requests or usage scenarios that I cant think of right now. It might be an easier implement it now then later to incorporate it into existing version.

 

Thanks,

Oleg

Feb 10, 2012 at 1:37 AM

Right on Oleg, thanks a bunch!!!

Trying to use the Shell method to be able to:

1. Read and work with the output stream from the session

2. Send multiple commands through an input stream at different times

3. Keep the session alive and interactive (for #2)

Thanks again! Much appreciated!

Feb 10, 2012 at 6:30 AM
Edited Feb 10, 2012 at 8:42 AM

I ended up having to use the shell functionality not necessarily because I wanted to, I would have much preferred the SSHCommand methods; however the server that I am connecting too will allow one command, then disconnects the entire session. I'm close to positive that it's a bug in the vendors code, and equally positive that it will never be fixed.

To that end I am stuck using the a shell, or having to re-connect to each host many many times in order to execute a string of commands.

After spending a bunch of time getting everything work, what I basically ended up with is a badly written hard coded version of an expect script. Your post got me thinking that, really, if you are connecting with a shell it is because you want to do some commands, look out the output, then do some more commands - sometimes based on the output of the last commands.

Perhaps some sort of expect type pipe stream would be a really good way to handle things.

 

Shell.WriteLine("show version"); 
Shell.WriteLine(); string output=null; bool results = Shell.Expect(out results, "^MyRouter(#|>)$", 60) if(results) Console.WriteLine(output);

The more I think about it the more I should have done something like that in the first place....

 

I also THINK there is a bug in the PipeStream library in the Read function. It blocks until it has sufficient data to completely fill the read buffer. This doesn't in practice seem to work particularly well, at least for me. In looking through the pipe steam object it blocks whenever a Write() call is done, so if there is data in the buffer there is almost usually a lot of data in the buffer. I ended up changing it to block until there was at least one byte in the buffer.

Thank you for this awesome library. I would like to buy you a beer if you have a PayPal account or something.

Feb 10, 2012 at 7:43 AM
Edited Feb 10, 2012 at 7:47 AM

One idea would be to take advantage of .nets event system. the shell class can be defined to have a start, stop and write, writeline commands. Start would start the shell and stop would disconnect and destroy. execute would check to make sure shell is started and then execute a line of code over shell.

As for returning data, if events are fired everytime output is recived, you can then put your parsing code in a seperate function. Optionaly maybe output can be read using shell.read or shell.readline simular to the write commands.

So example, end use can use it something like this (VB Example): 

Dim WithEvents Shell = new Ranci.SSHNet.Shell()

Private Sub StartStopShell()

  Shell.start(ConnectionInfo)

  Shell.writeline("ls")
  Shell.writeline("sudo ls -sl")
  Shell.writeline("echo ""Testing""")

  Shell.Stop()

End Sub

Private Sub GetOutput(ByVal sender As System.Object, ByVal message as string) Handles Shell.OuputEvent

  Console.Write(message)

End Sub

Of course you probably wouldnt have the start, stop and write commands all in one function, but for basic test should sufice.

Feb 10, 2012 at 9:09 AM

Also I should mention, Most people seem to be using shell becuase they cannot use some commands like sudo or su correctly. Would it be easier to implement this functionality into runcommand instead?

Coordinator
Feb 10, 2012 at 2:51 PM

Hi, thank you all for reply,

sbonnick, I cant incorporate it into command since it issues a different request.

 

What I am doing now is working on something like that:

            using (var ssh = new SshClient(connectionInfo))
            {
                ssh.Connect();

                var stream = ssh.CreateShellStream("xterm", 80, 24, 800, 600, "", 1024);

                var reader = new StreamReader(stream);
                var writer = new StreamWriter(stream);

                var text = reader.ReadToEnd();
                writer.WriteLine("ls -l");
                text = reader.ReadToEnd();

                ssh.Disconnect();
            }

I want to create a ShellStream that can be used as in example above.

Its obviously doesn't work yet since I am currently working on it but I hope I will have something workable by the end of the day so you guys can take a look at it give me a feedback. Of course I am not sure yet that this is what I will eventually check in but this is my goal for right now

 

Thanks,

Oleg

Coordinator
Feb 10, 2012 at 4:28 PM

I just checked code which has a new way to work with the shell.

Please take a look and let me know what problems or other features needed.

Here how I used it in my test:

			using (var ssh = new SshClient(connectionInfo))
			{
				ssh.Connect();

				using (var stream = ssh.CreateShellStream("xterm", 80, 24, 800, 600, "", 1024))
				{
					var reader = new StreamReader(stream);
					var writer = new StreamWriter(stream);
					writer.AutoFlush = true;

					while (!stream.DataAvailable)
						stream.DataAvailableWaitHandle.WaitOne();

					var text = reader.ReadToEnd();
					Console.Write(text);

					while (!stream.DataAvailable)
						stream.DataAvailableWaitHandle.WaitOne();

					text = reader.ReadToEnd();
					Console.Write(text);

					writer.WriteLine("sudo ufw status");
					writer.WriteLine("PASSWORD");

					while (!stream.DataAvailable)
						stream.DataAvailableWaitHandle.WaitOne();

					text = reader.ReadToEnd();
					Console.Write(text);
				}

				ssh.Disconnect();
			}

 

Thanks,

Oleg

Feb 10, 2012 at 7:26 PM

Wow. Right on Oleg!

Any chance we could get a VB binary of the newest revision?

Thanks again for your support and the amazing turnaround on the feature requests!

Feb 10, 2012 at 7:29 PM

Hey Oleg,


Your awesome for getting this done so quickly!!! 

and yes, VB binary would be awesome if possible!!!

 

Thank you for all the help!

Coordinator
Feb 10, 2012 at 7:34 PM

Yea, no problem.

As far as VB, I am not a VB person so it would be hard for me to do but you can use current assembly in your VB project and it should work just fine.

By the way, I am making more changes so please dont take it as last stable version, it also buggy yet, for example if it cannot open channel it will just hang and wont even throw an exception.

Thanks,

Oleg

Feb 10, 2012 at 8:11 PM
Edited Feb 10, 2012 at 11:18 PM

Hey Oleg,

Compiled the 14316 assembly and dropped it into my VB project.

I had a couple warnings and an error when I built the solution:

10 Warnings - Missing XML comments for publicly visible type or members
1 Error - The type or namespace name 'Tasks' does not exist in the namespace 'System.Threading' (are you missing an assembly reference?)

I also seem to be running into the situation you described above where my current implementation hangs during:

While Not SSHStreamClient.DataAvailable
    SSHStreamClient.DataAvailableWaitHandle.WaitOne()
End While


Also, would it be possible to have the ability for the Stream to wait for a return of the the shell prompt?

Something like:

 

SSHStreamClient.WaitForPrompt()

 

Thanks again!

Coordinator
Feb 10, 2012 at 8:41 PM

Hi,

I thought about and I am looking into whether its possible to identify shell prompt.

If I can do that I would defiantly will incorporate that feature.

The reason it hangs for you now is because currently there is no input coming back so it waits.

The hanging, that I mentioned earlier, can happen if you mess with some parameters but by default it all should work fine.

 

I'll probably will keep looking into next week, about to leave here.

 

Also, I would ignore XML warnings and the error, are you using .NET 3.5 ?, since there are no tasks there?

 

Hope it helps,

Oleg

Feb 10, 2012 at 9:35 PM
Edited Feb 10, 2012 at 9:39 PM

RE: Also, would it be possible to have the ability for the Stream to wait for a return of the the shell prompt?

I gave this topic a lot of thought last night and I don't think it would be terribly difficult to implement an expect (http://en.wikipedia.org/wiki/Expect) pipe stream as I suggested above. This could also elegantly address the issue of waiting for the command prompt as illustrated above.

It seems like it might make more sense to leave the shell as it is and just allow the expect pipe stream reader to be attached to the output stream of the shell.

e.g. something like this in the case of getting the results of some command after sudo'ing:

 

using (var ssh = new SshClient(connectionInfo))
{
  ssh.Connect();

  PipeStream input = new PipeStream();  
  StreamWriter inputSW = new StreamWriter(input);
  
  ExpectStreamReader output = new ExpectStreamReader()
  var shell = ssh.CreateShell(input, output, output, "xterm", 80, 24, 800, 600, "")
  
  bool outputResults;
  string[] outputBuffer = null
  
  output.Clear();
  inputSW.WriteLine();

  // Wait for a  $ at the end of a shell prompt, timeout after 30s
  output.Expect("\\$$", out outputBuffer, 30 * 1000);
  if(!outputResults)
    throw new InvalidDataException("Timed out waiting for shell prompt");
    
  inputSW.WriteLine("sudo su -");
  // Wait for a password prompt, timeout after 30s
  outputResults = output.Expect("^(?i)Password:", out outputBuffer, 30 * 1000);
  if(!outputResults)
    throw new InvalidDataException("Timed out waiting for password prompt");
  
  // output the password
  inputSW.WriteLine("password1234");
  
  // wait for the root shell prompt or a password error message
  outputResults = output.Expect("#$|^Sorry, try again\\.$", out outputBuffer, 30 * 1000);
  if(!outputResults)
    throw new InvalidDataException("Timed out waiting to sudo");
    
  // Check to see if the output contains Sorry, try again
  if(outputBuffer.Contains("Sorry, try again"))
    throw new InvalidDataException("Invalid password");
  
  inputSW.WriteLine("uptime");
  // Wait for a # at the end of a shell prompt, timeout after 30s
  output.Expect("#$", out outputBuffer, 30 * 1000);
  if(!outputResults)
    throw new InvalidDataException("Invalid response from uptime");
    
  ssh.Disconnect();
  
  return(outputBuffer.Join("\n"));
}

 

There would have to be special considering for matching where a newline has not yet been received, but I think it could work nicely. I'd be willing to put something together tonight if there was any interest in this type of approach. Please check out the Expect wikipedia link, I think it explains the concept better then the above.

 


Feb 10, 2012 at 10:53 PM

The expect idea would be great!

Would like to basically be able automate processes that would involve some logic between commands which are dependent on the output received from the previous commands.

Looking to open files on remote server, pull data from files, use that data/information for subsequent commands, perform file/folder manipulation etc...

Thanks Oleg and Giant, your help and ideas are greatly appreciated!

Feb 10, 2012 at 11:13 PM

Yes i agree, expect would be AWESOME!!!

 

I need to do basically the same things Destroyer mentioned with my sessions also.

Feb 10, 2012 at 11:58 PM
Edited Feb 11, 2012 at 12:00 AM

Just to add:

From the latest revision you provided Oleg and in regard to the previously mentioned issue.

Using SSH_Client = New SshClient(CurrentHostAddress, CurrentUser, New PrivateKeyFile(File.OpenRead(KeyFile), TempPassword))

	SSH_Client.Connect()

	Using SSHStreamClient = SSH_Client.CreateShellStream("xterm", 80, 24, 800, 600, "", 1024)
		Dim InputWriter = New StreamWriter(SSHStreamClient)
		Dim OutputReader = New StreamReader(SSHStreamClient)

		InputWriter.AutoFlush = True

		While Not SSHStreamClient.DataAvailable
			SSHStreamClient.DataAvailableWaitHandle.WaitOne()
		End While

		Dim OutBuffer = OutputReader.ReadToEnd

		While SSHStreamClient.DataAvailable
			OutBuffer = OutputReader.ReadToEnd
			Console.Write(OutBuffer)
		End While

		While Not SSHStreamClient.DataAvailable
			SSHStreamClient.DataAvailableWaitHandle.WaitOne()
		End While

		While SSHStreamClient.DataAvailable
			OutBuffer = OutputReader.ReadToEnd
			Console.Write(OutBuffer)
		End While

		InputWriter.WriteLine("ls -l \r\n" & vbCrLf)

		While Not SSHStreamClient.DataAvailable
			SSHStreamClient.DataAvailableWaitHandle.WaitOne()
		End While

		While SSHStreamClient.DataAvailable
			OutBuffer = OutputReader.ReadToEnd
			Console.Write(OutBuffer)
		End While

	End Using

	SSH_Client.Disconnect()
	
End Using

 


It appears to keep hanging at the While Not loop during the second loop of the while.

		While Not SSHStreamClient.DataAvailable
			SSHStreamClient.DataAvailableWaitHandle.WaitOne()
		End While

 

Also would the While loops with the Output buffer I have above work as I think they should?

 

		While SSHStreamClient.DataAvailable
			OutBuffer = OutputReader.ReadToEnd
			Console.Write(OutBuffer)
		End While



Thanks!!

Coordinator
Feb 11, 2012 at 6:24 PM

ok,

 

Thanks for input guys.

 

I will look into expect functionality and see what I can do. I dont know how much time I will have next week but wil try to do it as soon as possible.

 

As far as hanging, well, this is something I am trying to figure out myself yet.

In some case it worked, some other not, so may be if I implement expect, then there will be no need for any waiting. Just a thought.

 

Thanks again,

Oleg

Feb 12, 2012 at 3:09 AM
Edited Feb 12, 2012 at 7:26 AM

I was feeling motivated to put something together, it still needs some work/cleanup, some of the naming semantics are wrong & awkward, I'm sure it also has some bugs.

I'm also not sure I like how I'm:

  • Handling data that has been received, but does not yet have a trailing newline.
  • Returning data, would it make more sense to return a big multiline string then the array of strings that each represent one line. There are some pro/con to each.
  • There are a bunch of helper versions of ReadLines that try to make things easier, but they are not very consistent.
  • Needs the ability to set a maximum buffer size and block on the WriteFunction until the current buffer falls under that - which I think might complicate the locking aside from the fact that it is expensive to consistently determine the buffer size. Since things work on a line-by-line basis it might be possible to track the length of the strings when they are added and removed from the line buffer, but would have to be very careful not to miss some odd case.
  • How/if to handle non-ASCII encoding

It does appear to function as a proof of concept however.... It also looks way neater then how I handled this last time.

I uploaded the class here: http://www.filedropper.com/expectpipestream Is there a better place to put this stuff?

A somewhat verbose sample on how to sudo to root, and grab the contents of the /etc/shadow- file:

StreamWriter inputPipeStream = new StreamWriter(new PipeStream());
inputPipeStream.AutoFlush = true;
ExpectPipeStream outputPipeStream = new ExpectPipeStream();

SshClient sshClient = new SshClient("CentOS", "testuser", "mypassword123");
sshClient.Connect();
Shell sshShell = sshClient.CreateShell(inputPipeStream.BaseStream, outputPipeStream, outputPipeStream, "", 0, 0, 0, 0, "");
sshShell.Start();

// Define the strings to match for
Regex userPromptMatch = new Regex("^\\[testuser@CentOS ~\\]\\$ $"); // something that looks like [testuser@CentOS ~]$
Regex rootPromptMatch = new Regex("^\\[root@CentOS ~\\]# $"); // something that looks like [root@CentOS ~]#
Regex sudoPasswordPromptMatch = new Regex("^\\[sudo\\] password for testuser: $");

string[] buffer;
Regex matchedPattern;

// Wait for the user shell prompt
buffer = outputPipeStream.ReadLines(userPromptMatch, 5000, true);
if (buffer == null)
    throw new InvalidDataException("timeout waiting for prompt");

// Switch to root
inputPipeStream.WriteLine("sudo su -");
buffer = outputPipeStream.ReadLines(out matchedPattern, 5000, true, rootPromptMatch, sudoPasswordPromptMatch);
if (buffer == null)
    throw new InvalidDataException("timeout waiting for sudo su - prompt");

// Check if we got a sudo prompt, if so send the password
if (matchedPattern == sudoPasswordPromptMatch)
{
    inputPipeStream.WriteLine("mypassword123");
    buffer = outputPipeStream.ReadLines(rootPromptMatch, 5000, true);
    if(buffer == null)
        throw new InvalidDataException("sudo password not accepted");
}

// Clears anything that may still be in the buffer
outputPipeStream.Clear();

// Get the contents of the shadow file
inputPipeStream.WriteLine("cat /etc/shadow-");
buffer = outputPipeStream.ReadLines(rootPromptMatch, 5000, true);
if(buffer == null)
    throw new InvalidDataException("failed to get shadow- file contents");

// Cleanup the output, there are better ways...
List<string> resultsTemp = new List<string>(buffer);
resultsTemp.RemoveAt(0); // remove the first line, which is the command we typed
resultsTemp.RemoveAt(resultsTemp.Count - 1); // remove the last line, which is the prompt
string results = string.Join("\n", resultsTemp); // merge the results into a single string


sshClient.Disconnect();

 

There is also a ReadLine(), ReadLine(int timeout), and TryReadLine() method if you only want one line at a time.

Coordinator
Feb 13, 2012 at 12:56 PM

Thanks for you input.

 

I am not sure when I will be able to implement all or some of those request since I will be busy this week with another project, but I will try to free up some time for this later this week.

The only thing I want to see if I can do really quick now is to implement Expect functionality very quick and then check in the code.

I am sure it will be buggy still and missing some features so if anybody want to submit me a patch I will be happy to apply it or if any one wants to work on Shell feature, please let me know and I can add you as a developer to make it easy to check in the changes.

 

Thanks,

Oleg

Feb 13, 2012 at 6:40 PM

Oleg,

I'd be happy to spend some time on it if you want to sign me up as a developer; however I also don't have any more time to work on this until late this week.

Also you sure this belongs in the Shell class, it seems like it make more sense to keep it a bit more separate, keeping with the current idea of just exposing a stream.

Coordinator
Feb 13, 2012 at 11:28 PM

Hey,

 

I am also wont have any free time until probably later this week, I will be out of town this weekend also, so if you can do anything later that would be good.

I just added you as a developer to this project so you should be able to check in your changes directly, I just ask you to follow the coding convention if you can. In worth case, I'll change it later then.

 

As far as Shell class, I have this feeling that Shell class will become obsolote, since there is no need to start or stop anything, you open ShellStream and when you done with it you close it. I created it as different class so I dont have to worry about back compatibility with Shell class.

Let me know if you have any other questions.

 

Thanks,

Oleg

Feb 14, 2012 at 8:35 AM

Awsome progress guys! I may or may not have the time to add anything in the next few weeks (currently in China for work), but if you need the help I dont mind trying to contribute aswell.

Feb 15, 2012 at 1:41 PM

Hi guys,

First of all, thanks for putting the time and effort into this project! I am trying to integrate it into an existing VB app.. not quite there yet, but almost!

is it possible to get the latest release (with the shell changes) made available as a DLL? I've downloaded the code to compile it myself but the solution wont open in VS 2008 (says it needs a newer version)

 

Thanks!

 

Paul

Coordinator
Feb 16, 2012 at 1:27 PM

Hi Paul,

 

I will release a new version soon, I think once Shell functionality will become more or less stable.

Unfortunately it look like I wont have much time this week and leaving tomorrow for vacation, I probably could get back to it only next week.

Sorry for delay.

 

Thanks.

Oleg

Feb 17, 2012 at 1:08 PM
Edited Feb 17, 2012 at 1:20 PM

 

Hi guys,

 Thanks Oleg, I took your advice from my other thread and posted here!

You might be able to help me out. I know ShellStream is not yet stable, but I have been trying to use it anyway!

There are 2 problems I have at the moment. They may be related but I am not sure! 

My code creates and connects an SSHClient. I then use that client to create a shellstream to execute commands on the host.

Problem 1:

The first time the shell runs, the command works fine. The second time, it times out on the CreateShellStream call. Error is below.



---------------------------
TransactionQuery
---------------------------
An error occurred running the command. Session operation has timed out

 

   at Renci.SshNet.Session.WaitHandle(WaitHandle waitHandle)

   at Renci.SshNet.Channels.Channel.WaitHandle(WaitHandle waitHandle)

   at Renci.SshNet.Channels.ChannelSession.Open()

   at Renci.SshNet.ShellStream..ctor(Session session, String terminalName, UInt32 columns, UInt32 rows, UInt32 width, UInt32 height, Int32 bufferSize, KeyValuePair`2[] terminalModeValues)

   at Renci.SshNet.SshClient.CreateShellStream(String terminalName, UInt32 columns, UInt32 rows, UInt32 width, UInt32 height, Int32 bufferSize, KeyValuePair`2[] terminalModeValues)

   at TransactionQueryNS.SSHhandler.RunCommandInShell(String commandToRun) in C:\Tandem\TandemExec\TandemExec\SSHhandler\SSHhandler.vb:line 414

   at TransactionQueryNS.SSHhandler.RunCommand(String taclCommand, Boolean useShell) in C:\Tandem\TandemExec\TandemExec\SSHhandler\SSHhandler.vb:line 347

   at TransactionQueryNS.TransactionQueryMain.Execute() in C:\Tandem\TandemExec\TandemExec\TransactionQuery.vb:line 1320
---------------------------
OK  
---------------------------



Problem 2:

When dubugging, after I send the command to the host (at line writer.WriteLine(commandToRun) ) my fucntion appears to just exit.  it's not throwing an error and a value is being returned but it is not stepping into the bit of close to close the sream... no idea why.

 

I am not sure if my calls are all correct... they might not be. Also, I have no idea what to use for the Terminal Mode KeyValuePair... I just made something up... this may be part of the problem!

 

Any ideas? Thanks!

 

 

Paul

 

    Private Function RunCommandInShell(ByVal commandToRun As String) As String

        Dim termkvp As KeyValuePair(Of Common.TerminalModes, UInteger) = _
                    (New KeyValuePair(Of Common.TerminalModes, UInteger)(Common.TerminalModes.ECHO, 53))
        Dim reader As StreamReader
        Dim writer As StreamWriter
        Dim sshShellStream As ShellStream


        Dim returnText As String = ""


        Try

            If Not clientSSH.IsConnected Then
                clientSSH.Connect()

            End If
            sshShellStream = clientSSH.CreateShellStream("TACLShellStream", 80, 24, 800, 600, 10000000, termkvp)

            reader = New StreamReader(sshShellStream)
            writer = New StreamWriter(sshShellStream)
            writer.AutoFlush = True

            ' gotta read the initial crap from the stream
            While Not (sshShellStream.DataAvailable)
                sshShellStream.DataAvailableWaitHandle.WaitOne()
            End While

            While sshShellStream.DataAvailable
                returnText = returnText + reader.ReadToEnd()
            End While


            ' Change prompt on the host so we have a string to wait for
            writer.WriteLine("export PS1=""" + TransactionQueryMain.SSHPrompt + """")

            ' The promt change message and new line should now be on the stream.
            ' we wanna read past them and clear out the stream before we run the command
            While Not (sshShellStream.DataAvailable)
                sshShellStream.DataAvailableWaitHandle.WaitOne()
            End While

            While sshShellStream.DataAvailable
                returnText = returnText + reader.ReadToEnd()
            End While


            ' Write the command
            writer.WriteLine(commandToRun)

            'Expect our new prompt. Wait up to 5 seconds
            sshShellStream.Expect(TransactionQueryMain.SSHPrompt, TimeSpan.FromSeconds(5))


            ' Read to end of stream
            returnText = returnText + reader.ReadToEnd()

        Catch
            Throw

        End Try





        Try

            sshShellStream.Close()
            sshShellStream.Dispose()

        Catch ex As Exception
            ' Dont do anything here... 
            ' if these fail, garbage collection will (hopefully) clean them up
        End Try

        RunCommandInShell = returnText



    End Function

 

 

 

 

 

Feb 18, 2012 at 11:48 PM

I just checked in a new version of the ShellStream class. It still needs some work in these specific areas:

  1. Documentation
  2. There is currently no enforceable limit on the size of the read buffer. If ya go and generate an massive amount of output without reading it you will eventually run out of memory. After considering a number of different approaches I'm thinking setting MaxLineNumber limit would be very reasonably and fast to implement. It may still be possible to limit on the total number of bytes.
  3. I would really like to have some sort of adjustable timeout for allowing the current partial line to complete (by receiving a new line). Perhaps like 100ms or something. This would prevent the occasional instance where the current partial line still has data inbound. Just need to spend some time on the locking to make this work
  4. I'm currently handling all data as ASCII, is that correct? Presumably you can have Unicode SSH sessions right? How is this handled throughout the rest of the library?
  5. Passing in the terminal information in the below manner seems awkward - and what does the '53' signify? Thinking this should be improved.

There are functions for blocking for and reading a line - ExpectLine(), reading a line if it happens to be available - TryExpectLine, blocking for and reading specific text / patterns - Expect, and reading the entire buffer - ReadAll.

A quick example:

SshClient sshClient = new SshClient("CentOS", "testuser", "mypassword123");
sshClient.Connect();

KeyValuePair<Renci.SshNet.Common.TerminalModes, uint> termkvp = new KeyValuePair<Renci.SshNet.Common.TerminalModes, uint>(Renci.SshNet.Common.TerminalModes.ECHO, 53);

ShellStream shellStream = sshClient.CreateShellStream("", 0, 0, 0, 0, 0, termkvp);
shellStream.DataReceived += new ShellStream.DataReceivedHandler(shellStream_DataReceived);

// Define the strings to match for
Regex userPromptMatch = new Regex("^" + Regex.Escape("[testuser@CentOS ~]$ ") + "$"); // something that looks like [testuser@CentOS ~]$
Regex rootPromptMatch = new Regex("^" + Regex.Escape("[root@CentOS ~]# ") + "$"); // something that looks like [root@CentOS ~]#
Regex sudoPasswordPromptMatch = new Regex("^" + Regex.Escape("[sudo] password for testuser: ") + "$");

Regex matchedPattern;

string buffer;

// Wait for the user shell prompt
buffer = shellStream.Expect(userPromptMatch, 5000, LineMatchModes.Partial);
if (buffer == null)
    throw new InvalidDataException("timeout waiting for prompt");

// Switch to root
shellStream.WriteLine("sudo su -");
buffer = shellStream.Expect(out matchedPattern, 5000, LineMatchModes.Partial, rootPromptMatch, sudoPasswordPromptMatch);
if (buffer == null)
    throw new InvalidDataException("timeout waiting for sudo su - prompt");

// Check if we got a sudo prompt, if so send the password
if (matchedPattern == sudoPasswordPromptMatch)
{
    shellStream.WriteLine("mypassword123");
    buffer = shellStream.Expect(rootPromptMatch, 5000, LineMatchModes.Partial);
    if (buffer == null)
        throw new InvalidDataException("sudo password not accepted");
}

// Get the contents of the shadow file
shellStream.WriteLine("cat /etc/shadow-");
buffer = shellStream.Expect(rootPromptMatch, 5000, LineMatchModes.Partial);
if (buffer == null)
    throw new InvalidDataException("failed to get shadow- file contents");

sshClient.Disconnect();

Something else I have noticed is that either the move to the ShellStream or some other changes appears to cause the SSH Shell to start MUCH faster then previous versions!

I'll try to get some time tomorrow or next week to wrap things up.

Any feedback about form or function would be helpful.

Feb 20, 2012 at 7:05 AM

Hi, I have just downloaded the latest source code, but am getting build errors. Do I need to do something different with this build? Build 14363 builds OK for me...

I am usign VS2010 express... but have no experience with c#

 

cheers,

Paul.

 

Error 1 'System.Text.StringBuilder' does not contain a definition for 'Clear' and no extension method 'Clear' accepting a first argument of type 'System.Text.StringBuilder' could be found (are you missing a using directive or an assembly reference?) C:\tmp\Renci.SshClient\Renci.SshNet\ShellStream.cs 102 43 Renci.SshNet.NET35

Error 2 'System.Text.StringBuilder' does not contain a definition for 'Clear' and no extension method 'Clear' accepting a first argument of type 'System.Text.StringBuilder' could be found (are you missing a using directive or an assembly  reference?) C:\tmp\Renci.SshClient\Renci.SshNet\ShellStream.cs 250 31 Renci.SshNet.NET35

Error 3 The best overloaded method match for 'string.Join(string, string[])' has some invalid arguments C:\tmp\Renci.SshClient\Renci.SshNet\ShellStream.cs 353 49 Renci.SshNet.NET35

Error 4 Argument 2: cannot convert from 'System.Collections.Generic.List<string>' to 'string[]' C:\tmp\Renci.SshClient\Renci.SshNet\ShellStream.cs 353 67 Renci.SshNet.NET35

Coordinator
Feb 20, 2012 at 2:49 PM

Hi,

 

I will try to correct it today.

 

The problem that you have occur in .NET 3.5 and Silverlight versions.

If you unload those projects from the solution it should compile just fine.

 

Thanks,

Oleg

Feb 20, 2012 at 2:55 PM

My fault :(. I was having problems with the non .NET4 projects when I started and dropped them out of the solution - didn't think to add them back in before I checked in the changes. Olegkap, I'm happy to fix my mess when I get home from work tonight, looking quickly at it I don't think it will be that difficult to re-implement the missing methods.

Coordinator
Feb 21, 2012 at 3:34 AM

i all,

 

I just checked in another review of ShellStream class implemention which I hope has all requests in.

Here a usage examples that I used and works fine for me:

 

 

using (var stream = ssh.CreateShellStream("dumb", 80, 24, 800, 600, 1024))
{
    //  Simple line read
    var line = stream.ReadLine();
    Console.WriteLine(line);

	stream.Expect(new ExpectAction[] {
		new ExpectAction("oleg@cn-test4:~$", (s)=>{Console.Write(s);})
	});

        //  Sending "ls -l" command
	stream.WriteLine("ls -l");
	var r = stream.Expect("oleg@cn-test4:~$");
	Console.Write(r);

        //  Sending sudo command that might or might not require a password, therefor here is an option for multiple response
	stream.WriteLine("sudo ufw status");
        stream.Expect("sudo ufw status");   //  Ensure that command has been processed by the shell
        stream.Expect(
		new ExpectAction("[sudo] password for oleg:", (s)=>{
                Console.Write(s);
                stream.WriteLine(password);
            }),
		new ExpectAction("oleg@cn-test4:~$", (s)=>{
                Console.Write(s);
        })
	);

        //  Same command but running few times to ensure stre3am is still open and works as expected.
	var response = stream.Expect("oleg@cn-test4:~$");                   
	for (int i = 0; i < 10; i++)
	{
		Console.Write(response);
		stream.WriteLine("sudo ufw status");
                stream.Expect("sudo ufw status");   //  Ensure that command has been processed by the shell
                stream.Expect(
                       new ExpectAction("[sudo] password for oleg:", (s) =>
                       {
                         Console.Write(s);
                         stream.WriteLine(password);
                      }),
                      new ExpectAction("oleg@cn-test4:~$", (s) =>
                     {
                         Console.Write(s);
                     })
        );
        response = stream.Expect("oleg@cn-test4:~$");
	}

}

 

 

 

Please let me know if anything else is needed or need to be fixed.

I still want to make this class to behave like a stream so I will add more or probably will refactor some internals but as far as public interface, I think it should stay the same.

Please let me know your thought.

 

Thanks,

Oleg

Feb 21, 2012 at 6:57 AM

Hi Oleg,

 

Thanks a million for the update! From my initial build of the DLL + integration to my project, all looks OK. I am able to create a shell after the initial connect and re use it to run multiple commands. The simpler interface (not having to declare read/write streams or provide the terminal info) is much easier to deal with!

2 things you may look at at some point if you get a chance.

1. the ShellStream.DataAvailable always returns true even if you have read to the end of data. I have just taken it out of my code but may be worth fixing  it, or removing it all together if it's no longer required.

2. Would it be possible to provde a "isShellCreated" (or similar) property on ShellStream which would return true if the shell is ready for use and false otherwise?

 

Thanks Again!

Paul.

 

 

Feb 22, 2012 at 9:03 PM
Edited Feb 22, 2012 at 10:34 PM

Wow! Impressive guys, thanks for all your hard work.

Currently trying to implement build 14999 into my VB.net project and I have hit a bit of an issue.

How does one use the ExpectAction function in VB??

 

 

stream.Expect(new ExpectAction[] {
		new ExpectAction("oleg@cn-test4:~$", (s)=>{Console.Write(s);})
	});
	


stream.Expect( new ExpectAction("[sudo] password for oleg:", (s)=>{ Console.Write(s); stream.WriteLine(password); }), new ExpectAction("oleg@cn-test4:~$", (s)=>{ Console.Write(s); }) );

 

 

Any ideas or help would be greatly appreciated!

 

---

EDIT:

So I got the expect statements working, however it seems that reading from the stream has some issues.
When you call the stream.Readline it seems to permanently lock the _dataBuffer stream, at which point any further reads do not happen.

Feb 22, 2012 at 11:47 PM
Edited Feb 22, 2012 at 11:48 PM

Also, it seems that I am unable to tunnel to another SSH box within my current SSH session, it returns that the private key is not valid.

(It appears to not pass off the key used in the initial connection.)

Example:

 

Client = New SSHClient(ADDRESS1, User, New PrivateKeyFiles(File.OpenRead(OpenSSHKey), Password))

Using Stream = Client.CreateShellStream("xterm", 80, 24, 800, 600, 1024)

Dim StreamOut = Client.ReadLine

Stream.WriteLine("ssh ADDRESS2")


Additionally, it seems like any time I call any function that uses "lock" the stream does not work correctly.

 

stream.ReadLine
stream.Expect

 

I have to go to the Locals window and watch the actual Client._dataBuffer to see what is actually happening.

Would it be possible to directly read from the dataBuffer?

One last thing, if i do stream.flush and I get an exception that the feature is not implemented.
(would be an awesome feature if it could dump the current buffer to a [user?] variable and then clear itself.)

Feb 23, 2012 at 12:52 PM
Edited Feb 23, 2012 at 12:54 PM

I use readline and expect in the same functions. I can call the fucntions multiple times in the same SSH session.

Below is a bit of some of my functions. Might help you...

cheers,

 

Paul

 

 

 

'private class variables
    Private WithEvents clientSSH As SshClient
    Private WithEvents connectionInfo As PasswordConnectionInfo
    Private WithEvents sshShellStream As ShellStream




' ***** Connect function

' set connection info
connectionInfo = New PasswordConnectionInfo(hostIP, hostPort, userName, userPassword)
connectionInfo.Timeout = TimeSpan.FromSeconds(30)
connectionInfo.RetryAttempts = 3

' connect
clientSSH = New SshClient(connectionInfo)
clientSSH.Connect()

' Create the shell Stream now so we have it available later
sshShellStream = clientSSH.CreateShellStream(shellName, 80, 24, 800, 600, 10000000)




' ***** RunCommandInShell function

' read anything that is on the stream
returnText = returnText + sshShellStream.ReadLine()

' Change prompt on the host so we have a string to wait for

sshShellStream.WriteLine("export PS1=""" + shellPrompt + """")

returnText = returnText + sshShellStream.Expect("export PS1=""" + shellPrompt + """") ' Expect the propt command
returnText = returnText + sshShellStream.Expect(shellPrompt) ' now expect the new prompt


' Write the command to shell Stream
sshShellStream.WriteLine(commandToRun)



'The command has been run, now expect our new prompt
returnText = returnText + sshShellStream.Expect(shellPrompt)

 

 

 

 

Coordinator
Feb 23, 2012 at 12:58 PM
Edited Feb 23, 2012 at 12:59 PM

Hi guys,

thanks for feedback.

 

Paul,

I want to keep DataAvailable and make it work eventually, since I still want to implement it to work like a stream if one desires.

So as DestroyerOfWorlds, Flush function will be implemented and Read and Write should work correctly.

I also planning to replace StringBuilder with some byte array buffer, this is in effort to implement Read, Write and Flush operations. So I will take a look then into locking problem.

 

As far as how to do ExpectAction in VB.NET, I really dont know since I am not a VB person but I am sure it is possible, since ultimatly everything is being translated into CLR.

 

Thanks,

Oleg

Feb 23, 2012 at 3:57 PM
Edited Feb 23, 2012 at 3:57 PM

Wow, new shell stuff!  I think I was one of the first people to use the shell functionailty of tailing live files haha, I wonder if this new stuff fixed that memory leak I spotted a while back?

Thanks for the continued work!

Feb 24, 2012 at 1:01 AM
Edited Feb 24, 2012 at 1:02 AM

Hey All,

As always thanks for the continued development!!

Also, thanks Oleg and Paul for the help with my recent question.

t appears to be working to the point now when I have to tunnel into another ssh host.

Is it currently possible to tunnel to another host using shellstream?

Example: (Parts simplified)

 

connectionInfo = New PrivateKeyConnectionInfo(hostAddress, user, New PrivateKeyFile(File.OpenRead(PathToKeyFile), password))
connectionInfo.Timeout = TimeSpan.FromSeconds(60)
connectionInfo.RetryAttempts = 3

'Connect
Client = New SshClient(connectionInfo)
Client.Connect()

'Create ShellStream
SSH_ShellStream = Client.CreateShellStream("xterm", 80, 24, 800, 600, 102400)


'+++ - Run Commands

'Read the Stream
SSH_Output = SSH_Output + SSH_ShellStream.ReadLine()
Console.WriteLine(SSH_Output)

'Change Prompt on Terminal
SSH_ShellStream.WriteLine("export PS1='" + ShellPrompt + " \W'")

'Expect the Change Prompt command
SSH_Output = SSH_Output + SSH_ShellStream.Expect("export PS1='" + ShellPrompt + " \W'")
Console.WriteLine(SSH_Output)

''' ^ SEE BELOW NOTE ABOUT OUTPUT PER CHARACTER

'Clear the in buffer? Needed? The next command below doesn't work either way
SSH_ShellStream.WriteLine("")

'Connect (tunnel) to second box
SSH_ShellStream.WriteLine("ssh hostAddress2")

 

Also, when watching the output buffer, it creates a new line for each character, is that intended?
(From above comment in code)


prompt~$ prompt~$ P prompt~$ PS prompt~$ PS1 prompt~$ PS1= prompt~$ PS1=' prompt~$ PS1='u prompt~$ PS1='us ... prompt~$ PS1='username$'

 

As always thanks for the amazing library and all the hard work and quick turnarounds!

Coordinator
Feb 24, 2012 at 5:30 PM

Hi,

 

I can exactly see how you can get provided output using provided code but now, its not intended this way.

 

Also, I just finished another review of ShellStream, where now it can behave like a real stream.

 

Here some code example that I used in my testing:

            using (var ssh = new SshClient(connectionInfo))
            {
                ssh.Connect();

                using (var stream = ssh.CreateShellStream("dumb", 80, 24, 800, 600, 1024))
                {
                    var reader = new StreamReader(stream);
                    var writer = new StreamWriter(stream);
                    writer.AutoFlush = true;

                    while (stream.Length == 0)
                    {
                        Thread.Sleep(500);
                    }

                    //  Simple line read
                    var line = reader.ReadLine();
                    while (line != null)
                    {
                        Console.WriteLine(line);
                        line = reader.ReadLine();
                    }

                    writer.WriteLine("ls -l");

                    while (stream.Length == 0)
                    {
                        Thread.Sleep(500);
                    }


                    line = reader.ReadLine();
                    while (line != null)
                    {
                        Console.WriteLine(line);
                        line = reader.ReadLine();
                    }
                }

                ssh.Disconnect();
            }


Thanks,
Oleg

            using (var ssh = new SshClient(connectionInfo))
            {
                ssh.Connect();
 
                using (var stream = ssh.CreateShellStream("dumb"80248006001024))
                {
                    var reader = new StreamReader(stream);
                    var writer = new StreamWriter(stream);
                    writer.AutoFlush = true;
 
                    while (stream.Length == 0)
                    {
                        Thread.Sleep(500);
                    }
 
                    //  Simple line read
                    var line = reader.ReadLine();
                    while (line != null)
                    {
                        Console.WriteLine(line);
                        line = reader.ReadLine();
                    }
 
                    writer.WriteLine("ls -l");
 
                    while (stream.Length == 0)
                    {
                        Thread.Sleep(500);
                    }
 
 
                    line = reader.ReadLine();
                    while (line != null)
                    {
                        Console.WriteLine(line);
                        line = reader.ReadLine();
                    }
                }
 
                ssh.Disconnect();
            }
Feb 25, 2012 at 12:34 AM
Edited Feb 25, 2012 at 12:47 AM

Hey All,

Just compiled the 14604 version and it appears that there may be a problem.

During my code when it hits the Client.Connect() function the program hangs.

Any ideas?

-DoW

 

---

After letting it sit for a while it returns a "System.OutOfMemoryException" error in the Session.NET.cs.

Coordinator
Feb 25, 2012 at 12:38 AM

Ops,

Sorry, I have fix for its localy on my machine but didnt check it in yet.

I just did,

Please check it out.

Thanks,

Oleg

Feb 25, 2012 at 12:52 AM

Awesome, issue no longer occurs Oleg.

And wow... that was fast!! Thanks man.

Feb 27, 2012 at 12:37 PM

Hi guys,

 

would it be possible to provide async functionality for ShellStream.Expect similar to what's available for SSHCommand?

 

I use the async feature to update a progress indicator on my form  so the user knows that long running commands are actually doing soemthing (see snippet below). It would be nice to do something similar for commands running in shell....

 

Cheers,

 

Paul.

 

 

   Dim asynch As System.IAsyncResult


   ' write the command to connection
   tandemCmd = clientSSH.CreateCommand(commandToRun)

   ' Begin executing the command in the background and wait for it to complete
   asynch = tandemCmd.BeginExecute(Nothing, Nothing)

   While Not (asynch.IsCompleted)
        loopCount += 1

        RaiseEvent WorkingCountChanged(Me, New SSHHandlerEventArgs(loopCount))

        System.Threading.Thread.Sleep(500)

  End While

  tandemCmd.EndExecute(asynch)

Coordinator
Feb 27, 2012 at 1:15 PM

Its actually good suggestion,

I guess I forgot about to implement Async methods for everything.

Do you mind open a feature request for this so I dont forget about it, as I will have time to get to it only towards end of the week.

 

Thanks,

Oleg

Feb 27, 2012 at 1:24 PM

hi Oleg,

I cant see how to open a feature request, so I created an item in the Issue Tracker. Hope that's ok!

 

cheers,

 

Paul.

Coordinator
Feb 27, 2012 at 1:35 PM

Yea,

 

No problem, thats ok.

I just want something to remind me what to work on, cause in forum it can get lost and I will eventually forget about it.

 

Thanks,

Oleg

Feb 28, 2012 at 10:50 PM

Hey Oleg,

let me start out with... YOUR FUCKING AWESOME MAN! really appreciate your response time and willingness to check in code fixes

Is there anyway you can add tunneling functionality to the Shell class? Im trying to use my program to communicate with a box that can only be seen if i connect to its server first then ssh to the box i actually want to talk to.

Really appreciate all your help Oleg

Cheers

Coordinator
Feb 29, 2012 at 12:17 AM

Thanks man,

By tunneling you mean port forwarding?

If so you already have it but not in Shell class but in SshClient. Since to create shell you need to create SshClient anyway, therefore it will reuse the same connection and will open a new channel.

 

Hope it helps.

 

Let me know if I misunderstood your question/

 

Thanks,

Oleg

Feb 29, 2012 at 6:34 PM
Edited Feb 29, 2012 at 8:32 PM

I dont think i mean port forwarding, lol...

Basically what i need it to do is create a client and a shell  to connect to our master site and tunnel the connection with a key exchange to one of our destination sites within the same session and use shell stream to manipulate a directory and parse a file to return a specific string.

 

 

Feb 29, 2012 at 9:45 PM

Hey Oleg,

It seems to not return results from a command until the next command is sent

 

 SSH_ShellStream.Expect("cmdprompt")

        writer.WriteLine("cd /blah/blah/" & var1 & "/" & var2)

        While SSH_ShellStream.Length = 0
            Thread.Sleep(500)
            Console.WriteLine("No data available")
        End While

        SSH_Output = SSH_ShellStream.ReadLine()
        Console.Write(SSH_Output)          'no data is output to the console at this time!

        While SSH_ShellStream.Length = 0
            Thread.Sleep(500)
            Console.WriteLine("No data available")
        End While

        SSH_ShellStream.Expect("cmdprompt2 ")
        writer.WriteLine("ls")          

        SSH_Output = SSH_ShellStream.ReadLine()
        Console.Write(SSH_Output)         'output sent to console is from previous cmd

 

Any ideas on how to get a real time output from the stream?

 

Thanks a bunch man!

Cheers

Mar 1, 2012 at 5:20 PM

Hi Oleg (and others)

Many thanks for getting this functionality to work, although I have come across a small problem (very probably of my own making)

 

I am writing a relatively simple app that will control a remote camera via API commands issued via the SSH Shell, such that it will Pan, Tilt and Zoom.

The API Commands are again simple

- issue one command to set the camera in motion in any given direction.

- Then issue a second command to halt the camera movement

 

A Button control is use on a form such that the commands are linked to button control events - MouseDown (to start moving), and MouseUp (to halt).

 

This all works well unless the the user clicks on the button quite quickly and after a couple of clicks, the app enters an infinite loop or wait state.

On Pausing the Code execution, the IDE opens up your SemaphoreLight class, specifically at the following procedure, at the highlighted line: -

        /// <summary>
        /// Blocks the current thread until it can enter the <see cref="SemaphoreLight"/>.
        /// </summary>
        public void Wait()
        {

            lock (this._lock)
            {
                while (this._currentCount < 1)
                {
                    Monitor.Wait(this._lock);
                }

                this._currentCount--;

                Monitor.Pulse(this._lock);
            }
        }

I'm guessing that as the multiple button presses cause multiple commands to be issued in a short time frame, something gets locked up. Is there anything I can do to stop this from happening?

Mar 1, 2012 at 6:08 PM

intercept the button click event and limit with a cooldown

Mar 1, 2012 at 8:05 PM
brokenac3 wrote:

intercept the button click event and limit with a cooldown

Yeah OK, numpty alert, but I don't get terminology :)

Do you mean simply something like a short delay loop in the button click event, or is this something more complex? What is the order of firing of the events of button click, mouse down and mouse up? If the button click event occurs between the other two (i.e. Mouse Down --> Click --> Mouse Up), what about the step between Mouse Up and Mouse down again?

Mar 1, 2012 at 11:07 PM

Its not my code so i dont know whats going on or whats causing your issue, but if your guess about what is causing it is true, you could...

A) buffer the events and send them with a delay ( could be annoying if you flood the buffer, unless you add a timeout)

B) add a cool-down to whatever fires off that event ( you will have to wait (x) time to press the button again )

Mar 2, 2012 at 9:30 AM
Edited Mar 2, 2012 at 2:00 PM
swinster wrote:
Yeah OK, numpty alert, but I don't get terminology :)

Please realise I was talking about ME not you? I'm no full time programmer and just figure out stuff as I go, but re-read what I wrote and realised it might get misinterpreted

 

Still, I have just been playing with this a little more, and even slow user interaction causes the lock up after approximately 5 press and release (i.e. 10 commands issued). When I mean slow, I mean mouse down and release approx 5 seconds apart.

Mar 3, 2012 at 2:04 PM
Edited Mar 3, 2012 at 2:52 PM

Ok, these does seem to be something funny happening when passing commands to the Shell and its NOT as I first though to do with the speed of user issued commands. Basically, the Shell will LOCK on the line of code as show above after I have issued 10 commands. The following snipit show a simple impersonation of user requests with 2 second intervals between each command sent, followed by the procedure that I use to create the shell stream and issue the command.

 

 

        Client = New SshClient(ConnectionInfo)

        Try
            Client.Connect()

            Dim y As Integer = 0
            For x = 1 To 10
                ControlCamera("xcommand cameramove camera:1 direction:right")
                y += 1
                Console.WriteLine("y = " & y)
                Thread.Sleep(2000)
                ControlCamera("xcommand camerahalt camera:1")
                y += 1
                Console.WriteLine("y = " & y)
                Thread.Sleep(2000)
                ControlCamera("xcommand cameramove camera:1 direction:left")
                y += 1
                Console.WriteLine("y = " & y)
                Thread.Sleep(2000)
                ControlCamera("xcommand camerahalt camera:1")
                y += 1
                Console.WriteLine("y =" & y)
                Console.WriteLine("x =" & x)
                Thread.Sleep(2000)
            Next

        Catch ex As Exception
            MsgBox(ex.Message)
        Finally

        End Try


    Public Sub ControlCamera(ByVal xCommand As String)


        Using ShellStream = Client.CreateShellStream("Camera", 80, 24, 800, 600, 1024)

            Dim Reader As StreamReader = New StreamReader(ShellStream)
            Dim Writer As StreamWriter = New StreamWriter(ShellStream)
            Writer.AutoFlush = True

            Writer.WriteLine(xCommand)

        End Using

    End Sub

 

 

Each time I run the code, I get the 10th command issued (on the second iteration of the For..Next loop), but then the application locks as previously described i.e. during the sending of the 11th command string

Any idea?

Mar 3, 2012 at 9:48 PM

Hi,

 

You're creating a new shell stream each time you send a command to your device. Have you tried creating a shellstream in a class/global variable rather than as a local variable? you can then just create a single shell stream after you connect, and issue multiple commands on it. This makes more sense to me since if you were using a 'real' SSH shell, you would simply create 1 connection and type all your commands in 1 after the other.

Worth a shot!

 

cheers,

 

Paul

 

Mar 4, 2012 at 6:39 PM
Edited Mar 4, 2012 at 6:39 PM

Cheers Paul,

Your suggestion is exactly what I do for the main SSH client object, then check the IsConnnected property before I run any code required, and eventually issuing a disconnect and disposing the the SSH Client object. The reason for doing that was simply to save time on the authentication setup, but it was not something I thought about when looking at the ShellStream based on the SSH Client.

I had thought by creating and disposing of the stream within a "Using" block would be the most efficient way to set this up, however, if it's going to cause problems then I will set this up globally too. I'll give this a go tomorrow.

Mar 10, 2012 at 6:07 PM
Edited Mar 11, 2012 at 5:30 PM

Just an update. Setting everything global seems to do the trick. I now do the following 

  • declare both the SSHCLIENT and SHELLSTREAM at the top of the class,
  • One procedure to create the client, connect and create the stream,
  • One procedure to disconnect and destroy the objects
  • One procedure to pass the command into the stream.

All now seems to work as expected. I now need to see if I can get some of the of the reading of the Shell output to figure out what device the user has connected to as the API commands are different for different devices.

Apr 4, 2012 at 12:01 AM

I'm trying to get this working on a Cisco 65xx switch, and can't seem to get past the enable password prompt.  Oleg, if you're not familiar with Cisco, once you connect using the primary username/password, you need to issue the "enable" command to gain some elevated access levels.

Once that's issued, the router will respond with a "Password: " prompt, which requires a password to be entered.  For whatever reason, if I read to the end of the stream prior to entering a password, it's like something is either being entered, or a linefeed is sent across, in which case the Cisco replies with "Bad Password".  Here's the small routine I'm testing with:

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
        Using client = New SshClient("1.1.1.1", "username", "password")
            client.Connect()

            Using sshStream = client.CreateShellStream("dumb", 80, 24, 800, 600, 1024)

                Dim reader = New StreamReader(sshStream)
                Dim writer = New StreamWriter(sshStream)
                writer.AutoFlush = True

                While sshStream.Length = 0
                    Thread.Sleep(500)
                End While

                Response.Write(reader.ReadToEnd)

                writer.WriteLine("enable")

                While sshStream.Length = 0
                    Thread.Sleep(500)
                End While

                Response.Write(reader.ReadToEnd.ToString.Replace(vbCrLf, "<BR>"))
            End Using

            client.Disconnect()
        End Using
    End Sub

 

----

 

Here's what's returned:

test_6506>enable
Password: <--------------- I need to send a password here, yet something is being automatically sent in it's place.
% Access denied

test_6506>

Coordinator
Apr 4, 2012 at 2:05 PM

Hi,

 

Here is an example of using similar command that might require password:

            using (var stream = ssh.CreateShellStream("dumb", 80, 24, 800, 600, 1024))
            {
                //  Sending sudo command that might or might not require a password, therefor here is an option for multiple response
                stream.WriteLine("sudo ufw status");
                stream.Expect("sudo ufw status");   //  Ensure that command has been processed by the shell
                stream.Expect(
                    new ExpectAction("[sudo] password for oleg:", (s) =>
                    {
                        Console.Write(s);
                        stream.WriteLine(password);
                    }),
                    new ExpectAction("oleg@cn-test4:~$", (s) =>
                    {
                        Console.Write(s);
                    })
                );
            }

Hope it helps.

I used this example a while back so I hope it still should work and I didn't change much since then. Well, at least to give you an idea of how to go about it

Thanks,

Oleg

Apr 4, 2012 at 4:50 PM
Hi Oleg,

I'm writing in VB.NET and don't quite understand what the new ExpectAction is doing. As such, I haven't been able to duplicate what you're doing in VB.NET. I'm curious as to why the "Expect" command is needed at all - what is preventing me from reading to the end of the stream, writing the password followed by a crlf, then reading to the end of the stream again to review the response?

-Mark






On Apr 4, 2012, at 7:05 AM, olegkap wrote:

From: olegkap

Hi,


Here is an example of using similar command that might require password:


            using (var stream = ssh.CreateShellStream("dumb", 80, 24, 800, 600, 1024))
            {
                //  Sending sudo command that might or might not require a password, therefor here is an option for multiple response
                stream.WriteLine("sudo ufw status");
                stream.Expect("sudo ufw status");   //  Ensure that command has been processed by the shell
                stream.Expect(
                    new ExpectAction("[sudo] password for oleg:", (s) =>
                    {
                        Console.Write(s);
                        stream.WriteLine(password);
                    }),
                    new ExpectAction("oleg@cn-test4:~$", (s) =>
                    {
                        Console.Write(s);
                    })
                );
            }


Hope it helps.

I used this example a while back so I hope it still should work and I didn't change much since then. Well, at least to give you an idea of how to go about it

Thanks,

Oleg


Apr 6, 2012 at 12:42 PM

Hi Mark,

I suspect that this is something like the "promise" pattern in async programming. So that the program flow is not interupted while waiting for the server to respond.

C.

Apr 9, 2012 at 4:59 AM

Anyone else have any feedback as to how I can get the Expect statements working in VB.Net?  I can't seem to figure it out - I don't know what the (s) => is doing.  Destroyer, you mentioned that you got it working - care to share what you did?

-Mark

Apr 9, 2012 at 11:40 PM
Edited Apr 10, 2012 at 12:03 AM

Hey Mark,

Here is what I have for VB.NET using Expect:
(Some of it might be old or unnecessary)
UPDATE: So it appears that the below doesn't quite work as expected... (buh duh duh).

Dim CurrentUser As String = GetUserName()

Do Until Validpassword = True
	returnedPW = InputBox("Please enter your private key passphrase: ", PasswordTitle)

	If Not returnedPW = Nothing Then
		TempPassword = returnedPW
		Validpassword = True
	End If
Loop

Dim sshAddress1 As String = "ssh.address1.com"
Dim sshAddress2 As String = "ssh.address2.com"

Try
	Using SSHClientInstance = New SshClient(sshAddress1, CurrentUser, New PrivateKeyFile(File.OpenRead("LOCATION_OF_OPENSSH_KEY"), TempPassword))

		SSHClientInstance.Connect()

		Using SSHStreamClient = SSHClientInstance.CreateShellStream("xterm", 80, 24, 800, 600, 1024)

			Dim SSH_Line = SSHStreamClient.ReadLine
			Dim SSH_LineRead = SSHStreamClient.Read
			Dim trCursor1 As String = "[address2~]$"

			Console.WriteLine(SSH_LineRead) 

			SSHStreamClient.WriteLine("pwd")
			
			SSHStreamClient.Expect("pwd")

			Console.WriteLine(SSH_LineRead)

			SSHStreamClient.WriteLine("ssh address2.com")

			SSHStreamClient.Expect(trCursor1)

			Console.WriteLine(SSH_LineRead)
			
		End Using

        SSHClientInstance.Disconnect()
				
Catch SshEx As Exception
	MsgBox(SshEx.ToString)
End Try
Apr 10, 2012 at 12:23 AM
Edited Apr 10, 2012 at 12:24 AM

This version is the working code for expect that we have been using:

 

Dim ssh_Address As String = "ssh.address.com"

Try
	Dim SSH_connectionInfo = New PrivateKeyConnectionInfo(ssh_Address, CurrentUser, New PrivateKeyFile(File.OpenRead(KeyPath), TempPassword))

	'SSH Client Connection Info
	Dim SSH_Client = New SshClient(SSH_connectionInfo)
	SSH_connectionInfo.Timeout = TimeSpan.FromSeconds(60)
	SSH_connectionInfo.RetryAttempts = 3

	SSH_Client.Connect()

	If SSH_Client.IsConnected = False Then
		MessageBox.Show("Passphrase not valid or Connection Failed")
		Exit Sub
	End If

	'Create ShellStream
	Dim SSH_ShellStream = SSH_Client.CreateShellStream("xterm", 80, 24, 800, 600, 10240)
	Dim reader = New StreamReader(SSH_ShellStream)
	Dim writer = New StreamWriter(SSH_ShellStream)
	writer.AutoFlush = True

	'Run Commands
	While SSH_ShellStream.DataAvailable = False
		Thread.Sleep(500)
		Console.WriteLine("No data available")
	End While

	SSH_Output = SSH_ShellStream.ReadLine()
	Console.WriteLine(SSH_Output)

	While SSH_ShellStream.DataAvailable = False
		Thread.Sleep(500)
		Console.WriteLine("No data available")
	End While

	'Expect first prompt, change dir to different location
	SSH_ShellStream.Expect("[address1 ~]$")
	writer.WriteLine("cd dir1")

	While SSH_ShellStream.DataAvailable = False
		Thread.Sleep(500)
		Console.WriteLine("No data available")
	End While

	SSH_Output = SSH_ShellStream.ReadLine()
	Console.WriteLine(SSH_Output)

	While SSH_ShellStream.DataAvailable = False
		Thread.Sleep(500)
		Console.WriteLine("No data available")
	End While

	SSH_Output = SSH_ShellStream.ReadLine()
	Console.WriteLine(SSH_Output)

	Dim invalidprompt As String = " No such file or directory"

	If SSH_Output.Contains(invalidprompt) Then
		MessageBox.Show("Information is not valid")
		Exit Sub
	End If

	' expect new prompt, see possible revisions
	SSH_ShellStream.Expect("[address3_prompt]$ ")
	writer.WriteLine("ls")

	While SSH_ShellStream.DataAvailable = False
		Thread.Sleep(500)
		Console.WriteLine("No data available")
	End While

	SSH_Output = SSH_ShellStream.ReadLine()
	Console.WriteLine(SSH_Output)

	While SSH_ShellStream.DataAvailable = False
		Thread.Sleep(500)
		Console.WriteLine("No data available")
	End While

	SSH_Output = SSH_ShellStream.ReadLine()
	Console.WriteLine(SSH_Output)

	While SSH_ShellStream.DataAvailable = False
		Thread.Sleep(500)
		Console.WriteLine("No data available")
	End While
End Try

Apr 11, 2012 at 10:53 PM

Thanks Destroyer/Oleg, we've tried every iteration of code you both listed and can't get a simple password entered.  Here's a copy/paste of an SSH session from my Mac desktop to the device we're trying to connect to:

Work-iMac:~ work1$ ssh user@192.0.1.1
Password: 

cisco_6506>en
Password:  <----------------This is where we're having problems.  I'm trying to send a password to the device at this point, however no matter what I try, it fails.  It seems like it's passing several CRLF's at this point
cisco_6506#

 

This seems like it should be so simple (read some text, write some text, read some more, etc).... What am I missing?

 

-Mark

Apr 11, 2012 at 11:30 PM
Edited Apr 11, 2012 at 11:32 PM

Below is the code I've got now, which is really, really dumbed down:

 

Dim sshOutput As New StringBuilder

        Try
            Dim sshClient = New SshClient(strIPAddress, strUsername, strPassword)
            sshClient.ConnectionInfo.Timeout = TimeSpan.FromSeconds(10)
            sshClient.ConnectionInfo.RetryAttempts = 3
            sshClient.Connect()

            If sshClient.IsConnected = False Then
                ' Error, bad PW
                Exit Sub
            End If

            Dim sshStream = sshClient.CreateShellStream("dumb", 80, 24, 800, 600, 1024)
            Dim reader = New StreamReader(sshStream)
            Dim writer = New StreamWriter(sshStream)
            writer.AutoFlush = True

            sshStream.Expect(strRouterPrompt)
            Thread.Sleep(1000)
            writer.WriteLine("enable")
            Thread.Sleep(1000)
            sshStream.Expect("Password: ")
            Thread.Sleep(1000)
            sshStream.WriteLine(strEnablePW) ' Enable PW

            sshOutput.AppendLine(reader.ReadToEnd)
            sshClient.Disconnect()

            txtResponse.Text = sshOutput.ToString
        Catch ex As Exception
            Response.Write(ex.ToString)
        End Try

 

The response I get, regardless of what I try (and I've tried Oleg's code, although I can figure out how to duplicate the ExpectAction in VB.Net, and Destroyer's code), is this:

 


% Access denied
router_6506>

---

I get the EXACT same thing if I comment out this line above:

sshStream.WriteLine(strEnablePW) ' Enable PW

For whatever reason, it seems like sshNet is sending an extra linefeed after the "enable" command.  Is this a bug or user error?

Does anyone have this code working against a Cisco router?

Apr 11, 2012 at 11:49 PM

Ok, so I got this working, but it leaves some questions.  In order to get the password properly entered, I had to do the following:

        Dim sshOutput As New StringBuilder

        Try
            Dim sshClient = New SshClient(strIPAddress, strUsername, strPassword)
            sshClient.ConnectionInfo.Timeout = TimeSpan.FromSeconds(10)
            sshClient.ConnectionInfo.RetryAttempts = 3
            sshClient.Connect()

            If sshClient.IsConnected = False Then
                ' Error, bad PW
                Exit Sub
            End If

            Dim sshStream = sshClient.CreateShellStream("dumb", 80, 24, 800, 600, 1024)
            Dim reader = New StreamReader(sshStream)
            Dim writer = New StreamWriter(sshStream)
            writer.AutoFlush = True

            Thread.Sleep(3000)
            ' sshOutput.AppendLine("***" & reader.ReadToEnd & "***")
            sshStream.Expect(strRouterPrompt)
            Thread.Sleep(1000)
            sshStream.Write("enable" & vbCr & strEnablePW & vbCrLf)
            sshOutput.AppendLine("***" & reader.ReadToEnd & "***")

            ' sshStream.Write(strEnablePW) <--- This didn't work if I just did "enable" & vbcr above

            Thread.Sleep(1000)
            sshOutput.AppendLine("***" & reader.ReadToEnd & "***")
            sshClient.Disconnect()

            txtResponse.Text = sshOutput.ToString
        Catch ex As Exception
            Response.Write(ex.ToString)
        End Try

Apparently, the sshStream.WriteLine is doing a CRLF, which Cisco interprets as pressing <enter> after entering "enable", and a LF which bypasses the input for the PW, causing an "Access denied" (invalid password).

What's also interesting, is that when a password is enabled on a Cisco, it doesn't show the characters echo'd back.  sshNet doesn't seem to send the characters unless they're echoed back in the stream, if that makes any sense.  Look at the commented out line above, which doesn't work.  I have to put the enable, vbcr, password, and another vbcrlf all in the same line.

Apr 12, 2012 at 10:45 AM

Hi SupermanSC,

I haven't had time to look at this but it is something that might be of interest to me - however I have an alternative solution that you probably wont like or be able to use for politicly reasons, but her goes. Also, apologies if this is teaching you to suck eggs.

On the Cisco switch, are you using the inbuilt user database as a means of authentication? If so, when you add a user to the database add them as a privilege level 15. The user will at least be able to log into the switch at the Privilege Exec level, so no messing around with enable passwords/secrets. AT the end of the day the user must know the Priv EXEC password so why not just skip that step - especially if you are entering it auto-magically.

Even if this is only a temporary measure, this might at least get you further with your testing of the code whilst you can then figure out actually how to resolve the issue.

Hope it helps.

Chris

Sep 18, 2012 at 5:53 PM

I know this tread is quite old and have not had a chance to test the code, but I wanted to add a VB implementation for ExpectAction as requested in the thread. ExpectAction takes an Action(Of T) which was added with VB 10/VS 2010. If you're using an earlier version, you will need to upgrade to use this code. Essentially you replace the C# lambda syntax of s => { ... } with VB's version - Sub(s) ... End Sub. Here's the revised sample.


Using stream = ssh.CreateShellStream("dumb", 80, 24, 800, 600, 1024)
  ' Sending sudo command that might or might not require a password, therefor here is an option for multiple response
  stream.WriteLine("sudo ufw status")
  stream.Expect("sudo ufw status")  ' Ensure that command has been processed by the shell
  stream.Expect(
      New ExpectAction("[sudo] password for oleg:", 
          Sub(s)
             Console.Write(s)
             stream.WriteLine(password)
          End Sub),
      New ExpectAction("oleg@cn-test4:~$", 
           Sub(s) Console.Write(s))  ' We don't need the end sub here because it's a single line.
End Using

  
Oct 2, 2012 at 9:11 PM

Oleg & others,

Would this expanded shell functionality now work for commands like tail?  Say I wanted to tail a live file and write the output to a text box, would I use the new ShellStream class?

Dec 19, 2012 at 3:23 AM

Hi Oleg, for the new ExpectAction construct, is there a way to implement a timeout value for a whole Expect? If the server is spitting out something that I am not expecting, I don't want to hang the whole process.

Coordinator
Dec 20, 2012 at 10:29 PM

ykphuah,

One of the Expect method overloads accepts Timeout as a parameter.

Did you try to use that?

I think I tried to address there this exact scenario.

 

hershizer33,

 

I tried to make tail work in the past but always keep hitting the problem, you can make it work but and constatly read form the stream but the problem is there is no way for me to cancel that shell other then closing the whole client connection.

So because I could not find how to terminate a running shell session, so it could be terminated on the server, I did not implement this feature, at least not for right now :(

 

Thanks,

Oleg

Jan 2, 2013 at 2:41 AM
Edited Jan 2, 2013 at 2:42 AM

 

Hi all,

 

i have created ShellStream, from SshClient. i m facing a problem while checking that output of the shell stream contains my user promt or not. Previously i was checking it using Regex class match method, but its not useful in case of variable user promt. my user promt varies command by commands. here the code snippet.

 

 

// creating shell stream.
ssh = new SshClient(connectionInfo);
ssh.Connect();
tername = sessionInfo.m_host + "@" + sessionInfo.m_user;
shells = ssh.CreateShellStream(tername, 80, 24, 800, 600, 1024);

//checking for promt
shells.WriteLine(command);
String outp = "";
Regex regex = new Regex(@"\[.*@.*\][\$|\#]");
//my promt is variable
 var match = regex.Match(outp);
 while (shells.DataAvailable || !match.Success || outp=="")
{
outp += shells.Read();
match = regex.Match(outp);
}                
 


when i open sh shell. its user promt
sh-4.2$ like this.

kindly suggest any solution for it. 
 
Coordinator
Jan 3, 2013 at 9:26 PM

Hi,m

 

For this reason, where you need to handle multiple prompts you have Expect method with ExpectAction parameters where you can specify what to do for each individual prompt.

 

Thanks,

Oleg

Jan 10, 2013 at 7:24 PM
Edited Jan 11, 2013 at 1:29 AM

I've read through this thread several times, I started with SharpSSH, and had it working, but every-time I issued a new command to set the environment or do something else dependent it failed me, then I cam across your project, and figured since it was derived from that project, it would be further along, and actually have support. And thank you Oleg for moving the ball forward, I'm amazed what a nightmare this endeavor has become.

A Bit of background, there is a Xwindows user interface, that has 0 support to move over to Windows, but for some of my projects I need that flexibility, and the XWindows is actually an interface to a command-line tool. So I figured mimic the xwindows functionality in C# by telnetting to a Linux box, and bring the functionality of it to windows.

I'm trying to grab data from the prompt, and I'm having a heck of a time, my problem seems twofold.

1) I'm trying to grab the Directories under a specific folder to populate a "Project" Dropdown, so that the correct environment can be set.

so I run, cd /company/projects/

then I want to wait for the prompt to appear, which I can do with 

 

stream = ssh.CreateShellStream("xterm", 80, 24, 800, 600, 1024);
Regex userPromptMatch = new Regex("\\$ $");

string test = stream.Expect(userPromptMatch);

 

which right now is relegated to jut look for a $ at the end.

and then get the data from the stream between the last read "reader.ReadToEnd();"

the issue is that as far as I can tell the stream.Expect doesn't return anything, and here's my second issue:

2) If I use the "reader.ReadToEnd();" i get 

 

cd /comapny/projects/Last login: Thu Jan 10 12:12:35 2013 from 10.55.2.36 ]2;server:~ /usr/company/adm/login: No such file or directory.
101 user@server:~ $ Divider
cd /company/projects/ ]2;server:/company/projects 102 user@server:/dd/shows $  102 user@server:/dd/shows $ 

 

so I added Encoding.UTF8 to it, just to make sure, and I get no difference, do I have to figure out what each of the ASCII codes is, and replace them to get a "Mortal" readable string ?

 

var reader = new StreamReader(stream, Encoding.UTF8);
Apr 12, 2013 at 1:21 PM
Edited Apr 13, 2013 at 2:17 AM
Dear All,

I am trying to use the shellstream functionality in vb.net and somehow i am not able to use the Expect method, I am not able to figure out what needs to passed to ExpectAction parameter.

please help me.

Thanks,

Subhash
May 22, 2013 at 6:09 AM
supermanSC wrote:
I'm trying to get this working on a Cisco 65xx switch, and can't seem to get past the enable password prompt.  Oleg, if you're not familiar with Cisco, once you connect using the primary username/password, you need to issue the "enable" command to gain some elevated access levels. Once that's issued, the router will respond with a "Password: " prompt, which requires a password to be entered.  For whatever reason, if I read to the end of the stream prior to entering a password, it's like something is either being entered, or a linefeed is sent across, in which case the Cisco replies with "Bad Password".  Here's the small routine I'm testing with:     Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click        Using client = New SshClient("1.1.1.1", "username", "password")            client.Connect()            Using sshStream = client.CreateShellStream("dumb", 80, 24, 800, 600, 1024)                Dim reader = New StreamReader(sshStream)                Dim writer = New StreamWriter(sshStream)                writer.AutoFlush = True                While sshStream.Length = 0                    Thread.Sleep(500)                End While                Response.Write(reader.ReadToEnd)                writer.WriteLine("enable")                While sshStream.Length = 0                    Thread.Sleep(500)                End While                Response.Write(reader.ReadToEnd.ToString.Replace(vbCrLf, "<BR>"))            End Using            client.Disconnect()        End Using    End Sub   ----   Here's what's returned: test_6506>enablePassword: <--------------- I need to send a password here, yet something is being automatically sent in it's place.% Access deniedtest_6506>
I banged my head on this for a couple of days. I was also using the library to manage cisco routers. SharpSSH worked perfectly fine, but I couldn't get the expect function work correctly in SSH.NET.

After re-reading this thread multiple times, this post got me thinking that there had to be something different between SharpSSH's writeline method and SSH.NET's.

After looking at both library's source I found that SharpSSH is only sending a \r where SSH.NET is sending \r\n. The Cisco's are interpreting the \r\n as a double enter key press.

I changed my code to use write and appended the \r and everything works like a champ.