Stopping command-line applications programatically with Ctrl-C event from .Net – a working demo

The story behind

The problem of starting and stopping console/command-line applications from a host program is much more widespread, than one would think at the first glance. Starting an application and even redirecting its output is relatively easy to achieve. Stopping it nicely, on the other hand, is hard. “Nicely” is the keyword here. One can always call Kill() member function of a Process object. Yet that would terminate the child process immediately, without giving it a chance to do any clean-up, which in some cases can be disastrous.

On a Unix environment, a programmer would typically issue a SIGINT and/or SIGTERM signal, before resorting to SIGKILL. Windows has something almost, but not entirely, like this. It can generate a ConsoleControlEvent, which translates from a Ctrl-C or Ctrl-Break key press. These events are sent not to an application, but to the console/terminal with witch an application is registered and then propagated to all handlers in each application until one of the applications acknowledges the event. So, in reality, one sends a termination signal to all application, which are started from/with a particular console, and not just to a specific application. This, too, can spell trouble.

A popular work-around, which almost never works is to locate the window handle of a process (this assumes that the command line process has a window, which is not always the case) and sending a series of messages, to it, simulating key-down and key-up events, mapping to the ‘Ctrl’ and to the ‘C’ keys on the keyboard.

I was aiming for this approach, until I stumbled across a more elegant, and seemingly more fool-proof solution. Each solution will be outlined and briefly explained in the list below. Download and study the source code for the actual implementation (all worker functions are concentrated in a static class in Experiments.cs). If you happen to have a working solution for the PostMessage scenario, please share it with the community though the comments in this post.

Source code: [download]

Outline of the scenarios

If the started application (ping.exe) fails to stop (mainly as a result of your own experiments or of Scenario 2), hit the yellow Panic button to forcefully terminate it.
Only two scenarios represent real solutions to the problem – 1 and 4, with #4 being preferred. Scenario #2 was never completed due to time constraints (feel free to fill in the blanks ;)), while Scenario #3 is there for the sake of completeness.

  1. Start with a visible window using .Net, hide with pinvoke, run for 6 seconds, show with pinvoke, stop with .Net.

    This method is a mix of .Net and pinvoke. It stops the process by closing its command window, similar to clicking the ‘X’ button in the upper right corner. This generates a Ctrl-C event to all programs, registered with that console. To find the window, it must exist and be visible. Here I use method FindWindowHandleFromProcessObjectWithVisibleWindow() in the Experiments class, which uses .Net functionality. Alternatively I could have used a pinvoke method, which makes use of EnumWindows. This is implemented in FindWindowHandleFromPid() in the Experiments class.
    The downside of this method is that it briefly shows the commend window when starting and when stopping the process. Another problem is that a process takes long to close, it will be terminated. This method works fine for ping, but for my needs it was terminating the process to quickly.

  2. Pinvoke only: Start with invisible window, get window handle, run for 6 seconds, stop using window handle. (Unfinished!)

    It turned out that it was impossible to start a command window with an invisible, but still existing window using pinvoke CreateProcess. The start sequence ended up being the same as for Scenario 1, but bypassing .Net. Stopping a process with PostMessage did not work out either, even though I read of successful attempts. I abandoned this scenario as I did not want to use an inordinate amount of time on what started to look like a dead end.

  3. Brutal .Net: Start without window; run for 6 seconds; stop by killing the process.

    This scenario is is here for completeness. This is the most brutal method, which relies entirely on .Net functionality. When stopping the process, it simply kills it, without giving it any chance to clean up. Note that ping command does not display any summary when it is stopped in this fashion.

  4. Start without a window using .Net, run for 6 seconds, stop by attaching console and issuing ConsoleCtrlEvent.

    After finding a hint at the bottom of this thread, I finally bit the bullet and started the process without a window. When the time comes to stop a process, the parent attaches its console to the child, stops itself listening to Ctrl-C event and issues the event to the console, so that the attached child terminates. It then reinstates the Ctrl-C handler for itself and frees the console.

    UPDATE: Please read comment by Michal Svoboda below for an improved version for this scenario.

Resources

During the research phase, I got ideas and inspiration from the following posts and sites. Some them were dead ends, while others lead to something usable. The post in particular (the first link) was a real revelation, and is incorporated in the fourth scenario in the demo application.

20 thoughts on “Stopping command-line applications programatically with Ctrl-C event from .Net – a working demo

  1. You my friend are a fine man indeed. So far so good… worked very first time! If only Id read this 12 hours ago… thank you sir 🙂

  2. My god… After 6 hours of trial and error, you had the right answer all along. You’re magnificent and I can’t thank you enough!

  3. Sorry here is the complete working example. just paste into a new C# console project and run. hit ctrl-c or X the window out.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading;
    
    namespace TestTrapCtrlC {
        public class Program {
            static bool exitSystem = false;
    
            #region Trap application termination
            [DllImport("Kernel32")]
            private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);
    
            private delegate bool EventHandler(CtrlType sig);
            static EventHandler _handler;
    
            enum CtrlType {
                CTRL_C_EVENT = 0,
                CTRL_BREAK_EVENT = 1,
                CTRL_CLOSE_EVENT = 2,
                CTRL_LOGOFF_EVENT = 5,
                CTRL_SHUTDOWN_EVENT = 6
            }
    
            private static bool Handler(CtrlType sig) {
                Console.WriteLine("Exiting system due to external CTRL-C, or process kill, or shutdown");
    
                //do your cleanup here
                Thread.Sleep(5000); //simulate some cleanup delay
    
                Console.WriteLine("Cleanup complete");
    
                //allow main to run off
                exitSystem = true;
    
                //shutdown right away so there are no lingering threads
                Environment.Exit(-1);
    
                return true;
            }
            #endregion
    
            static void Main(string[] args) {
                // Some biolerplate to react to close window event, CTRL-C, kill, etc
                _handler += new EventHandler(Handler);
                SetConsoleCtrlHandler(_handler, true);
    
                //start your multi threaded program here
                Program p = new Program();
                p.Start();
    
                //hold the console so it doesn’t run off the end
                while (!exitSystem) {
                    Thread.Sleep(500);
                }
            }
    
            public void Start() {
                // start a thread and start doing some processing
                Console.WriteLine("Thread started, processing..");
            }
        }
    }
    
  4. Jason Jakob, your solution is similar to my example #4, with the difference that I start a process without a console window, which is desirable when you write a front-end application that takes over the stdout/stderr streams of the child process. My solution is also slightly more robust.

  5. This is great, man. I’ve search to find a solution for hours. I was missing the console attaching.
    Thank you for a very well written article.

  6. Pingback: Can I send a ctrl-C (SIGINT) to an application on Windows? - dex page

  7. Thank you for your article – it help me a lot.
    The solution #4 work fine for me.

    Unfortunately, sometimes it happens, the process is not exited after Ctrl-C and the application stuck in “process.WaitForExit();”.
    As solution for this, I propose little improvement of your “StopProgramByAttachingToItsConsoleAndIssuingCtrlCEvent” method – to add there timer which (after a specified timeout) kills the process if the process has not exited automatically.

    Here is the updated method:

            private static void StopProgramByAttachingToItsConsoleAndIssuingCtrlCEvent(Process process, int waitForExitTimeout = 2000)
            {
                if (!AttachConsole((uint) process.Id))
                {
                    return;
                }
    
                // Disable Ctrl-C handling for our program
                SetConsoleCtrlHandler(null, true);
    
                // Sent Ctrl-C to the attached console
                GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);
    
                // Wait for the graceful end of the process.
                // If the process will not exit in time specified by 'waitForExitTimeout', the process will be killed
                using (new Timer((dummy => {if (!process.HasExited) process.Kill(); }), null, waitForExitTimeout, Timeout.Infinite))
                {
                    // Must wait here. If we don't wait and re-enable Ctrl-C handling below too fast, we might terminate ourselves.
                    process.WaitForExit();    
                }
    
                FreeConsole();
    
                // Re-enable Ctrl-C handling or any subsequently started programs will inherit the disabled state.
                SetConsoleCtrlHandler(null, false);
            }
    
  8. Was totally stumped by this issue – really appreciate your taking the time to post it.

  9. Pingback: How to get shutdown hook to execute on a process launched from Eclipse - QuestionFocus

  10. Pingback: Can I send a ctrl-C (SIGINT) to an application on Windows? - ExceptionsHub

  11. I found this helpful post when implementing support for this in the MedallionShell NuGet package (https://github.com/madelson/MedallionShell#stopping-a-command). I ended up using a variant of the console-attach method for the Windows implementation. It tries to handle edge cases like signaling the current process, signaling a process which shares the current process’s console, and signaling from a process which is actively using its console (so we don’t want to detach the current console). The implementation starts here for anyone who is interested: https://github.com/madelson/MedallionShell/blob/master/MedallionShell/Signals/WindowsProcessSignaler.cs Happy to receive any comments or suggestions!

  12. Pingback: How to get shutdown hook to execute on a process launched from Eclipse – inneka.com

  13. Pingback: Can I send a ctrl-C (SIGINT) to an application on Windows? – inneka.com

Comments are closed.