-1

I've got a tricky issue with a console app, from which I'm trying to redirect StandardInput, StandardOutput and StandardError.

I've got a working solution for other console app - that's not a new thing for me. But this app seems to have something special, which is making impossible to redirect all outputs with a standard approach.

Console app works like this:

  • directly after startup writes a few lines and waits for user input
  • no matter what input is - console app is showing some output and again wait for new input
  • console app never ends, it has to be closed

I've tried already solutions based on:

  • StandardOutput/Error.ReadToEnd()
  • taki care of OutputDataReceived & ErrorDataReceived with read line by line with ReadLine
  • reading by char
  • waiting for the end of process (which is not ending, so I'm running into a deadlock)
  • to start console app in a preloaded cmd.exe and grab this (StandardOutput stopped to show just after launch of this console app)
  • to manually flush input

All the time I had completely no output and no error stream from console app - nothing.

After a multitude attempts I've discovered, that I can receive StandardOutput only when I'll close StandardInput after programatically inputting the data.

But in this case, console app is going wild, falling into loop of writing few lines to StandardOutput as on start-up, which makes final output big and full of garbages.

With MedallionShell library I'm able to try to gracefully kill it with Ctrl+C, but this solution is still far from acceptable, because:

  • sometimes console app will produce so much garbages before I will be able to kill it, that it crashes my app
  • even if this won't crash, searching for expected output in a lot of garbages is nasty and tragically slows down automatization (6000 records in... 15 minutes)
  • I'm unable to provide more than one input at a time, so I have to start console app just to receive one output, close and start again for another output

I've got no sources for that console app, so I'm even not able to recreate the issue or fix it - it's some very legacy app at my company, which I'm trying to make at least a bit automatic...

Code, with which I've got at least anything now (with MediallionShell):

  var command = Command.Run(Environment.CurrentDirectory + @"\console_app.exe");
  command.StandardInput.WriteLine("expected_input");
  command.StandardInput.Close(); // without that I'll never receive any output or error stream from this stubborn console app
  command.TrySignalAsync(CommandSignal.ControlC); // attempt to kill gracefully, not always successfull...
  var result = command.Result;
  textBox1.AppendText(String.Join(Environment.NewLine, command.GetOutputAndErrorLines().ToArray().Take(10))); // trying to get rid of garbages
  command.Kill(); // additional kill attempt if Ctrl+C didn't help, sometimes even this fails

Any help will be appreciated, I'm also still searching for solution and now I'm checking this one: PostMessage not working with console window whose output is redirected and read asynchronously but author there had an output and I don't...

niknejm
  • 21
  • 3
  • '*console app never ends, it has to be closed*' - then closing the StandardInput stream is the right way to close the console app. Please include the code how do you start the app and how do you read the redirected output(and error)-stream, the root cause is probably there. – Steeeve Nov 01 '21 at 08:31
  • Welcome to Stack Overflow. Please take the [tour] to learn how Stack Overflow works and read [ask] on how to improve the quality of your question. Then [edit] your question to include your source code as a [mcve], which can be compiled and tested by others. There are a lot of unknowns, specially how the console app actually works/act/react. – Progman Nov 01 '21 at 11:12
  • It's not clear what you consider "garbage". Please elaborate. – Tu deschizi eu inchid Nov 01 '21 at 13:20

1 Answers1

-1

You haven't provided a sample Console program to test with, but something like the following may work:

Create a Console project (Console (.NET Framework)) - used for testing

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleTestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //prompt for input - 1st prompt
            Console.Write("Please enter filename: ");
            string filename = Console.ReadLine();

            if (System.IO.File.Exists(filename))
            {
                Console.WriteLine("'" + filename + "' exists.");
            }
            else
            {
                Console.Error.WriteLine("'" + filename + "' doesn't exist.");
            }

            //prompt for input - 2nd prompt
            Console.Write("Would you like to exit? ");
            string answer = Console.ReadLine();

            Console.WriteLine("Your answer was: " + answer);

            Console.WriteLine("Operation complete.");
        }
    }
}

Then, create a Windows Forms project Windows Forms (.NET Framework) and run one of the following:

Option 1:

private void RunCmd(string exePath, string arguments = null)
{
    //create new instance
    ProcessStartInfo startInfo = new ProcessStartInfo(exePath, arguments);
    startInfo.Arguments = arguments; //arguments
    startInfo.CreateNoWindow = true; //don't create a window
    startInfo.RedirectStandardError = true; //redirect standard error
    startInfo.RedirectStandardOutput = true; //redirect standard output
    startInfo.RedirectStandardInput = true;
    startInfo.UseShellExecute = false; //if true, uses 'ShellExecute'; if false, uses 'CreateProcess'
    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
    startInfo.ErrorDialog = false;

    //create new instance
    using (Process p = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
    {
        //subscribe to event and add event handler code
        p.ErrorDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code 
                Debug.WriteLine("Error: " + e.Data);
            }
        };

        //subscribe to event and add event handler code
        p.OutputDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code
                Debug.WriteLine("Output: " + e.Data);
            }
        };

        p.Start(); //start

        p.BeginErrorReadLine(); //begin async reading for standard error
        p.BeginOutputReadLine(); //begin async reading for standard output

        using (StreamWriter sw = p.StandardInput)
        {
            //provide values for each input prompt
            //ToDo: add values for each input prompt - changing the for loop as necessary
            for (int i = 0; i < 2; i++)
            {
                if (i == 0)
                    sw.WriteLine(@"C:\Temp\Test1.txt"); //1st prompt
                else if (i == 1)
                    sw.WriteLine("Yes"); //2nd prompt
                else
                    break; //exit
            }
        }

        //waits until the process is finished before continuing
        p.WaitForExit();
    }
}

Option 2:

private void RunCmd(string exePath, string arguments = null)
{
    //create new instance
    ProcessStartInfo startInfo = new ProcessStartInfo(exePath, arguments);
    startInfo.Arguments = arguments; //arguments
    startInfo.CreateNoWindow = true; //don't create a window
    startInfo.RedirectStandardError = true; //redirect standard error
    startInfo.RedirectStandardOutput = true; //redirect standard output
    startInfo.RedirectStandardInput = true;
    startInfo.UseShellExecute = false; //if true, uses 'ShellExecute'; if false, uses 'CreateProcess'
    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
    startInfo.ErrorDialog = false;

    //create new instance
    using (Process p = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
    {
        //subscribe to event and add event handler code
        p.ErrorDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code 
                Debug.WriteLine("Error: " + e.Data);
            }
        };

        //subscribe to event and add event handler code
        p.OutputDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code
                Debug.WriteLine("Output: " + e.Data);
            }
        };

        p.Start(); //start

        p.BeginErrorReadLine(); //begin async reading for standard error
        p.BeginOutputReadLine(); //begin async reading for standard output

        using (StreamWriter sw = p.StandardInput)
        {
            //provide values for each input prompt
            //ToDo: add values for each input prompt - changing the for loop as necessary
            sw.WriteLine(@"C:\Temp\Test1.txt"); //1st prompt
            sw.WriteLine("Yes"); //2nd prompt
        }

        //waits until the process is finished before continuing
        p.WaitForExit();

    }
}

Option 3:

Note: This one is modified from here.

private void RunCmd(string exePath, string arguments = null)
{
    //create new instance
    ProcessStartInfo startInfo = new ProcessStartInfo(exePath, arguments);
    startInfo.Arguments = arguments; //arguments
    startInfo.CreateNoWindow = true; //don't create a window
    startInfo.RedirectStandardError = true; //redirect standard error
    startInfo.RedirectStandardOutput = true; //redirect standard output
    startInfo.RedirectStandardInput = true;
    startInfo.UseShellExecute = false; //if true, uses 'ShellExecute'; if false, uses 'CreateProcess'
    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
    startInfo.ErrorDialog = false;

    //create new instance
    using (Process p = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
    {
        p.Start(); //start

        Read(p.StandardOutput);
        Read(p.StandardError);

        using (StreamWriter sw = p.StandardInput)
        {
            //provide values for each input prompt
            //ToDo: add values for each input prompt - changing the for loop as necessary
            sw.WriteLine(@"C:\Temp\Test1.txt"); //1st prompt
            sw.WriteLine("Yes"); //2nd prompt
        }

        //waits until the process is finished before continuing
        p.WaitForExit();

    }
}

private static void Read(StreamReader reader)
{
    new System.Threading.Thread(() =>
    {
        while (true)
        {
            int current;
            while ((current = reader.Read()) >= 0)
                Console.Write((char)current);
        }
    }).Start();
}
Tu deschizi eu inchid
  • 4,117
  • 3
  • 13
  • 24