How to start a process in Unity3D but not stuck Unity? - c#

I'm trying to start a process to excute a shell script with C# in Unity3D on MacOS, and I write the code below.
[MenuItem("Test/Shell")]
public static void TestShell()
{
Process proc = new Process();
proc.StartInfo.FileName = "/bin/bash";
proc.StartInfo.WorkingDirectory = Application.dataPath;
proc.StartInfo.Arguments = "t.sh";
proc.StartInfo.CreateNoWindow = false;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Debug.Log(e.Data);
}
});
proc.Start();
proc.BeginOutputReadLine();
proc.WaitForExit();
proc.Close();
}
Shell script::
echo "1"
sleep 2s
open ./
echo "4"
When I run this code, Unity3D get stuck until the shell script excute complete.
I tried to uncommit "proc.WaitForExit();", it did open the finder and not stuck anymore, but output nothing.
So how can I start a process in Unity3D and get the output of the shell script immediately?

As said simply run the entire thing in a separate thread:
[MenuItem("Test/Shell")]
public static void TestShell()
{
var thread = new Thread(TestShellThread);
thread.Start();
}
private static void TestShellThread ()
{
Process proc = new Process();
proc.StartInfo.FileName = "/bin/bash";
proc.StartInfo.WorkingDirectory = Application.dataPath;
proc.StartInfo.Arguments = "t.sh";
proc.StartInfo.CreateNoWindow = false;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Debug.Log(e.Data);
}
});
proc.Start();
proc.BeginOutputReadLine();
proc.WaitForExit();
proc.Close();
}
Note though in general: If you want to use the result in any Unity API related things besides logging you will need to dispatch them back into the main thread!

Related

C# Realtime standard output/error capture of a process

I'm developing a C# application and I need to run an external console process (for example a python script) and receive the output data of the script in real-time. The python script is something like this:
import time
while 1:
print("Hi Foo!")
time.sleep(.3)
The following C# code prints the python script output on the C# console in real-time:
static void Main(string[] args)
{
using (Process process = new Process())
{
process.StartInfo.FileName = "python.exe";
process.StartInfo.Arguments = "test.py";
process.StartInfo.UseShellExecute = false;
process.Start();
process.WaitForExit();
}
}
However, when I try to capture the output data and write them on the console manually, I fail. The recommended solution according to other posts is something like this, but it doesn't work:
static void Main(string[] args)
{
using (Process process = new Process())
{
process.StartInfo.FileName = "python.exe";
process.StartInfo.Arguments = "test.py";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.EnableRaisingEvents = true;
process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
}
}
The process.StandardOutput.ReadToEnd() works in blocking mode waiting for the process to exit and returns the whole output all at once. What exactly is the problem with the real-time output capturing and how can I fix it?
using (var process = new Process())
{
process.StartInfo.FileName = #"python.exe";
process.StartInfo.Arguments = "-u test.py";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.Start();
while (!process.StandardOutput.EndOfStream)
{
string line = process.StandardOutput.ReadLine();
Console.WriteLine(line);
// do something with line
}
process.WaitForExit();
Console.Read();
}

C# program that ouputs lmstat on remote license server

I'm working on my first C# program. I've created a GUI where you select the network-licensed software package in a combobox, and it displays the license usage and statistics in a textbox (lmutil.exe).
Here's the problem: upon first selecting from the combobox, nothing happens, but when you select another software from the combobox list, it outputs the license stats from the previously selected software. Below is the code I have:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ComboBoxItem_Selected(object sender, RoutedEventArgs e)
{
if (ComboBox1.Text == "ComboItem1")
{
Process proc = new Process();
proc.StartInfo.FileName = "lmutil.exe";
proc.StartInfo.Arguments = "lmstat -a -c port#host";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.CreateNoWindow = true;
// start the process
proc.Start();
string s = proc.StandardOutput.ReadToEnd();
TextBox1.Text = s;
}
else
{
if (ComboBox1.Text == "ComboItem2")
{
Process proc = new Process();
proc.StartInfo.FileName = "lmutil.exe";
proc.StartInfo.Arguments = "lmstat -a -c port#host";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.CreateNoWindow = true;
// start the process
proc.Start();
string s = proc.StandardOutput.ReadToEnd();
TextBox1.Text = s;
}
else
{
if (ComboBox1.Text == "ComboItem3")
{
Process proc = new Process();
proc.StartInfo.FileName = "lmutil.exe";
proc.StartInfo.Arguments = "lmstat -a -c port#host";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.CreateNoWindow = true;
// start the process
proc.Start();
string s = proc.StandardOutput.ReadToEnd();
TextBox1.Text = s;
}
}
}
Not sure what GUI library you are using (Window is not a class I recognise, (I use WinForms)) but the following seems to work for me (with a real port and host name substituted for one of the three items (I only have one license server to test with)).
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void comboBox1_SelectedIndexChanged(Object sender, EventArgs e){
switch (this.comboBox1.Text){
case "item1":
this.textBox1.Text = this.getLMStat(158, "ONE");
MessageBox.Show("DONE");
break;
case "item2":
this.textBox1.Text = this.getLMStat(158, "TWO");
MessageBox.Show("DONE");
break;
case "item3":
this.textBox1.Text = this.getLMStat(158, "THREE");
MessageBox.Show("DONE");
break;
default:
MessageBox.Show("Unsupported Value");
break;
}
}
private String getLMStat(int port, String server){
try {
Process proc = new Process();
proc.StartInfo.FileName = "lmutil.exe";
proc.StartInfo.Arguments = "lmstat -a -c " + port + "#" + server;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.CreateNoWindow = true;
// start the process
proc.Start();
return proc.StandardOutput.ReadToEnd();
}
catch (Exception){return "Do something with Exception";}
}
}
The message boxes are there so that you know when lmstat has returned, since it hangs for a little while (especially when the port and/or host is invalid).
Are you handling the correct event for the combo box selection changing?
Process.Start() starts the process but does not wait for it to finish. You can call proc.WaitForExit() immediately after string s = proc.StandardOutput.ReadToEnd();

Find Drive Letter Using Net Use

What I'm trying to do
Launch PSExec to open CMD on a remote computer passing "Net Use * \Server\Share" command
Launch PSExec again and remove the share i just created.
I can't seem to figure out how to get the drive letter that the wild card used.
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.FileName = #"\\Server\PStools\PSExec.exe";
p.StartInfo.Arguments = #"\\ComputerName -e -s cmd.exe ""/C Net USE * \\Server\Share /Persistent:NO""";
p.Start();
The net use command with a wildcard will pick the first available drive letter in the sequence from Z to A. It reports the selected drive letter in the console output like so:
C:\>net use * \\server\share
Drive Z: is now connected to \\server\share.
The command completed successfully.
C:\>_
So what you need is to capture the output of the PSExec command and parse it to find the allocated drive letter.
I haven't tried this with PSExec as yet, but this is the code I use for capturing the output of commands via cmd.exe:
static class CommandRunner
{
static StringBuilder cmdOutput = new StringBuilder();
public static string Run(string command)
{
if (string.IsNullOrWhiteSpace(command))
return null;
using (var proc = new Process())
{
proc.StartInfo.FileName = "cmd.exe";
proc.StartInfo.Arguments = "/c " + command;
proc.StartInfo.LoadUserProfile = false;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.UseShellExecute = false;
proc.EnableRaisingEvents = true;
proc.OutputDataReceived += proc_DataReceived;
proc.ErrorDataReceived += proc_DataReceived;
try
{
proc.Start();
proc.BeginErrorReadLine();
proc.BeginOutputReadLine();
proc.WaitForExit();
}
catch (Exception e)
{
cmdOutput.AppendLine("***Exception during command exection***");
cmdOutput.AppendLine(e.Message);
cmdOutput.AppendLine("*** ***");
}
}
return cmdOutput.ToString();
}
static void proc_DataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
cmdOutput.AppendLine(e.Data);
}
}
To get the output of a command on the local machine call it like this:
string output = CommandRunner.Run("net use");
Shouldn't be too hard to add a method that executes commands on a remote PC using PSExec instead of the local cmd.exe. Something similar to the following:
public static string Remote(string target, string command, string peFlags = "-e -s")
{
if (string.IsNullOrWhiteSpace(command))
return null;
using (var proc = new Process())
{
proc.StartInfo.FileName = #"C:\PSTools\PSExec.exe";
proc.StartInfo.Arguments = string.Format(#"\\{0}{1} cmd.exe ""/c {2}""", target, peFlags == null ? "" : " " + peFlags, command);
proc.StartInfo.LoadUserProfile = false;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.UseShellExecute = false;
proc.EnableRaisingEvents = true;
proc.OutputDataReceived += proc_DataReceived;
try
{
proc.Start();
proc.BeginOutputReadLine();
proc.WaitForExit();
}
catch
{ }
}
return cmdOutput.ToString();
}
NOTE: I removed the stderr redirection here because I want only the output of the remote program, not the various lines added to the output by PSExec.

Process standard output in C# returning empty string

I'm trying to list the supported interfaces on which dumpcap.exe can capture data.
The command i want to run is "dumpcap.exe -D"
On my system, it gives the output as :-
1. \Device\NPF_{1B627AA8-2A1D-4C90-B560-517CF71B33A5} (Intel(R) 825
Network Connection)
2. \Device\NPF_{7ECC4D31-DF17-49AB-960D-628D0580F3C6} (Microsoft)
I'm trying to read the above output by runing the same command from a WPF C# application. I have the following code to do so:-
Process proc = new Process();
//set the path to dumpcap.exe (verified by me)
proc.StartInfo.FileName = "..\\..\\..\\..\\..\\Dumpcap\\dumpcap.exe";
StringBuilder dumpcapArgs = new StringBuilder();
dumpcapArgs.Append("-D");
proc.StartInfo.Arguments = dumpcapArgs.ToString();
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.UseShellExecute = false;
proc.Start();
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
MessageBox.Show(line);
}
When i debug, the execution does not go in my while loop at all. The StandardOutput is already at the end of stream. Digging further into StandardOutput base stream class members, i see a lot of System.NotSupportedExceptions in length, position etc.
What is possibly wrong with the above code?
Quick Observation
I tried another sample C# console application with the following code:
Process proc = new Process();
proc.StartInfo.FileName = "C:\\Karan\\Dumpcap\\dumpcap.exe";
proc.StartInfo.Arguments = "-D";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
Console.WriteLine(proc.StandardOutput.ReadToEnd());
proc.WaitForExit();
This prints the output as expected!
However, if i try to read the standardoutput in a string variable, i get an empty string.
For example, this returns me an empty string:-
Process proc = new Process();
proc.StartInfo.FileName = "C:\\Karan\\Dumpcap\\dumpcap.exe";
proc.StartInfo.Arguments = "-D";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
String output = proc.StandardOutput.ReadToEnd();
proc.WaitForExit();
Have you set EnableRaisingEvents to true?
I'm not sure if this will do the trick since I usually do the redirecting of the output in a different way:
private Process proc;
private string procOutput = string.Empty;
private void StartProc()
{
this.procOutput = string.Empty;
this.proc = new Process();
...
this.proc.StartInfo.UseShellExecute = false;
this.proc.StartInfo.CreateNoWindow = true;
this.proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
this.proc.EnableRaisingEvents = true;
this.proc.OutputDataReceived += this.ProcOutputDataReceivedHandler;
this.proc.Exited += this.ProcExitedHandler;
this.proc.Start();
this.proc.BeginOutputReadLine();
}
private void ProcOutputDataReceivedHandler(object sendingProcess, DataReceivedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Data))
{
this.procOutput += e.Data;
}
}
private void ProcExitedHandler(object sender, EventArgs e)
{
// Check the exitcode for possible errors happened during execution.
MessageBox.Show(this.procOutput);
}
You never start the process. You should do so using proc.Start();
Length, Position and similar members of Stream are not supported, because the stream representing the standard out of a console application is unknown in length and a forward only stream. So that's normal and not an error in your code.
Try this:
string output = proc.StandardOutput.ReadToEnd();
proc.WaitForExit();
foreach(var line in output.Split('\r'))
{
MessageBox.Show(line);
}
Why does this work? Potentially there is nothing in the output stream when you first query EndOfStream. This is a race condition.
I consulted this article before posting. Give it a read http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandardoutput.aspx

.NET Process fails on Windows 8

I have two weird problems running an app on Windows 8 (which works fine on Windows 7)
I'm running external "a.exe" app.
First issue is that when I run "a.exe" using Process - I don't get any output.
If I run a batch file which executes "a.exe" and write output to a file - there is an output.
The second issue is that in both cases (Batch and Process) "a.exe" fails. But from command line it works.
Here is the code:
proc = new Process();
proc.StartInfo.FileName = Path;
proc.StartInfo.Arguments = args;
proc.StartInfo.WorkingDirectory = GetDirectoryName(Path);
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.Verb = "runas administrator";
proc.OutputDataReceived += (sendingProcess, line) =>
{
string s = line.Data;
if (!string.IsNullOrWhiteSpace(s))
{
_sbStdout.AppendLine(line.Data);
}
};
proc.ErrorDataReceived += (sendingProcess, line) =>
{
string s = line.Data;
if (!string.IsNullOrWhiteSpace(s))
{
_sbStderr.AppendLine(line.Data);
}
};
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
// Wait for exit or timeout
bool res = true;
if (timeout <= 0 || timeout == null)
proc.WaitForExit();
else
res = proc.WaitForExit((int)timeout);
ExitCode = proc.ExitCode;
What is wrong?

Categories