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

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?

Related

Change button text AND function on click?

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

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

C# Async StandardInput Redirection

I am creating WinForm application which starts a process with redirection. I am using Async method:
proc = new Process();
proc.StartInfo.FileName = commandLocation + procesName;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.Arguments = arguments;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardInput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.WorkingDirectory = workingDirectory;
proc.StartInfo.CreateNoWindow = true;
proc.OutputDataReceived += (sender, args) => AppendLine(args.Data);
proc.ErrorDataReceived += (sender, args) => AppendLine(args.Data);
proc.Start();
proc.StandardInput.WriteLine(inputText);
proc.BeginOutputReadLine();
while (!proc.HasExited) //Instead of WaitForExit
{
Thread.Sleep(100); //Because of CPU usage
Application.DoEvents();
}
Here is AppendLine method for displaying output to richtextbox:
private void AppendLine(string line)
{
if (richTextBoxOutput.InvokeRequired)
{
Action act = () =>
{
this.richTextBoxOutput.AppendText(line + Environment.NewLine);
};
this.BeginInvoke(act);
}
else
{
richTextBoxOutput.AppendText(line + Environment.NewLine);
}
}
Everything works well. But when I run a console application that needs input (for example Console.ReadLine()), my application crashes link (not responding). How can I send input to the redirected console application throughout its run?

In C# How do you read from DOS program you executed if the program is constantly outputting?

I have found many examples of coding on how to execute cmd.exe and execute a command, and execute even nslookup and interact, but the problem I am having is with a particular dos program that when it starts, it does not stop "outputting". here is some code and I will put a comment and the errors I get from C#
Here is how I have it setup in a more advanced way so I can receive output from the program on events
public void StartApplication(string appNameAndPath)
{
StreamReader outputStream;
Process p = new Process();
p.StartInfo.FileName = appNameAndPath;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = false;//for now just so I can see it
p.Start();
//here is my advanced example
if(advanced == true)
{
outputStream = p.StandardOutput;
DoReadOutPut();
}
else
{//here is a simple example
while (p.StandardOutput.ReadLine() != null) //this hangs here until the application exists
{
txt += (p.StandardOutput.ReadLine());
}
}
}
void DoReadOutput()
{
outputStream.BaseStream.BeginRead( readOutputBuffer, 0, readOutputBuffer.Length, new AsyncCallback( OnReadOutputCompleted ), null );
//this does sometimes fire but only with 0 bytes, on other dos programs it would say Memory read not allowed
}
void OnReadOutputCompleted( IAsyncResult result )
{
int cbRead = outputStream.BaseStream.EndRead( result );
ProcessOutput( readOutputBuffer, cbRead );
DoReadOutput();
}
private void ProcessOutput(byte[] buffer, int cbRead)
{
string text = p.StartInfo.StandardOutputEncoding.GetString(buffer, 0, 10000); //this is where it hangs until the program exits or is not writing anymore
this.Invoke((Action)delegate
{
SetTextBoxValue(text);//im doing this because im on another thread otherwise textBox1.Text - text"
});
}
I do not want to have to use API and GetText and create an engine to ReadLastLine, can anyone help me with this? I suppose you would want an example exe, creating a C# application that while(true){Console.WriteLine("bla");} would suffice as the example exe but not the exe I am having trouble with. The exe takes over the dos window and has an "old school interface"
async/await can help here....
await Exec(yourExe,parameters);
Task Exec(string exe,string args)
{
var tcs = new TaskCompletionSource<object>();
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = exe;
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.Arguments = args;
var proc = Process.Start(psi);
proc.OutputDataReceived += (s, e) =>
{
this.Invoke((Action) (()=>richTextBox1.AppendText(e.Data + Environment.NewLine)));
};
proc.Exited += (s, e) => tcs.SetResult(null);
proc.EnableRaisingEvents = true;
proc.BeginOutputReadLine();
return tcs.Task;
}
You need to handle callback events to read streams:
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
Process proc = new Process();
proc.StartInfo = startInfo;
proc.ErrorDataReceived += new DataReceivedEventHandler(DataReceiveHandler);
proc.OutputDataReceived += new DataReceivedEventHandler(DataReceiveHandler);
proc.Start();
proc.BeginErrorReadLine();
proc.BeginOutputReadLine();
proc.WaitForExit();
Code borrowed from this post

StandardOutput blocks when asked for user input

I'm currently building a simple application, and it starts openvpn.exe. However, openvpn.exe asks for an username and password.
But, when this happens, my program does not read the string, it simply waits until the CMD is closed and then continues on with the code. So, it blocks until the window is closed.
Is there any way to circumvent this? My code is as follows:
void button_Connect_Click(object sender, EventArgs e)
{
var proc = new Process();
proc.StartInfo.FileName = #"C:\Program Files (x86)\OpenVPN\bin\openvpn.exe";
proc.StartInfo.Arguments = "--config config.ovpn --auto-proxy";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.WorkingDirectory = #"C:\Program Files (x86)\OpenVPN\bin";
// set up output redirection
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
// Input
proc.StartInfo.RedirectStandardInput = true;
// Other
proc.EnableRaisingEvents = true;
proc.StartInfo.CreateNoWindow = false;
// see below for output handler
proc.ErrorDataReceived += proc_DataReceived;
proc.OutputDataReceived += proc_DataReceived;
proc.Start();
myStreamWriter = proc.StandardInput;
proc.BeginErrorReadLine();
proc.BeginOutputReadLine();
proc.WaitForExit();
}
void proc_DataReceived(object sender, DataReceivedEventArgs e)
{
// output will be in string e.Data
if (e.Data != null)
{
string Data = e.Data.ToString();
if (Data.Contains("Enter Auth Username"))
{
this.myStreamWriter.WriteLine("myinput");
}
}
}
The proc_DataReceived is triggered on new line. Assuming the password is the only input that console app is asking for, you can just send it to your myStreamWriter right after starting the process. It will be buffered and consumed when necessary.

Categories

Resources