0

For the past days, I'm trying to make the following piece of code work synchronously:

Process process = new Process();
process.StartInfo.FileName = @"c:\plink.exe";
process.StartInfo.Arguments = "-t -ssh -l username -pw password 1.1.1.1";
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardInput = true;
process.Start();

StreamWriter sw = process.StandardOutput;
StreamReader sr = process.StandardInput;

string output="";
sw.WriteLine ("configure terminal");
sw.Flush();
while (sr.Peek() > -1) 
{ 
    output += (char) sr.Read(); 
}

if (output.Contains("(config)")
{
    output="";
    sw.WriteLine ("interface TenGig1/0/1");
    sw.Flush();
    while (!sr.EndOfStream) 
    { 
        output+= (char) sr.Read(); 
    }
    if (output.Contains("(config-if")) 
    { 
        sw.WriteLine ("shutdown"); 
        sw.Flush();
    }
}

The problem - read hangs!

Everywhere VisualStudio/IIS 8 would like. Every Read operation susceptible and more often each Read in the code above hangs, instead reading successfully. Please notice that in the same project, I have many asynchronous redirects which work like a charm. Here however, I'm forced to make it synchronously - since I read and respond interactively to what I red. Any suggestion will be welcomed.

Edit 1 Deleted the debug steps I took before asking for community help. Clarification The test conditions for first and second while are different, because every other way the stream appears to be at EndOfStream.

Alex
  • 11
  • 5
  • 2
    _"... I'm stuck with developing inside some bank internal network, which physically isn't connected to the Internet, hence I cannot replace plink with some C# module,...."_ - Why on earth would being in a bank's protected network prevent you from replacing **plink.exe** with pure c# code? –  Oct 01 '17 at 21:29
  • 1
    ...apart from that, your question is incomprehensible and borderline _[rant in disguise](https://stackoverflow.com/help/dont-ask)_ –  Oct 01 '17 at 21:30
  • No quite. I pose I real question I'd like to get some help for solving it. My code should be working, according to MSDN, hence it should be working - that I'd like to help me solve. As for Internet connection - I'm unaware of native C# code able to perform SSH connection. Such help will be appreciated too. – Alex Oct 01 '17 at 21:34
  • Which asynchronous redirects? Which read hangs? Who told you `Read` is not synchronous? *Works here but not there*, where is *here* and where is *there*? This is completely unclear. Please read [ask] and create a proper [mcve] – Camilo Terevinto Oct 01 '17 at 21:34
  • Also, [read this](https://stackoverflow.com/a/12902619/3932049) – Camilo Terevinto Oct 01 '17 at 21:35
  • As I wrote - every read is susceptible. Nobody told me Read isn't synchronous - on the contrary, it is synchronous and I need it synchronous. As I wrote, every synchronous read - as the example I asked about - is susceptible to hang. More often it hangs than reads successfully. Hence I gave my complete code, immediately afterwards presented the issue - yes, I agree I could spare the last paragraphs, I added them simply in order to show the troubleshooting steps I made, before asking the community for help. – Alex Oct 01 '17 at 21:40
  • Thank you Camilio, but that's exactly what I meant - SSH.Net is available from GitHub. As far as I know, it does not come with standard VS2008 installation. Hence requiring Internet access which isn't available at this stage. – Alex Oct 01 '17 at 21:45
  • Simple. I just answered Camilios' question ("Who told you Read is not synchronous?"). – Alex Oct 01 '17 at 22:00

2 Answers2

0

I have got an IIS-Application that does nearly the same thing. My code is:

var a_proc = new Process
{
    StartInfo = new ProcessStartInfo
    {

        FileName = @"C:\Test.exe",
        Arguments = $"-path {a_file}",
        UseShellExecute = false,
        RedirectStandardOutput = true,
        CreateNoWindow = true
    }
};

a_proc.Start();

while (!a_proc.StandardOutput.EndOfStream)
{
    string line = a_proc.StandardOutput.ReadLine().Trim();
    a_result += line + '\n';
}

This code will start the exe and read any console output until the console application closes. It reads synchronous that means it will wait at this line string line = a_proc.StandardOutput.ReadLine().Trim(); until the console writes a new line. This can be seen as "hangs" but actually it just waits for a new line to read.

I guess the problem is at your line while (!sr.EndOfStream) the code waits for the console to close. But as I see you want to write/"shutdown" the console after that. This makes no sense to me. Try to use StandardOutput.ReadLine() to read the line and after that check if it contains "(config-if".

I hope this helps!

RoJaIt
  • 451
  • 3
  • 10
  • Hello RoJalt and thank you. In fact, I tried to ReadLine() and it hangs completely. Probably it expects \n\r at the end, which aren't there (so far, it's logical). Therefore I use Read() for reading character by character (whatever the character is). As for EndOfStream - AFIK it seeks till EndOfStream is reached, which in my case is **moving** further, with each write (plink will respond with additional output, for every write I make). Hence it's not "console closure" but **Read till** current EndOfStream. Unfortunately ReadLine didn't solved it for now. – Alex Oct 01 '17 at 22:36
  • Did you try the ReadToEnd function as well? – RoJaIt Oct 02 '17 at 08:54
  • To bad, it responds to any input. In my case I can write while the program is working and it will receive and handle the input after finishing it's task. So you could have used `while(!output.Contains...` and write after leaving the loop. – RoJaIt Oct 02 '17 at 08:55
0

After a few days of research, I think I came across an answer - which I decided to share here, since I'm not the only one who run into this. Maybe my efforts, will be useful to somebody. At first, I'll post a short answer and afterwards, I'll post my research and explanations. Since I do not consider myself to be C# or Dot Net Guru, I'll more than glad for community collaboration on this, correcting and improving this answer. That way we get StackOverfllow better for everyone, no? :)

Short Answer:

There's nothing you can you about it. Such deadlocks seems to be inherent in the way synchronous reads are implemented (I'm using .Net 4.0 Framework), hence if you can - avoid running into troubles in the first place and prefer asynchronous reads. Since your code may ran on your PC (=development environment) but fail deployed to IIS, as it did exactly with me. Probably the only workaround you can use, is when your expected output (= you sure (!) there is an output, waiting to be red) ends with definitive pattern, which you can recognize and stop reading.

Long Answer:

The issue seams to arise when you're trying to read from standard output, when there's nothing waiting for you there.

This can arise when (a) there's no output in the first place or, (b) you just finished reading what have been there and "advanced to the next" (non-existing) character or string. While (b) being more dangerous IMHO, since it's easy not to read when you expect no output, but much harder to stop, when there are no conditions for you to know when (at least, I was unable to find such by debugging my code and documented in MSDN - the silence on my question is another evidence for the same).

On first glance, you can use Peek() method, which promises "Returns the next available character but does not consume it" and you can easily find across the web examples, when using Peek() seems to do the trick. Unfortunately, MSDN also says "or -1 if there are no characters to be read or if the stream does not support seeking". Well guess what, at least in my case - standard output does not support seeking. Hence Peek() was rather unusable. It did worked a couple of time (maybe someone with deeper understanding can explain why), but I suspect I didn't got the whole output in such cases.

Now, I'd like to iterate the methods I've used and their outcome, so you can learn from my research and know what to expect from your code:

  • StreamReader.Read() within While(!StreamReader.EndOfStream) - Hangs at execution.
  • StreamReader.Read() within While(StreamReader.Peek() > -1) - Skips the loop.
  • StreamReader.ReadLine() - Hangs at execution.
  • StreamReader.ReadToEnd() - Hangs at execution.
  • StreamReader.BaseStream.Length - Exception at runtime: Stream does not support seeking
  • StreamReader.Read() / StreamReader.BaseStream.ReadByte() - A special case, upon which I got to my conclusion - both read fine, till reading exactly the next character after current buffer ends. Than hangs.

It's seems to me that current implementation or Process.StandardOutput lacks any method of recognizing when we red all there is. That's why in my humble opinion, all methods besides Read() and ReadByte() hangs. And those two will hang also, if you stepped across the current stream edge. In my case, I was able to move to SSH.Net (which I'd like to thank the contributors of that wonderful project). By the way, it never hangs at sshReader.ReadToEnd() :)

PPS: My development environment for those of you wondering: VisualStudio 2010 Ultimate on Windows 7 64bit and IIS 8 (clean install) with Framework 4.

Community
  • 1
  • 1
Alex
  • 11
  • 5