{"id":111216,"date":"2025-05-23T07:00:00","date_gmt":"2025-05-23T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111216"},"modified":"2025-05-23T08:09:30","modified_gmt":"2025-05-23T15:09:30","slug":"20250523-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250523-00\/?p=111216","title":{"rendered":"How can I detect if one of my helper processes is launching child processes?"},"content":{"rendered":"<p>A customer&#8217;s program has a plug-in model. They already run the plug-ins in a separate process, but they wanted to understand, among other things, whether any of those plug-ins in turn launch child processes of their own. This would help them evaluate ideas for improving their plug-in model and reach out to plug-in authors who may be affected. They asked for ideas on how they could instrument this.<\/p>\n<p>One of the things they considered was patching the import address table for all of the <code>Create\u00adProcess*<\/code> functions so they could intercept attempts to create a child process. Another thing they considered was using the existing Detours library. They asked which method was better.<\/p>\n<p>Better is not to do either of these things.<\/p>\n<p>Instead, you can put the plug-in host process in a job object, and then monitor the job object. Specifically, job objects will queue the <code>JOB_<wbr \/>OBJECT_<wbr \/>MSG_<wbr \/>NEW_<wbr \/>PROCESS<\/code> completion when a new process is created, and it will queue the <code>JOB_<wbr \/>OBJECT_<wbr \/>MSG_<wbr \/>EXIT_<wbr \/>PROCESS<\/code> or <code>JOB_<wbr \/>OBJECT_<wbr \/>MSG_<wbr \/>ABNORMAL_<wbr \/>EXIT_<wbr \/>PROCESS<\/code> completion when a process exits. (The system uses the process exit code to decide whether an exit was abnormal.)<\/p>\n<p>So let&#8217;s demonstrate this. We start with the existing <a title=\"How do I wait until all processes in a job have exited?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20130405-00\/?p=4743\"> Little Program that creates a job object and listens for completions<\/a> and listen for the additional completions.<\/p>\n<pre>#define UNICODE\r\n#define <a title=\"TEXT vs. _TEXT vs. _T, and UNICODE vs. _UNICODE\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20040212-00\/?p=40643\">_UNICODE<\/a>\r\n#define STRICT\r\n#include &lt;windows.h&gt;\r\n#include &lt;stdio.h&gt;\r\n#include &lt;atlbase.h&gt;\r\n#include &lt;atlalloc.h&gt;\r\n#include &lt;shlwapi.h&gt;\r\n\r\nint __cdecl wmain(int argc, PWSTR argv[])\r\n{\r\n CHandle Job(CreateJobObject(nullptr, nullptr));\r\n if (!Job) {\r\n  wprintf(L\"CreateJobObject, error %d\\n\", GetLastError());\r\n  return 0;\r\n }\r\n\r\n\r\n CHandle IOPort(CreateIoCompletionPort(INVALID_HANDLE_VALUE,\r\n                                       nullptr, 0, 1));\r\n if (!IOPort) {\r\n  wprintf(L\"CreateIoCompletionPort, error %d\\n\",\r\n          GetLastError());\r\n  return 0;\r\n }\r\n\r\n\r\n JOBOBJECT_ASSOCIATE_COMPLETION_PORT Port;\r\n Port.CompletionKey = Job;\r\n Port.CompletionPort = IOPort;\r\n if (!SetInformationJobObject(Job,\r\n       JobObjectAssociateCompletionPortInformation,\r\n       &amp;Port, sizeof(Port))) {\r\n  wprintf(L\"SetInformation, error %d\\n\", GetLastError());\r\n  return 0;\r\n }\r\n\r\n\r\n PROCESS_INFORMATION ProcessInformation;\r\n STARTUPINFO StartupInfo = { sizeof(StartupInfo) };\r\n PWSTR CommandLine = PathGetArgs(GetCommandLine());\r\n\r\n\r\n if (!CreateProcess(nullptr, CommandLine, nullptr, nullptr,\r\n                    FALSE, CREATE_SUSPENDED, nullptr, nullptr,\r\n                    &amp;StartupInfo, &amp;ProcessInformation)) {\r\n  wprintf(L\"CreateProcess, error %d\\n\", GetLastError());\r\n  return 0;\r\n }\r\n\r\n\r\n if (!AssignProcessToJobObject(Job,\r\n         ProcessInformation.hProcess)) {\r\n  wprintf(L\"Assign, error %d\\n\", GetLastError());\r\n  return 0;\r\n }\r\n\r\n\r\n ResumeThread(ProcessInformation.hThread);\r\n CloseHandle(ProcessInformation.hThread);\r\n CloseHandle(ProcessInformation.hProcess);\r\n\r\n\r\n DWORD CompletionCode;\r\n ULONG_PTR CompletionKey;\r\n LPOVERLAPPED Overlapped;\r\n\r\n <span style=\"border: solid 1px currentcolor; border-bottom: none;\">while (GetQueuedCompletionStatus(IOPort, &amp;CompletionCode,             <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\">         &amp;CompletionKey, &amp;Overlapped, INFINITE)) {                    <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\"> if ((HANDLE)CompletionKey == Job) {                                  <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\">   if (CompletionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO) {        <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\">     break; \/\/ all processes have exited - done                       <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\">   } else if (CompletionCode == JOB_OBJECT_MSG_NEW_PROCESS) {         <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\">     wprintf(L\"Process %d created\\n\", PtrToInt(Overlapped));          <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\">   } else if (CompletionCode == JOB_OBJECT_MSG_EXIT_PROCESS) {        <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\">     wprintf(L\"Process %d exited\\n\", PtrToInt(Overlapped));           <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\">   } else if (CompletionCode == JOB_OBJECT_MSG_ABNORMAL_NEW_PROCESS) {<\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\">     wprintf(L\"Process %d exited abnormally\\n\", PtrToInt(Overlapped));<\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\">   }                                                                  <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\"> }                                                                    <\/span>\r\n <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                                     <\/span>\r\n\r\n wprintf(L\"All done\\n\");\r\n\r\n return 0;\r\n}\r\n<\/pre>\n<p>The original program checked only for <code>JOB_<wbr \/>OBJECT_<wbr \/>MSG_<wbr \/>ACTIVE_<wbr \/>PROCESS_<wbr \/>ZERO<\/code> to exit the loop, but we added handlers for the three process-create\/exit completion codes. (These code do not exit the loop.)<\/p>\n<p>Left as an exercise (for further diagnostics) is using the process ID to get information like the path to the process. Note that there is a race condition if the process is very short-lived and exits before you can get any information from it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You can ask a job object to keep track for you.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-111216","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You can ask a job object to keep track for you.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111216","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=111216"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111216\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=111216"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111216"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111216"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}