I'm learning C# and I'm creating a simple WinForms application, and what it does is starting a simple OpenVPN client:
void button_Connect_Click(object sender, EventArgs e)
{
var proc = new Process();
proc.StartInfo.FileName = "CMD.exe";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.WorkingDirectory = #"C:\Program Files (x86)\OpenVPN\bin";
proc.StartInfo.Arguments = "/c openvpn.exe --config config.ovpn --auto-proxy";
// 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();
StreamWriter 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"))
{
myStreamWriter("myusername");
}
//MessageBox.Show(Data);
}
}
What it does now, is send all the output of the CMD to my program, which run commands depending on the output.
My current issue is, I need to write to the stream. I use myStreamWriter in proc_DataReceived, however it's not in the same context so it doesn't work.
I get the following error: The name 'myStreamWriter' does not exist in the current context, which is obviously non existent in that scope.
How do I make this work? Get/set properties? Like I said I'm quite new to C# so any help is appreciated.
In your form, add this field:
public class Form1Whatevver: Form {
private StreamWriter myStreamWriter;
}
Then in your code:
proc.Start();
this.myStreamWriter = proc.StandardInput;
proc.BeginErrorReadLine();
proc.BeginOutputReadLine();
Now, myStreamWriter will be available to proc_DataReceived()
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"))
{
if (myStreamWriter != null) // Just in case
{
myStreamWriter("myusername");
}
}
//MessageBox.Show(Data);
}
}
The simple quick-fix is to define myStreamWriter as a field in the class (i.e. outside the scopes of your methods), then instantiate it from within one of the scopes:
private StreamWriter _myStreamWriter;
void button_Connect_Click(object sender, EventArgs e)
{
// Do some stuff
_myStreamWriter = proc.StandardInput;
// Do some stuff
}
Having done that should let you use it wherever you want within the class.
The concept you are talking about is Scope not context.
The are several ways you could alter your code so that you can access myStreamWriter from proc_DataReceived.
Arguably the simplest, is to pass the object you wish to effect as a parameter to the function. This is often a good approach in the perspective of future multi-threading and a drive towards a functional style.
void proc_DataReceived(
object sender,
DataReceivedEventArgs e,
StreamWriter writer)
{
...
writer("myusername");
...
}
However proc_DataReceived is obviously an event handler and you probably don't want to change the signature of the function.
If you declare myStreamWriter as a Private Member Variable at the class level you will be able to access from anywhere within the class. The this is optional but good form.
class YourForm : Form
{
private StreamWriter myStreamWriter;
void button_Connect_Click(...
{
...
this.myStreamWriter = proc.StandardInput;
...
}
void proc_DataRecieved(...
{
...
this.myStreamWriter("myusername");
...
}
}
A property implementation is not necessary unless you want to access the StreamWriter from outside the class/form.
Related
i'm trying to open a process with c# and react to it, when it's been closed. This work's for me:
private void StartProc()
{
var process = new System.Diagnostics.Process { StartInfo = { FileName = "PathTo.exe" } };
process.Start();
process.EnableRaisingEvents = true;
process.Exited += this.Editor_Exited;
}
private void Editor_Exited(object sender, EventArgs e)
{
MessageBox.Show("Process canceled");
}
Lets say I'm opening a text editor with this code. If there is already an instance of this text editor the code won't open a second instance and also jumps instant in the Editor_Exited Code.
I want the code to open a new instance and don't jump in the Editor_Exited code.
string processName = "PathTo.exe";
var process = new System.Diagnostics.Process { StartInfo = { FileName = processName } };
if (process.Start())
{
process.EnableRaisingEvents = true;
process.Exited += this.Editor_Exited;
}
else
{
var p = Process.GetProcessesByName(processName);
p.WaitForExit();
}
I get this is not 100% what you are asking for, but its a work around
I created a GUI that calls a batch file and shows the command line output in a textbox after clicking a button. The batch file runs and the output is redirected properly for some time until ~80 lines in and the textbox output suddenly starts displaying the actual code in the batch file. Does this indicate a bug in the batch file or a problem with my script? I don't really know how to start debugging this problem.
I also notice that the batch file I'm calling from the GUI makes calls to other batch files. Could this be causing problems as well?
I should also mention that the batch file successfully runs from the command line.
private void buildProg_Click(object sender, EventArgs e)
{
using (Process process = new Process())
{
process.StartInfo.WorkingDirectory = some_directory;
process.StartInfo.FileName = "start.bat";
process.StartInfo.Arguments = "arg1 arg2";
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.OutputDataReceived += proc_OutputDataReceived;
process.EnableRaisingEvents = true;
process.Start();
process.BeginOutputReadLine();
}
}
private void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
this.Invoke((Action)(() =>
{
textBox1.AppendText(Environment.NewLine + e.Data);
}));
}
When I run it from the GUI the batch file seems to be tripping up at this part.
if exist %version_file_path% (
set /p _version= <%version_file_path%
) else (
echo %version_file_path% not found
pause
)
There's two things happening here;
1) The /p statement is asking for user input, pausing the command and waiting.
2) You're disposing the Process before the events are firing, meaning we wouldn't be able to simulate the input it needs to continue executing.
This pretty much solves it:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.FormClosing += Form1_FormClosing;
}
Process p;
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
p?.Dispose();
}
private void button1_Click(object sender, EventArgs e)
{
if (p != null)
p.Dispose();
p = new Process();
p.StartInfo.WorkingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
p.StartInfo.FileName = "test.bat";
p.StartInfo.Arguments = "";
p.StartInfo.CreateNoWindow = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.OutputDataReceived += proc_OutputDataReceived;
p.Start();
p.BeginOutputReadLine();
}
private void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
this.Invoke((Action)(() =>
{
textBox1.AppendText(Environment.NewLine + e.Data);
}));
//can use either of these lines.
(sender as Process)?.StandardInput.WriteLine();
//p.StandardInput.WriteLine();
}
}
I'm using System.Diagnostics.Process class to convert wav file to mp3 file in a separated process. The method that does the job like this:
public void ConvertWavToMp3 (TempFile srcFile, string title, Action<TempFile, Exception> complete)
{
var argument_fmt = "-S --resample 16 --tt {0} --add-id3v2 {1} {2}";
var dstFile = new TempFile(Path.GetTempFileName());
var proc = new System.Diagnostics.Process ();
proc.EnableRaisingEvents = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.FileName = "lame";
proc.StartInfo.Arguments = String.Format (argument_fmt,
title,
srcFile.Path,
dstFile.Path);
proc.Exited += delegate(object sender, EventArgs e) {
proc.WaitForExit();
srcFile.Delete();
complete(dstFile, null);
};
proc.Start();
}
I'm worried about GC because as proc is only a local variable, theoretically it doesn't exist anymore when the method returns. Therefor, proc can be garbage collected and the callback function complete will never be called.
But I don't really want to record proc somewhere and dispose it after the process exits, as that would expose the internal mechanism of how the wav to mp3 conversion is implemented.
Is my concern about GC valid? If GC of is potential problem, is there any way that I could prevent it without having to return the proc in this method?
BTW, I'm using Mono on linux.
Edit
Thanks for replies. I'm confirmed that I need to keep a copy of the process. So here's what I did:
public class LameConverter : IAudioConverter
{
// We need to store a reference to the process in case it was GCed.
IList<Process> _ProcList = new List<Process>();
public void ConvertWavToMp3 (TempFile srcFile, string title, Action<TempFile, Exception> complete)
{
// .. skipped ..
proc.Exited += delegate(object sender, EventArgs e) {
lock (this) {
_ProcList.Remove(proc);
}
proc.Dispose();
srcFile.Delete();
complete(dstFile, null);
};
proc.Start();
lock (this) {
_ProcList.Add(proc);
}
}
}
As long as the caller holds a reference to LameConverter, I don't need to worry about the GC anymore.
Any object without a root in the application is a candidate for garbage collection. In order to ensure that your callback fires you will need to find some place to store a reference to proc otherwise you will have undefined behavior.
One option in your case would be to return an object that encapsulates proc without exposing it via the public interface. Unfortunately in your case you must leak a bit of the underlying implementation to the caller of ConvertWavToMp3 in order to ensure that the desired behavior occurs.
Here's an alternate code sample that will work. However, it will block the call to ConvertWavToMp3(...) while the process is executing. Probably not what you want.
public void ConvertWavToMp3 (TempFile srcFile, string title, Action<TempFile, Exception> complete)
{
var argument_fmt = "-S --resample 16 --tt {0} --add-id3v2 {1} {2}";
var dstFile = new TempFile(Path.GetTempFileName());
var proc = new System.Diagnostics.Process ();
proc.EnableRaisingEvents = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.FileName = "lame";
proc.StartInfo.Arguments = String.Format (argument_fmt,
title,
srcFile.Path,
dstFile.Path);
using(var wh = new System.Threading.ManualResetEvent(false))
{
proc.Exited += delegate(object sender, EventArgs e) {
proc.WaitForExit();
srcFile.Delete();
complete(dstFile, null);
wh.Set();
};
proc.Start();
wh.WaitOne();
}
}
Like I said, this is probably not what you want, unless you're in, say, a console app. If you're in a GUI app, keep a reference to your proc. Something like:
public class MyForm : Form
{
// other form stuff
private System.Diagnostics.Process _encoderProc;
private void doEncode_Click(object sender, EventArgs e)
{
var argument_fmt = "-S --resample 16 --tt {0} --add-id3v2 {1} {2}";
var dstFile = new TempFile(Path.GetTempFileName());
var proc = new System.Diagnostics.Process ();
proc.EnableRaisingEvents = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.FileName = "lame";
proc.StartInfo.Arguments = String.Format (argument_fmt,
title,
srcFile.Path,
dstFile.Path);
proc.Exited += delegate(object sender, EventArgs e) {
proc.WaitForExit();
srcFile.Delete();
this.BeginInvoke((MethodInvoker)delegate {
// INSERT CODE HERE: your UI-related stuff that you want to do with dstFile
this._encoderProc = null;
});
};
proc.Start();
this._encoderProc = proc;
}
}
Note the use of BeginInvoke(...). If you're going to do UI-related stuff, it needs to be on the UI thread, and that Exited event won't fire on the UI thread. Hopefully this gets you moving in the right direction.
what is wrong why is that the richtextbox doesnt get the stream of Process output? theres no text display in richtextbox..
private void button1_Click(object sender, EventArgs e)
{
Process sortProcess;
sortProcess = new Process();
sortProcess.StartInfo.FileName = "sort.exe";
sortProcess.StartInfo.Arguments = this.comboBox1.SelectedItem.ToString();
// Set UseShellExecute to false for redirection.
sortProcess.StartInfo.CreateNoWindow = true;
sortProcess.StartInfo.UseShellExecute = false;
// Redirect the standard output of the sort command.
// This stream is read asynchronously using an event handler.
sortProcess.StartInfo.RedirectStandardOutput = true;
sortOutput = new StringBuilder("");
// Set our event handler to asynchronously read the sort output.
sortProcess.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
// Redirect standard input as well. This stream
// is used synchronously.
sortProcess.StartInfo.RedirectStandardInput = true;
// Start the process.
sortProcess.Start();
// Start the asynchronous read of the sort output stream.
sortProcess.BeginOutputReadLine();
sortProcess.WaitForExit();
richTextBox1.AppendText(sortOutput.ToString());
}
private static void SortOutputHandler(object sendingProcess,
DataReceivedEventArgs outLine)
{
sortOutput.Append(Environment.NewLine +
"[" + numOutputLines.ToString() + "] - " + outLine.Data);
}
}
so when sort.exe launches, it displays text, i want all those text be displayed also in richtextbox in RealTime (i dont want to wait for the process to exit, and then read all output)
how can i do it? any wrong part of my code? thanks
UPDATE #botz
i added this in my code
private void SortOutputHandler(object sendingProcess,
DataReceivedEventArgs outLine)
{
sortOutput.Append(Environment.NewLine +
"[" + numOutputLines.ToString() + "] - " + outLine.Data);
richTextBox1.AppendText(sortOutput.ToString());
}
but it throws this exception
Cross-thread operation not valid: Control 'richTextBox1' accessed from a thread other than the thread it was created on.
WaitForExit() blocks your UI Thread, so you don't see the new output.
Either wait for the process in a separate thread or replace WaitForExit() with something like this:
while (!sortProcess.HasExited) {
Application.DoEvents(); // This keeps your form responsive by processing events
}
In your SortOutputHandler, you can now directly append output to your textbox. But you should remember to check if you need to invoke it on the UI Thread.
You can check if it's on the UI thread this way in your handler:
if (richTextBox1.InvokeRequired) { richTextBox1.BeginInvoke(new DataReceivedEventHandler(SortOutputHandler), new[] { sendingProcess, outLine }); }
else {
sortOutput.Append(Environment.NewLine + "[" + numOutputLines.ToString() + "] - " + outLine.Data);
richTextBox1.AppendText(sortOutput.ToString());
}
This is working for me:
private void button1_Click(object sender, EventArgs e)
{
using (Process sortProcess = new Process())
{
sortProcess.StartInfo.FileName = #"F:\echo_hello.bat";
sortProcess.StartInfo.CreateNoWindow = true;
sortProcess.StartInfo.UseShellExecute = false;
sortProcess.StartInfo.RedirectStandardOutput = true;
// Set event handler
sortProcess.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
// Start the process.
sortProcess.Start();
// Start the asynchronous read
sortProcess.BeginOutputReadLine();
sortProcess.WaitForExit();
}
}
void SortOutputHandler(object sender, DataReceivedEventArgs e)
{
Trace.WriteLine(e.Data);
this.BeginInvoke(new MethodInvoker(() =>
{
richTextBox1.AppendText(e.Data ?? string.Empty);
}));
}
The example you started with was a console application, which doesn't care much about multithreaded access. For Windows Forms when you update a control this has to be done from the main UI thread, which is why BeginInvoke is needed. If you want to check rapidly if a handler like SortOutputHandler is working properly you can use System.Diagnostics.Trace.Write*, which doesn't need BeginInvoke.
EDIT: echo_hello.bat simply echoes the "hello" string:
#echo off
echo hello
If you are going to update the ui from another thread, you need to make sure you are on the main ui thread. In the method check for InvokeRequired. See InvokeRequired
Complete application and source code available from this external link of codeproject :
http://www.codeproject.com/Articles/335909/Embedding-a-Console-in-a-C-Application
This is tutorial of implementation of https://github.com/dwmkerr/consolecontrol.
As I said in the comment I posted to the question, by definition of what a sort does, it is impossible for there to be any output until all the input has been read. So the sort program is a bad example of getting output in realtime. So the following is for anyone in the future that wants to do something like this for console programs in general. The following uses a BackgroundWorker to get the output asynchronously and put it into a TextBox. A RichTextBox could easily be used instead.
public partial class MainWindow : Window
{
const string Path = #"C:\Windows\system32\sort.exe";
BackgroundWorker Processer = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
Processer.WorkerReportsProgress = true;
Processer.WorkerSupportsCancellation = true;
Processer.ProgressChanged += Processer_ProgressChanged;
Processer.DoWork += Processer_DoWork;
}
private void Processer_DoWork(object sender, DoWorkEventArgs e)
{
StreamReader StandardOutput = e.Argument as StreamReader;
string data = StandardOutput.ReadLine();
while (data != null)
{
Processer.ReportProgress(0, data);
data = StandardOutput.ReadLine();
}
}
private void Processer_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
string data = e.UserState as string;
if (data != null)
DataBox.Text += data + "\r\n";
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DataBox.Text = string.Empty;
ProcessStartInfo StartInfo = new ProcessStartInfo(Path);
StartInfo.RedirectStandardOutput = true;
StartInfo.RedirectStandardInput = true;
StartInfo.UseShellExecute = false;
Process p = null;
try { p = Process.Start(StartInfo); }
catch (Exception ex)
{
MessageBox.Show($"Error starting {Path}: {ex.Message}");
return;
}
// Get the output
Processer.RunWorkerAsync(p.StandardOutput);
// Put the input
p.StandardInput.WriteLine("John");
p.StandardInput.WriteLine("Alice");
p.StandardInput.WriteLine("Zoe");
p.StandardInput.WriteLine("Bob");
p.StandardInput.WriteLine("Mary");
// Tell the program that is the last of the data
p.StandardInput.Close();
}
}
For the sort program it is not necessary to call ReportProgress until after all the data has been read but this is a more generalized sample.
For instance, a thread that is a BackgroundWorker, can be cast like:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
System.ComponentModel.BackgroundWorker senderWorker
= sender as System.ComponentModel.BackgroundWorker;
}
The code above represents what I have for my Background worker thread. I cast [sender] as a BackGround Worker - because I know thats what he is.
I can't seem to find what I should cast it to if: instead of a Background worker, what if I had used a Process class, and executed say a DOS batch file, using:
enter code here
Process proc = new Process();
proc.FileName = "some_dos_batch_file.bat";
proc.Exited = ProcessExited;
proc.Start();
Sorry about syntax, but when this process completes, its completion will be handled by 'ProcessExited' below. But What should I cast the sender arg to in THAT case - NOT a Background Worker obviously, but I'm not sure to what? I would like to use the .Results property the same as I did for the Background worker.
Thanks - sorry for the confusion.
enter code here
void ProcessExited(object sender, EventArgs e)
{
}
I hope I have understood your question, if not, please clarify. If you are talking about threading and using the System.Diagnostics.Process then you would need to use Thread events...consider this below a simple class called TestARP that shells out to the command line using a hidden window to retrieve the MAC/IP address of the active connection, with the output of the command redirected to a stream which is appended to a stringbuilder instance:
public class TestARP
{
private StringBuilder sbRedirectedOutput = new StringBuilder();
public string OutputData
{
get { return this.sbRedirectedOutput.ToString(); }
}
public void Run()
{
System.Diagnostics.ProcessStartInfo ps = new System.Diagnostics.ProcessStartInfo();
ps.FileName = "arp";
ps.ErrorDialog = false;
ps.Arguments = "-a";
ps.CreateNoWindow = true;
ps.UseShellExecute = false;
ps.RedirectStandardOutput = true;
ps.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
using (System.Diagnostics.Process proc = new System.Diagnostics.Process())
{
proc.StartInfo = ps;
proc.Exited += new EventHandler(proc_Exited);
proc.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(proc_OutputDataReceived);
proc.Start();
proc.WaitForExit();
proc.BeginOutputReadLine();
while (!proc.HasExited) ;
}
}
void proc_Exited(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("proc_Exited: Process Ended");
}
void proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
if (e.Data != null) this.sbRedirectedOutput.Append(e.Data + Environment.NewLine);
//System.Diagnostics.Debug.WriteLine("proc_OutputDataReceived: Data: " + e.Data);
}
}
If you were to run this in a thread the Process's events will still get caught (only on the thread itself), but if you're talking about waiting for the thread to finish, look at this class code here called ThreadTestARP that runs the above class on a thread...
public class ThreadTestARP
{
private TestARP _testARP = new TestARP();
private ManualResetEvent _mre = new ManualResetEvent(false);
public ThreadTestARP()
{
}
public TestARP ARPTest
{
get { return this._testARP; }
}
public void Run()
{
Thread t = new Thread(new ThreadStart(RunThread));
t.Start();
this._mre.WaitOne();
// Blocks here...
t.Join();
}
private void RunThread()
{
this._testARP.Run();
this._mre.Set();
}
}
Note how the ManualResetEvent _mre is used to signal to say in the context of the thread, "right, I am done, back to the creator..."
Why can't you cast to a Process object? You can still access some members of Process objects, such as ExitCode or ExitTime, that have terminated.
http://msdn.microsoft.com/en-us/library/system.diagnostics.process.exited.aspx