How to shutdown explorer.exe gracefully from C++

August 01, 2009

This blog entry is just about a number of hacks I use in a setup that requires explorer.exe to be restarted.

I don't want to start a discussion whether this is good practice or not.

Rebooting was not an option.

The following code has been tested on Vista and Windows 7 with elevated User Access Control, UAC.

Collection of ideas

The web does contain some fragments, and I collected some ideas. There are articles on stackoverflow.com which get rather philosophical and others that provide source code that simply did not work but got me starting.

Operation modes of explorer.exe

Basically, explorer works in two different configurations:

Both configurations need to be considered.

Shut Down Mechanisms

This section will discuss some of the mechnisms to shut down applications.

Restart Manager

There is a meachnism to restart applications built in the Microsoft Installer. When uninstalling a shell extension, for example, MSI displays a dialog and asks the user to restart explorer.exe. The mechanism is provided by the Restart Manager API.

Sadly, it seems not always to work. explorer.exe might be shut down but not restarted. Some people have reported similar problems. Additionally, the API looks cryptic and requires at least Windows Vista, but my setup was targeting XP also.

TerminateProcess()

Some people suggested that TerminateProcess() would just be an option. Life could be simple, after termination, moreover, explorer.exe automatically restarts itsself. But TerminateProcess() would instantly terminate any pending IO, potentially bringing your system and data in an unstable state. It is never a good practice to call TerminateProcess() except when there is no other option.

WM_QUIT

Posting WM_QUIT would shut down the application gracefully. The main message loop of any Windows application should react on WM_QUIT.

There are two methods to send a message to another process:

a) Find all windows of that process and post a WM_QUIT to them. b) Use the Toolhelp API to find out about the first thread of the process and post WM_QUIT directly using PostThreadMessage().

I felt that a) is more complex but more stable. An application should behave properly when messages are sent to windows, but a message that is sent to a thread queue felt too intrusive.

Step by step

Step 1, Find all explorer.exe windows

To find all explorer.exe windows, we snap all processes using the toolhelp API:

HANDLE snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

then get all process ids:

vector<DWORD> queryProcessIds(HANDLE snapshot)
{
    vector<DWORD> r;

    PROCESSENTRY32 entry;
    zero(entry);
    entry.dwSize = sizeof(entry);

    if (!::Process32First(snapshot, &entry))
        return r;

    r.push_back(entry.th32ProcessID);

    while (true)
    {
        PROCESSENTRY32 entry;
        zero(entry);
        entry.dwSize = sizeof(entry);

        if (!::Process32Next(snapshot, &entry))
            return r;

        r.push_back(entry.th32ProcessID);
    }
}

To filter out explorer.exe windows, I assumed that explorer.exe is always started from the Windows directory. So we go for the first module in each process and ask where it was loaded from.

vector<DWORD> filterExplorerProcessIds(const vector<DWORD>& ids)
{
    vector<DWORD> r;

    TCHAR winDir[MAX_PATH];
    if (!::GetWindowsDirectory(winDir, MAX_PATH))
        return r;

    wstring winD(winDir);
    winD = winD + L"\\explorer.exe";

    for(vector<DWORD>::const_iterator it = ids.begin(); it != ids.end(); ++it)
    {
        if (isExplorerProcess(winD, *it))
            r.push_back(*it);
    }

    return r;
}

bool isExplorerProcess(const wstring& explorerPath, DWORD processId)
{
    HANDLE snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processId);
    if (snapshot == INVALID_HANDLE_VALUE)
        return false;

    bool r = isExplorerProcess(explorerPath, snapshot);

    ::CloseHandle(snapshot);
    return r;
}

bool isExplorerProcess(const wstring& explorerPath, HANDLE moduleSnapshot)
{
    MODULEENTRY32 entry;
    zero(entry);
    entry.dwSize = sizeof(entry);

    if (!::Module32First(moduleSnapshot, &entry))
        return false;

    return 0 == _wcsicmp(entry.szExePath, explorerPath.c_str());
}

Step 2: Send messages to the windows

Having the process ids of all the explorer.exe instances at hand, we can try to send them messages.

After some experimentation, I learned that the two instances behave differently:

A WM_ENDSESSION closes the folder windows. The following code does both: it initiates the taskbar shutdown and the closes all folder windows:

void tryClose(DWORD dwPID)
{
    // TerminateAppEnum() posts WM_QUIT to all windows whose PID
    // matches your process's.
    ::EnumWindows((WNDENUMPROC)TerminateAppEnum, (LPARAM) dwPID);
}

BOOL CALLBACK TerminateAppEnum( HWND hwnd, LPARAM lParam )
{
    DWORD dwID;

    GetWindowThreadProcessId(hwnd, &dwID);

    if(dwID == (DWORD)lParam)
    {
        // Win7: Separate Explorer Windows do close their windows on
        // WM_ENDSESSION (independent of lparam), but the process 
        // does not shutdown :(
        ::PostMessage(hwnd, WM_ENDSESSION, NULL, ENDSESSION_CLOSEAPP);

        // Win7: Explorer itsself ends gracefully on a WM_QUIT
        ::PostMessage(hwnd, WM_QUIT, 0, 0);
    }

    return true;
}

To summarize: We now have shut down the explorer task bar process and closed all folder windows. But the explorer.exe process that previously managed the folder windows is not gone yet.

Step 3: end folder windows' explorer.exe

I learned by experimentation that the remaining explorer.exe process finally terminates on a WM_QUIT after the windows are closed. Sending a WM_QUIT, as long there are open folder windows, simply gets ignored.

To send out a WM_QUIT directly to a process without an associated window handle, we have to send the message to the first thread.

bool postWMQuitToProcess(DWORD processId)
{
    HANDLE snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, processId);
    if (snapshot == INVALID_HANDLE_VALUE)
        return false;

    bool r = postWMQuitToProcess(processId, snapshot);

    ::CloseHandle(snapshot);
    return r;
}

bool postWMQuitToProcess(DWORD processId, HANDLE snapshot)
{
    bool r = false;

    THREADENTRY32 entry;
    zero(entry);
    entry.dwSize = sizeof(entry);

    if (!::Thread32First(snapshot, &entry))
        return false;
    if (entry.th32OwnerProcessID == processId && ::PostThreadMessage(entry.th32ThreadID, WM_QUIT, 0, 0))
        return true;

    zero(entry);
    entry.dwSize = sizeof(entry);

    while (::Thread32Next(snapshot, &entry))
    {
        if (entry.th32OwnerProcessID != processId)
            continue;

        if (::PostThreadMessage(entry.th32ThreadID, WM_QUIT, 0, 0))
            return true;
    }

    return false;
}

Step 4: final termination

And finally, if something does not work out, we may terminate remaining explorer.exe processes.

At first we need to find out when the processes are terminated:

To wait for a termination of a process, we need to get a HANDLE by using OpenProcess(). Then there are two different methods to test if the process is alive:

.

bool waitForTermination( DWORD dwPID, DWORD dwTimeout )
{
    // If we can't open the process with the proper rights,
    // then we give up immediately.
    HANDLE hProc = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPID);
    if(!hProc)
        return true; // we assume, it is already terminated.

    int pollDelay = 100;

    for(int rounds = (dwTimeout / pollDelay); true; --rounds)
    {
        DWORD exitCode;
        if (!::GetExitCodeProcess(hProc, &exitCode))
            break;

        if (exitCode != STILL_ACTIVE)
        {
            ::CloseHandle(hProc);
            return true;
        }

        if (!rounds)
            break;

        ::Sleep(pollDelay);
    };

    ::CloseHandle(hProc);

    return false;
}

When a process has not been terminated by other means after a while, we hope that it is safe to finally kill it the ungentle way:

bool terminateProcess(DWORD dwPID)
{
    HANDLE hProc = ::OpenProcess(PROCESS_TERMINATE, FALSE, dwPID);
    if(!hProc)
        return true; // we assume, it is already termianted.

    bool ret = ::TerminateProcess(hProc,0) ? true: false;
    ::CloseHandle(hProc);
    return ret;
}

Gluing it all together

The final shutdownExplorer() function first sends messages to the explorer.exe windows, then waits 10 seconds for their termination while sending WM_QUIT to their main processes. Processes that are alive another 10 seconds later, get terminated.

bool shutdownExplorer()
{
    HANDLE snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snapshot == INVALID_HANDLE_VALUE)
        return false;

    vector<DWORD> pids = queryProcessIds(snapshot);
    pids = filterExplorerProcessIds(pids);

    ::CloseHandle(snapshot);

    for (vector<DWORD>::const_iterator it = pids.begin(); it != pids.end(); ++it)
        tryClose(*it);

    // win7: explorer windows (when started in separate process) do 
    // not terminate after their windows are closed, but they will accept a WM_QUIT after a while

    // first round (10 seconds)

    for (int i = 0; i != 20; ++i)
        for (vector<DWORD>::const_iterator it = pids.begin(); it != pids.end(); ++it)
            if (!waitForTermination(*it, 500))
            {
                // Log::W("Process active after closing and sending WM_QUIT to its windows (500 milliseconds), sending WM_QUIT to first thread");
                postWMQuitToProcess(*it);
            }

    // second round (10 seconds) for waiting.

    for (vector<DWORD>::const_iterator it = pids.begin(); it != pids.end(); ++it)
        if (!waitForTermination(*it, 10000))
        {
            // Log::W("Process active after sending WM_QUIT to first thread (15 seconds), terminating");
            terminateProcess(*it);
        }

    return true;
}

Conclusion and outlook

Admittedly, this a lot of code to shut down explorer.exe, but when it comes to usability, saving one restart at each installation is worth the effort.

The code above is not thoroughly tested yet and probably needs to be reviewed and modified for the final setup. I will post updates here if there are any significant changes.

Feel free to use the code above, and when you find out some simpler or safer ways to restart the Windows Explorer, I am happy to update this blog entry.

armin

explorer.exe, I own you