Search code examples
c#processsynchronous

C# Process.StandardOutput.ReadLine() hangs


So basicly the ReadLine() method just hangs right on the last output line.

I dont need to end the process. The main goal is to do "cmd-shell" with stdin and stdout (in variables/Printed out).

If the same can be achieved with async methods, please, leave it in comments. Thanks in advance.

        {
            Process p = new Process()
            {
                StartInfo = new ProcessStartInfo("cmd")
                {
                    UseShellExecute = false,
                    RedirectStandardInput = true,
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true,
                    Arguments = "/K",
                    
                }
            };
            p.ErrorDataReceived += (s, e) =>
            {
                if (!string.IsNullOrEmpty(e.Data))
                {
                    Console.WriteLine(e.Data);
                }
            };
            p.Start();
            while (true)
            {
                Console.Write("/SHELL/ #> ");
                input = Console.ReadLine();
                p.StandardInput.WriteLine(input);
                p.BeginErrorReadLine();
                Console.WriteLine("Errorgoing...");
                while (p.StandardOutput.Peek() > -1) {
                    Console.WriteLine(p.StandardOutput.Peek());
                    Console.WriteLine(p.StandardOutput.ReadLine());
                    
                    // When it hangs, Peek() outputs 67, that represents "C" char.
                    // Seems like just the last stdout line dont want to be printed out.
                }
            }
        }
    }```

Solution

  • After few implemented tricks I've ended up with the following code. Quite tricky.

    static async Task Main(string[] args)
    {
        using (SemaphoreSlim semaphore = new SemaphoreSlim(0, 1))
        {
            Process p = new Process()
            {
                StartInfo = new ProcessStartInfo("cmd")
                {
                    UseShellExecute = false,
                    RedirectStandardInput = true,
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true,
                    Arguments = "/K set PROMPT=PROMPT$P$G$_",
                }
            };
            p.ErrorDataReceived += (s, e) =>
            {
                if (e.Data?.Length > 0)
                {
                    Console.WriteLine(e.Data);
                }
            };
            bool wasprompt = false;
            string prompt = "";
            p.OutputDataReceived += (s, e) =>
            {
                if (e.Data?.Length > 0)
                {
                    if (e.Data.StartsWith("PROMPT") && e.Data.EndsWith(">"))
                    {
                        prompt = e.Data.Substring(6, e.Data.Length - 7);
                        semaphore.Release();
                        wasprompt = true;
                    }
                    else
                    {
                        if (!wasprompt)
                            Console.WriteLine(e.Data);
                        else
                            wasprompt = false;
                    }
                }
            };
            p.Start();
            p.BeginErrorReadLine();
            p.BeginOutputReadLine();
            await semaphore.WaitAsync();
            while (!p.HasExited)
            {
                Console.Write($"/SHELL/ {prompt}#> ");
                string input = Console.ReadLine();
                p.StandardInput.WriteLine(input);
                if (input == "exit") break;
                await semaphore.WaitAsync();
            }
            p.WaitForExit();
        }
    
        Console.WriteLine("Bye.");
        Console.ReadKey();
    }
    

    Few times tested and looks like it works as you expect.

    The idea was adding $_ to PROMPT environment variable to force the prompt printed to output. Then I catch that prompt and implement input/output synchronization with SemaphoreSlim.