By SiberianGuy


2010-07-27 11:06:38 8 Comments

I'm creating new processes using System.Diagnostics.Process class from my application.

I want this processes to be killed when/if my application has crashed. But if I kill my application from Task Manager, child processes are not killed.

Is there any way to make child processes dependent on parent process?

12 comments

@Lenor 2018-10-28 13:39:44

Just my 2018 version. Use it aside your Main() method.

    using System.Management;
    using System.Diagnostics;

    ...

    // Called when the Main Window is closed
    protected override void OnClosed(EventArgs EventArgs)
    {
        string query = "Select * From Win32_Process Where ParentProcessId = " + Process.GetCurrentProcess().Id;
        ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
        ManagementObjectCollection processList = searcher.Get();
        foreach (var obj in processList)
        {
            object data = obj.Properties["processid"].Value;
            if (data != null)
            {
                // retrieve the process
                var childId = Convert.ToInt32(data);
                var childProcess = Process.GetProcessById(childId);

                // ensure the current process is still live
                if (childProcess != null) childProcess.Kill();
            }
        }
        Environment.Exit(0);
    }

@springy76 2018-10-30 09:11:32

I doubt this gets executed when the parent process is being killed.

@Ron 2016-05-04 18:07:39

This answer started with @Matt Howells' excellent answer plus others (see links in the code below). Improvements:

  • Supports 32-bit and 64-bit.
  • Fixes some problems in @Matt Howells' answer:
    1. The small memory leak of extendedInfoPtr
    2. The 'Win32' compile error, and
    3. A stack-unbalanced exception I got in the call to CreateJobObject (using Windows 10, Visual Studio 2015, 32-bit).
  • Names the Job, so if you use SysInternals, for example, you can easily find it.
  • Has a somewhat simpler API and less code.

Here's how to use this code:

// Get a Process object somehow.
Process process = Process.Start(exePath, args);
// Add the Process to ChildProcessTracker.
ChildProcessTracker.AddProcess(process);

To support Windows 7 requires:

In my case, I didn't need to support Windows 7, so I have a simple check at the top of the static constructor below.

/// <summary>
/// Allows processes to be automatically killed if this parent process unexpectedly quits.
/// This feature requires Windows 8 or greater. On Windows 7, nothing is done.</summary>
/// <remarks>References:
///  https://stackoverflow.com/a/4657392/386091
///  https://stackoverflow.com/a/9164742/386091 </remarks>
public static class ChildProcessTracker
{
    /// <summary>
    /// Add the process to be tracked. If our current process is killed, the child processes
    /// that we are tracking will be automatically killed, too. If the child process terminates
    /// first, that's fine, too.</summary>
    /// <param name="process"></param>
    public static void AddProcess(Process process)
    {
        if (s_jobHandle != IntPtr.Zero)
        {
            bool success = AssignProcessToJobObject(s_jobHandle, process.Handle);
            if (!success && !process.HasExited)
                throw new Win32Exception();
        }
    }

    static ChildProcessTracker()
    {
        // This feature requires Windows 8 or later. To support Windows 7 requires
        //  registry settings to be added if you are using Visual Studio plus an
        //  app.manifest change.
        //  https://stackoverflow.com/a/4232259/386091
        //  https://stackoverflow.com/a/9507862/386091
        if (Environment.OSVersion.Version < new Version(6, 2))
            return;

        // The job name is optional (and can be null) but it helps with diagnostics.
        //  If it's not null, it has to be unique. Use SysInternals' Handle command-line
        //  utility: handle -a ChildProcessTracker
        string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id;
        s_jobHandle = CreateJobObject(IntPtr.Zero, jobName);

        var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();

        // This is the key flag. When our process is killed, Windows will automatically
        //  close the job handle, and when that happens, we want the child processes to
        //  be killed, too.
        info.LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;

        var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        try
        {
            Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

            if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation,
                extendedInfoPtr, (uint)length))
            {
                throw new Win32Exception();
            }
        }
        finally
        {
            Marshal.FreeHGlobal(extendedInfoPtr);
        }
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType,
        IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    // Windows will automatically close any open job handles when our process terminates.
    //  This can be verified by using SysInternals' Handle utility. When the job handle
    //  is closed, the child processes will be killed.
    private static readonly IntPtr s_jobHandle;
}

public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public JOBOBJECTLIMIT LimitFlags;
    public UIntPtr MinimumWorkingSetSize;
    public UIntPtr MaximumWorkingSetSize;
    public UInt32 ActiveProcessLimit;
    public Int64 Affinity;
    public UInt32 PriorityClass;
    public UInt32 SchedulingClass;
}

[Flags]
public enum JOBOBJECTLIMIT : uint
{
    JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
}

[StructLayout(LayoutKind.Sequential)]
public struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UIntPtr ProcessMemoryLimit;
    public UIntPtr JobMemoryLimit;
    public UIntPtr PeakProcessMemoryUsed;
    public UIntPtr PeakJobMemoryUsed;
}

I carefully tested both the 32-bit and 64-bit versions of the structs by programmatically comparing the managed and native versions to each other (the overall size as well as the offsets for each member).

I've tested this code on Windows 7, 8, and 10.

@Frank Q. 2016-11-02 23:18:19

what about closing job handle ??

@Ron 2016-11-10 05:33:56

@FrankQ. It's important to let Windows close s_jobHandle for us when our process terminates, because our process may terminate unexpectedly (like by crashing or if the user uses Task Manager). See my comment on s_jobHandle's.

@Yiping 2018-10-22 06:06:41

It is working for me. May I ask what's your opinion on using the JOB_OBJECT_LIMIT_BREAKAWAY_OK flag? docs.microsoft.com/en-us/windows/desktop/api/winnt/…

@Ron 2018-10-23 18:20:10

@yiping It sounds like CREATE_BREAKAWAY_FROM_JOB allows your child process to spawn a process that can outlive your original process. That's a different requirement than what the OP asked for. Instead, if you can make your original process spawn that long-lived process (and not use ChildProcessTracker) that would be simpler.

@Jannik 2019-04-09 08:40:11

@Ron Can you add an overload, that accepts just the process handle and not the whole process?

@Ron 2019-04-10 18:40:50

@Jannik Here you go. public static void AddProcess(IntPtr processHandle) { if (s_jobHandle != IntPtr.Zero) AssignProcessToJobObject(s_jobHandle, processHandle); } I'm not sure I would recommend this approach because now there's no (easy) way to tell if the assignment to the job object failed due to the process having exited already versus some other kind of error.

@Justin Harris 2017-07-24 17:24:50

Use event handlers to make hooks on a few exit scenarios:

var process = Process.Start("program.exe");
AppDomain.CurrentDomain.DomainUnload += (s, e) => { process.Kill(); process.WaitForExit(); };
AppDomain.CurrentDomain.ProcessExit += (s, e) => { process.Kill(); process.WaitForExit(); };
AppDomain.CurrentDomain.UnhandledException += (s, e) => { process.Kill(); process.WaitForExit(); };

@Alsty 2017-06-08 09:40:45

I was looking for a solution to this problem that did not require unmanaged code. I was also not able to use standard input/output redirection because it was a Windows Forms application.

My solution was to create a named pipe in the parent process and then connect the child process to the same pipe. If the parent process exits then the pipe becomes broken and the child can detect this.

Below is an example using two console applications:

Parent

private const string PipeName = "471450d6-70db-49dc-94af-09d3f3eba529";

public static void Main(string[] args)
{
    Console.WriteLine("Main program running");

    using (NamedPipeServerStream pipe = new NamedPipeServerStream(PipeName, PipeDirection.Out))
    {
        Process.Start("child.exe");

        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

Child

private const string PipeName = "471450d6-70db-49dc-94af-09d3f3eba529"; // same as parent

public static void Main(string[] args)
{
    Console.WriteLine("Child process running");

    using (NamedPipeClientStream pipe = new NamedPipeClientStream(".", PipeName, PipeDirection.In))
    {
        pipe.Connect();
        pipe.BeginRead(new byte[1], 0, 1, PipeBrokenCallback, pipe);

        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

private static void PipeBrokenCallback(IAsyncResult ar)
{
    // the pipe was closed (parent process died), so exit the child process too

    try
    {
        NamedPipeClientStream pipe = (NamedPipeClientStream)ar.AsyncState;
        pipe.EndRead(ar);
    }
    catch (IOException) { }

    Environment.Exit(1);
}

@Thomas Maierhofer 2015-04-08 23:15:22

I've made a child process management library where the parent process and the child process are monitored due a bidirectional WCF pipe. If either the child process terminates or the parent process terminates each other is notified. There is also a debugger helper available which automatically attaches the VS debugger to the started child process

Project site:

http://www.crawler-lib.net/child-processes

NuGet Packages:

https://www.nuget.org/packages/ChildProcesses https://www.nuget.org/packages/ChildProcesses.VisualStudioDebug/

@Marco Regueira 2014-06-03 10:25:07

There is another relevant method, easy and effective, to finish child processes on program termination. You can implement and attach a debugger to them from the parent; when the parent process ends, child processes will be killed by the OS. It can go both ways attaching a debugger to the parent from the child (note that you can only attach one debugger at a time). You can find more info on the subject here.

Here you have an utility class that launches a new process and attaches a debugger to it. It has been adapted from this post by Roger Knapp. The only requirement is that both processes need to share the same bitness. You cannot debug a 32bit process from a 64bit process or vice versa.

public class ProcessRunner
{
    #region "API imports"

    private const int DBG_CONTINUE = 0x00010002;
    private const int DBG_EXCEPTION_NOT_HANDLED = unchecked((int) 0x80010001);

    private enum DebugEventType : int
    {
        CREATE_PROCESS_DEBUG_EVENT = 3,
        //Reports a create-process debugging event. The value of u.CreateProcessInfo specifies a CREATE_PROCESS_DEBUG_INFO structure.
        CREATE_THREAD_DEBUG_EVENT = 2,
        //Reports a create-thread debugging event. The value of u.CreateThread specifies a CREATE_THREAD_DEBUG_INFO structure.
        EXCEPTION_DEBUG_EVENT = 1,
        //Reports an exception debugging event. The value of u.Exception specifies an EXCEPTION_DEBUG_INFO structure.
        EXIT_PROCESS_DEBUG_EVENT = 5,
        //Reports an exit-process debugging event. The value of u.ExitProcess specifies an EXIT_PROCESS_DEBUG_INFO structure.
        EXIT_THREAD_DEBUG_EVENT = 4,
        //Reports an exit-thread debugging event. The value of u.ExitThread specifies an EXIT_THREAD_DEBUG_INFO structure.
        LOAD_DLL_DEBUG_EVENT = 6,
        //Reports a load-dynamic-link-library (DLL) debugging event. The value of u.LoadDll specifies a LOAD_DLL_DEBUG_INFO structure.
        OUTPUT_DEBUG_STRING_EVENT = 8,
        //Reports an output-debugging-string debugging event. The value of u.DebugString specifies an OUTPUT_DEBUG_STRING_INFO structure.
        RIP_EVENT = 9,
        //Reports a RIP-debugging event (system debugging error). The value of u.RipInfo specifies a RIP_INFO structure.
        UNLOAD_DLL_DEBUG_EVENT = 7,
        //Reports an unload-DLL debugging event. The value of u.UnloadDll specifies an UNLOAD_DLL_DEBUG_INFO structure.
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct DEBUG_EVENT
    {
        [MarshalAs(UnmanagedType.I4)] public DebugEventType dwDebugEventCode;
        public int dwProcessId;
        public int dwThreadId;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)] public byte[] bytes;
    }

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern bool DebugActiveProcess(int dwProcessId);

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern bool WaitForDebugEvent([Out] out DEBUG_EVENT lpDebugEvent, int dwMilliseconds);

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern bool ContinueDebugEvent(int dwProcessId, int dwThreadId, int dwContinueStatus);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool IsDebuggerPresent();

    #endregion

    public Process ChildProcess { get; set; }

    public bool StartProcess(string fileName)
    {
        var processStartInfo = new ProcessStartInfo(fileName)
        {
            UseShellExecute = false,
            WindowStyle = ProcessWindowStyle.Normal,
            ErrorDialog = false
        };

        this.ChildProcess = Process.Start(processStartInfo);
        if (ChildProcess == null)
            return false;

        new Thread(NullDebugger) {IsBackground = true}.Start(ChildProcess.Id);
        return true;
    }

    private void NullDebugger(object arg)
    {
        // Attach to the process we provided the thread as an argument
        if (DebugActiveProcess((int) arg))
        {
            var debugEvent = new DEBUG_EVENT {bytes = new byte[1024]};
            while (!this.ChildProcess.HasExited)
            {
                if (WaitForDebugEvent(out debugEvent, 1000))
                {
                    // return DBG_CONTINUE for all events but the exception type
                    var continueFlag = DBG_CONTINUE;
                    if (debugEvent.dwDebugEventCode == DebugEventType.EXCEPTION_DEBUG_EVENT)
                        continueFlag = DBG_EXCEPTION_NOT_HANDLED;
                    ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, continueFlag);
                }
            }
        }
        else
        {
            //we were not able to attach the debugger
            //do the processes have the same bitness?
            //throw ApplicationException("Unable to attach debugger") // Kill child? // Send Event? // Ignore?
        }
    }
}

Usage:

    new ProcessRunner().StartProcess("c:\\Windows\\system32\\calc.exe");

@Matt Howells 2011-01-11 12:02:28

From this forum, credit to 'Josh'.

Application.Quit() and Process.Kill() are possible solutions, but have proven to be unreliable. When your main application dies, you are still left with child processes running. What we really want is for the child processes to die as soon as the main process dies.

The solution is to use "job objects" http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx.

The idea is to create a "job object" for your main application, and register your child processes with the job object. If the main process dies, the OS will take care of terminating the child processes.

public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public int nLength;
    public IntPtr lpSecurityDescriptor;
    public int bInheritHandle;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public Int16 LimitFlags;
    public UInt32 MinimumWorkingSetSize;
    public UInt32 MaximumWorkingSetSize;
    public Int16 ActiveProcessLimit;
    public Int64 Affinity;
    public Int16 PriorityClass;
    public Int16 SchedulingClass;
}

[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UInt32 ProcessMemoryLimit;
    public UInt32 JobMemoryLimit;
    public UInt32 PeakProcessMemoryUsed;
    public UInt32 PeakJobMemoryUsed;
}

public class Job : IDisposable
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(object a, string lpName);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    private IntPtr m_handle;
    private bool m_disposed = false;

    public Job()
    {
        m_handle = CreateJobObject(null, null);

        JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
        info.LimitFlags = 0x2000;

        JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

        if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
            throw new Exception(string.Format("Unable to set information.  Error: {0}", Marshal.GetLastWin32Error()));
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    private void Dispose(bool disposing)
    {
        if (m_disposed)
            return;

        if (disposing) {}

        Close();
        m_disposed = true;
    }

    public void Close()
    {
        Win32.CloseHandle(m_handle);
        m_handle = IntPtr.Zero;
    }

    public bool AddProcess(IntPtr handle)
    {
        return AssignProcessToJobObject(m_handle, handle);
    }

}

Looking at the constructor ...

JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;

The key here is to setup the job object properly. In the constructor I'm setting the "limits" to 0x2000, which is the numeric value for JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.

MSDN defines this flag as:

Causes all processes associated with the job to terminate when the last handle to the job is closed.

Once this class is setup...you just have to register each child process with the job. For example:

[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

Excel.Application app = new Excel.ApplicationClass();

uint pid = 0;
Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
 job.AddProcess(Process.GetProcessById((int)pid).Handle);

@Austin Salonen 2011-03-16 20:34:09

I would add a link to CloseHandle

@Falcon 2011-03-17 13:32:54

This won't work on Windows 7 or Vista.

@Rich 2011-08-12 11:15:59

This is the correct answer, and should be accepted as the correct answer. The currently accepted answer is flawed, in a similar way as using a bool for a semaphore is flawed. As for this not working on Windows 7, I'd be interested to hear exactly why that is ... as the SDK doesn't mention that.

@Cameron 2011-09-03 01:24:36

This does work on Windows 7 at least on my systems.

@Alexander Yezutov 2012-02-06 17:55:09

Unfortunately, I was unable to run this in 64bit mode. Here I posted a working example, which is based on this.

@Holf 2012-02-21 14:28:42

Very helpful. I'm using it to kill test web servers running on build agents if an Integration Test fails before cleanup code can terminate their processes. However, I did run into problems when running my tests from a Test Runner GUI in Visual Studio. As soon as I tried it from a Build Script using a command line test runner everything worked just fine. Thought it worth mentioning to save somebody else wasting an hour trying to track down an issue which won't really matter.

@SiberianGuy 2012-02-22 10:44:22

@Rich, I reaccepted the answer

@Esoteric Screen Name 2012-08-07 20:56:29

@Matt Howells - Where does Win32.CloseHandle originate? Is this imported from kernel32.dll? There's a matching signature there, but you're not importing it explicitly like the other API functions.

@hB0 2012-08-23 09:46:00

@Gusdor 2013-08-07 12:20:28

I really would have rolled with my own class to wrap a process and kill it in the finaliser. Would seem more straight forward. What are the downsides of that approach?

@Trevor Elliott 2013-08-20 13:39:16

@Gusdor: Your finaliser won't be called if your app crashes...

@SerG 2014-03-19 09:50:13

Why do you use JOBOBJECT_EXTENDED_LIMIT_INFORMATION if JOBOBJECT_BASIC_LIMIT_INFORMATION is enough here?

@SerG 2014-03-19 10:17:09

Ok, MSDN says: The JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag requires use of a JOBOBJECT_EXTENDED_LIMIT_INFORMATION structure.

@Keith 2015-06-23 18:43:04

Doesn't Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false) leave the unmanaged memory allocated to the extendedInfoPtr? Is there a memory leak if DestroyStructure is not called?

@Keith 2015-06-24 12:30:51

Confirmed - this example causes an unmanaged memory leak each time the new Job() constructor is called because the unmanaged memory allocated is never used.

@Ron 2016-05-05 19:52:46

The fields LimitFlags, PriorityClass, and SchedulingClass should be UInt32, not Int16. And in JOBOBJECT_EXTENDED_LIMIT_INFORMATION, the four UInt32 fields should be UIntPtr. Use this answer instead: stackoverflow.com/a/9164742/386091 . Or my answer: stackoverflow.com/a/37034966/386091 .

@Joseph 2016-07-06 23:40:09

On Windows 10 x64 if second parameter of CreateJobObject is null then SetInformationJobObject fail with error 0.

@It's a trap 2017-09-12 07:00:04

This code won't compile

@Andrew Arnott 2018-07-13 16:37:17

I fixed the compilation error and the memory leak. I also replaced all the p/invoke signatures and native structs with a simple package reference to PInvoke.Win32. You can see the final result here: gist.github.com/AArnott/2609636d2f2369495abe76e8a01446a4

@Alexey 2013-08-07 11:52:38

call job.AddProcess better to do after start of the process:

prc.Start();
job.AddProcess(prc.Handle);

When calling AddProcess before the terminate, child processes are not killed. (Windows 7 SP1)

private void KillProcess(Process proc)
{
    var job = new Job();
    job.AddProcess(proc.Handle);
    job.Close();
}

@SerG 2014-03-19 10:02:40

Calling job.AddProcess after start of the process would kill the purpose of using this object.

@mbaynton 2012-11-28 21:46:48

Here's an alternative that may work for some when you have control of the code the child process runs. The benefit of this approach is it doesn't require any native Windows calls.

The basic idea is to redirect the child's standard input to a stream whose other end is connected to the parent, and use that stream to detect when the parent has gone away. When you use System.Diagnostics.Process to start the child, it's easy to ensure its standard input is redirected:

Process childProcess = new Process();
childProcess.StartInfo = new ProcessStartInfo("pathToConsoleModeApp.exe");
childProcess.StartInfo.RedirectStandardInput = true;

childProcess.StartInfo.CreateNoWindow = true; // no sense showing an empty black console window which the user can't input into

And then, on the child process, take advantage of the fact that Reads from the standard input stream will always return with at least 1 byte until the stream is closed, when they will start returning 0 bytes. An outline of the way I ended up doing this is below; my way also uses a message pump to keep the main thread available for things other than watching standard in, but this general approach could be used without message pumps too.

using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;

static int Main()
{
    Application.Run(new MyApplicationContext());
    return 0;
}

public class MyApplicationContext : ApplicationContext
{
    private SynchronizationContext _mainThreadMessageQueue = null;
    private Stream _stdInput;

    public MyApplicationContext()
    {
        _stdInput = Console.OpenStandardInput();

        // feel free to use a better way to post to the message loop from here if you know one ;)    
        System.Windows.Forms.Timer handoffToMessageLoopTimer = new System.Windows.Forms.Timer();
        handoffToMessageLoopTimer.Interval = 1;
        handoffToMessageLoopTimer.Tick += new EventHandler((obj, eArgs) => { PostMessageLoopInitialization(handoffToMessageLoopTimer); });
        handoffToMessageLoopTimer.Start();
    }

    private void PostMessageLoopInitialization(System.Windows.Forms.Timer t)
    {
        if (_mainThreadMessageQueue == null)
        {
            t.Stop();
            _mainThreadMessageQueue = SynchronizationContext.Current;
        }

        // constantly monitor standard input on a background thread that will
        // signal the main thread when stuff happens.
        BeginMonitoringStdIn(null);

        // start up your application's real work here
    }

    private void BeginMonitoringStdIn(object state)
    {
        if (SynchronizationContext.Current == _mainThreadMessageQueue)
        {
            // we're already running on the main thread - proceed.
            var buffer = new byte[128];

            _stdInput.BeginRead(buffer, 0, buffer.Length, (asyncResult) =>
                {
                    int amtRead = _stdInput.EndRead(asyncResult);

                    if (amtRead == 0)
                    {
                        _mainThreadMessageQueue.Post(new SendOrPostCallback(ApplicationTeardown), null);
                    }
                    else
                    {
                        BeginMonitoringStdIn(null);
                    }
                }, null);
        }
        else
        {
            // not invoked from the main thread - dispatch another call to this method on the main thread and return
            _mainThreadMessageQueue.Post(new SendOrPostCallback(BeginMonitoringStdIn), null);
        }
    }

    private void ApplicationTeardown(object state)
    {
        // tear down your application gracefully here
        _stdInput.Close();

        this.ExitThread();
    }
}

Caveats to this approach:

  1. the actual child .exe that is launched must be a console application so it remains attached to stdin/out/err. As in the above example, I easily adapted my existing application that used a message pump (but didn't show a GUI) by just creating a tiny console project that referenced the existing project, instantiating my application context and calling Application.Run() inside the Main method of the console .exe.

  2. Technically, this merely signals the child process when the parent exits, so it will work whether the parent process exited normally or crashed, but its still up to the child processes to perform its own shutdown. This may or may not be what you want...

@Esben Skov Pedersen 2012-12-06 09:26:10

I have used this trick a few times. Works very well

@adam smith 2012-02-29 22:45:29

This post is intended as an extension to @Matt Howells' answer, specifically for those who run into problems with using Job Objects under Vista or Win7, especially if you get an access denied error ('5') when calling AssignProcessToJobObject.

tl;dr

To ensure compatibility with Vista and Win7, add the following manifest to the .NET parent process:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <v3:trustInfo xmlns:v3="urn:schemas-microsoft-com:asm.v3">
    <v3:security>
      <v3:requestedPrivileges>
        <v3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </v3:requestedPrivileges>
    </v3:security>
  </v3:trustInfo>
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <!-- We specify these, in addition to the UAC above, so we avoid Program Compatibility Assistant in Vista and Win7 -->
    <!-- We try to avoid PCA so we can use Windows Job Objects -->
    <!-- See https://stackoverflow.com/questions/3342941/kill-child-process-when-parent-process-is-killed -->

    <application>
      <!--The ID below indicates application support for Windows Vista -->
      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
      <!--The ID below indicates application support for Windows 7 -->
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
    </application>
  </compatibility>
</assembly>

Note that when you add new manifest in Visual Studio 2012 it will contain the above snippet already so you do not need to copy it from hear. It will also include a node for Windows 8.

full explanation

Your job association will fail with an access denied error if the process you're starting is already associated with another job. Enter Program Compatibility Assistant, which, starting in Windows Vista, will assign all kinds of processes to its own jobs.

In Vista you can mark your application to be excluded from PCA by simply including an application manifest. Visual Studio seems to do this for .NET apps automatically, so you're fine there.

A simple manifest no longer cuts it in Win7. [1] There, you have to specifically specify that you're compatible with Win7 with the tag in your manifest. [2]

This led me to worry about Windows 8. Will I have to change my manifest once again? Apparently there's a break in the clouds, as Windows 8 now allows a process to belong to multiple jobs. [3] So I haven't tested it yet, but I imagine that this madness will be over now if you simply include a manifest with the supportedOS information.

Tip 1: If you're developing a .NET app with Visual Studio, as I was, here [4] are some nice instructions on how to customize your application manifest.

Tip 2: Be careful with launching your application from Visual Studio. I found that, after adding the appropriate manifest, I still had problems with PCA when launching from Visual Studio, even if I used Start without Debugging. Launching my application from Explorer worked, however. After manually adding devenv for exclusion from PCA using the registry, starting applications that used Job Objects from VS started working as well. [5]

Tip 3: If you ever want to know if PCA is your problem, try launching your application from the command line, or copy the program to a network drive and run it from there. PCA is automatically disabled in those contexts.

[1] http://blogs.msdn.com/b/cjacks/archive/2009/06/18/pca-changes-for-windows-7-how-to-tell-us-you-are-not-an-installer-take-2-because-we-changed-the-rules-on-you.aspx

[2] http://ayende.com/blog/4360/how-to-opt-out-of-program-compatibility-assistant

[3] http://msdn.microsoft.com/en-us/library/windows/desktop/ms681949(v=vs.85).aspx: "A process can be associated with more than one job in Windows 8"

[4] How can I embed an application manifest into an application using VS2008?

[5] How to stop the Visual Studio debugger starting my process in a job object?

@Johnny Kauffman 2014-08-11 17:51:09

These are great additions to the topic, thank you! I made use of every aspect of this answer, including the links.

@Stefan Egli 2010-07-27 12:19:49

I see two options:

  1. If you know exactly what child process could be started and you are sure they are only started from your main process, then you could consider simply searching for them by name and kill them.
  2. Iterate through all processes and kill every process that has your process as a parent (I guess you need to kill the child processes first). Here is explained how you can get the parent process id.

@Giorgi 2010-07-27 11:09:28

One way is to pass PID of parent process to the child. The child will periodically poll if the process with the specified pid exists or not. If not it will just quit.

You can also use Process.WaitForExit method in child method to be notified when the parent process ends but it might not work in case of Task Manager.

@SiberianGuy 2010-07-27 11:17:13

How can I pass parent PID to child? Is there any system solution? I can't modify child process binaries.

@Giorgi 2010-07-27 11:19:38

Well, if you can't modify child process you can not use my solution even if you pass PID to it.

@Distortum 2012-03-17 05:29:11

@Idsa You can pass it via the command line: Process.Start(string fileName, string arguments)

@RichardOD 2012-09-04 21:08:01

Rather than poll you can hook into the Exit event on the process class.

@Ivan Ferrer Villa 2012-10-23 12:03:08

Tried it but parent process not always exits if it has childs alive (at least in my case Cobol->NET). Easy to check watching the process hierarchy in Sysinternals ProcessExplorer.

Related Questions

Sponsored Content

26 Answered Questions

[SOLVED] Why not inherit from List<T>?

30 Answered Questions

[SOLVED] Find (and kill) process locking port 3000 on Mac

  • 2010-10-04 12:19:16
  • oma
  • 1225973 View
  • 1443 Score
  • 30 Answer
  • Tags:   macos process

44 Answered Questions

[SOLVED] Deep cloning objects

  • 2008-09-17 00:06:27
  • NakedBrunch
  • 749986 View
  • 2062 Score
  • 44 Answer
  • Tags:   c# .net clone

23 Answered Questions

[SOLVED] How to make child process die after parent exits?

21 Answered Questions

1 Answered Questions

6 Answered Questions

9 Answered Questions

[SOLVED] How can I cause a child process to exit when the parent does?

  • 2008-11-06 17:15:57
  • Alex
  • 45068 View
  • 43 Score
  • 9 Answer
  • Tags:   java process

1 Answered Questions

[SOLVED] Keep the child process if the parent process was killed

  • 2013-06-12 14:11:58
  • XXXXXXXXXXXXXX
  • 444 View
  • 1 Score
  • 1 Answer
  • Tags:   vb.net process

Sponsored Content