I'm having issues with a process started in C# not outputting to console despite it's output being redirected.
I'm running a console application inside a unityci docker container which allows me to start unity in batch mode and is supposed to output something to console.
If I use bash to start unity using unity-editor -projectPath myProject -executeMethod myMethod -logFile - I get all the output displayed in console as expected.
If I use C# to start a bash process using the same arguments, I get no output.
Here's the code I'm using to start a new process:
void StartProcess()
{
string argsString = "-projectPath myPath -executeMethod myMethod -logFile -";
ProcessStartInfo startInfo = new ProcessStartInfo(argsString)
{
FileName = "/bin/bash",
Arguments = $"-c unity-editor \"{argsString}\"",
RedirectStandardOutput = true,
RedirectStandardError = false,
UseShellExecute = false,
CreateNoWindow = true,
};
using Process proc = Process.Start(startInfo);
OutputDataReceived += OnOutputDataReceived;
proc.BeginOutputReadLine();
proc.WaitForExit();
}
void OnOutputDataReceived(object obj, DataReceivedEventArgs args)
{
if(args.Data.StartsWith("[")
Console.WriteLine(args.Data);
}
The unity-editor command is part of the unityci docker container I'm using. It does the following:
#!/bin/bash
if [ -d /usr/bin/unity-editor.d ] ; then
for i in /usr/bin/unity-editor.d/*.sh; do
if [ -r $i ]; then
. $i
fi
done
fi
xvfb-run -ae /dev/stdout "$UNITY_PATH/Editor/Unity" -batchmode "$#"
So in the end I have a console application that runs an executable that starts another application, and the console output gets lost somewhere in between.
Can someone explain where the output is going and how to get it to display in console?
I figured it out. The problem was how I was starting the process.
Instead of starting a instance of bash to run a script like this:
ProcessStartInfo startInfo = new ProcessStartInfo("/bin/bash", $"-c unity-editor \"{argsString}\"");
I should have just started the script as a process directly:
ProcessStartInfo startInfo = new ProcessStartInfo("unity-editor", argsString);
I was under the impression that you can't start bash scripts as processes directly as that would throw a System.ComponentModel.Win32Exception: Exec format error exception, but having #!/bin/bash as the first line of the script allows them to be run without issue.
Related
I have some script files that would usually be edited and run through a ui, but the offer to run on the command line when using the syntax: program.exe scriptfile.fmw --userparameter "some parameter".
If I simply write
arguments = "program.exe scriptfile.fmw --userparameter \"some parameter\"";
Process process = Process.Start("cmd.exe", $"/K {arguments};
process.WaitForExit();
the commandline starts, calls the correct exe and runs the script.
Now I want to retrieve the response of the script after it ran to catch possible error messages.
As far as I can see this requires to use ProcessStartInfo and that's basically where my problem seems to start.
ProcessStartInfo startInfo = new ProcessStartInfo()
{
FileName = "cmd.exe",
Arguments = $"/K {arguments}",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
Process process = Process.Start(startInfo);
process.BeginErrorReadLine();
process.WaitForExit();
The code above opens a commandline window, but never uses the arguments given.
It escapes me on how I should hand the argument line over to the cmd.exe - process.
I was playing around with RedirectStandardInput and its StreamWriter, but never managed to get anything written into the cmd-window.
The same goes for the RedirectStandardOutput, where I would like to gather the script response in the cmd-window as a string or string[], to parse for specific exit codes of the script.
I think this is what you want, have a look below :
Process process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo()
{
FileName = "program.exe", //You must use your program directly without invoking through cmd.exe
Arguments = "scriptfile.fmw --userparameter \"some parameter\"", //Add parameters and arguments here needed by your application
UseShellExecute = false,
EnableRaisingEvents = true, //You are missing this
RedirectStandardOutput = true,
RedirectStandardError = true
};
process.ErrorDataReceived += process_ErrorDataReceived; //You should listen to its output error data by subscribing to this event
process.BeginErrorReadLine();
process.Start(startInfo);
process.WaitForExit(); // You may now avoid this
Then at here do anything with your received error data!
private static void process_ErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
DoSomething(e.Data); // Handle your error data here
}
EDIT-(1) : Please try this solution and ping me if it works or if you need some extra help. Everyone in comments is suggesting that you must not use cmd.exe to invoke your program as it may causes debugging overhead, performance issue and you might not get error details as well.
Cheers!
I´m trying to get the return code of a batch script, which intern calls a couple other batch and exe files. When I execute the script in a CMD window and print the errorlevel, I get the correct error code, however when I do the same in C# with a process, I always get 0 as the error code.
This is my C# code
private Process ExecuteBatchFile(string batchFile)
{
Process process = new Process
{
StartInfo = new ProcessStartInfo
{
RedirectStandardOutput = false,
RedirectStandardError = false,
UseShellExecute = true,
FileName = "CMD.exe",
WorkingDirectory = Constants.ToolsPath,
Arguments = $"/c \"{batchFile} & pause\""
}
};
process.Start();
return process;
}
batchFile = $"testScript.bat -tns {Project.TnsName} & echo Error: %errorlevel%";
The output I get for the echo is Error: 0 and the process.ExitCode value is 0
If I open a CMD window and enter
cmd.exe /c "testScript.bat -tns MYTNS & echo Error: %errorlevel% & pause"
I get the correct errorlevel value.
I´m guessing it has something to do with the batch script but I don´t understand why it works in a CMD window but not in a C# Process, especially since I´m using the same method to connect a network drive and to execute an exe file.
Edit: Code without using CMD:
private Process ExecuteBatchFile(string batchFile, string args)
{
Process process = new Process
{
StartInfo = new ProcessStartInfo
{
RedirectStandardOutput = false,
RedirectStandardError = false,
UseShellExecute = true,
FileName = batchfile,
WorkingDirectory = Constants.ToolsPath,
Arguments = args
}
};
process.Start();
return process;
}
batchFile = "testScript.bat";
args = $"-tns {Project.TnsName}";
I suspect your .bat file has a line somewhere like exit /b 1. When running this .bat file from a cmd instance, the /b flag allows the .bat file to exit without closing the parent cmd process (great for running manually!).
Unfortunately, that means that when you do Process.Start("testScript.bat");, under the hood C# uses cmd to run your bat. The .bat file exits with the /b flag, signaling to the parent cmd process it shouldn't crash and therefore it exits "successfully" (exit code 0).
You have two options:
Remove the /b flag (which will correctly return the exit code .bat > cmd > process.ExitCode) - though this will mean that executing the .bat manually via a cmd instance will terminate the parent cmd upon exit
Add to your C# code to write a wrapper .bat that will pipe all args to your bat file and properly return %exitcode%:
private int ExecuteBatWithWrapper(string batFile, string args){
string runnerPath = Path.Combine(Path.GetDirectoryName(batFile), "runner.bat");
File.WriteAllText(runnerPath, $"call {batFile} %*\nexit %ERRORLEVEL%");
Process process = Process.Start(runnerPath, args); //Alternatively construct with ProcessStartInfo
File.Delete(runnerPath);
return process.ExitCode;
}
(of course, if this is used in any sort of important environment, you should incorporate that into some sort of using statement that upon disposal deletes the runner, but this is just proof-of-concept)
See https://bytes.com/topic/c-sharp/answers/511381-system-diagnostics-process-bat-file-always-returns-exit-code-0-a#post1989782 for a similar post
Mostly just as a curiosity, I wrote a little app to start up Terminator shell on Windows, using Ubuntu/WSL and Xming window server.
Doing things manually from the shell, I can run Firefox, gedit, Terminator, etc on Windows, it's pretty cool.
So I checked the location of bash.exe using where bash and it returned...
C:\Windows\System32\bash.exe
However when I tried to run this code...
using (var xminProc = new Process())
{
xminProc.StartInfo.FileName = #"C:\Program Files (x86)\Xming\Xming.exe";
xminProc.StartInfo.Arguments = ":0 -clipboard -multiwindow";
xminProc.StartInfo.CreateNoWindow = true;
xminProc.Start();
}
using (var bashProc = new Process())
{
bashProc.StartInfo.FileName = #"C:\Windows\System32\bash.exe";
bashProc.StartInfo.Arguments = "-c \"export DISPLAY=:0; terminator; \"";
bashProc.StartInfo.CreateNoWindow = true;
bashProc.Start();
}
I get the error...
System.ComponentModel.Win32Exception: 'The system cannot find the file specified'
And checking my entire system for bash.exe reveals it really be in another place altogether...
I'm not sure if this location is one that I can rely on, I'm worried it's ephemeral and can change during a Windows Store update, although I may be wrong about that.
Why does the command prompt show bash.exe to be in System32 but it's really in another location altogether?
Can I get C# to also use the System32 location?
As #Biswapriyo stated first set the platafrom to x64 on your solution:
Then you may run on your ubuntu machine from c# as:
Console.WriteLine("Enter command to execute on your Ubuntu GNU/Linux");
var commandToExecute = Console.ReadLine();
// if command is null use 'ifconfig' for demo purposes
if (string.IsNullOrWhiteSpace(commandToExecute))
{
commandToExecute = "ifconfig";
}
// Execute wsl command:
using (var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = #"cmd.exe",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
CreateNoWindow = true,
}
})
{
proc.Start();
proc.StandardInput.WriteLine("wsl " + commandToExecute);
System.Threading.Thread.Sleep(500); // give some time for command to execute
proc.StandardInput.Flush();
proc.StandardInput.Close();
proc.WaitForExit(5000); // wait up to 5 seconds for command to execute
Console.WriteLine(proc.StandardOutput.ReadToEnd());
Console.ReadLine();
}
How should I restart a dotnetcore C# console app?
I have tried suggestions found for C# console apps, but doesnt work for dotnetcore.
(This is not asp.net, which is where so many dotnetcore answers point)
OK, so im going to assume in this answer that it is ok with you if your program will start a new instance of your program and then close itself.
Here we go:
Since a dotnet console app can be started from the console, I think the best way to start a new instance of your console application would be thorugh using shell commands. To run shell commands from your program, add this helper class to your application: (If you are using windows instead of mac/linux, please see the end of this post)
using System;
using System.Diagnostics;
public static class ShellHelper
{
public static string Shell(this string cmd)
{
var escapedArgs = cmd.Replace("\"", "\\\"");
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = $"-c \"{escapedArgs}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
}
};
process.Start();
string result = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return result;
}
}
Then since this is a extension method, just import it and then create a string with the command to restart your app and then use the Shell() method.
So if you are in development and you normally start your app by running dotnet run then make sure you are in the proper directory and then just use this line of code "dotnet run".Shell();
If you want to get the feedback from running the command then just assign the return value like this string result = "dotnet run".Shell();
Then once you have started the new process you just exit your current program by either returning on your main method etc.
Please Note: The above code is for mac/linux, If you are on windows, then the following two lines of the above code:
FileName = "/bin/bash",
Arguments = $"-c \"{escapedArgs}\"",
Should be replaced with:
FileName = "cmd.exe",
Arguments = $"/c \"{escapedArgs}\"",
I'm making a program to work with some video files.
I'm using the ffmpeg executable to merge several files in a single file.
This command takes several minutes to finish, so, I need a way to "monitor" the output, and show a progress bar on GUI.
Looking at the following stackoverflow topics:
How to parse command line output from c#?
Process.start: how to get the output?
How To: Execute command line in C#, get STD OUT results
I made this code:
Process ffmpeg = new Process
{
StartInfo =
{
FileName = #"d:\tmp\ffmpeg.exe",
Arguments = "-f concat -safe 0 -i __sync.txt -c copy output.mp4",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
WorkingDirectory = #"d:\tmp"
}
}
ffmpeg.EnableRaisingEvents = true;
ffmpeg.OutputDataReceived += (s, e) => Debug.WriteLine(e.Data);
ffmpeg.ErrorDataReceived += (s, e) => Debug.WriteLine($#"Error: {e.Data}");
ffmpeg.Start();
ffmpeg.BeginOutputReadLine();
ffmpeg.WaitForExit();
When I run this code, the ffmpeg start to merge files, I can see the ffmpeg process on Windows Task Manager, and if I wait long enough, the ffmpeg finish the job without any error. But, the Debug.WriteLine(e.Data) is never called (no output on Debug window). Tried to change to Console.WriteLine too (again, no output).
So, after this, I tried this another version:
Process ffmpeg = new Process
{
StartInfo =
{
FileName = #"d:\tmp\ffmpeg.exe",
Arguments = "-f concat -safe 0 -i __sync.txt -c copy output.mp4",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
WorkingDirectory = #"d:\tmp"
}
}
ffmpeg.Start();
while (!ffmpeg.StandardOutput.EndOfStream)
{
var line = ffmpeg.StandardOutput.ReadLine();
System.Diagnostics.Debug.WriteLine(line);
Console.WriteLine(line);
}
ffmpeg.WaitForExit();
Again, the ffmpeg is started without any error, but the C# "hangs" on While (!ffmpeg.StandardOutput.EndOfStream) until ffmpeg is finished.
If I execute the exact command on Windows prompt, a lot of output text is showed with progress of ffmpeg.
I found the problem.
For some reason, ffmpeg output the progress on stderr, not stdout.
So, on first version, after the ffmpeg.BeginOutputReadLine();, I included the following line:
ffmpeg.BeginErrorReadLine();.
Now, I can monitor the progress using the stderr of ffmpeg.
Final code:
Process ffmpeg = new Process
{
StartInfo = {
FileName = #"d:\tmp\videos\ffmpeg.exe",
Arguments = "-f concat -safe 0 -i __sync.txt -c copy output.mp4",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
WorkingDirectory = #"d:\tmp\videos\gopro"
}
};
ffmpeg.EnableRaisingEvents = true;
ffmpeg.OutputDataReceived += (s, e) => Debug.WriteLine(e.Data);
ffmpeg.ErrorDataReceived += (s, e) => Debug.WriteLine($#"Error: {e.Data}");
ffmpeg.Start();
ffmpeg.BeginOutputReadLine();
ffmpeg.BeginErrorReadLine();
ffmpeg.WaitForExit();
I have found this post few weeks ago when I was looking for answer for my problem.
I tried to start ffmpeg process and pass arguments to it but it take sooo long to do everything. At this point I use Xabe.FFmpeg as it doing this out of the box and don't have to worry about ffmpeg executables because have feature to download latest version.
bool conversionResult = await new Conversion().SetInput(Resources.MkvWithAudio)
.AddParameter(String.Format("-f image2pipe -i pipe:.bmp -maxrate {0}k -r {1} -an -y {2}",bitrate, fps, outputfilename))
.Start();
There is documentation available here that shows how to get current percent of conversion. At this moment I thing showing output for concatenating videos doesn't work but they are working on it.