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?
Related
I've created a button that starts a "CMD" process and pings a specific machine.
I've got it to Ping successfully, and change the button text back and forth after clicking the button, but I'm not sure how to STOP the ping process (which is a ping -t command) on the second click of the same button.
HERE is my code so far, which selects the button, changes the text on click, starts the process and checks for errors. I've tried to add an "else" statement and say proc.Kill(), but it cant find the proc variable everywhere I try. Is there a correct way to do this?
public void Btn_Ping_Click_1(object sender, EventArgs e)
{
if (Btn_Ping.Text == "Ping")
{
Btn_Ping.Text = "Stop Ping";
}
else if (Btn_Ping.Text == "Stop Ping")
{
Btn_Ping.Text = "Ping";
}
th = new Thread(thread1);
th.Start();
}
public void thread1()
{
if (Btn_Ping.Text == "Stop Ping")
{
try
{
string command = "/c ping " + Txt_Main.Text.Trim() + " -t";
ProcessStartInfo procStartInfo = new ProcessStartInfo("CMD", command);
Process proc = new Process();
proc.StartInfo = procStartInfo;
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardInput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.CreateNoWindow = true;
proc.OutputDataReceived += new DataReceivedEventHandler(proc_OutPutDataRecieved);
proc.Start();
proc.BeginOutputReadLine();
proc.WaitForExit();
}
catch (Exception)
{
//If an error occurs within the try block, it will be handled here
}
}
void proc_OutPutDataRecieved(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
string newLine = e.Data.Trim() + Environment.NewLine;
MethodInvoker append = () => richTextBox1.Text += newLine;
richTextBox1.BeginInvoke(append);
}
}
}
Declare proc at the class level (instead of inside thread1). Then add to the button Click event:
if(proc != null)
{
if (!proc.HasExited)
proc.Kill();
proc = null;
}
else
{
th = new Thread(thread1);
th.Start();
}
Use Task objects rather than threads. Pass CancelationToken objects in to them like this:
private CancellationTokenSource _cts = null;
public void Btn_Ping_Click_1(object sender, EventArgs e)
{
if (Btn_Ping.Text == "Ping")
{
_cts = new CancellationTokenSource();
Btn_Ping.Text = "Stop Ping";
var task = new Task(() => task1(cts.Token));
task.Start();
}
else if (Btn_Ping.Text == "Stop Ping")
{
Btn_Ping.Text = "Ping";
_cts.Cancel();
}
}
public void task1(CancellationToken ct)
{
try
{
string command = "/c ping " + Txt_Main.Text.Trim() + " -t";
var procStartInfo = new ProcessStartInfo("CMD", command);
var proc = new Process {StartInfo = procStartInfo};
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardInput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.CreateNoWindow = true;
proc.OutputDataReceived += new DataReceivedEventHandler(proc_OutPutDataRecieved);
proc.Start();
proc.BeginOutputReadLine();
while (!proc.WaitForExit(250))
{
if (ct.IsCancellationRequested)
{
proc.Kill();
return;
}
}
}
catch (Exception)
{
//If an error occurs within the try block, it will be handled here
}
}
Iam currently starting batch-script with this method (async)
private void executor(string path)
{
//Vars
ProcessStartInfo processInfo;
Process process;
processInfo = new ProcessStartInfo(path);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;
process = Process.Start(processInfo);
process.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
{
//Handle Output
});
process.ErrorDataReceived += new DataReceivedEventHandler((sender, e) =>
{
//Handle Errors
});
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
//Handle Exit
}
The user chose the script (which will be performed by my program) and can run it. But the user can chose a script, which contains a pause-Command.
This will cause a deadlock.
How can I check that the script need a user-input?
I found a solution. Iam not longer use the OutputDataReceived-Event.
Here is my new (well-working) code:
private void executor(string path)
{
//Vars
ProcessStartInfo processInfo;
Process process;
processInfo = new ProcessStartInfo(path);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;
process = Process.Start(processInfo);
process.ErrorDataReceived += new DataReceivedEventHandler((sender, e) =>
{
//Handle Errors
});
process.BeginErrorReadLine();
//Try read while script is running (will block thread until script ended)
while (!process.HasExited)
while (!process.StandardOutput.EndOfStream)
{
char c = (char)process.StandardOutput.Read();
if (c == '\n')
{
_outputList.Add(_lastOutputStringBuilder.ToString());
_lastOutputStringBuilder.Clear();
//Handle Output
}
else
_lastOutputStringBuilder.Append(c);
}
//Handle Exit
}
With this code, I can store the last line (which must not end with a linebreak) and can check it for lines like "Press a key ..."
I find myself in a race condition when subscribing to the output and error stream of System.Diagnostics.Process.
Here is a minimal example of what I do:
private string execute(string command, string arguments, int mstimeout)
{
string report = string.Empty;
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
Process p = new Process();
DataReceivedEventHandler ErrorDataReceived = (o, e) => { error.Append(e.Data); };
DataReceivedEventHandler OutputDataReceived = (o, e) => { output.Append(e.Data); };
try
{
p.StartInfo.FileName = command;
p.StartInfo.Arguments = arguments;
p.EnableRaisingEvents = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardOutput = true;
p.OutputDataReceived += OutputDataReceived;
p.ErrorDataReceived += ErrorDataReceived;
p.Start();
p.BeginErrorReadLine();
p.BeginOutputReadLine();
p.WaitForExit(mstimeout);
report = output.ToString() + "\n" + error.ToString();
}
finally
{
p.OutputDataReceived -= OutputDataReceived;
p.ErrorDataReceived -= ErrorDataReceived;
}
return report;
}
When debugging slowly the behaviour is what I hoped it would be. When running without stops the report ends up empty.
I assume there is a race condition where the underlying streaming objects are disposed before all the output was handled.
Is there something I can do to wait for all the output to be processed?
I don't thing you can do anything. I think that Microsoft has completely miss the shot on staring process that you want to get their outputs (output and error). There will always be a problem. At the minimum, it is the race condition you have.
I reported a bug at Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/3119134/race-condition-in-process-asynchronous-output-stream-read
Just as reference, this is the code I'm using now (it contains the same race condition problem any implementation running in asynchronous mode would have).
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
namespace HQ.Util.General
{
public class ProcessExecutionWithOutputCapture
{
// ************************************************************************
public class ProcessWithOutputCaptureResult
{
public string Error { get; internal set; }
public string Output { get; internal set; }
public string ExecutionError
{
get
{
if (String.IsNullOrEmpty(Error))
{
return Error;
}
return Exception?.ToString();
}
}
public bool HasTimeout { get; internal set; }
/// <summary>
/// Can be cancel through the eventCancel which will cancel the wait (and if set, will kill the process)
/// </summary>
public bool HasBeenCanceled { get; internal set; }
public int ExitCode { get; internal set; }
public Exception Exception { get; internal set; }
public bool HasSucceded => !HasTimeout && Exception == null;
}
// ************************************************************************
private StringBuilder _sbOutput = new StringBuilder();
private StringBuilder _sbError = new StringBuilder();
private AutoResetEvent _outputWaitHandle = null;
private AutoResetEvent _errorWaitHandle = null;
// Could be usefull when user want to exit to not wait for process to end and kill it (if wanted)
public EventWaitHandle AdditionalConditionToStopWaitingProcess { get; set; }
public bool IsAdditionalConditionToStopWaitingProcessShouldAlsoKill { get; set; }
public ProcessWindowStyle ProcessWindowStyle { get; set; } = ProcessWindowStyle.Hidden;
public bool CreateWindow { get; set; } = false;
public static ProcessWithOutputCaptureResult ExecuteWith(string executablePath, string arguments, int timeout = Timeout.Infinite, ProcessWindowStyle processWindowStyle = ProcessWindowStyle.Hidden, bool createWindow = false)
{
var p = new ProcessExecutionWithOutputCapture();
return p.Execute(executablePath, arguments, timeout);
}
// ************************************************************************
/// <summary>
/// Only support existing exectuable (no association or dos command which have no executable like 'dir').
/// But accept full path, partial path or no path where it will use regular system/user path.
/// </summary>
/// <param name="executablePath"></param>
/// <param name="arguments"></param>
/// <param name="timeout"></param>
/// <returns></returns>
private ProcessWithOutputCaptureResult Execute(string executablePath, string arguments = null, int timeout = Timeout.Infinite)
{
ProcessWithOutputCaptureResult processWithOutputCaptureResult = null;
using (Process process = new Process())
{
process.StartInfo.FileName = executablePath;
process.StartInfo.Arguments = arguments;
process.StartInfo.UseShellExecute = false; // required to redirect output to appropriate (output or error) process stream
process.StartInfo.WindowStyle = ProcessWindowStyle;
process.StartInfo.CreateNoWindow = CreateWindow;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
_outputWaitHandle = new AutoResetEvent(false);
_errorWaitHandle = new AutoResetEvent(false);
bool asyncReadStarted = false;
try
{
process.OutputDataReceived += ProcessOnOutputDataReceived;
process.ErrorDataReceived += ProcessOnErrorDataReceived;
process.Start();
// Here there is a race condition. See: https://connect.microsoft.com/VisualStudio/feedback/details/3119134/race-condition-in-process-asynchronous-output-stream-read
process.BeginOutputReadLine();
process.BeginErrorReadLine();
asyncReadStarted = true;
// See: ProcessStartInfo.RedirectStandardOutput Property:
// https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Diagnostics.ProcessStartInfo.RedirectStandardOutput);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2);k(DevLang-csharp)&rd=true
// All 4 next lines should only be called when not using asynchronous read (process.BeginOutputReadLine() and process.BeginErrorReadLine())
//_sbOutput.AppendLine(process.StandardOutput.ReadToEnd());
//_sbError.AppendLine(process.StandardError.ReadToEnd());
//_sbOutput.AppendLine(process.StandardOutput.ReadToEnd());
//_sbError.AppendLine(process.StandardError.ReadToEnd());
var waitHandles = new WaitHandle[1 + (AdditionalConditionToStopWaitingProcess == null ? 0 : 1)];
waitHandles[0] = new ProcessWaitHandle(process);
if (AdditionalConditionToStopWaitingProcess != null)
{
waitHandles[1] = AdditionalConditionToStopWaitingProcess;
}
bool hasSucceded = false;
int waitResult = WaitHandle.WaitAny(waitHandles, timeout);
if (waitResult == 1) // The wait has been interrrupted by an external event
{
if (IsAdditionalConditionToStopWaitingProcessShouldAlsoKill)
{
process.Kill();
}
}
else if (waitResult == 0) // Process has completed normally, no timeout or external event
{
// Ensure internal process code has completed like ensure to wait until stdout et stderr had been fully completed
hasSucceded = process.WaitForExit(timeout);
if (_outputWaitHandle.WaitOne(timeout) && _errorWaitHandle.WaitOne(timeout))
{
processWithOutputCaptureResult = new ProcessWithOutputCaptureResult();
processWithOutputCaptureResult.ExitCode = process.ExitCode;
processWithOutputCaptureResult.Output = _sbOutput.ToString();
processWithOutputCaptureResult.Error = _sbError.ToString();
}
}
else // Process timeout
{
processWithOutputCaptureResult = new ProcessWithOutputCaptureResult();
processWithOutputCaptureResult.HasTimeout = true;
}
}
catch (Exception ex)
{
if (ex.HResult == -2147467259)
{
processWithOutputCaptureResult = new ProcessWithOutputCaptureResult();
processWithOutputCaptureResult.Exception = new FileNotFoundException("File not found: " + executablePath, ex);
}
else
{
processWithOutputCaptureResult = new ProcessWithOutputCaptureResult();
processWithOutputCaptureResult.Exception = ex;
}
}
finally
{
if (asyncReadStarted)
{
process.CancelOutputRead();
process.CancelErrorRead();
}
process.OutputDataReceived -= ProcessOnOutputDataReceived;
process.ErrorDataReceived -= ProcessOnOutputDataReceived;
_outputWaitHandle.Close();
_outputWaitHandle.Dispose();
_errorWaitHandle.Close();
_errorWaitHandle.Dispose();
}
}
return processWithOutputCaptureResult;
}
// ************************************************************************
private void ProcessOnOutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data == null)
{
_outputWaitHandle.Set();
}
else
{
_sbOutput.AppendLine(e.Data);
}
}
// ************************************************************************
private void ProcessOnErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data == null)
{
_errorWaitHandle.Set();
}
else
{
_sbError.AppendLine(e.Data);
}
}
// ************************************************************************
}
}
Usage (as an application that forward the execution):
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using HQ.Util.General;
using System.Reflection;
namespace ExecutionForwarder
{
class Program
{
static void Main(string[] args)
{
Stopwatch stopwatch = Stopwatch.StartNew();
Console.WriteLine($"App: {Assembly.GetEntryAssembly().FullName}");
Console.WriteLine($"Executing from folder: {Environment.CurrentDirectory}");
Console.WriteLine($"at: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
Console.WriteLine($"With args: [{string.Join(" ", args.Skip(1))}]");
if (args.Length == 1 && args[0].ToLower().StartsWith("-delay:"))
{
int millisec;
if (Int32.TryParse(args[0].Substring(args[0].IndexOf(":") + 1), out millisec))
{
Console.WriteLine($"Sleeping for {millisec} milliseconds and will exit.");
Thread.Sleep(millisec);
}
else
{
Console.Error.WriteLine("Error while trying to read the delay.");
Environment.ExitCode = -99;
}
}
else
{
if (args.Length == 0)
{
Console.Error.WriteLine($"Can't forward execution. There is no argument (executable) provided.");
Environment.ExitCode = -99;
}
else
{
var result = ProcessExecutionWithOutputCapture.ExecuteWith(args[0], string.Join(" ", args.Skip(1)));
Console.Write(result.Output);
Console.Error.Write(result.Error);
Environment.ExitCode = result.ExitCode;
}
}
Console.WriteLine($"Done in {stopwatch.ElapsedMilliseconds} millisecs");
}
}
}
The problem was a timeout in certain situations. I needet to Kill the Process to avoid follow up issues.
if(!p.WaitForExit(mstimeout))
{
p.Kill();
}
For good measure I threw in a cleanup in the finally part that is likely not needet.
finally
{
p.OutputDataReceived -= OutputDataReceived;
p.ErrorDataReceived -= ErrorDataReceived;
p.Dispose();
p = null;
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true);
}
Edit: I deleted the finally part since the comment below seems correct.
Edit: There was a deeper problem where the timeout was hit because of a required input. I ended up invoking a different command that is silent.
For future reference - i settled on this now:
private string execute(string command, string arguments, int mstimeout)
{
bool timeout = false;
string report = string.Empty;
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
Process p = new Process();
DataReceivedEventHandler StoreError = (o, e) => { error.Append(e.Data); };
DataReceivedEventHandler StoreOutput = (o, e) => { output.Append(e.Data); };
try
{
Debug.WriteLine(command);
Debug.WriteLine(arguments);
p.StartInfo.FileName = command;
p.StartInfo.Arguments = arguments;
p.EnableRaisingEvents = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardOutput = true;
p.OutputDataReceived += StoreOutput;
p.ErrorDataReceived += StoreError;
p.Start();
p.BeginErrorReadLine();
p.BeginOutputReadLine();
if (!p.WaitForExit(mstimeout))
{
p.Kill();
timeout = true;
Debug.WriteLine("Process killed");
}
else
{
p.WaitForExit();
}
}
finally
{
report = output.ToString() + "\n" + error.ToString();
Debug.WriteLine(report);
p.Dispose();
}
if (timeout)
{
throw new TimeoutException("Timeout during call: " + command + " " + arguments);
}
return report;
}
I am using iperf-2.0.5-2-win32 tool to find network bandwidth. I have written codes in c# which opens the cmd prompt, pass iperf parameters to start server side & client side. iperf-2.0.5-2-win32 exe will not open directly, need to open through cmd prompt only.
At present the output(Transfer rate & Bandwidth) is displaying on cmd prompt itself. I want these output to be displayed in textbox
I have tried StreamReader also. But it takes null, I have also tried OutputDataReceived Event, its also taking null.
Found few codes for ipconfig & ping.but those were not working with iperf codes.
button_click event(),
{
Process Client_proc = new Process();
ProcessStartInfo Client_command = new ProcessStartInfo("cmd.exe");
string ip = txtIP.Text;
Client_command.CreateNoWindow = true;
Client_command.WindowStyle = ProcessWindowStyle.Hidden;
Client_command.WorkingDirectory = #"E:\Iperf\RunEXE_Through_Application\iperf-2.0.5-2-win32";
Client_command.Arguments = "/c START iperf -c " + ip;
Client_proc.StartInfo = Client_command;
Client_command.RedirectStandardOutput = true;
Client_command.UseShellExecute = false;
Client_proc.OutputDataReceived += new DataReceivedEventHandler(Client_proc_OutputDataReceived);
Client_proc.Start();
Client_proc.BeginOutputReadLine();
Client_proc.WaitForExit();
}
void Client_proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
string newLine = e.Data.Trim() + Environment.NewLine;
MethodInvoker append = () => txtOutput.Text += newLine;
txtOutput.BeginInvoke(append);
}
}
Plz help me.Earlier responses are appreciated
Thanks
you use this complete code for your disposal
It is not perfect (some problems when using multiple streams )
public void RunProcess(string FileName, string Arguments, bool EventWhenExit )
{
process = new Process();
process.EnableRaisingEvents = true;
process.OutputDataReceived += new DataReceivedEventHandler(OnDataReceivedEvent);
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.LoadUserProfile = false;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = FileName; // Gets or sets the application or document to start.
process.StartInfo.Arguments = Arguments;//Gets or sets the set of command-line arguments to use when starting the application
Thread.Sleep(1000);
if (EventWhenExit)
{
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(myprocess_Exited);/*New line */
}
process.Start();
process.BeginOutputReadLine();
PID = process.Id;
}
private void myprocess_Exited(object sender, EventArgs e)
{
process.Refresh();
Thread.Sleep(2000);
onProcessEnd(this, "ENDOF " + Proc.ToString());
Console.WriteLine("Process exsiting ");
}
private void OnDataReceivedEvent(object sender, DataReceivedEventArgs e)
{
string OutputFromProcess = e.Data;
//fire event to event handler class for further use
onDataOutputFromProcess(this, OutputFromProcess, Proc.ToString());
}
than in your GUI layer you should bind to onDataOutputFromProcess event
there you should have something like
if (screenToPrint.InvokeRequired) //&& this.Visible)
{
try
{
this.Invoke(new Action<AppendToScreenParam>(AppendTextFullConfig), new object[] { append });
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return;
}
else
{
screenToPrint.SelectionFont = font;
screenToPrint.SelectionColor = append.Color;
//screenToPrint.AppendText(append.Message);
string TextToPrint = string.Format("{0}\n", append.Message);
screenToPrint.AppendText(TextToPrint);
}
}
Maybe it is because iperf process is returning error. Subscribe the ErrorDataReceived event with Client_proc.ErrorDataReceived += Client_proc_ErrorDataReceived; and see the results. If command returns error, you can see the error message as output.
void Client_proc_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
this.txtOutput.BeginInvoke(new MethodInvoker(() => { this.txtOutput.Text = e.Data; }));
}
}
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.. ;-)