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');
Related
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);
}
I'm new at c# / winforms and try to batch convert video clips with handbrake. The convert itself is working when the processes are opened in an own windows without redirecting the Stdout/Stderr.
But when I redirect the output to a winforms textbox only the first clip is converted. As I can see in the taskmanager the handbrake_Cli is already opened but doing nothing.
I think that there is some STDerr/STDout in any buffer and is waiting to get flushed.... but I don't know how to do. Would be glad if anybody can give me a hint :-)
ProcessStartInfo info = new ProcessStartInfo(" \"" + textBoxHandbrakeCLI.Text + "\"");
process.StartInfo = info;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = false;
process.Exited += new EventHandler(process_Exited);
process.EnableRaisingEvents = true;
eventHandled = new TaskCompletionSource<bool>();
//Redirect output
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true;
process.OutputDataReceived += (sender, eventArgs) => MyProcOutputHandler(sender, eventArgs);
process.ErrorDataReceived += (sender, eventArgs) => MyProcOutputHandler(sender, eventArgs);
foreach(pseudo)
{
process.Start();
if (!errorRedirect)
{
process.BeginErrorReadLine();
process.BeginOutputReadLine();
errorRedirect = true;
}
}
private void MyProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder(outLine.Data);
WriteStatus(sb.ToString());
}
public void WriteStatus(string value)
{
if (InvokeRequired)
{
this.Invoke(new Action<string>(WriteStatus), new object[] { value});
return;
}
try
{
textBoxAdvancedStatus.AppendText(Environment.NewLine + value);
}
catch
{
//stay calm
}
}
I got a workaround for my problem but still don´t know why i need it.
foreach(pseudo) {
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelErrorRead();
process.CancelOutputRead();
}
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.
}
}
}
}```
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.
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?
I am trying to read the CGMiner output in a C# program I am writing. I successfully read/write the standard thread input/output. But for some reason CGMiner does not write to the standard cmd window output, and I can't read it in C#. Any ideas?
This is my process start:
public void start() {
proc = new Process();
proc.StartInfo.FileName = "CMD.exe";
proc.StartInfo.RedirectStandardInput = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.UseShellExecute = false;
proc.OutputDataReceived += (s, e) => updateConsoleOutput(e);
proc.Start();
proc.BeginOutputReadLine();
}
This is the function I use to write to the console:
public void RunCommand(string cmd = "") {
if (cmd.Length > 0) {
ConsoleInput = cmd;
}
StreamWriter myStreamWriter = proc.StandardInput;
myStreamWriter.WriteLine(ConsoleInput);
myStreamWriter.Flush();
ConsoleInput = String.Empty;
}
These are the functions I use to read from the console:
public delegate void consoleOutputCallback(string message);
private void updateConsoleOutput(DataReceivedEventArgs outLine) {
if (!String.IsNullOrEmpty(outLine.Data)) {
this.Dispatcher.Invoke(
new consoleOutputCallback(updateConsoleText),
new object[] { outLine.Data }
);
}
}
public void updateConsoleText(string message) {
this.OutputBlock.Text += message + "\n";
}
HINT: Don't know if it helps, but CGMiner will overwrite the entire console window, and cursor always stay at top left and does not move. All command before running CGMiner is overwritten.
Forgot to add, this is console command I use:
cd C:\cgminer\
del *.bin
cgminer.exe -o stratum+tcp://global.wemineltc.com:3335 -O yongke.1:x -g 2
You need to set the --per-device-stats flag in order for GPU stats to be written into stream
And don't forget to add this to the code in question
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.RedirectStandardError = true;
proc.ErrorDataReceived += (s, e) => updateConsoleOutput(e);
....
proc.Start();
proc.BeginErrorReadLine();
Most of miners use standart Error stream instead of standart Output stream (to write both output data and errors) for some reason..
the only thing that made it work for me was
-T
here is my working code
Task StartGPUMiner(object set)
{
MinerParams m = new MinerParams();
m = (MinerParams)set;
var tcs = new TaskCompletionSource<object>();
Process p = new Process();
ProcessStartInfo start = new System.Diagnostics.ProcessStartInfo();
start.FileName = m.ApplicationPath + "\\cgminer\\cgminer.exe";
start.Arguments = " -I " + m.GpuIntisity + " -T --scrypt -o " + m.sProtocol + m.ServerName + ":" + m.ServerPort + " -u " + m.UserName + "." + m.WorkerName + " -p " + m.ThePassword + " " + m.GpuParams;
start.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
start.RedirectStandardOutput = true;
start.UseShellExecute = false;
start.CreateNoWindow = true;
var proc = Process.Start(start);
proc.OutputDataReceived += (s, e) =>
{
try
{
this.Invoke((Action)delegate
{
txtLog.Text += (e.Data + Environment.NewLine);
});
}
catch { }
};
try
{
proc.Exited += (s, e) => tcs.SetResult(null);
proc.EnableRaisingEvents = true;
proc.BeginOutputReadLine();
}
catch { }
return tcs.Task;
}