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
Related
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.
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!
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();
}
I have a vbs script, which runs fine by itself or running via a batch file. However when I trigger the same script in my console demo app, I get a vbs script error about not being able to create "the ActiveX Component can't be created".
Code in Console:
{
Console.WriteLine("Triggering VBS Script");
var cmd = #"C:\Users\ehubba\Desktop\ClientHealth\SCCMClientActionCount.vbs";
var process = new Process
{
StartInfo = new ProcessStartInfo
{
Arguments = cmd,
FileName = #"cscript",
CreateNoWindow = true,
UseShellExecute = false,
Verb = "runas",
WindowStyle = ProcessWindowStyle.Normal
}
};
try
{
process.Start();
process.WaitForExit();
System.Console.WriteLine(process.ExitCode);
process.Close();
}
catch (Exception ex)
{
System.Console.WriteLine(ex.ToString());
}
Console.ReadKey();}
Note: I've tried different variations of the StartInfo (Working Directory, Filename, arguments, etc all resulting in the same error).
The main goal I'm trying to do here is run a vbs script which will tally SCCM Client actions and return the value in the Exit Code.
**UPDATE VBS Code
Dim ArrayAction
Dim cpApplet
Dim actions
Dim countAction
Dim action
Set cpApplet = CreateObject("CPAPPLET.CPAppletMgr")
Set actions = cpApplet.GetClientActions
If Err.Number = 0 Then
countAction = 0
For Each action In actions
countAction = countAction + 1
Next
End If
WScript.QUIT(countAction)
*UPDATE 2:
In my c# app in the Process.Start if I add a username and password to the StartInfo it will work. I'm running the Console as myself, however it will fail if I don't enter my credentials into the Process.Start. Can someone explain as this is not a viable option for me.
Hellp. In my 'plumbing' I have 3 command that should performed sequentially, and each request must wait until the end of the previous command. Now I have done the 1st request, but 2nd and 3rd just skips...
Could you please suggest how to change this 'plumbing'?
string strCmdText = s1;
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
var process = new Process { StartInfo = startInfo };
process.Start();
process.StandardInput.WriteLine(strCmdText);
process.WaitForExit();
string strCmdText1 = s2;
process.StandardInput.WriteLine(strCmdText1);
process.WaitForExit();
string strCmdText2 = s3;
process.StandardInput.WriteLine(strCmdText2);
process.StandardInput.WriteLine("exit");
Thank you.
Let's go through your code:
You start an instance of cmd.exe:
var process = new Process { StartInfo = startInfo };
process.Start();
You write a command to its standard input:
process.StandardInput.WriteLine(strCmdText);
And then you wait for cmd.exe to exit:
process.WaitForExit();
Now, you write another command to its standard input:
string strCmdText1 = s2;
process.StandardInput.WriteLine(strCmdText1);
Wait, what? cmd.exe exited in the previous step, so there's no more process you could send a command to in the first place.
Then you wait for the process to exit, but it's already dead a long time ago:
process.WaitForExit();
And you repeat the same non-working code:
string strCmdText2 = s3;
process.StandardInput.WriteLine(strCmdText2);
process.StandardInput.WriteLine("exit");
You should better understand what's the the problem now. It looks like cmd.exe quits after executing your first command.
There's a couple things you can try:
Get rid of cmd.exe altogether. Unless you execute some batch script you can directly call the intended executable (like python.exe).
Start 3 different instances of cmd.exe for your 3 commands.
Try to pass some arguments to cmd.exe, like /Q.
Try the first approach first, it's the cleanest one.