How to restart Windows Explorer programmatically using Restart manager

For shell extension programmers, restart Windows Explorer is one of steps in their setup programs. A programmer may also want to force some shell setting changes that would only be read by Explorer on start up. For example, this posts is inspired by a programmer who want to toggle task bar layout automatically depending on the screen resolution, and is used to demonstrate the new Windows Vista restart manager API like RmStartSession, RmRegisterResources, RmGetList, RmShutdown, RmRestart and RmEndSession.

Traditionally restarting explorer is a hackish process. Most of people kill the process by brutal force like taskkill or TerminateProcess, however this approach produces a pretty mess, and if HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\AutoRestartShell is on, Windows would restart explorer. This is not want the user want – not only because changes in the previous explorer session are only saved during a clean shutdown, but the restarting also needs to be delayed to allow some time to change the task bar layout. A grace way to terminate would be to send WM_QUIT, or use Vista’s new restart manager API.

Restart manager is introduced in Vista to minimize system restarts when installing patches. For example, you can now upgrade IE without a restart. Here it is merely used to restart a particular program, but it has more interesting usage, such as restarting an application after crash.

The first step of restarting explorer would be registering the main explorer process for restarting. RmRegisterResources can be used to restart a process in a session started by RmStartSession, however the machine could have extra explorer processes if the “launch folder in a separate process” setting is on, or if other users are logged in. Here the code loop through all explorer processes and try to find the oldest one for restarting purpose. This is assuming only one user is going to use the computer, an IsProcessInSession check is needed if multiple users would be logged on concurrently.

//returns the process id and create time for the oldest explorer.exe 
RM_UNIQUE_PROCESS GetExplorerApplication()
{
    RM_UNIQUE_PROCESS  result={0};
    DWORD bytesReturned=0;
    DWORD processIdSize=4096;
    std::vector<DWORD> processIds;
    processIds.resize(1024);
    EnumProcesses(processIds.data(),processIdSize,&bytesReturned);
    while(bytesReturned==processIdSize)
    {
        processIdSize+=processIdSize;
        processIds.resize(processIdSize/4);
        EnumProcesses(processIds.data(),processIdSize,&bytesReturned);
    }    std::for_each(processIds.begin(), processIds.end(), [&result] (DWORD processId) {
         HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,
                                   FALSE, processId);
         if (hProcess) {
            std::wstring imageName;
            imageName.resize(4096);
            if(GetProcessImageFileName (hProcess,(LPWSTR)imageName.data(),4096)>0)
            {
                if(wcscmp(L"explorer.exe",PathFindFileName(imageName.data()))==0)
                {
                    //this is assmuing the user is not running elevated and won't see explorer processes in other sessions
                    FILETIME ftCreate, ftExit, ftKernel, ftUser;
                    if (GetProcessTimes(hProcess, &ftCreate, &ftExit,&ftKernel, &ftUser))
                    {
                        if(result.dwProcessId==0)
                        {
                            result.dwProcessId=processId;
                            result.ProcessStartTime=ftCreate;
                        }
                        else if(CompareFileTime(&result.ProcessStartTime,&ftCreate)>0)
                        {
                            result.dwProcessId=processId;
                            result.ProcessStartTime=ftCreate;
                        }
                    }
                }
            }
            CloseHandle(hProcess);
         }
    });
    return result;
}

Next step would be telling the restart manager to request a shutdown via RmShutdown and restart via RmRestart after some registry tweak:

//taskbar position calculating code omitted
    DWORD dwSession=0;
    WCHAR szSessionKey[CCH_RM_SESSION_KEY+1] = { 0 };
    DWORD dwError = RmStartSession(&dwSession, 0, szSessionKey);
    if (dwError == ERROR_SUCCESS) {
        RM_UNIQUE_PROCESS rgApplications[1]={GetExplorerApplication()};
        dwError=RmRegisterResources(
            dwSession,0,NULL,1,rgApplications,0,NULL);
        DWORD dwReason;
        UINT nProcInfoNeeded;
        UINT nProcInfo = 10;
        RM_PROCESS_INFO rgpi[10];
        dwError = RmGetList(dwSession, &nProcInfoNeeded,
                       &nProcInfo, rgpi, &dwReason);
        if(dwReason==RmRebootReasonNone)//now free to restart explorer
        {
            RmShutdown(dwSession,RmForceShutdown,NULL);//important, if we change the registry before shutting down explorer will override our change
            //using undocumented setting structure, could break any time
            //edge setting is stored at HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StuckRects2!Settings
            HKEY hKey={0};
            DWORD result=0;
            result=::RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StuckRects2"),
                    0, KEY_READ|KEY_WRITE, &hKey) ;
            if (result== ERROR_SUCCESS)
            {
                std::vector<BYTE> data;
                data.resize(256);
                TCHAR settingValue[]= _T("Settings");
                DWORD dwKeyDataType=0;
                DWORD dwDataBufSize=data.size();
                result=::RegQueryValueEx(hKey,settingValue, NULL, &dwKeyDataType,
                    (LPBYTE) data.data(), &dwDataBufSize);
                while(ERROR_MORE_DATA==result)
                {
                    data.resize(256+data.size());
                    dwDataBufSize=data.size();
                    result=::RegQueryValueEx(hKey,settingValue, NULL, &dwKeyDataType, 
                        (LPBYTE) data.data(), &dwDataBufSize);
                }
                data.resize(dwDataBufSize);
                if(result==ERROR_SUCCESS)
                {
                    switch ( dwKeyDataType )
                    {
                        case REG_BINARY:
                            if(data.size()==40)
                            {
                                BYTE taskbarPosition=data[12];
                                taskbarPosition=edge;
                                data[12]=taskbarPosition;
                                RECT* taskbarRect=(RECT*)&data[24];
                                CopyRect (taskbarRect,&abd.rc);
                                result=::RegSetValueEx(hKey,settingValue,0,REG_BINARY,(LPBYTE) data.data(), dwDataBufSize);
                            }
                            break;
                    }
                }
                ::RegCloseKey( hKey );
            }
            RmRestart (dwSession,0,NULL);
        }
    }
    RmEndSession(dwSession);

Finally the RmEndSession function is called to free up resources. For other lockable resource like files the steps to restart affected processes are similar. This API can also be used to write restart manager custom actions if the installer authoring software does not support the restart manager.

Advertisement

7 responses to “How to restart Windows Explorer programmatically using Restart manager”

  1. That’s just messy. There must be a better api than that.

    1. Actually, there is a surprisingly simple method for reliably restarting windows explorer in C++:

      // your kill all processes function (usually with CreateToolhelp32Snapshot)
      MyKillProcessesFunc(“explorer.exe”);
      //
      system(“start explorer”);

      This has the benefit of being way faster than restart manager and also correctly starts explorer with file copying and icon overlays working correctly. ShellExecute is extremely buggy compared to start explorer.

      1. That breaks your build because the output DLL is in a leaked memory map, and won’t save settings that are saved during a normal shutdown. For the targeted problems of this post, this method is worse than rebooting the machine.

      2. But is there a way to somehow speed up the restart manager? I wouldn’t mind RM, if it could do its work with minimal delay (< 5 seconds). Right now it can take 60 seconds to restart explorer on a fresh installation of windows. That is simply unacceptable.

  2. A command line tool would be great.. This is a really common task for installing Windows extensions.

    1. Actually, the code can be simplified quite a bit, so its not as convoluted as it would seem. The only real difficulty here is acquiring the desired process ID and Creation time. Here’s an optimized (and simplified) version of the Restart Manager:

      RM_UNIQUE_PROCESS GetExplorerApplication()
      {
      RM_UNIQUE_PROCESS out = { 0, { -1, -1 } };
      DWORD bytesReturned;
      WCHAR imageName[MAX_PATH]; // process image name buffer
      DWORD processIds[2048]; // max 2048 processes (more than enough)

      // enumerate all running processes (usually around 60-70)
      EnumProcesses(processIds, sizeof(processIds), &bytesReturned);
      int count = bytesReturned / sizeof(DWORD); // number of processIds returned

      for (int i = 0; i < count; ++i)
      {
      DWORD processId = processIds[i];
      if (HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId))
      {
      GetProcessImageFileNameW(hProc, imageName, MAX_PATH);
      if (wcscmp(L"explorer.exe", PathFindFileNameW(imageName)) == 0)
      {
      FILETIME ftStart, ftExit, ftKernel, ftUser;
      GetProcessTimes(hProc, &ftStart, &ftExit, &ftKernel, &ftUser);

      if (ftStart.dwLowDateTime < out.ProcessStartTime.dwLowDateTime)
      {
      out.dwProcessId = processId;
      out.ProcessStartTime = ftStart;
      }
      }
      CloseHandle(hProc);
      }
      }
      return out; // return count in pResults
      }

      static DWORD RmSession = -1;
      static wchar_t RmSessionKey[CCH_RM_SESSION_KEY + 1];

      // shuts down the explorer and is ready for explorer restart
      void BeginExplorerRestart()
      {
      if (RmStartSession(&RmSession, 0, RmSessionKey) == ERROR_SUCCESS)
      {
      RM_UNIQUE_PROCESS rgApplications[] = { GetExplorerApplication() };
      RmRegisterResources(RmSession, 0, 0, 1, rgApplications, 0, 0);

      DWORD rebootReason;
      UINT nProcInfoNeeded, nProcInfo = 16;
      RM_PROCESS_INFO affectedApps[16];
      RmGetList(RmSession, &nProcInfoNeeded, &nProcInfo, affectedApps, &rebootReason);

      if (rebootReason == RmRebootReasonNone) // no need for reboot?
      {
      // shutdown explorer
      RmShutdown(RmSession, RmForceShutdown, 0);
      }
      }
      }
      // restarts the explorer
      void FinishExplorerRestart()
      {
      if (DWORD dwError = RmRestart(RmSession, 0, NULL))
      printf("\n RmRestart error: %d\n\n", dwError);

      RmEndSession(RmSession);
      RmSession = -1;
      RmSessionKey[0] = L'';
      }

      The restart manager cannot restore currently opened File Explorer windows, so you'd have to do that manually.

  3. 你好,我在安装更新shell namespace extension时遇到需要重启explorer。使用了你的代码测试重启explorer,发现了一些问题。重启后打开资源管理器,发现左边的tree view与顶部的功能区不见了。但是我手动在任务管理器里面杀掉explorer,再创建explorer新任务,windows资源管理器左边的tree view与顶部的功能区又正常显示了。不知道原因是什么。操作系统为windows10家庭中文版,版本号1803,操作系统版本17134.885.Visual studio 版本为2019社区版。下面是我的测试代码:

    // RestartExplorer.cpp : 此文件包含 “main” 函数。程序执行将在此处开始并结束。
    //

    #include

    #include
    #include
    #include
    #include
    #include
    #include

    RM_UNIQUE_PROCESS GetExplorerApplication()
    {
    RM_UNIQUE_PROCESS out = { 0, { -1, -1 } };
    DWORD bytesReturned;
    WCHAR imageName[MAX_PATH]; // process image name buffer
    DWORD processIds[2048]; // max 2048 processes (more than enough)
    // enumerate all running processes (usually around 60-70)
    EnumProcesses(processIds, sizeof(processIds), &bytesReturned);
    int count = bytesReturned / sizeof(DWORD); // number of processIds returned
    for (int i = 0; i < count; ++i)
    {
    DWORD processId = processIds[i];
    if (HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId))
    {
    GetProcessImageFileNameW(hProc, imageName, MAX_PATH);
    if (wcscmp(L"explorer.exe", PathFindFileNameW(imageName)) == 0)
    {
    FILETIME ftStart, ftExit, ftKernel, ftUser;
    GetProcessTimes(hProc, &ftStart, &ftExit, &ftKernel, &ftUser);
    if (ftStart.dwLowDateTime < out.ProcessStartTime.dwLowDateTime)
    {
    out.dwProcessId = processId;
    out.ProcessStartTime = ftStart;
    }
    }
    CloseHandle(hProc);
    }
    }
    return out; // return count in pResults
    }
    static DWORD RmSession = -1;
    static wchar_t RmSessionKey[CCH_RM_SESSION_KEY + 1];
    // shuts down the explorer and is ready for explorer restart
    void BeginExplorerRestart()
    {
    if (RmStartSession(&RmSession, 0, RmSessionKey) == ERROR_SUCCESS)
    {
    RM_UNIQUE_PROCESS rgApplications[] = { GetExplorerApplication() };
    RmRegisterResources(RmSession, 0, 0, 1, rgApplications, 0, 0);
    DWORD rebootReason;
    UINT nProcInfoNeeded, nProcInfo = 16;
    RM_PROCESS_INFO affectedApps[16];
    RmGetList(RmSession, &nProcInfoNeeded, &nProcInfo, affectedApps, &rebootReason);
    if (rebootReason == RmRebootReasonNone) // no need for reboot?
    {
    // shutdown explorer
    RmShutdown(RmSession, RmForceShutdown, 0);
    }
    }
    }
    // restarts the explorer
    void FinishExplorerRestart()
    {
    if (DWORD dwError = RmRestart(RmSession, 0, NULL))
    printf("\n RmRestart error: %d\n\n", dwError);
    RmEndSession(RmSession);
    RmSession = -1;
    RmSessionKey[0] = L' ';
    }

    int main()
    {
    BeginExplorerRestart();
    FinishExplorerRestart();
    return 0;
    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.