C# execute, wait, read command's output - c#

I'm trying to read all system information on a windows pc, using C#. Here is my code :
public static string GetSystemInfo()
{
String command = "systeminfo";
ProcessStartInfo cmdsi = new ProcessStartInfo("cmd.exe");
cmdsi.Arguments = command;
Process cmd = Process.Start(cmdsi);
cmd.WaitForExit();
return cmd.StandardOutput.ReadToEnd();
}
But it just opens a console, doesn't execute systeminfo command.
How this can be solved?

The following snippet will work
public static string GetSystemInfo()
{
var command = "/c systeminfo";
var cmdsi = new ProcessStartInfo("cmd.exe");
cmdsi.Arguments = command;
cmdsi.RedirectStandardOutput = true;
cmdsi.UseShellExecute = false;
var cmd = Process.Start(cmdsi);
var output = cmd.StandardOutput.ReadToEnd();
cmd.WaitForExit();
return output;
}
You should set RedirectStandardOutput to true and read output before calling WaitForExit, otherwise you can get a deadlock, per MSDN
The example avoids a deadlock condition by calling
p.StandardOutput.ReadToEnd before p.WaitForExit. A deadlock condition
can result if the parent process calls p.WaitForExit before
p.StandardOutput.ReadToEnd and the child process writes enough text to
fill the redirected stream. The parent process would wait indefinitely
for the child process to exit.
/c means terminating command line after execution

You need to prepend "/c" to the command
String command = "/c systeminfo";
/c indicates that you want to execute a command that follows
Update
ProcessStartInfo.RedirectStandardOutput needs to be set to true as mentioned in Pavel's Answer.

Related

Executing "systeminfo" cmd command WaitForExit doesn't continue

I try execute systeminfo cmd commnand and I have a problem. My problem is that when it comes to method WaitForExit(), the program doesn't continue. I try
using (var cmd = new Process())
{
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.CreateNoWindow = false;
cmd.StartInfo.UseShellExecute = false;
cmd.Start();
cmd.StandardInput.WriteLine("systeminfo");
cmd.StandardInput.Flush();
cmd.WaitForExit();
var result = cmd.StandardOutput.ReadToEnd();
cmd.StandardInput.Close();
cmd.StandardOutput.Close();
return result;
}
Is there a particular reason you go through cmd here? That's so utterly unnecessary here and also explains your problem. You run an interactive instance of cmd, pretending as if you typed systeminfo and then wait for cmd to exit. Just as a normal interactive instance, it won't do that. It will just sit there, waiting for more commands. Instead, why not just call systeminfo directly?
using (var systeminfo= new Process
{
FileName = "systeminfo",
RedirectStandardOutput = true,
CreateNoWindow = false,
UseShellExecute = false,
})
{
systeminfo.Start();
var result = systeminfo.StandardOutput.ReadToEnd();
systeminfo.WaitForExit();
return result;
}
You can try the following options:
Option 1:
Subscribe to Process.Exited event and when the systeminfo process exits, read the stream to get the result of the process.
Option 2:
Redirect the systeminfo process output to a text file. When process exists event occurs, read the result from the text file.
also instead of cmd.StandardInput.WriteLine("systeminfo"); pass the systeminfo process as the argument. It will provide you more control.
ex: cmd.StartInfo.Arguments = "/C systeminfo.exe";
You must add command to cmd, cmd run that and return results and exit.
Use command like "cmd /c ver" works fine and return windows version.

Execute couple command in cmd. Executed only one command

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.

Maintaining variables when executing cmd from C# application

I want to call some cmd prompt commands from a .net C# application. However before the command is run I need to set up a varaibles within a batch file. Is there a way to maintain the variables to subsequent calls to the command prompt?
I provide an example below of the problem.
Say I have a simple batch file C:\setEnv.bat containing
set notepad=C:\windows\system32\notepad.exe
Then if I have C# code like thus:
var proc1 = new ProcessStartInfo();
string cmd1 = #"CALL C:\setEnv.bat";
string cmd2 = #"ECHO notepad=%notepad%>C:\test.txt";
proc1.UseShellExecute = true;
var process = new Process();
var startInfo = new ProcessStartInfo();
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/C " +cmd1;
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
startInfo.Arguments = "/C " + cmd2;
process.Start();
process.WaitForExit();
Now I'm guessing that the /C is terminating the command prompt thus losing my variables but if I don't have it then the cmd prompt doesn't return after finishing the command.
I thought I could double the cmd1 & cmd2 to a single call but then I would need to do that for every call to the cmd prompt and feels a little redundant calling C:\setenv.bat again and again.
You may use way in this question
For example, use command "set" in cmd1:
string cmd1 = "\" CALL C:\\setEnv.bat && set \"";
startInfo.RedirectStandardOutput = true;
startInfo.UseShellExecute = false;
//...
process.Start();
string variables = process.StandardOutput.ReadToEnd();
// next parse variables string
And before start second process set parsed variables in "startInfo.EnvironmentVariables"
A few issues/ideas:
1) How many values to you need to track from one execution to the next? Primarily, can you just use the errorlevel return value or do you need more details?
2) Can you pipe the results of the first directly into the second as one call?
3) Can you output the values from the first call to a file and in the 2nd call, pipe that as input into the 2nd cmd?
4) I assume that you have some processing that is done after the first cmd is run, but before the 2nd cmd is called. Could you create an .exe that can be called by the 1st batch file (after the other code) that does that processing. That way, you have 3 cmds all of which can be run in the same environment and your variables stick around.

Reading error output hangs for command invoked on CMD process

I have c# console app for ffmpeg command invocation. Here is it
class Program
{
static void Main(string[] args)
{
ProcessStartInfo cmd = new ProcessStartInfo("cmd.exe");
cmd.RedirectStandardInput = true;
cmd.RedirectStandardOutput = true;
cmd.RedirectStandardError = true;
cmd.UseShellExecute = false;
cmd.CreateNoWindow = true;
cmd.WindowStyle = ProcessWindowStyle.Hidden;
Process console = Process.Start(cmd);
console.StandardInput.WriteLine(#"cd C:\Users\vishnu.aravind");
console.StandardInput.WriteLine(#"ffmpeg -i sunp.avi -i Alarm03.wav -c:v copy -c:a aac -strict experimental output.avi");
string errors = console.StandardError.ReadToEnd();
Console.WriteLine("Video file is created.");
Console.Read();
}
}
if I remove the line of code
string errors = console.StandardError.ReadToEnd();
this program will work fine. Else it hangs. I need the error information if any, what to do, please help.
Cause of a problem
Your program hangs because
cmd is an interactive command line application and thus continually produces output for std. out and std. error stream
Process.StandardError.ReadToEnd() reads the output of std. error stream of a process till it reaches end of stream
as cmd is an interactive command line application - it will not end it's std. error stream until process of cmd terminates - and that's why Process.StandardError.ReadToEnd() hangs when it is invoked on running cmd process.
Solution for a problem
To get output for each command you execute there are at least two following options:
Start each command with separate Process instance - and read output with standard methods of StreamReader class (ReadToEnd() and Read() methods).
Use single Process instance for cmd application - to read and write to it interactively:
Start process as in following example:
Process interactiveProcess = new Process();
string processOutput = "";
interactiveProcess.StartInfo.FileName = this.processPath;
interactiveProcess.StartInfo.Arguments = commandLineParameters;
interactiveProcess.StartInfo.UseShellExecute = false;
interactiveProcess.StartInfo.CreateNoWindow = false;
interactiveProcess.StartInfo.RedirectStandardInput = true;
interactiveProcess.StartInfo.RedirectStandardError = true;
interactiveProcess.Start();
interactiveProcess.EnableRaisingEvents = true;
interactiveProcess.ErrorDataReceived += new DataReceivedEventHandler((process, outputEventArgs) => processOutput += outputEventArgs.Data);
interactiveProcess.BeginErrorReadLine();
Write commands to cmd with following code
interactiveProcess.StandardInput.WriteLine(command);
To read response you will need to use System.Threading.Thread.Sleep() method and wait till processOutput variable is populated with output during execution of started commands.
Try to wait for exit before reading errors:
cmd.WaitForExit();

To run cmd as administrator along with command?

Here is my code:
try
{
ProcessStartInfo procStartInfo = new ProcessStartInfo(
"cmd.exe",
"/c " + command);
procStartInfo.UseShellExecute = true;
procStartInfo.CreateNoWindow = true;
procStartInfo.Verb = "runas";
procStartInfo.Arguments = "/env /user:" + "Administrator" + " cmd" + command;
///command contains the command to be executed in cmd
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
I want to keep
procStartInfo.UseShellExecute = true
procStartInfo.RedirectStandardInput = false;
Is it possible to execute the command without using process.standardinput?
I try to execute command I've passed in argument but the command does not executes.
As #mtijn said you've got a lot going on that you're also overriding later. You also need to make sure that you're escaping things correctly.
Let's say that you want to run the following command elevated:
dir c:\
First, if you just ran this command through Process.Start() a window would pop open and close right away because there's nothing to keep the window open. It processes the command and exits. To keep the window open we can wrap the command in separate command window and use the /K switch to keep it running:
cmd /K "dir c:\"
To run that command elevated we can use runas.exe just as you were except that we need to escape things a little more. Per the help docs (runas /?) any quotes in the command that we pass to runas need to be escaped with a backslash. Unfortunately doing that with the above command gives us a double backslash that confused the cmd parser so that needs to be escaped, too. So the above command will end up being:
cmd /K \"dir c:\\\"
Finally, using the syntax that you provided we can wrap everything up into a runas command and enclose our above command in a further set of quotes:
runas /env /user:Administrator "cmd /K \"dir c:\\\""
Run the above command from a command prompt to make sure that its working as expected.
Given all that the final code becomes easier to assemble:
//Assuming that we want to run the following command:
//dir c:\
//The command that we want to run
string subCommand = #"dir";
//The arguments to the command that we want to run
string subCommandArgs = #"c:\";
//I am wrapping everything in a CMD /K command so that I can see the output and so that it stays up after executing
//Note: arguments in the sub command need to have their backslashes escaped which is taken care of below
string subCommandFinal = #"cmd /K \""" + subCommand.Replace(#"\", #"\\") + " " + subCommandArgs.Replace(#"\", #"\\") + #"\""";
//Run the runas command directly
ProcessStartInfo procStartInfo = new ProcessStartInfo("runas.exe");
procStartInfo.UseShellExecute = true;
procStartInfo.CreateNoWindow = true;
//Create our arguments
string finalArgs = #"/env /user:Administrator """ + subCommandFinal + #"""";
procStartInfo.Arguments = finalArgs;
//command contains the command to be executed in cmd
using (System.Diagnostics.Process proc = new System.Diagnostics.Process())
{
proc.StartInfo = procStartInfo;
proc.Start();
}
why are you initializing the process object with arguments and then later on override those Arguments? and btw: the last bit where you set Arguments you concatenate 'command' right upto 'cmd', that doesn't make much sense and might be where it fails (looks like you're missing a space).
Also, you are currently using the standard command line, you might want to look into using the runas tool instead. you can also call runas from command line.
Also, why are you running 'command' from the command line? why not start it directly from Process.Start with admin privileges supplied then and there? here's a bit of pseudocode:
Process p = Process.Start(new ProcessStartInfo()
{
FileName = <your executable>,
Arguments = <any arguments>,
UserName = "Administrator",
Password = <password>,
UseShellExecute = false,
WorkingDirectory = <directory of your executable>
});

Categories