How can I capture the log from process sync? - c#

I want to use process to print log to richtextbox, but it does not work, I don't know why.
When I use LogWithColor, It will block the program, can't print anything.
When I use richTextBox1.AppendText, or richTextBox1.Text +=, It will print, but will auto close the program, do not print "Finished." And VS2019 Debuger can't get inside, It will cause Exception:
System.InvalidOperationException”(In System.Windows.Forms.dll)
public readonly string ffmpegExe = #"C:\Users\jared\AppData\Local\ffmpeg-4.4-full_build\bin\ffmpeg.exe";
private void OutputHandler(object sendingProcess, DataReceivedEventArgs oneLine)
{
// LogWithColor(richTextBox1, Color.Black, oneLine.Data); // does not work
// richTextBox1.AppendText(oneLine.Data); // it print, but I don’t know why the program will be closed auto
}
private void ErrorHandler(object sendingProcess, DataReceivedEventArgs oneLine)
{
LogWithColor(richTextBox1, Color.Red, oneLine.Data); // does not work
// richTextBox1.AppendText(oneLine.Data); // it print, but I don’t know why the program will be closed auto
}
private delegate void LogWithColorDelegate(RichTextBox rtb, Color color, string text);
private void LogWithColor(RichTextBox rtb, Color color, string text)
{
if (InvokeRequired)
{
if (rtb.IsHandleCreated)
{
rtb.Invoke(new LogWithColorDelegate(LogWithColor),
new object[] { rtb, color, text });
}
}
else
{
rtb.AppendText(Environment.NewLine);
rtb.SelectionColor = color;
rtb.AppendText(text);
// rtb.Text += Environment.NewLine + text; // still does not work
}
}
private void button1_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(ffmpegExe) || !File.Exists(ffmpegExe))
{
return;
}
LogWithColor(richTextBox1, Color.Black, "Start..."); // work properly.
using (Process p = new Process())
{
// RunCommand(p, ffmpegExe, "-h");
// ffmpeg.exe -h
p.StartInfo.FileName = ffmpegExe;
p.StartInfo.Arguments = "-h";
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.EnableRaisingEvents = true; // update for user9938 comment
p.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
p.ErrorDataReceived += new DataReceivedEventHandler(ErrorHandler);
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit();
}
LogWithColor(richTextBox1, Color.Black, "Finished.");
}

The issue is because you're waiting for the process execution completion using the UI thread. It will block the UI/main thread until the process has exited. However, the process will never exit because you're redirecting the output/error data and the listener thread is blocked. Please, read more about WaitForExit.
There are some solutions to solve the issue. You can use for example the ThreadPool, a Task or a new Thread. But, if you're using C# 5 and .NET Framework 4.5 or greater, I would recommend you to use the async/await.
Here is a code snip using asynchronous programming:
private async void button1_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(ffmpegExe) || !File.Exists(ffmpegExe))
{
return;
}
LogWithColor(richTextBox1, Color.Black, "Start...");
await Task.Run(() =>
{
using (var p = new Process())
{
p.StartInfo = new ProcessStartInfo(ffmpegExe, "-h")
{
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
};
p.EnableRaisingEvents = true;
p.OutputDataReceived += (_, data) =>
{
LogWithColor(richTextBox1, Color.Black, data.Data);
};
p.ErrorDataReceived += (_, data) =>
{
LogWithColor(richTextBox1, Color.Red, data.Data);
};
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit();
}
});
LogWithColor(richTextBox1, Color.Black, "Finished.");
}
private void LogWithColor(RichTextBox rtb, Color color, string text)
{
if (text == null)
{
return;
}
if (InvokeRequired)
{
Invoke(new Action(() => LogWithColor(rtb, color, text)));
return;
}
rtb.AppendText(Environment.NewLine);
rtb.SelectionColor = color;
rtb.AppendText(text);
}

Related

C# communicate with CLI process

I want to run a process in the Windows console, after that, I want to pass (with button click) some commands and see the result in a RichTextBox.
I’m able to launch the program and read the responses after starting, but when I try to send any commands, it doesn’t work: I’m not able to communicate with the process.
Below the code:
public static void SortOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) {
// Collect the sort command output.
if (!String.IsNullOrEmpty(outLine.Data)) {
numOutputLines++;
// Add the text to the collected output.
sortOutput.Append(Environment.NewLine + $"[{numOutputLines}] - {outLine.Data}");
//RichTextBox
MCM.ActiveForm.Invoke(MCM.AffichageTextDelegate, new object[] { outLine.Data });
}
}
public static async Task<int> RunProcessAsync() {
using (var process = new Process {
StartInfo = {
FileName = "AAA.exe",
Arguments = “-v COM74",
UseShellExecute = false,
CreateNoWindow = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
RedirectStandardError = true
},
EnableRaisingEvents = true
})
{ return await RunProcessAsync(process).ConfigureAwait(false); }
}
private static Task<int> RunProcessAsync(Process process) {
var tcs = new TaskCompletionSource<int>();
process.Exited += (s, ea) => tcs.SetResult(process.ExitCode);
process.OutputDataReceived += (s, ea) => Console.WriteLine(ea.Data);
sortOutput = new StringBuilder();
process.OutputDataReceived += SortOutputHandler;
process.ErrorDataReceived += (s, ea) => Console.WriteLine("ERR: " + ea.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
return tcs.Task;
}
private async void Btn_Click(object sender, EventArgs e) {
await RunProcessAsync();
}
It's working with a synchronous process :
public static Process myProcess;
public static StreamWriter myStreamWriter;
Process myProcess = new Process();
myProcess.StartInfo.FileName = "AAA.exe";
myProcess.StartInfo.Arguments = '-' + "p" + " " + ComboBoxPortCom.Text;
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.RedirectStandardInput = true;
myProcess.StartInfo.RedirectStandardOutput = true;
myProcess.StartInfo.RedirectStandardError = true;
myProcess.OutputDataReceived += (s, ea) => Console.WriteLine(ea.Data);
sortOutput = new StringBuilder();
myProcess.OutputDataReceived += SortOutputHandler;
myProcess.ErrorDataReceived += (s, ea) => Console.WriteLine("ERR: " + ea.Data);
myProcess.Start();
myProcess.BeginOutputReadLine();
myProcess.BeginErrorReadLine();
myStreamWriter = myProcess.StandardInput;
myStreamWriter.WriteLine("Command1" + '\n');

Can I capture the output of a started process without 'stealing' the output?

Lots of answers to other questions tell you that you can capture the output of a process using code looking something like this:
public async Task Run()
{
await Task.Run(() =>
{
using (Process process = new Process()
{
StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Normal,
RedirectStandardOutput = true,
RedirectStandardError = true,
FileName = "cmd",
Arguments = "path/filename.bat",
}
})
{
process.OutputDataReceived += Process_OutputDataReceived;
process.ErrorDataReceived += Process_ErrorDataReceived;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
}
});
}
private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
log.Error(e.Data);
}
}
private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
log.Info(e.Data);
}
}
There are lots of variants which achieve the same thing more or less but they all redirect the standard output/errors - is there a way to capture them while still having them show in the cmd window?

Sending DOS commands using Process - missing data on OutputDataReceived callback

I have been looking for a solution before posting but I gave up!
I just want to interactively send DOS command using Standard input. It works well but I always don't get the last line (the prompt line) on the OutputDataReceived callback.
Any ideas?
Process p = null;
private void Start_Click(object sender, EventArgs e)
{
p = new Process();
p.StartInfo = new ProcessStartInfo();
p.EnableRaisingEvents = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.FileName = "cmd.exe";
p.ErrorDataReceived += ErrorDataReceived;
p.OutputDataReceived += OutputDataReceived;
p.Start();
p.BeginErrorReadLine();
p.BeginOutputReadLine();
p.StandardInput.WriteLine("dir");
}
private void OutputDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data + "\n");
}
private void WriteToStandardInput_Click(object sender, EventArgs e)
{
p.StandardInput.WriteLine(txt_command.Text); //can be "dir" or "cd temp"
}
Adding p.StandardInput.Close() solves the problem, reason is when you close the input stream it actually terminates the process (which you start using 'p.start'). So as I said, you need start separate process for each command.
~Nilesh
OK, I have found a solution... I have created 2 tasks as shown below which are reading constantly from the output and error stream and print it to a rich text box. The trick was not to use BeginErrorReadLine and BeginOutputReadLine.
I hope I was able to help others...
public partial class Form1 : Form
{
private Process p = new Process();
private SynchronizationContext context;
public Form1()
{
InitializeComponent();
context = SynchronizationContext.Current;
}
private void Start_Click (object sender, EventArgs e)
{
p.StartInfo = new ProcessStartInfo();
p.EnableRaisingEvents = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.UseShellExecute = false;
p.Start();
Task.Run(() => ReadFromStreamLoop(p.StandardOutput));
Task.Run(() => ReadFromStreamLoop(p.StandardError));
}
private void ReadFromStreamLoop (StreamReader s)
{
int count = 0;
char[] buffer = new char[1024];
do
{
StringBuilder builder = new StringBuilder();
count = s.Read(buffer, 0, 1024);
builder.Append(buffer, 0, count);
context.Post(new SendOrPostCallback(delegate (object state)
{
richTextBox1.AppendText(builder.ToString());
}), null);
} while (count > 0);
}
private void WriteToStandardInput_Click (object sender, EventArgs e)
{
p.StandardInput.WriteLine(txt_command.Text); //can be "dir" or "cd temp"
}
}
If you are looking for event end of command execution, then every time spawn a new process. At the end of processing you can show the prompt on main process. Manage your environment variable through invoking(main) process.
See the example on - https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.outputdatareceived?view=netframework-4.7.2

Accessing forms control from function handler on C#

I have created an interactive shell on C# but I don't know how to access my forms control and assign received values to my textbox, I know that the threads can't access the UI thread but in this case, I don't seem to be able to fix the problem, there is going to be many input and output in that shell, and I want to make sure that everything is shown to user.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Process process = new Process();
process.StartInfo.FileName = "echoo.exe";
process.StartInfo.Arguments = "";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler);
process.StartInfo.RedirectStandardInput = true;
process.Start();
StreamWriter sw = process.StandardInput;
process.BeginOutputReadLine();
process.BeginErrorReadLine();
sw.WriteLine("sent");
process.WaitForExit();
}
static void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
Form1.textBox1.Text = outLine.Data;
}
}
If you write this in your setter:
static void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
if(textBox1.InvokeRequired)
{
textBox1.BeginInvoke((MethodInvoker) delegate() {textBox1.Text = outLine.Data;});
}
else
{
textBox1.Text = outLine.Data;
}
}
It'll force the set onto the UI thread. I found this from this question: stack question

cannot get input from redirect Standard Input

I need get an enter command for my C# process.
my C# code work for test batch file, not for the fortran console application
I read this post: C# Process Call, Interact with Standard Input and Standard Output
but it does not work for me.
Anyone can give me some tips?
my sample batch file (test.bat):
note: this batch file simulate my another application.
#echo off
cls
dir
echo "please input enter key"
pause
tree
my C# code:
private Process _process = null;
private bool _bEnterCR = false;
private void Begin_Click(object sender, RoutedEventArgs e)
{
this.tbOutput.Text = "";
_bEnterCR = false;
if (null != _process)
{
_process.Dispose();
}
string strPathName = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
// Use ProcessStartInfo class
_process = new Process();
_process.StartInfo.CreateNoWindow = true;
_process.StartInfo.UseShellExecute = false;
_process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
_process.StartInfo.FileName = strPathName + "\\test.bat";
_process.StartInfo.WorkingDirectory = strPathName + "\\Output\\";
_process.StartInfo.RedirectStandardError = true;
_process.StartInfo.RedirectStandardInput = true;
_process.StartInfo.RedirectStandardOutput = true;
//_process.EnableRaisingEvents = true;
_process.OutputDataReceived += new DataReceivedEventHandler(OnOutputDataReceived);
_process.ErrorDataReceived += new DataReceivedEventHandler(OnOutputDataReceived);
//_process.Exited += new EventHandler(OnProcessExited);
_process.Start();
_process.BeginOutputReadLine();
_process.BeginErrorReadLine();
}
private void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (String.IsNullOrEmpty(e.Data) == false)
{
if (e.Data.Contains("please input enter key") && _bEnterCR == false)
{
Debug.WriteLine("Pause Found, Entering <CR> command");
// work for batch file, not for console application
_process.StandardInput.Write(#"\r\n");
_bEnterCR = true;
}
new Thread(() =>
{
this.Dispatcher.Invoke(new Action(() =>
{
tbOutput.AppendText(e.Data + Environment.NewLine);
}));
}).Start();
}
}

Categories