Here's a snippet of code the works just fine. As I am simulating a long running process, any keystrokes are queued up. The Console.Available returns true and false just as the documentation indicates it should. Everything works great here:
while (true) {
Console.WriteLine("Starting long task...");
// Simulate some long task by sleeping 3 seconds
System.Threading.Thread.Sleep(3000);
Console.WriteLine("Long task is finished.");
if (Console.KeyAvailable)
Console.WriteLine("A key is available: " + Console.ReadKey(false).Key);
else
Console.WriteLine("*** No Key available ***");
}
Here's the problem: When I substitute the Thread.Sleep() with code to create and run a real Process, the Console.KeyAvailable ceases to work. Console.KeyAvailable behaves erratically, usually returning false but sometimes returning true if I type enough keys fast enough.
Has anyone an explanation for this?
while (true) {
LongRunningProcess("someFile.bin");
if (Console.KeyAvailable)
Console.WriteLine("A key is available: " + Console.ReadKey(false).Key);
else
Console.WriteLine("*** No Key available ***");
}
private static bool LongRunningProcess(String filename) {
ProcessStartInfo processStartInfo = new ProcessStartInfo("BlahBlahBlah.exe", filename);
processStartInfo.UseShellExecute = false;
processStartInfo.RedirectStandardError = true;
Process p = new Process();
p.StartInfo = processStartInfo;
p.Start();
StreamReader stdError = p.StandardError;
int readResult = stdError.Read();
p.Close();
if (readResult != -1) // error was written to std error
return false;
return true;
}
The started process shows a window that becomes the active window. Your console application only receives keyboard input while your console window is the active window. Try setting the CreateNoWindow property of the ProcessStartInfo to true.
Related
I have an application that spawns multiples threads, one of which runs an iPerf executable which is used to monitor network reliability. This process will run indefinitely, until the user attempts to close the window. This is where the issue comes in. I am trying to shut that process down gracefully so that the iPerf server does not get hung up, but I can not seem to get this working. I can shut it down just fine if I run the command manually from a command prompt and press Ctrl+c, but this does not seem to be easily done programmatically.
I have tried multiple things, including process.Kill(); or process.StandardInput.Close() or even process.StandardInput.WriteLine("\x3"); but none of these seem to send a graceful shutdown message to the process. process.Kill(); causes the server to hang or fail to start up the next time, and the other two options do not stop the server at all. But the manual ctrl+c works just fine.
Here is a snippet of my code:
iperf_proc = new Process();
iperf_proc.StartInfo.FileName = Application.StartupPath + ".\\iperf3.exe";
String argumentStr = " -c " + test_data.host + " -t 0";
iperf_proc.StartInfo.Arguments = argumentStr;
iperf_proc.StartInfo.UseShellExecute = false;
iperf_proc.StartInfo.RedirectStandardOutput = true;
iperf_proc.StartInfo.RedirectStandardInput = true;
iperf_proc.StartInfo.RedirectStandardError = true;
iperf_proc.Start();
iperfRunning = true;
iperf_proc.BeginOutputReadLine();
iperf_proc.BeginErrorReadLine();
while (false == iperf_proc.HasExited)
{
if (true == processCancelled)
{
iperf_proc.StandardInput.Close(); // Doesn't Work!
iperf_proc.StandardInput.WriteLine("\x3"); // Doesn't Work!
iperf_proc.StandardInput.Flush(); // Doesn't Work!
}
}
iperf_proc.WaitForExit();
Any help is greatly appreciated. Thank you!
UPDATE:
Based on the suggestion from Hans in the comment, I tried adding some stuff to the code to get the ctrl+c event sent over.
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, uint dwProcessGroupId);
private enum CtrlEvents
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT = 1
}
private void closeBtn_Click(object sender, EventArgs e)
{
processCancelled = true;
//iperf_proc.CloseMainWindow();
bool succeeded = GenerateConsoleCtrlEvent((uint)CtrlEvents.CTRL_C_EVENT, (uint)iperf_proc.Id);
}
This did not work at all. The process is still running and it does the function added returns false. I did check that the process id being passed matches the id of the process in task manager. That all is fine, but the GenerateConsoleCtrlEvent function returns false. Any idea why this may be?
BeginOutputReadLine
Link describes how to do exactly what you are looking for in the example.
Why not use the built in Exited event to wait on so you are not blocking? Especially if you are spawning multiple threads. If you want to block then WaitForExit() is available as well.
if (true == processCancelled)
{
iperf_proc.StandardInput.Close(); // Doesn't Work!
iperf_proc.StandardInput.WriteLine("\x3"); // Doesn't Work!
iperf_proc.StandardInput.Flush(); // Doesn't Work!
}
If you close the standard input, how are you going to write/flush it?
After you have WaitForExit() you also need to Close()
I'm making an app that will spawn up a process that has a command-line interpreter. I need to supply commands to this CLI from another machine. Now, I have to detect when the command is finished, so I'm checking for when the CLI's prompt appears in the standard output of the process I'm spawning. Here's a snippet of code:
private string StartProcess(string input)
{
try
{
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
AutoResetEvent commandFinished = new AutoResetEvent(false);
ProcessStartInfo startInfo = new ProcessStartInfo()
{
FileName = "myprocess.exe",
Arguments = "",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
UserName = System.Environment.UserName
};
Process myProcess = new Process()
{
StartInfo = startInfo,
EnableRaisingEvents = true
};
myProcess.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
{
if (e.Data != null)
{
string prompt = "user >";
if (e.Data.Substring(e.Data.Length - prompt.Length).Equals(prompt))
{
Console.WriteLine("Received prompt! Sending CommandFinished signal!");
commandFinished.Set();
Console.WriteLine("CommandFinished signal set!");
}
else
{
output.AppendLine(e.Data);
}
}
else
{
// Data finished
Console.WriteLine("StdOut data finished! Sending CommandFinished signal!");
commandFinished.Set();
Console.WriteLine("CommandFinished signal set!");
}
});
myProcess.ErrorDataReceived += new DataReceivedEventHandler((sender, e) =>
{
if (e.Data != null)
{
Console.WriteLine("Error Data received: " + e.Data.ToString());
error.AppendLine(e.Data);
}
});
myProcess.Start();
myProcess.BeginOutputReadLine();
myProcess.BeginErrorReadLine();
Console.WriteLine("Executing input command: " + input);
myProcess.StandardInput.WriteLine(input);
Console.WriteLine("Waiting for input command to complete...");
commandFinished.WaitOne();
Console.WriteLine("Command complete!");
return output.ToString();
}
catch (Exception ex)
{
Console.WriteLine("EXCEPTION: " + ex.ToString());
throw ex;
}
}
Now, the code is hanging on the call to WaitOne(). I'm confused as to why - I don't detect the CLI prompt in the output, and I never get any WriteLines telling me the prompt was received in the OutputDataReceived event, or that the Data received was null. I.e. the OutputDataReceived event isn't being raised when the previous command has completed and the prompt is displayed.
The input command I'm supplying does take a while, but it does finish. Am I using AutoResetEvent wrong here?
Without a good, minimal, complete code example it's impossible to know for sure what might be wrong with the code. But I can see one obvious explanation:
The OutputDataReceived event is raised for each line of output that is received. That is, a string terminated by a newline character.
Without specifics about your external process, I can't say for sure. But most CLI type scenarios involve the display of a prompt without a newline character. I.e. the prompt is written to the console and user input is expected to be echoed to the console immediately following the prompt, rather than on the next line.
If that's the case for your scenario, then almost certainly you are failing to detect the prompt because the event won't be raised for the prompt alone. After the final line of output from the previous command's operation, the next time the event will be raised is after the command has been sent to the process via standard input. This is obviously too late to be useful in knowing when to send that command. :)
To get this to work, you will have to read input from the process via one of the other mechanisms available that are not line-based.
Anybody knows how I could make a Monitor program in C# to control that an application will always be running? That I need it's a double monitor application, I will explain: I have a application Ap1 that have to control that Ap2 process it's always started, and Ap2 have to control that Ap1 process it's always started. In resume, if I kill Ap1 process the Ap2 application should start Ap1 immediatelly (and vice versa if Ap2 die).
This the code that I'm developing but didn't work, I don't know but when I kill the program monitored no started again.
public void Monitor()
{
Console.WriteLine("Monitoring {0} process...", processname);
while (IsProcessRunning() == true)
{
Process[] runningNow = Process.GetProcesses();
foreach (Process process in runningNow)
{
if (process.ProcessName == processname)
{
System.Threading.Thread.Sleep(1000);
Console.WriteLine("Process:{0} is running actually", process.ProcessName);
}
else { /* Provide a messagebox. */ }
}
// Sleep till the next loop
Thread.Sleep(intInterval);
}
while (IsProcessRunning() != true)
{
ProcessMonitor proc = new ProcessMonitor("ConsoleApplication1", 1000);//Check if is running each 1 second
Console.WriteLine("Process:{0} is NOT running actually", processname);
//Application folder of exe
String applicationFolder = "C:\\App";
//Get the executable file
String procPath = applicationFolder + #"\Ap1.exe";
Console.WriteLine("Running {0} process...", proc.Name);
//Lauch process
Process p = Process.Start(procPath);
Console.WriteLine("Process running {0} OK", proc.Name);
//p.WaitForExit(10000);
}
}
And the main program:
static void Main(string[] args)
{
ProcessMonitor proc = new ProcessMonitor("ConsoleApplication1", 1000);//Check if is running each 1 second
if (proc.IsProcessRunning() != true)
{
Console.WriteLine("{0} is not running.", proc.Name);
//Application folder of exe
String applicationFolder = "C:\\App";
//Get the executable file
String procPath = applicationFolder + #"\Ap1.exe";
Console.WriteLine("Running {0} process...", proc.Name);
//Lauch process
Process p = Process.Start(procPath);
Console.WriteLine("Process running {0} OK", proc.Name);
//p.WaitForExit(10000);
}
else
{
proc.Monitor();
}
proc.FreezeOnScreen();
}
You could set up a task in the task scheduler that launches your program at a set interval (say every half hour). I believe you can set it to not start the task if there is already an instance running. (Correct me if I am wrong)
We needed this once and simply monitored the process list periodically, checking for the name of the process. If the process wasn't present for a given amount of time, we restarted it. You can use Process.GetProcessesByName.
I am running a program that runs driverquery.exe command and gets back the ouput
pProcess.StartInfo.CreateNoWindow = true;
debugLog.WriteToLog("");
pProcess.StartInfo.UseShellExecute = false;
pProcess.StartInfo.RedirectStandardOutput = true;
pProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
debugLog.WriteToLog("before start method");
pProcess.Start();
debugLog.WriteToLog("after start method");
if (windowTitleToHide.Length > 0)
{
WindowsUtil.HideWindow(windowTitleToHide);
}
if (null != attributeName && !"".Equals(attributeName))
Console.WriteLine(attributeName + " : ");
debugLog.WriteToLog("before read to end");
StreamReader reader = pProcess.StandardOutput;
String strOutput = string.Empty;
while (reader.Peek() > -1)
strOutput += reader.ReadLine();
debugLog.WriteToLog("after read to end");
Console.WriteLine(strOutput);
debugLog.WriteToLog("before wait for exit");
pProcess.WaitForExit();
debugLog.WriteToLog("after wait for exit");
pProcess.Close();
The process takes about 30 minutes to finish.If i run the same process via cmd it always finishes in 2 minutes. I have tried using readtoend instead of readline but that also did not help. Can some tell what is wrong here? in my logs i can see the last line getting printed as before wait for exit
PS: When i see the processes in the taskmanager the driverquery.exe is running but not consuming any CPU cycles. The process calling this code is consuming about 99% CPU. I know for sure that the calling code is not doing any other task while running this code.
I might guess the problem is related to :
while (reader.Peek() > -1)
strOutput += reader.ReadLine();
You probably are not reading the full output of your application. If there is any pause in its output then reader.Peek() will return -1 before the application is finished its full output. If it is outputting a lot of data you may even be overflowing the output stream since your process will have given up reading after emptying the stream once. If this is the case, the child process may be generating a lot of exceptions outputting to a full stream (which will dramatically increase execution time). Profiling and debugging would tell you more about what is actually going on.
You might try an async approach like this :
pProcess.StartInfo.RedirectStandardOutput = true;
pProcess.EnableRaisingEvents = true; // Enable events
pProcess.OutputDataReceived += outputRedirection; // hook up
pProcess.Start();
pProcess.BeginOutputReadLine(); // use async BeginOutputReadLine
pProcess.WaitForExit();
where
static void outputRedirection(object sendingProcess,
DataReceivedEventArgs outLine)
{
try
{
if (outLine.Data != null)
Console.WriteLine(outLine.Data);
// or collect the data, etc
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return;
}
}
Rather than polling in a tight loop (which is likely to empty the output stream faster than the child process will fill it, and therefore fail), this waits for data to come in. Your main process will still block on the call to WaitForExit but a threadpool thread will handle the incoming events.
EDIT
Here is an SSCCE that works just fine :
static void Main(string[] args)
{
Stopwatch spw = new Stopwatch();
spw.Start();
Process pProcess = new Process();
pProcess.StartInfo.FileName = "driverquery.exe";
pProcess.StartInfo.CreateNoWindow = true;
pProcess.StartInfo.UseShellExecute = false;
pProcess.StartInfo.RedirectStandardOutput = true;
pProcess.EnableRaisingEvents = true;
pProcess.OutputDataReceived += outputRedirection;
pProcess.Start();
pProcess.BeginOutputReadLine();
pProcess.WaitForExit();
pProcess.Close();
spw.Stop();
Console.WriteLine();
Console.WriteLine("Completed in : " +
spw.ElapsedMilliseconds.ToString()
+ "ms");
}
Using outputRedirection as define above --> output :
If this isn't working for you, then please show us your complete, real code. Something else you are doing is wrong.
I'm calling Process.Start, but it blocks the current thread.
pInfo = new ProcessStartInfo("C:\\Windows\\notepad.exe");
// Start process
mProcess = new Process();
mProcess.StartInfo = pInfo;
if (mProcess.Start() == false) {
Trace.TraceError("Unable to run process {0}.");
}
Even when the process is closed, the code doesn't respond anymore.
But Process.Start is really supposed to block? What's going on?
(The process start correctly)
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
namespace Test
{
class Test
{
[STAThread]
public static void Main()
{
Thread ServerThread = new Thread(AccepterThread);
ServerThread.Start();
Console.WriteLine (" --- Press ENTER to stop service ---");
while (Console.Read() < 0) { Application.DoEvents(); }
Console.WriteLine("Done.");
}
public static void AccepterThread(object data)
{
bool accepted = false;
while (true) {
if (accepted == false) {
Thread hThread = new Thread(HandlerThread);
accepted = true;
hThread.Start();
} else
Thread.Sleep(100);
}
}
public static void HandlerThread(object data)
{
ProcessStartInfo pInfo = new ProcessStartInfo("C:\\Windows\\notepad.exe");
Console.WriteLine("Starting process.");
// Start process
Process mProcess = new Process();
mProcess.StartInfo = pInfo;
if (mProcess.Start() == false) {
Console.WriteLine("Unable to run process.");
}
Console.WriteLine("Still living...");
}
}
}
Console output is:
--- Press ENTER to stop service ---
Starting process.
Found it:
[STAThread]
Makes the Process.Start blocking. I read STAThread and Multithreading, but I cannot link the concepts with Process.Start behavior.
AFAIK, STAThread is required by Windows.Form. How to workaround this problem when using Windows.Form?
News for the hell:
If I rebuild my application, the first time I run application work correctly, but if I stop debugging and restart iy again, the problem araise.
The problem is not raised when application is executed without the debugger.
No, Process.Start doesn't wait for the child process to complete... otherwise you wouldn't be able to use features like redirected I/O.
Sample console app:
using System;
using System.Diagnostics;
public class Test
{
static void Main()
{
Process p = new Process {
StartInfo = new ProcessStartInfo("C:\\Windows\\notepad.exe")
};
p.Start();
Console.WriteLine("See, I'm still running");
}
}
This prints "See, I'm still running" with no problems on my box - what's it doing on your box?
Create a ProcessStartInfo and set UseShellExecute to false (default value is true). Your code should read:
pInfo = new ProcessStartInfo("C:\\Windows\\notepad.exe");
pInfo.UseShellExecute = false;
// Start process
mProcess = new Process();
mProcess.StartInfo = pInfo;
if (mProcess.Start() == false) {
Trace.TraceError("Unable to run process {0}.");
}
I had the same issue and starting the executable creating the process directly from the executable file solved the issue.
I was experiencing the same blocking behavior as the original poster in a WinForms app, so I created the console app below to simplify testing this behavior.
Jon Skeet's example uses Notepad, which only takes a few milliseconds to load normally, so a thread block may go unnoticed. I was trying to launch Excel which usually takes a lot longer.
using System;
using System.Diagnostics;
using static System.Console;
using System.Threading;
class Program {
static void Main(string[] args) {
WriteLine("About to start process...");
//Toggle which method is commented out:
//StartWithPath(); //Blocking
//StartWithInfo(); //Blocking
StartInNewThread(); //Not blocking
WriteLine("Process started!");
Read();
}
static void StartWithPath() {
Process.Start(TestPath);
}
static void StartWithInfo() {
var p = new Process { StartInfo = new ProcessStartInfo(TestPath) };
p.Start();
}
static void StartInNewThread() {
var t = new Thread(() => StartWithPath());
t.Start();
}
static string TestPath =
Environment.GetFolderPath(Environment.SpecialFolder.Desktop) +
"\\test.xlsx";
}
Calls to both StartWithPath and StartWithInfo block my thread in a console app. My console does not display "Process Started" until after the Excel splash screen closes and the main window is open.
StartInNewThread will display both messages on the console immediately, while the splash screen for Excel is still open.
We had this problem when launching a .bat script that was on a network drive on a different domain (we have dual trusted domains). I ran a remote C# debugger and sure enough Process.Start() was blocking indefinitely.
When repeating this task interactively in power shell, a security dialog was popping up:
As far as a solution, this was the direction we went. The person that did the work modified domain GPO to accomplish the trust.
Start server via command prompt:
"C:\Program Files (x86)\IIS Express\iisexpress" /path:\Publish /port:8080
This take access to sub-threads of the tree process of OS.
If you want to launch process and then make the process independent on the "launcher" / the originating call:
//Calling process
using (System.Diagnostics.Process ps = new System.Diagnostics.Process())
{
try
{
ps.StartInfo.WorkingDirectory = #"C:\Apps";
ps.StartInfo.FileName = #"C:\Program Files\Microsoft Office\Office14\MSACCESS.EXE"; //command
ps.StartInfo.Arguments = #"C:\Apps\xyz.accdb"; //argument
ps.StartInfo.UseShellExecute = false;
ps.StartInfo.RedirectStandardOutput = false;
ps.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Maximized;
ps.StartInfo.CreateNoWindow = false; //display a windows
ps.Start();
}
catch (Exception ex)
{
MessageBox.Show(string.Format("==> Process error <=={0}" + ex.ToString(), Environment.NewLine));
}
}