I'm working on a cmd output to RichTextBox.
Is there any way to merge/join all the progress (%) into a single line of the RichTextBox? Instead of creating a line for each %. I would like it to be like cmd (except removing the blank lines as it is now).
private async void btnStart_Click(object sender, EventArgs e){
await Task.Factory.StartNew(() =>
{
Execute1("Prtest.exe", " x mode2 C:\\input.iso C:\\output.iso");
});
}
private void Execute1(string filename, string cmdLine){
var fileName = filename;
var arguments = cmdLine;
var info = new ProcessStartInfo();
info.FileName = fileName;
info.Arguments = arguments;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.CreateNoWindow = true;
using (var p = new Process())
{
p.StartInfo = info;
p.EnableRaisingEvents = true;
p.OutputDataReceived += (s, o) =>
{
tConsoleOutput(o.Data);
};
p.ErrorDataReceived += (s, o) =>
{
tConsoleOutput(o.Data);
};
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit();
}
}
public void tConsoleOutput(string text){
BeginInvoke(new Action(delegate ()
{
rtConsole.AppendText(text + Environment.NewLine);
rtConsole.ScrollToCaret();
//remove empty lines
rtConsole.Text = Regex.Replace(rtConsole.Text, #"^\s*$(\n|\r|\r\n)", "", RegexOptions.Multiline);
}));
}
Real cmd.exe output:
Processing: 100%
Sectors: 43360
Completed.
C# RichTextBox (rtConsole) output:
Processing: 2%
Processing: 4%
Processing: 7%
Processing: 9%
Processing: 11%
Processing: 14%
Processing: 16%
Processing: 39%
Processing: 100%
Sectors: 43360
Completed.
UPDATE: Solved
Big Thanks #Jackdaw
Try the method below:
static public void tConsoleOutput(RichTextBox rtb, string line)
{
var pattern = #"^Processing: \d{1,3}%.*$";
if (!line.EndsWith(Environment.NewLine))
line += Environment.NewLine;
var isProcessing = Regex.Match(line, pattern).Success;
rtb.Invoke((MethodInvoker)delegate
{
var linesCount = rtb.Lines.Length;
if (linesCount > 1 && isProcessing)
{
var last = rtb.Lines[linesCount - 2];
if (Regex.Match(last, pattern).Success)
{
var nlSize = Environment.NewLine.Length;
// Update latest line
var sIndex = rtb.GetFirstCharIndexFromLine(linesCount - nlSize);
var eIndex = sIndex + last.Length + nlSize;
rtb.Select(sIndex, eIndex - sIndex);
rtb.SelectedText = line;
return;
}
}
rtb.AppendText(line);
});
}
And seems like that:
I don't know about the code that you have written (or, if it's not yours, where it comes from) but I can tell you that the easiest way of doing this is the \r character, which resets your caret to the beginning of the line.
This means that you must make sure not to use Console.WriteLine but Console.Write instead.
An example:
Console.Write("00.00% Done");
Thread.Sleep(1500);
Console.Write("\r100.00% Done");
Related
I have this class which runs a process:
public static async Task<ProcessResult> ExecuteShellCommand(string command, string arguments="", int timeout=1000, bool insertWait=false)
{
var result = new ProcessResult();
using (var process = new Process())
{
process.StartInfo.FileName = command;
process.StartInfo.Arguments = arguments;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
var outputBuilder = new StringBuilder();
var outputCloseEvent = new TaskCompletionSource<bool>();
process.OutputDataReceived += (s, e) =>
{
// The output stream has been closed i.e. the process has terminated
if (e.Data == null)
{
outputCloseEvent.SetResult(true);
}
else
{
outputBuilder.AppendLine(e.Data);
}
};
var errorBuilder = new StringBuilder();
var errorCloseEvent = new TaskCompletionSource<bool>();
process.ErrorDataReceived += (s, e) =>
{
// The error stream has been closed i.e. the process has terminated
if (e.Data == null)
{
errorCloseEvent.SetResult(true);
}
else
{
errorBuilder.AppendLine(e.Data);
}
};
bool isStarted;
try
{
process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
process.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler);
isStarted = process.Start();
StreamReader reader = process.StandardOutput;
string output = reader.ReadToEnd();
result.Output = output;
}
catch (Exception error)
{
// Usually it occurs when an executable file is not found or is not executable
result.Completed = true;
result.ExitCode = -1;
result.Output = error.Message;
isStarted = false;
}
if (isStarted)
{
// Reads the output stream first and then waits because deadlocks are possible
process.BeginOutputReadLine();
process.BeginErrorReadLine();
if (insertWait)
{
await Task.Delay(150000);
}
// Creates task to wait for process exit using timeout
var waitForExit = WaitForExitAsync(process, timeout);
// Create task to wait for process exit and closing all output streams
var processTask = Task.WhenAll(waitForExit, outputCloseEvent.Task, errorCloseEvent.Task);
// Waits process completion and then checks it was not completed by timeout
if (await Task.WhenAny(Task.Delay(timeout), processTask) == processTask && waitForExit.Result)
{
result.Completed = true;
result.ExitCode = process.ExitCode;
// Adds process output if it was completed with error
if (process.ExitCode != 0)
{
result.Output = $"{outputBuilder}{errorBuilder}";
}
}
else
{
try
{
// Kill hung process
process.Kill();
}
catch
{
}
}
}
}
return result;
}
This line calls the ExecuteShellCommand method:
var result = TestHelper.ExecuteShellCommand(MessageInjectorOptions.MessageInjectorFilename, MessageInjectorOptions.MessageInjectorParameters + " " + binaryFile + " " + topic + " " + partition, 300000, true);
My logging shows that this is the command that gets run:
C:\Program Files\Java\jre1.8.0_281\bin\java.exe -jar C:\Users\Administrator\Downloads\test_tool\Jar\Injector\Injector-1.0.jar FILE TOPIC 2
This should push messages contained in FILE to a Kafka topic but the messages don't appear on the topic so I assume the jar doesn't run. If I copy and paste the command to a dos terminal and run it I can see the messages on the topic.
Is there anything wrong with my code that might cause Process to not run correctly?
This question already has answers here:
C# get process output while running
(2 answers)
Closed 2 years ago.
i want to create IDE for python that can run line by line like anaconda.
so i want return the output after each python command .
Edit: the solution is:
public Process cmd = new Process();
public string output = "";
{
cmd.StartInfo.FileName = "python.exe";
cmd.StartInfo.Arguments = #"-i";//this was the problem
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.UseShellExecute = false;
cmd.OutputDataReceived += new DataReceivedEventHandler((sender2, e2) =>
{
if (!String.IsNullOrEmpty(e2.Data))
{
output += e2.Data.ToString()+"\n";
}
});
cmd.Start();
cmd.BeginOutputReadLine();
}
{
cmd.StandardInput.WriteLine(result);//the output will take some time to complete
}
register to the OutputDataReceived event to get data :
cmd.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
// do what you need with the output (for instance append output)
}
});
and after cmd.Start() add:
cmd.BeginOutputReadLine();
this causes the process to read the redirected output..
so the final code would be:
Process cmd = new Process();
cmd.StartInfo.FileName = #"python.exe";
cmd.StartInfo.Arguments = #"-i";
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.UseShellExecute = false;
cmd.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
// do what you need with the output (for instance append output)
cmd.StandardInput.WriteLine("python comand 1");
cmd.StandardInput.WriteLine("python comand 2");
cmd.StandardInput.WriteLine("another comand 3");
}
});
cmd.Start();
cmd.BeginOutputReadLine();
the exe I'm running basically has a button that on click writes a line to the console, and then reads 3 lines, so the event gets data "my output", and bla1,bla2 and bla3 get the values I wrote to the input:
private void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("my output");
string bla = Console.ReadLine();
string bla2 = Console.ReadLine();
string bla3 = Console.ReadLine();
}
I used to be weary of using BackgroundWorker because it required so many functions to work correctly. However when I swapped to C# from VB.NET (about a month ago) I stumbled across a really easy way to instance them;
Example;
private void cmdMaxCompressPNG_Click(object sender, EventArgs e) {
pbStatus.Maximum = lstFiles.Items.Count;
List<string> FileList = Load_Listbox_Data();
var bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += delegate {
foreach (string FileName in FileList) {
ShellandWait("optipng.exe", String.Format("\"{0}\"", FileName));
bw.ReportProgress(1);
}
};
bw.ProgressChanged += (object s, ProgressChangedEventArgs ex) => {
pbStatus.Value += 1;
};
bw.RunWorkerCompleted += delegate {
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");
};
bw.RunWorkerAsync();
}
There it is, all in one function! Simple to write, easy to understand, and no real leg work. I even made a Snippet out of it. I've since converted all my multiple part BackgroundWorker functions, into this little piece of elegant code. I've also started using them more liberally than in the past. Yesterday I was reading an article regarding Async and Await and how that's apparently how I should be doing things. I'm having trouble wrapping my head around it.
I've tried to use local functions, but I can't get the wording correct. It keeps trying to put it as synchronous.
How would I convert the above into an equally tight implementation of Await/Async logic?
[Edit]
ShellandWait;
private void ShellandWait(string ProcessPath, string Arguments, bool boolWait = true) {
System.Diagnostics.Process ShellProcess = new System.Diagnostics.Process();
ShellProcess.StartInfo.FileName = ProcessPath;
ShellProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
ShellProcess.StartInfo.Arguments = Arguments;
ShellProcess.StartInfo.CreateNoWindow = true;
ShellProcess.StartInfo.UseShellExecute = false;
ShellProcess.StartInfo.RedirectStandardOutput = true;
ShellProcess.Start();
if (boolWait) { ShellProcess.WaitForExit(); }
if (boolWait) { ShellProcess.Close(); }
}
The original code processes only one file at a time so you could use a simple loop and only execute ShellandAwait asynchronously:
private void cmdMaxCompressPNG_Click(object sender, EventArgs e)
{
pbStatus.Maximum = lstFiles.Items.Count;
var FileList = Load_Listbox_Data();
foreach (var FileName in FileList)
{
//Only thing that needs to run in the background
await Task.Run(()=>ShellandWait("optipng.exe", String.Format("\"{0}\"", FileName));
//Back in the UI
pbStatus.Value += 1;
}
};
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");
It would be even better if ShellandWait was modified so it *doesn't block. I assume it uses Process.WaitForExit() to block. The method should await asynchronously instead by listening to the Exited event. Such events can be converted to tasks as shown in Tasks and the Event-based Asynchronous Pattern.
The method would look something like this :
Task<string> ShellAsync(string commandPath,string argument)
{
var tcs = new TaskCompletionSource<string>();
var process = new Process();
//Configure the process
//...
process.EnableRaisingEvents = true;
process.Exited += (s,e) => tcs.TrySetResult(argument);
process.Start();
return tcs.Task;
}
This would allow the loop to be simplified to :
foreach (var FileName in FileList)
{
await ShellAsync("optipng.exe", String.Format("\"{0}\"", FileName));
//Back in the UI
pbStatus.Value += 1;
}
The way I went about this (after reading some more) is to use Task.Run()
private async void cmdMaxCompressPNG_Click(object sender, EventArgs e) {
pbStatus.Maximum = lstFiles.Items.Count;
List<string> FileList = Load_Listbox_Data();
await Task.Run(() => {
foreach (string FileName in FileList) {
ShellandWait("optipng.exe", String.Format("\"{0}\"", FileName));
pbStatus.GetCurrentParent().Invoke(new MethodInvoker(delegate { pbStatus.Value += 1; }));
}
});
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");
}
Note the async next to private.
I had to get fancy with the progress bar since it was a status strip progress bar. If it would have been a standard control I could have used;
pbStatus.Invoke((Action)(() => pbStatus.Value += 1))
Answer to progress bar found at -> Update progress bar from Task.Run async
And here -> How to Invoke the progress bar in Status strip?
I'd consider doing this with Microsoft's Reactive Framework. I think it's much more powerful than using Tasks.
private void cmdMaxCompressPNG_Click(object sender, EventArgs e)
{
pbStatus.Maximum = lstFiles.Items.Count;
var query =
from FileName in Load_Listbox_Data().ToObservable()
from u in Observable.Start(() =>
System.Diagnostics.Process
.Start("optipng.exe", String.Format("\"{0}\"", FileName))
.WaitForExit())
select u;
query
.ObserveOn(this) //marshall back to UI thread
.Subscribe(
x => pbStatus.Value += 1,
() =>
{
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");
});
}
Just NuGet "System.Reactive.Windows.Forms" and add using System.Reactive.Linq; to get it working.
The question sounds a bit, dense. Here is a slightly longer version:
I need to have the main loop wait for user input and also have a process running and waiting for input from a stream to which the user input is to be sent.
Full story: I'm building a Cmd emulator and at first everything looked fine: The user enters a command, it gets echoed to the output area, processed and StdOut and StdErrOut are captured and also added to the output TextBox.
The only problem was, that, as the cmd process was created and started separately for each command, no state was kept. Neither variables nor codepage nor working directory etc..
So I decided to invent a little hack: Entering an opening or closing parenthesis starts and stops collecting the commands instead of executing them. After the closing parenthesis the list of commands ('batch') is used in the processBatch method to feed them all to the cmd process vie its redirected input. Worked fine.
The only problem was, obviously, now I got state but lost immediate response, so any errors wouldn't pop up until the batch was run.
So I decided to combine the good parts and, well, I knew I was heading for trouble when I realized, that to keep two loops working & waiting I have to use threading. Which I haven't done in years..
In the layout I chose the main() loop waits for user input and startCMDtask() runs startCMD() in a task. Here the input stream is scanned until is has data and then the cmd process is to process them..
But it doesn't work.
List<string> batch = new List<string>();
public volatile string output = "+";
public volatile string outputErr = "-";
Process CMD;
Task cmdTask;
volatile Queue<string> cmdQueue = new Queue<string>();
volatile public bool CMDrunning = false;
Tthis works just fine
private void processBatch()
{
Process p = new Process();
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "cmd.exe";
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.UseShellExecute = false;
p.StartInfo = info;
p.Start();
using (StreamWriter sw = p.StandardInput)
{
if (sw.BaseStream.CanWrite)
foreach(string line in batch) sw.WriteLine(line);
}
output = "^"; outputErr = "~";
try { output = p.StandardOutput.ReadToEnd(); } catch { }
try { outputErr = p.StandardError.ReadToEnd(); } catch { }
try { p.WaitForExit(); } catch { }
tb_output.AppendText(output + "\r\n" + outputErr + "\r\n");
}
These don't quite, but almost..
private void setupCMD()
{
CMD = new Process();
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "cmd.exe";
// info.Arguments = "/K"; // doesn't make a difference
info.CreateNoWindow = true;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.UseShellExecute = false;
CMD.StartInfo = info;
}
private void startCMDtask()
{
var task = Task.Factory.StartNew(() => startCMD());
cmdTask = task;
}
private void startCMD()
{
try { CMD.Start(); CMDrunning = true; }
catch { output = "Error starting cmd process.\r\n"; CMDrunning = false; }
using (StreamWriter sw = CMD.StandardInput)
{
if (sw.BaseStream.CanWrite)
do {
try
{
string cmd = cmdQueue.Dequeue();
if (cmd != null & cmd !="")
{
sw.WriteLine(cmd);
processOutputStreams();
}
}
catch {}
} while (CMDrunning);
}
private void processOutputStreams()
{
string newOutput = ""; string newOutputErr = "";
while (CMD.StandardOutput.Peek() > 0)
newOutput += (char)(CMD.StandardOutput.Read());
newOutput += "!?"; // at this point stdout is correctly captured (1)
try {
while (CMD.StandardError.Peek() > 0) // from here execution jumps away (2)
{ newOutputErr += (char)(CMD.StandardError.Read()); }
} catch {
newOutputErr = "?"; // never comes here
}
lock (output) // no noticable difference
lock (outputErr) //
{ // if I jump here (3) from (1) the result is displayed
// but not if i comment out the 2nd while loop (2)
if (newOutput != null & newOutput != "") output += newOutput + "\r\n";
if (newOutputErr != null & newOutputErr != "") outputErr += newOutputErr + "\r\n";
}
}
This is the call from the input processor in the main thread:
lock (cmdQueue) cmdQueue.Enqueue(cmd);
I have no idea which part is the problem: the process, the cmd shell, the input stream, the output stream, the threading, the locks or all of it in turns..??
I finally got it working. The reason for the erratic behaviour I described in the code samples was that the 3 streams were not accessed in an async'ed manner.
To rectify I discarded the processOutput function and replaced it by two calls that the process itself triggers. MS documetation gives a fine example here
I also made the StreamWriter sync, that feeds the process and the whole task it runs in as well.
Here is the new code:
private void startCMDtask()
{
var task = Task.Factory.StartNew(() => startCMD());
cmdTask = task;
}
private async void startCMD()
{
try { CMD.Start(); CMDrunning = true; }
catch { cmdErrOutput.Append("\r\nError starting cmd process.");
CMDrunning = false; }
CMD.BeginOutputReadLine();
CMD.BeginErrorReadLine();
using (StreamWriter sw = CMD.StandardInput)
{
if (sw.BaseStream.CanWrite)
do {
try
{
string cmd = cmdQueue.Dequeue();
if (cmd != null & cmd !="") await sw.WriteLineAsync(cmd);
}
catch { }
} while (CMDrunning);
try { CMD.WaitForExit(); }
catch { cmdErrOutput.Append("WaitForExit Error.\r\n"); }
}
}
This is how the process is set up now:
private void setupCMD()
{
CMD = new Process();
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "cmd.exe";
info.CreateNoWindow = true;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.UseShellExecute = false;
CMD.OutputDataReceived += new DataReceivedEventHandler(cmdOutputDataHandler);
CMD.ErrorDataReceived += new DataReceivedEventHandler(cmdErrorDataHandler);
cmdOutput = new StringBuilder();
cmdErrOutput = new StringBuilder();
CMD.StartInfo = info;
}
And here are the output handlers:
private static void cmdOutputDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
if (!String.IsNullOrEmpty(outLine.Data))
{ // Add the text to the collected output.
cmdOutput.Append(Environment.NewLine + outLine.Data);
}
}
private static void cmdErrorDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
if (!String.IsNullOrEmpty(outLine.Data))
{ // Add the text to the collected error output.
cmdErrOutput.Append(Environment.NewLine + outLine.Data);
}
}
At the end of the user input porcessing this is how the input queue is ged and the output fetched:
cmdUnDoStack.Push(cmd);
Application.DoEvents();
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => updateOutputArea(uiScheduler));
Using this little routine:
private void updateOutputArea(TaskScheduler uiScheduler)
{
Task.Factory.StartNew(() =>
{
tb_output.AppendText(cmdOutput + "\r\n" + cmdErrOutput + "\r\n");
cmdOutput.Clear();
cmdErrOutput.Clear();
}, System.Threading.CancellationToken.None, TaskCreationOptions.None, uiScheduler);
}
And now for the special treament some of the good old commands like CLS or COLOR need.. ;-)
I am currently having issues redirecting both STDError and STDOutput. What I want to do is when there is an error to be printed to a rich text box along with normal output if no error is thrown.
The issue I am having is that if I add the line to redirect the SDT Error:
string error = process1.StandardError.ReadToEnd();
rchsdtOut.Text = error;
Then my normal STD Out doesn't redirect to the text fild, but if there is an error that is printed.
process1 = new System.Diagnostics.Process();
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.RedirectStandardError = true;
process1.StartInfo.CreateNoWindow = true;
process1.StartInfo.FileName = "java.exe ";
//String abc = txtSingleBar.Text.Replace("\\", "/");
toLoad = lstBarToLoad.Items[i].Text;
process1.StartInfo.Arguments = "-Xmx512M -jar";
process1.StartInfo.Arguments += toLoad;
Console.WriteLine(process1.StartInfo.Arguments);
try
{
process1.Start();
process1.OutputDataReceived += (s, a) => myMethod(a);
process1.BeginOutputReadLine();
string error = process1.StandardError.ReadToEnd();
rchsdtOut.Text = error;
}
Method to write events to a text fild
private void myMethod(DataReceivedEventArgs e)
{
if (e.Data != null)
{
Action action = () => rchsdtOut.Text += "\r\n" + e.Data.ToString();
rchsdtOut.BeginInvoke(action, null);
Console.WriteLine(e.Data.ToString());
}
}//end of private
Bascally what I want is both to be redirected, SDTOut and SDTError if one should occur.
Any ideas?
Why not just take this:
process1.OutputDataReceived += (s, a) => myMethod(a);
process1.BeginOutputReadLine();
and add this: (Don't forget to add myErrorMethod!)
process1.ErrorDataReceived += (s, a) => myErrorMethod(a);
process1.BeginErrorReadLine();
Then take out this:
string error = process1.StandardError.ReadToEnd();
and instead, do this (if you want to wait for it to end):
process1.WaitForExit();
Basically, you cannot mix synchronous and asynchronous output reads. You have to either use Begin_______ReadLine() or read the stream objects, but not both.