Waiting for grandchild processes in windows - winapi

Is it possible to wait for all processes launched by a child process in Windows? I can't modify the child or grandchild processes.
Specifically, here's what I want to do. My process launches uninstallA.exe. The process uninistallA.exe launches uninstallB.exe and immediately exits, and uninstallB.exe runs for a while. I'd like to wait for uninstallB.exe to exit so that I can know when the uninstall is finished.

Create a Job Object with CreateJobObject. Use CreateProcess to start UninstallA.exe in a suspended state. Assign that new process to your job object with AssignProcessToJobObject. Start UninstallA.exe running by calling ResumeThread on the handle of the thread you got back from CreateProcess.
Then the hard part: wait for the job object to complete its execution. Unfortunately, this is quite a bit more complex than anybody would reasonably hope for. The basic idea is that you create an I/O completion port, then you create the object object, associate it with the I/O completion port, and finally wait on the I/O completion port (getting its status with GetQueuedCompletionStatus). Raymond Chen has a demonstration (and explanation of how this came about) on his blog.

Here's a technique that, while not infallible, can be useful if for some reason you can't use a job object. The idea is to create an anonymous pipe and let the child process inherit the handle to the write end of the pipe.
Typically, grandchild processes will also inherit the write end of the pipe. In particular, processes launched by cmd.exe (e.g., from a batch file) will inherit handles.
Once the child process has exited, the parent process closes its handle to the write end of the pipe, and then attempts to read from the pipe. Since nobody is writing to the pipe, the read operation will block indefinitely. (Of course you can use threads or asynchronous I/O if you want to keep doing stuff while waiting for the grandchildren.)
When (and only when) the last handle to the write end of the pipe is closed, the write end of the pipe is automatically destroyed. This breaks the pipe and the read operation completes and reports an ERROR_BROKEN_PIPE failure.
I've been using this code (and earlier versions of the same code) in production for a number of years.
// pwatch.c
//
// Written in 2011 by Harry Johnston, University of Waikato, New Zealand.
// This code has been placed in the public domain. It may be freely
// used, modified, and distributed. However it is provided with no
// warranty, either express or implied.
//
// Launches a process with an inherited pipe handle,
// and doesn't exit until (a) the process has exited
// and (b) all instances of the pipe handle have been closed.
//
// This effectively waits for any child processes to exit,
// PROVIDED the child processes were created with handle
// inheritance enabled. This is usually but not always
// true.
//
// In particular if you launch a command shell (cmd.exe)
// any commands launched from that command shell will be
// waited on.
#include <windows.h>
#include <stdio.h>
void error(const wchar_t * message, DWORD err) {
wchar_t msg[512];
swprintf_s(msg, sizeof(msg)/sizeof(*msg), message, err);
printf("pwatch: %ws\n", msg);
MessageBox(NULL, msg, L"Error in pwatch utility", MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
ExitProcess(err);
}
int main(int argc, char ** argv) {
LPWSTR lpCmdLine = GetCommandLine();
wchar_t ch;
DWORD dw, returncode;
HANDLE piperead, pipewrite;
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
char buffer[1];
while (ch = *(lpCmdLine++)) {
if (ch == '"') while (ch = *(lpCmdLine++)) if (ch == '"') break;
if (ch == ' ') break;
}
while (*lpCmdLine == ' ') lpCmdLine++;
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
if (!CreatePipe(&piperead, &pipewrite, &sa, 1)) error(L"Unable to create pipes: %u", GetLastError());
GetStartupInfo(&si);
if (!CreateProcess(NULL, lpCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
error(L"Error %u creating process.", GetLastError());
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED) error(L"Error %u waiting for process.", GetLastError());
if (!GetExitCodeProcess(pi.hProcess, &returncode)) error(L"Error %u getting exit code.", GetLastError());
CloseHandle(pipewrite);
if (ReadFile(piperead, buffer, 1, &dw, NULL)) {
error(L"Unexpected data received from pipe; bug in application being watched?", ERROR_INVALID_HANDLE);
}
dw = GetLastError();
if (dw != ERROR_BROKEN_PIPE) error(L"Unexpected error %u reading from pipe.", dw);
return returncode;
}

There is not a generic way to wait for all grandchildren but for your specific case you may be able to hack something together. You know you are looking for a specific process instance. I would first wait for uninstallA.exe to exit (using WaitForSingleObject) because at that point you know that uninstallB.exe has been started. Then use EnumProcesses and GetProcessImageFileName from PSAPI to find the running uninstallB.exe instance. If you don't find it you know it has already finished, otherwise you can wait for it.
An additional complication is that if you need to support versions of Windows older than XP you can't use GetProcessImageFileName, and for Windows NT you can't use PSAPI at all. For Windows 2000 you can use GetModuleFileNameEx but it has some caveats that mean it might fail sometimes (check docs). If you have to support NT then look up Toolhelp32.
Yes this is super ugly.

Use a named mutex.

One possibility is to install Cygwin and then use the ps command to watch for the grandchild to exit

Related

Do I have to specify handles to be inherited with PROC_THREAD_ATTRIBUTE_LIST when using CreateProcess in threads?

I'm writing a program that spawns multiple child processes simultaneously and communicates to each of them by redirecting their stdin/stdout to anonymous pipes (as described here).
This is my actual code adapted from above example:
HANDLE h_child_stdout_r = NULL;
HANDLE h_child_stdout_w = NULL;
HANDLE h_child_stdin_r = NULL;
HANDLE h_child_stdin_w = NULL;
SECURITY_ATTRIBUTES saAttr;
BOOL bSuccess = TRUE;
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
bSuccess &= CreatePipe(&h_child_stdout_r, &h_child_stdout_w, &saAttr, 0);
// Ensure the read handle to the pipe for STDOUT is not inherited.
bSuccess &= SetHandleInformation(h_child_stdout_r, HANDLE_FLAG_INHERIT, 0);
// Create a pipe for the child process's STDIN.
bSuccess &= CreatePipe(&h_child_stdin_r, &h_child_stdin_w, &saAttr, 0);
// Ensure the write handle to the pipe for STDIN is not inherited.
bSuccess &= SetHandleInformation(h_child_stdin_w, HANDLE_FLAG_INHERIT, 0);
if(!bSuccess)
{
throw dlg_exception("Failed to create child process handles");
}
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
siStartInfo.hStdError = h_child_stdout_w;
siStartInfo.hStdOutput = h_child_stdout_w;
siStartInfo.hStdInput = h_child_stdin_r;
DWORD processFlags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
// Create the child process.
bSuccess = CreateProcess(
const_cast<wchar_t *>(utf8::widen(exeName).c_str()), // application name
const_cast<wchar_t *>(utf8::widen(cmd).c_str()), // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
processFlags, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
// If an error occurs, exit the application.
if (!bSuccess)
{
string msg = "Create process failed with error code ";
msg.append( std::to_string(GetLastError()) );
throw dlg_exception(msg.c_str());
}
// Close handles to the child process and its primary thread.
// Some applications might keep these handles to monitor the status
// of the child process, for example.
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
// Close handles to the stdin and stdout pipes no longer needed by the child process.
// If they are not explicitly closed, there is no way to recognize that the child process has ended.
CloseHandle(h_child_stdout_w);
CloseHandle(h_child_stdin_r);
The above function will be called from multiple threads that are responsible for creating child processes and reading their output.
Everything works as expected but just to make sure I started searching about thread-safety of CreateProcess and I came across this article which in short says there will be an issue if multiple calls to CreateProcess are made from different threads because then each child process will inherit ALL handles from the parent process.
Now I have two questions:
1- Will this really be an issue for me? Since I am only redirecting the child process's stdin and stdout to the pipe handles, so I'm not sure if there will be a problem even if all the handles are inherited by the child. The child is merely writing to its own stdout and doesn't care about what handles it has inherited right?
2- I tried inspecting the child processes with Sysinternal's Process Explorer but as far as I could tell only two handles were inherited by each child not all of them.
Here's a screenshot of Process Explorer. As you can see the parent process has created 4 child processes. For each process I created, a new pair of handles would appear in parent process's list of handles (I have highlighted the first two):
As you can see there is four pairs of these, one for each child process.
And for each child process the handle list looks like this:
After reading the article what I was expecting was that the fourth child process would have all the handles listed because it's supposed to inherit all inheritable handles. But it only has two handles like all other childs.
So I'm wondering if I can get away with my current code without doing the PROC_THREAD_ATTRIBUTE_LIST which looks complicated and unfun.

NtCreateProcess(Ex) - Can I have a child process inherit the parents address space while running under a different process name?

I am calling NtCreateProcessEx with the section handle argument set to NULL in order to create a user mode process that is initialized with a copy of the parents address space.
I want the child process to run under a different image name other than the one of the parent process.
Is this even possible?
Here's my call to NtCreateProcessEx:
HANDLE fileHandle;
OBJECT_ATTRIBUTES ObjectAttributes = { 0 };
UNICODE_STRING InputString;
RtlInitUnicodeString( &InputString, L"C:\\Users\\user\\Documents\\codeblocks_projects\\test\\bin\\Release\\test.exe" );
ObjectAttributes.Length = sizeof( OBJECT_ATTRIBUTES );
ObjectAttributes.ObjectName = &InputString;
NTSTATUS status = NtCreateProcessEx( &fileHandle, PROCESS_QUERY_INFORMATION, &ObjectAttributes, GetCurrentProcess(), PS_INHERIT_HANDLES, NULL, NULL, NULL, FALSE );
printf_s( "%x\n", status );
Status is 0xC0000033 - STATUS_OBJECT_NAME_INVALID, if I don't pass any object attributes, the call works fine.
What am I missing here?
My guess is that this not only poorly documented; it is also impossible. At least, it seems effectively impossible nowadays, perhaps due to security concerns.
The documentation for NtOpenProcess indicates that even identifying a process by name hasn't been possible since Vista:
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-ntopenprocess
I just tried an alternative approach using NtSetInformationProcess:
#define PHNT_NO_INLINE_INIT_STRING
#include <phnt_windows.h>
#include <phnt.h>
#include <stdio.h>
int main() {
NTSTATUS status;
HANDLE handle;
status
= NtCreateProcess(&handle,
PROCESS_ALL_ACCESS,
NULL,
NtCurrentProcess(),
/*InheritObjectTable=*/TRUE,
NULL,
NULL,
NULL
);
UNICODE_STRING newName;
RtlInitUnicodeString(&newName, L"dummy.exe");
status
= NtSetInformationProcess(
handle,
ProcessImageFileName,
&newName,
sizeof newName
);
// fails with 0xC0000003, STATUS_INVALID_INFO_CLASS
printf("status: %x\n", status);
// pause to observe the new "zombie child" in Process Explorer
printf("sleeping...\n");
Sleep(5000);
return 0;
}
As noted in the code, NtSetInformationProcess fails with STATUS_INVALID_INFO_CLASS, even though this is allowed by the corresponding NtQueryInformationProcess:
https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess
Looking at the ReactOS source, this seems to be a deliberate omission. I also found this:
https://reactos.org/pipermail/ros-diffs/2004-November/002066.html
As of this writing, the above reads "don't allow the ProcessImageFileName information class for NtSetInformationProcess() anymore".
All this suggests it may have been possible in the past. After all, how was exec() in the old POSIX subsystem implemented? After fork() created an initial thread in the new process and set the thread's context to be a copy of the parent's, the child might then call exec(), whereupon the POSIX implementation probably destroyed and re-created the child process from within. At some point, the subsystem would have to set the ProcessImageFileName somehow.

Detecting named pipe disconnects with I/O completion

I have a question about the correct approach for detecting client disconnects using named pipes with I/O completion ports. We have a server that creates child processes with stdin/stdout redirected to named pipes. The pipes are opened OVERLAPPED.
We've seen that after the client issues CreateFile() the I/O completion port
receives a packet with lpNumberOfBytes of zero -- which quite effectively indicates a connection from the client. But detecting when the child process has closed its' stdin/stdout and exited does not generate a similar event.
We've come up with two approaches to detecting the named pipe disconnects;
1) periodically poll the process HANDLE of the child process to detect when the process has ended,
OR
2) create a separate thread which blocks on WaitForSingleObject() on the child process's HANDLE and when it becomes signaled the process has ended, to then generate PostQueuedCompletionStatus() to the I/O completion port with a prearranged COMPLETION_KEY.
Neither of these is difficult -- but I wanted to make sure I wasn't missing something obvious. Has anyone found an alternative to being notified when a named pipe associated with IOCP has been closed?
Ok, I discovered why the IOCP was not delivering disconnect packets, and it had todo with how I was testing the issue. We had developed a unittest harness and our unittest was acting as both server and client. When the child process ended, the child's write-pipe handle was still open in the unittest, and therefore IOCP did not unblock any handler threads.
To effectively run a pipe server requires you create a new thread and within that thread to do the work of connecting to the pipe, creating the child process and waiting for the process to end. After the child ends to then close the pipe handle which causes IOCP to then deliver a dequeue packet with lpNumberOfBytes set to zero.
Here is a sample of how we did this from a thread created with _beginthread().
void __cdecl childproc(void* p) {
TCHAR* pipename = (TCHAR*)p;
/* make sure pipe handle is "inheritable" */
SECURITY_ATTRIBUTES sattr;
sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
sattr.bInheritHandle = TRUE;
sattr.lpSecurityDescriptor = NULL;
HANDLE pipe = ::CreateFile(
pipename,
GENERIC_READ | GENERIC_WRITE,
0,
&sattr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (pipe == INVALID_HANDLE_VALUE) {
_tprintf(_T("connect to named pipe failed %ld\n", GetLastError());
_endthread();
}
/* redirect stdin/stdout/stderr to pipe */
PROCESS_INFORMATION procinfo;
STARTUPINFO startinfo;
memset(&procinfo, 0, sizeof(procinfo));
memset(&startinfo, 0, sizeof(startinfo));
startinfo.cb = sizeof(startinfo);
startinfo.hStdError = pipe;
startinfo.hStdOutput = pipe;
startinfo.hStdInput = pipe;
startinfo.dwFlags |= STARTF_USESTDHANDLES;
/* create child to do a simple "cmd.exe /c dir" */
DWORD rc = ::CreateProcess(
_T("C:\\Windows\\System32\\cmd.exe"),
_T("C:\\Windows\\System32\\cmd.exe /C dir"),
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&startinfo,
&procinfo);
if (rc == 0) {
_tprintf(_T("cannot create child process: %ld\n"), GetLastError());
_endthread();
}
if (::WaitForSingleObject(procinfo.hProcess, INFINITE) != WAIT_OBJECT_0) {
_tprintf(_T("error waiting for child to end: %ld\n"), GetLastError());
}
/* cleanup */
::CloseHandle(procinfo.hProcess);
::CloseHandle(procinfo.hThread);
::CloseHandle(pipe);
_endthread();
}

Cannot find the main window handle of the just stareted Dialog APP

Here is the scenario:
I have 2 apps. One of them is my main app, and the second is a dialog based app, which is started from the first one. I'm trying to capture the main handle of the dialog based app from my main app. The problem is that I cannot find it with EnumWindows. The problem disappears if I put sleep for a second, just before start enumerating windows.
This is the code:
...
BOOL res = ::CreateProcess( NULL, _T("MyApp.exe"), NULL, NULL, FALSE, NULL, NULL, NULL, &siStartInfo, &piProcInfo );
ASSERT(res);
dwErr = WaitForInputIdle(piProcInfo.hProcess, iTimeout);
ASSERT(dwErr == 0);
//Sleep(1000); //<-- uncomment this will fix the problem
DWORD dwProcessId = piProcInfo.dwProcessId;
EnumWindows(EnumWindowsProc, (LPARAM)&dwProcessId);
....
BOOL IsMainWindow(HWND handle)
{
return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle);
}
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
DWORD* pParam = (DWORD*)lParam;
DWORD dwTargetProcessId = *pParam;
DWORD dwProcessId = 0;
::GetWindowThreadProcessId(hwnd, &dwProcessId);
if (dwProcessId == dwTargetProcessId )
{
TCHAR buffer[MAXTEXT];
::SendMessage(hwnd, WM_GETTEXT, (WPARAM)MAXTEXT,(LPARAM)buffer);
if( IsMainWindow(hwnd))
{
g_hDlg = hwnd;
return FALSE;
}
}
return TRUE;
}
There are exactly 2 windows which belongs to my process and tracing their text shows:
GDI+ Window
Default IME
I'm not quite sure what does this mean. These might be the default captions, assigned to the windows, before their initialization.... but I call EnumWindows after WaitForInputIdle ...
Any help will be appreciated.
CreateProcess returns, when the OS has created the process object including the object representing the primary thread. This does not imply, that the process has started execution.
If you need to query another process for information that is only available after that process has run to a certain point, you will need to install some sort of synchronization. An obvious option is a named event object (see CreateEvent), that is signaled, when the second process has finished its initialization, and the dialog is up and running. The first process would then simply WaitForSingleProcess, and only continue, once the event is signaled. (A more robust solution would call WaitForMultipleObjects on both the event and the process handle, to respond to unexpected process termination.)
Another option would be to have the second process send a user-defined message (WM_APP+x) to the first process, passing its HWND along.
WaitForInputIdle sounds like a viable solution. Except, it isn't. WaitForInputIdle was introduced to meet the requirements of DDE, and merely checks, if a thread in the target process can receive messages. And that really means any thread in that process. It is not strictly tied to a GUI being up and running.
Additional information on the topic can be found here:
WaitForInputIdle should really be called WaitForProcessStartupComplete
WaitForInputIdle waits for any thread, which might not be the thread you care about

Does CancelSynchronousIo work with WNetAddConnection2?

I'm trying and failing to cancel a call to WNetAddConnection2 with CancelSynchronousIo.
The call to CancelSynchronousIo succeeds but nothing is actually cancelled.
I'm using a 32-bit console app running on Windows 7 x64.
Has anyone done this successfully? Am I doing something dumb? Here's a sample console app (which needs to be linked with mpr.lib):
DWORD WINAPI ConnectThread(LPVOID param)
{
NETRESOURCE nr;
memset(&nr, 0, sizeof(nr));
nr.dwType = RESOURCETYPE_ANY;
nr.lpRemoteName = L"\\\\8.8.8.8\\bog";
// result is ERROR_BAD_NETPATH (i.e. the call isn't cancelled)
DWORD result = WNetAddConnection2(&nr, L"pass", L"user", CONNECT_TEMPORARY);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
// Create a new thread to run WNetAddConnection2
HANDLE hThread = CreateThread(0, 0, ConnectThread, 0, 0, 0);
if (!hThread)
return 1;
// Retry the cancel until it fails; keep track of how often
int count = 0;
BOOL ok;
do
{
// Sleep to give the thread a chance to start
Sleep(1000);
ok = CancelSynchronousIo(hThread);
++count;
}
while (ok);
// count will equal two here (i.e. one successful cancellation and
// one failed cancellation)
// err is ERROR_NOT_FOUND (i.e. nothing to cancel) which makes
// sense for the second call
DWORD err = GetLastError();
// Wait for the thread to finish; this takes ages (i.e. the
// WNetAddConnection2 call is not cancelled)
WaitForSingleObject(hThread, INFINITE);
return 0;
}
According to Larry Osterman (I hope he doesn't mind me quoting him): "The question was answered in the comments: wnetaddconnection2 isn’t a simple IOCTL call." So the answer (unfortunately) is no.
First, WNetAddConnection2 is system-wide, not per-process. This is important, as calling WNetAddConnection2 many times can wreck system stability - particularly with explorer.
I use WNetGetResourceInformation first to check if the connection already exists before even thinking of calling it - my process may have previously run and then shutdown. The connection may still exist. When my Windows service(s) needs to add such a connection I use a nasty little trick in order to prevent these totally non-abortable API's from stalling my own service shutdown.
The trick is to run these calls in a separate process: they are system-wide, after all. You can normally wait for the process to complete as if you called the functions yourself but you can terminate the process and give up waiting if you need to abort in order to shutdown.
Sadly, however, certain Windows resources, such as named pipe handles and handles to files open on remote computers, can take about 16 seconds to close following failure or shutdown of a remote machine. CancelSynchronousIo does not seem to even help with those but will likely add additional long delay.

Resources