I have a couple of old windows programs that we still need to use. Both are designed to run from batch files with command line options so using Process to start and monitor them works fine.
Both programs display progress windows after starting and I would like to hide those windows but they don't seem to respond to the Process setting that control window display.
ProcessStartInfo info = new ProcessStartInfo() {
FileName = appName,
Arguments = arguments,
RedirectStandardError = true,
RedirectStandardOutput = true,
WorkingDirectory = workingDirectory,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
};
StringBuilder errorData = new StringBuilder(OUTPUT_SIZE);
StringBuilder outputData = new StringBuilder(OUTPUT_SIZE);
using (Process process = new Process { StartInfo = info }) {
process.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => ErrorDataReceived(sender, e, errorData));
process.OutputDataReceived += new DataReceivedEventHandler((sender, e) => OutputDataReceived(sender, e, outputData));
if (exited != null) {
process.EnableRaisingEvents = true;
process.Exited += exited;
}
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
}
Is there another way to force the window hidden or do I just have to use them they way they are?
I've done a bunch of searching but all the answer I find just use some variation of the code above.
Related
This question already has answers here:
Determining end of console output
(1 answer)
C# StandardOutput hangs, how can I detect it's waiting for input?
(2 answers)
Closed 2 years ago.
Introduction
I am creating a R.A.T (Remote Administration Tool) In c# with TCP client-server configurations.
Everything was going quite fine until I realized a need to detect whether or not a command has been finished executing in a command-prompt process created by my c# application.
Please have a look at the code below.
private static Process CMDProc = null;
private static StreamWriter ToCMDShell = null;
public static void StartCMD()
{
ProcessStartInfo PSInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
CMDProc = new Process { StartInfo = PSInfo };
CMDProc.Start();
ToCMDShell = CMDProc.StandardInput;
ToCMDShell.AutoFlush = true;
CMDProc.BeginOutputReadLine();
CMDProc.BeginErrorReadLine();
CMDProc.OutputDataReceived += (s, e) => { /*Do something with e.Data*/ };
CMDProc.ErrorDataReceived += (s, e) => { /*Do something with e.Data*/ };
ToCMDShell.WriteLineAsync("ping 8.8.8.8"); //Execute a long running command in cmd terminal.
}
What I Want To Achieve
As you may guess that ping command takes variable amount of time to complete depending upon the speed of the internet connection, now what I want is to run a method called CMDCommandExecuted() when a long running command like "ping" finished executing in the terminal which was invoked using the c# code ToCMDShell.WriteLineAsync("any dos command to execute");.
What I Had Tried Till Now
I tried to read the e.Data from the output stream received from the CMDProc.OutputDataReceived Event-Handler but had no luck, because maybe for some other long running commands other than the ping no data at all is being written to the output stream so it is not a bulletproof solution.
And yes I had tried to search for my solutions on the internet as well, yet no luck!
That's why I am here seeking for your help.
It appears that the WriteLineAsync doesn't complete until the long running command does (e.g. the command window is ready for new input), so you just need to Wait on the return from WriteLineAsync when you need to send more data, or know the previous command is done.
private static Process CMDProc = null;
private static StreamWriter ToCMDShell = null;
public static void StartCMD() {
ProcessStartInfo PSInfo = new ProcessStartInfo {
FileName = "cmd.exe",
Arguments = "/k",
CreateNoWindow = false,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
CMDProc = new Process { StartInfo = PSInfo };
CMDProc.Start();
ToCMDShell = CMDProc.StandardInput;
ToCMDShell.AutoFlush = true;
CMDProc.BeginOutputReadLine();
CMDProc.BeginErrorReadLine();
CMDProc.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
CMDProc.ErrorDataReceived += (s, e) => Console.WriteLine($"ERR: {e.Data}");
var run = ToCMDShell.WriteLineAsync("ping 8.8.8.8"); //Execute a long running command in cmd terminal.
// do some stuff
run.Wait(); // wait for long command to complete
ToCMDShell.WriteLine("exit"); //Done
}
Use CMDProc.WaitForExit() to wait for completion and CMDProc.ExitCode to get final status code.
If you get some data from the stream that indicates the process is hung or frozen or needs to be killed, call CMDProc.Kill().
If you get some data from the stream that indicates you should do something else, you can spawn other processes or send additional WriteLine calls to the process to do further processing.
The following program sends the ping command output back to me correctly. Maybe you just need that wait command or a console read line to give it time.
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MyNamespace
{
class Program
{
private static Process CMDProc = null;
private static StreamWriter ToCMDShell = null;
public static void Main()
{
StartCMD();
}
public static void StartCMD()
{
ProcessStartInfo PSInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
CMDProc = new Process { StartInfo = PSInfo };
CMDProc.Start();
ToCMDShell = CMDProc.StandardInput;
ToCMDShell.AutoFlush = true;
CMDProc.BeginOutputReadLine();
CMDProc.BeginErrorReadLine();
CMDProc.OutputDataReceived += (s, e) =>
{
Console.WriteLine("PROC: {0}", e.Data);
if (e.Data != null && e.Data.Contains("Average ="))
{
// last line, you don't have to exit here, you could do something else instead...
ToCMDShell.WriteLine("exit");
}
};
CMDProc.ErrorDataReceived += (s, e) => Console.WriteLine("PROC ERR: {0}", e.Data);
ToCMDShell.WriteLine("ping 8.8.8.8"); //Execute a long running command in cmd terminal.
CMDProc.WaitForExit();
Console.WriteLine("Job done, press ENTER to quit");
Console.ReadLine();
}
}
}
I have an exe which I can run in console like below
util.exe argument1
I need to invoke this from a CSharp application which I can do like below
private string Command(string arg)
{
var p = new Process
{
StartInfo =
{
FileName = "util.exe",
Arguments = arg,
RedirectStandardOutput = true,
RedirectStandardInput = true,
CreateNoWindow = true,
UseShellExecute = false
}
};
var stdOutput = new StringBuilder();
p.OutputDataReceived += (sender, args) => stdOutput.AppendLine(args.Data);
p.Start();
p.BeginOutputReadLine();
p.WaitForExit();
return stdOutput.ToString();
}
var result = Command(argument1) //call
However there is an issue. util.exe authenticates user using the Windows logged in credentials. In the application I need to execute the commands as a different user(Like below)
util.exe login funcationaluser fuucntioanluserpwd
util.exe argument1
What is the right way for doing this? Can I reuse the process?
Edit: Please note the username and password is specific to util.exe , not a system username/password
I have UI app that calls cmd periodically with different arguments and I want to periodically update UI with cmd output results.
Here's the code I use, but the problem is that UI is updated only when all commands are executed and not after each command and I didn't find solution for periodic update of UI when each command is executed:
ProcessStartInfo psi = new ProcessStartInfo()
{
FileName = "cmd.exe",
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
};
Process p = new Process();
p.StartInfo = psi;
p.Start();
var reposToUpdate = ConfigurationManager.AppSettings["UpdateAndMergeReposOnBranch"];
foreach (XmlNode repoPathNode in reposPaths)
{
var repoPath = repoPathNode.Attributes["root"].Value;
p.StandardInput.WriteLine(string.Format("cd {0}", repoPath));
p.StandardInput.WriteLine(#"hg update --check");
p.StandardInput.Flush();
}
p.StandardInput.Close();
string output = p.StandardOutput.ReadToEnd();
rtbOutput.Text = output;
You could subscribe to the Process.OutputDataReceived event instead of using the Process.StandardOutput.ReadToEnd method:
ProcessStartInfo psi = new ProcessStartInfo()
{
FileName = "cmd.exe",
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
};
Process p = new Process();
p.StartInfo = psi;
p.Start();
// Output handling:
p.OutputDataReceived += (o, e) => Console.WriteLine(e.Data);
p.BeginOutputReadLine();
var reposToUpdate = ConfigurationManager.AppSettings["UpdateAndMergeReposOnBranch"];
foreach (XmlNode repoPathNode in reposPaths)
{
var repoPath = repoPathNode.Attributes["root"].Value;
p.StandardInput.WriteLine(string.Format("cd {0}", repoPath));
p.StandardInput.WriteLine(#"hg update --check");
p.StandardInput.Flush();
}
p.StandardInput.Close();
In the example above all data printed to the Console. Alternatively you can append output to the TextBox:
p.OutputDataReceived += (o, e) => rtbOutput.Invoke((MethodInvoker)(() => rtbOutput.Text += e.Data));
Please note that you should also handle the Process.Exited event.
What you are looking for is the BeginOutputReadLine method and associated event to receive data as it happens in the process.
p.OutputDataReceived += OnOutputDataReceived;
p.BeginOutputReadLine ();
p.WaitForExit();
then elsewhere add a method
void OnOutputDataReceived (object sender, DataReceivedEventArgs e)
{
// DO Something with the output
}
there is also a ErrorDataReceived event that will hook to stderr.
I'm trying to run the following code in my C# WPF application. Whenever I use something like dir (I should probably mention the output is the directory of my working folder in Visual Studio, not System32), it allows it. However, if I use systeminfo or set the working directory to C:\Windows\System32, it hangs...
MessageBox.Show("STARTED");
var processInfo = new ProcessStartInfo("cmd.exe", "/c systeminfo") {
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
//WorkingDirectory = #"C:\Windows\System32\"
};
// *** Redirect the output ***
Process process = Process.Start(processInfo);
if (process == null) return false;
process.WaitForExit();
MessageBox.Show("Done");
string output = process.StandardOutput.ReadToEnd().ToLower();
string error = process.StandardError.ReadToEnd();
int exitCode = process.ExitCode;
MessageBox.Show(output);
MessageBox.Show(error);
MessageBox.Show(exitCode.ToString(CultureInfo.InvariantCulture));
Any ideas?
Just tried this and works as expected.
Of course you need to read the redirected output before the process closes.
var processInfo = new ProcessStartInfo("cmd.exe", "/c systeminfo")
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
WorkingDirectory = #"C:\Windows\System32\"
};
StringBuilder sb = new StringBuilder();
Process p = Process.Start(processInfo);
p.OutputDataReceived += (sender, args) => sb.AppendLine(args.Data);
p.BeginOutputReadLine();
p.WaitForExit();
Console.WriteLine(sb.ToString());
I have an app which calls another process in a command window and that process has updating stats that output to the console window. I thought this was a fairly simple operation but I can't seem to get it to work. Am I missing something?
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
Process process = new Process
{
ProcessStart =
{
RedirectStandardOutput = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = arg,
FileName = assemblyLocation.Substring(0, assemblyLocation.LastIndexOf("\\")) + "\\ffmpeg.exe",
CreateNoWindow = true
}
};
process.Start();
Console.WriteLine(process.StandardOutput.ReadToEnd());
process.WaitForExit();
Ideally what I would like is as the output changes within that process I hit or data comes into the reader that I get events off it.
Any help would be great, I feel like this is a newbie question but seem to be missing something.
I've experienced this before. Sometimes, the way in which the process you're calling outputs to the console is not compatible with this sort of output redirection. I've been fortunate enough in this case to be able to modify the external process to get around this.
You might try running your code on another process that outputs to the console, and see if it works properly. It reads about right to me right now.
EDIT:
I went and pulled a code block I've used to do this. This is in a WPF app which redirects the process output to the window. Notice the event binding. Since this is WPF I have to invoke my call to write the data out. Since you aren't worried about blocking, ou should be able to simply replace that with:
Console.WriteLine(e.Data);
Hopefully it helps!
private static void LaunchProcess()
{
Process build = new Process();
build.StartInfo.WorkingDirectory = #"dir";
build.StartInfo.Arguments = "";
build.StartInfo.FileName = "my.exe";
build.StartInfo.UseShellExecute = false;
build.StartInfo.RedirectStandardOutput = true;
build.StartInfo.RedirectStandardError = true;
build.StartInfo.CreateNoWindow = true;
build.ErrorDataReceived += build_ErrorDataReceived;
build.OutputDataReceived += build_ErrorDataReceived;
build.EnableRaisingEvents = true;
build.Start();
build.BeginOutputReadLine();
build.BeginErrorReadLine();
build.WaitForExit();
}
// write out info to the display window
static void build_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
string strMessage = e.Data;
if (richTextBox != null && !String.Empty(strMessage))
{
App.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Send, (ThreadStart)delegate()
{
Paragraph para = new Paragraph(new Run(strMessage));
para.Margin = new Thickness(0);
para.Background = brushErrorBrush;
box.Document.Blocks.Add(para);
});
}
}
I'm not sure exactly what problem you're running into, but if you're looking to act on output as soon as it's generated, try hooking into the process's OutputDataReceived event. You can specify handlers to receive output asynchronously from the process. I've used this approach successfully.
Process p = new Process();
ProcessStartInfo info = p.info;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
p.OutputDataReceived += p_OutputDataReceived;
p.ErrorDataReceived += p_ErrorDataReceived;
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit();
..
void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine("Received from standard out: " + e.Data);
}
void p_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine("Received from standard error: " + e.Data);
}
See the OutputDataReceived event off Process for more information.
Using lambda expressions, etc:
var info = new ProcessStartInfo(path)
{
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
Verb = "runas",
};
var process = new Process
{
EnableRaisingEvents = true,
StartInfo = info
};
Action<object, DataReceivedEventArgs> actionWrite = (sender, e) =>
{
Console.WriteLine(e.Data);
};
process.ErrorDataReceived += (sender, e) => actionWrite(sender, e);
process.OutputDataReceived += (sender, e) => actionWrite(sender, e);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
Interestingly you can't read from standard output and standard error at the same time:
if you redirect both standard output and standard error and then try to read both, for
example using the following C# code.
[C#]
string output = p.StandardOutput.ReadToEnd();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
In this case, if the child process writes any text to standard error it will block the
process, because the parent process cannot read from standard error until it has finished
reading from standard output. However, the parent process will not read from standard
output until the process ends. A recommended solution to this situation is to create two
threads so that your application can read the output of each stream on a separate thread.
http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandardoutput(v=vs.71).aspx
flowing code worked in VS2010
void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (String.IsNullOrEmpty(e.Data) == false)
{
new Thread(() =>
{
this.Dispatcher.Invoke(new Action(() =>
{
// Add you code here
}));
}).Start();
}
}
Check that the output you are expecting is not being sent to the StandardError output instead of the StandardOutput output