Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cleanly shut down a console app started with Process.Start?

Tags:

This is looking like an impossible task. Absolutely nothing I've found works. The question is how to cleanly close a console application started with Process.Start that has been started with no console window and without using shell execute: (ProcessStartInfo.CreateNoWindow = true; ProcessStartInfo.UseShellExecute = false;).

It is given that the application being started will shut down "cleanly" if it receives a ctrl-c or ctrl-break signal, but there seems to be no way to send it one that works (particularly GenerateConsoleCtrlEvent).

  • Process.Kill doesn't work. It leaves corrupt files behind due to abrupt killing of the process.
  • Process.CloseMainWindow doesn't work. There is no main window in this case, so the function returns false and does nothing.
  • Calling EnumThreadWindows on all threads for the process and sending a WM_CLOSE to every window does nothing, and there aren't any thread windows anyway.
  • GenerateConsoleCtrlEvent doesn't work. It's only useful for processes in the same group (which .NET gives you no control over), with an unwanted side effect of closing the calling process anyway. The function does not allow you to specify a process id.

Whoever can provide code that accepts a "Process" object started with the parameters above which results in a clean shutdown of the started process without affecting the calling process will be marked as the answer. Use 7z.exe (7-zip archiver) as an example console app, which begins compressing a large file, and will leave a corrupt, unfinished file behind if not terminated cleanly.

Until someone provides a functional example or code that leads to a functional example, this question is unanswered. I have seen dozens of people asking this question and dozens of answers online, and none of them work. .NET seems to provide no support for cleanly closing a console application given its process id, which is odd considering it's started with a .NET Process object. Part of the problem is the inability to create a process in a new process group, which makes using GenerateConsoleCtrlEvent useless. There has to be a solution to this.

like image 257
Triynko Avatar asked Apr 22 '13 04:04

Triynko


People also ask

How do I stop a console program?

Hold the Ctrl button and press the C key at the same time. It sends the SIGKILL signal to the running program to force quit the command.

How to end the process in c#?

Process. Start("cmd.exe","/c taskkill /IM notepad.exe"); This code will close the Notepad (if it is running). Type the program name you want to close with it's extension (.exe).


1 Answers

I spent several hours trying to figure this one out myself. As you mentioned, the web is replete with answers that simply don't work. A lot of people suggest using GenerateConsoleCtrlEvent, but they don't provide any context, they just provide useless code snippets. The solution below uses GenerateConsoleCtrlEvent, but it works. I've tested it.

Note that this is a WinForms app and the process I'm starting and stopping is FFmpeg. I haven't tested the solution with anything else. I am using FFmpeg here to record a video and save the output to a file called "video.mp4".

The code below is the contents of my Form1.cs file. This is the file that Visual Studio creates for you when you create a WinForms solution.

using System; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms;  namespace ConsoleProcessShutdownDemo {     public partial class Form1 : Form {      BackgroundWorker worker;     Process currentProcess;      public Form1() {         InitializeComponent();     }      private void Worker_DoWork(object sender, DoWorkEventArgs e) {         const string outFile = "video.mp4";          var info = new ProcessStartInfo();         info.UseShellExecute = false;         info.CreateNoWindow = true;         info.FileName = "ffmpeg.exe";         info.Arguments = string.Format("-f gdigrab -framerate 60 -i desktop -crf 0 -pix_fmt yuv444p -preset ultrafast {0}", outFile);         info.RedirectStandardInput = true;          Process p = Process.Start(info);          worker.ReportProgress(-1, p);     }      private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) {         currentProcess = (Process)e.UserState;     }      private void btnStart_Click(object sender, EventArgs e) {         btnStart.Enabled = false;         btnStop.Enabled = true;          worker = new BackgroundWorker();          worker.WorkerSupportsCancellation = true;         worker.WorkerReportsProgress = true;         worker.DoWork += Worker_DoWork;         worker.ProgressChanged += Worker_ProgressChanged;          worker.RunWorkerAsync();      }      private void btnStop_Click(object sender, EventArgs e) {         btnStop.Enabled = false;         btnStart.Enabled = true;          if (currentProcess != null)             StopProgram(currentProcess);     }          //MAGIC BEGINS       [DllImport("kernel32.dll", SetLastError = true)]     static extern bool AttachConsole(uint dwProcessId);      [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]     static extern bool FreeConsole();      [DllImport("kernel32.dll", SetLastError = true)]     private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);      [DllImport("Kernel32", SetLastError = true)]     private static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add);      enum CtrlTypes {         CTRL_C_EVENT = 0,         CTRL_BREAK_EVENT,         CTRL_CLOSE_EVENT,         CTRL_LOGOFF_EVENT = 5,         CTRL_SHUTDOWN_EVENT     }      private delegate bool HandlerRoutine(CtrlTypes CtrlType);      public void StopProgram(Process proc) {          int pid = proc.Id;          FreeConsole();          if (AttachConsole((uint)pid)) {              SetConsoleCtrlHandler(null, true);             GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);              Thread.Sleep(2000);              FreeConsole();              SetConsoleCtrlHandler(null, false);         }          proc.WaitForExit();          proc.Close();     }       //MAGIC ENDS } 

}

like image 174
Michael Avatar answered Oct 01 '22 23:10

Michael