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.
- 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 theExperiments
class, which uses .Net functionality. Alternatively I could have used a pinvoke method, which makes use of EnumWindows. This is implemented inFindWindowHandleFromPid()
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 forping
, but for my needs it was terminating the process to quickly. - 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.
- 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. - 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.
- Can I send a ctrl-C (SIGINT) to an application on Windows? – scroll to the bottom for the real answer from KindDragon!
- GenerateConsoleCtrlEvent function (Windows)
- Pinvoke.net – Look-up all the really useful Windows user32 and kernel32 functions, like CreateProcess, AttachConsole, SendMessage, PostMessage, and more.
- How do I trap ctrl-c in a C# console app
- Graceful shutdown of console apps in Win Vista, 7, 2008
- How to send CTRL+C signal to detached command-line process? – some really interesting ideas here!
- Process Creation Flags (Windows)
- .NET – WindowStyle = hidden vs. CreateNoWindow = true?
- Kill child process when parent process is killed
- How to send keys instead of characters to a process?
- Sending Ctrl+C to Apache
- c# PostMessage, RegisterHotkeys, FindWindow – problems!
- CreatePipe function (Windows)
- WM_KEYDOWN : how to use it?
- Send ctrl+c to a cmd.exe process in c# – An answer by Chris lower down in that thread was an inspiration for the second scenario
- A complete library – Windows Input Simulator (C# SendInput Wrapper – Simulate Keyboard and Mouse)
- SendSignal – A complete working program in C++, which utilises some intersting stack peeking techniques. With source.
- Controlling another process’s console with C#
- Kill child process when parent process is killed
- Working example of CreateJobObject/SetInformationJobObject pinvoke in .net?
- Sending CTRL-S message to a window
- post message – Visual C# General
- Send Ctrl+C to process
Awesome life saver 🙂 🙂
Thank you! Really helpful.
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 🙂
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!
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.
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.
Your post helped me.Thanks alot
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.
good job!
Pingback: Can I send a ctrl-C (SIGINT) to an application on Windows? - dex page
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:
Thank you, Michal. I’ve updated the main post, pointing to your improved solution.
Was totally stumped by this issue – really appreciate your taking the time to post it.
Pingback: How to get shutdown hook to execute on a process launched from Eclipse - QuestionFocus
Pingback: Can I send a ctrl-C (SIGINT) to an application on Windows? - ExceptionsHub
Thank you very much… this solved my issue
Glad it was of help 🙂
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!
Pingback: How to get shutdown hook to execute on a process launched from Eclipse – inneka.com
Pingback: Can I send a ctrl-C (SIGINT) to an application on Windows? – inneka.com