Change button text AND function on click? - c#

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
}
}

Related

.NET (Windows Forms) : Update Textbox with output from external script

I want to create a small C#-program which has multiple buttons which execute a Powershell script and put the results asynchronously into a textbox. I wanted to do it the following way:
private void Button_Click(object sender, RoutedEventArgs e)
{
var ps1File = #"SomeScript.ps1";
Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName = "powershell.exe";
proc.StartInfo.Arguments = $"-NoProfile -ExecutionPolicy unrestricted \"{ps1File}\"";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
var err = "";
tbConsole.Text = "Loading printers ...";
proc.OutputDataReceived += (o, e2) =>
{
if (e2.Data == null) err = e2.Data;
else
{
if (e2.Data == null) err = e2.Data;
else tbConsoleError.AppendText(e2.Data);
}
};
proc.ErrorDataReceived += (o, e2) =>
{
if (e2.Data == null) err = e2.Data;
else tbConsoleError.AppendText(e2.Data);
};
proc.Start();
// and start asynchronous read
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
// wait until it's finished in your background worker thread
proc.WaitForExit();
tbConsole.AppendText("... finished");
}
Now when I hit the button the sript runs but while editing the textboxes it gives me an error that I cannot edit the textbox as it is managed by a different thread.
When I now try to edit my code to use "invoke" and delegate I find that my MainWindow (objet-reference: "this") does not have an "invoke"-method. What am I doing wrong?

Process doesn't appear to be running in C# application

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?

Create and actively use Process without freezing main thread

I'm currently trying to create new NodeJS process and while it's running, put it's console output into my winform textbox.
Whenever this process is executed, it's freezing main thread as if form is waiting for this process to exit. After the process is closed thats when the console output is added to the textbox.
What I'm trying to achieve is simultaneously have this node process running in the background and have whatever it's outputing in the textbox.
Edit 1:
I Managed to run the console without freezing main thread but the output only shows when the process is closed
My current code:
private void Btn_connect_Click(object sender, EventArgs e)
{
if(backgroundWorker1.IsBusy != true)
{
backgroundWorker1.RunWorkerAsync();
}
}
private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
nodeProcess = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "node.exe";
startInfo.Arguments = #"path" + " arg1 arg2 arg3";
startInfo.UseShellExecute = false;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.RedirectStandardOutput = true;
nodeProcess.StartInfo = startInfo;
nodeProcess.Start();
while (worker.CancellationPending != true)
{
Thread.Sleep(200);
AddText(nodeProcess.StandardOutput.ReadToEnd());
worker.ReportProgress(1);
}
e.Cancel = true;
}
public void AddText(string text)
{
if(txt_log.InvokeRequired)
{
txt_log.Invoke(new Action<string>(AddText), new object[] { text });
return;
}
txt_log.Text += "\n " + text;
}
Instead of a BackgroundWorker you could try using Process.BeginOutputReadLine, Process.OutputDataReceived, and Process.Exited.
void StartProcess()
{
Process nodeProcess = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "node.exe";
startInfo.Arguments = #"path" + " arg1 arg2 arg3";
startInfo.UseShellExecute = false;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.RedirectStandardOutput = true;
nodeProcess.StartInfo = startInfo;
nodeProcess.EnableRaisingEvents = true;
nodeProcess.Exited += nodeProcess_Exited;
nodeProcess.OutputDataReceived += nodeProcess_OutputDataReceived;
nodeProcess.Start();
nodeProcess.BeginOutputReadLine();
}
void nodeProcess_Exited(object sender, EventArgs e)
{
// Do something when the process exits, if you need to.
// You'll want to check InvokeRequired before you modify any of your form's controls.
}
void nodeProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (txt_log.InvokeRequired)
{
txt_log.Invoke(new Action<string>(AddText), new object[] { e.Data });
return;
}
txt_log.Text += "\n " + e.Data;
}

Check for User-Input in Batch-Script

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 ..."

How to display output of iperf cmd prompt in textbox

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; }));
}
}

Categories

Resources