How do I send ctrl+c to a process in c#?

Kevlar picture Kevlar · Nov 12, 2008 · Viewed 63.3k times · Source

I'm writing a wrapper class for a command line executable. This exe accepts input from stdin until I hit Ctrl+C in the command prompt shell, in which case it prints output to stdout based on the input. I want to simulate that Ctrl+C press in C# code, sending the kill command to a .NET Process object. I've tried calling Process.Kill(), but that doesn't seem to give me anything in the process's StandardOutput StreamReader. Might there be anything I'm not doing right? Here's the code I'm trying to use:

ProcessStartInfo info = new ProcessStartInfo(exe, args);
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
Process p = Process.Start(info);

p.StandardInput.AutoFlush = true;
p.StandardInput.WriteLine(scriptcode);

p.Kill();

string error = p.StandardError.ReadToEnd();
if (!String.IsNullOrEmpty(error)) 
{
    throw new Exception(error);
}
string output = p.StandardOutput.ReadToEnd();

The output is always empty, even though I get data back from stdout when I run the exe manually.

Edit: This is C# 2.0 by the way.

Answer

Vitaliy Fedorchenko picture Vitaliy Fedorchenko · Mar 26, 2015

Despite of the fact that using GenerateConsoleCtrlEvent for sending Ctrl+C signal is a right answer it needs significant clarification to get it work in different .NET application types.

If your .NET application doesn't use its own console (WinForms/WPF/Windows Service/ASP.NET) basic flow is:

  1. Attach main .NET process to console of process you want to Ctrl+C
  2. Prevent main .NET process from stopping because of Ctrl+C event with SetConsoleCtrlHandler
  3. Generate console event for current console with GenerateConsoleCtrlEvent (processGroupId should be zero! Answer with code that sends p.SessionId will not work and incorrect)
  4. Disconnect from console and restore Ctrl+C handling by main process

The following code snippet illustrates how to do that:

Process p;
if (AttachConsole((uint)p.Id)) {
    SetConsoleCtrlHandler(null, true);
    try { 
        if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT,0))
            return false;
        p.WaitForExit();
    } finally {
        FreeConsole();
        SetConsoleCtrlHandler(null, false);
    }
    return true;
}

where SetConsoleCtrlHandler, FreeConsole, AttachConsole and GenerateConsoleCtrlEvent are native WinAPI methods:

internal const int CTRL_C_EVENT = 0;
[DllImport("kernel32.dll")]
internal static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, uint dwProcessGroupId);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
internal static extern bool FreeConsole();
[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);
// Delegate type to be used as the Handler Routine for SCCH
delegate Boolean ConsoleCtrlDelegate(uint CtrlType);

Things become more complex if you need to send Ctrl+C from .NET console application. Approach will not work because AttachConsole returns false in this case (main console app already has a console). It is possible to call FreeConsole before AttachConsole call but as result original .NET app console will be lost which is not acceptable in most cases.

My solution for this case (that really works and has no side effects for .NET main process console):

  1. Create small supporting .NET console program that accepts process ID from command line arguments, looses its own console with FreeConsole before AttachConsole call and sends Ctrl+C to target process with code mentioned above
  2. Main .NET console process just invokes this utility in new process when it needs to send Ctrl+C to another console process