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.

About these ads

About Sheng Jiang 蒋晟

Microsoft MVP in Visual C , 2004- Forum moderator of the Visual C and .Net forums on CSDN Forum moderator of Chinese forums on Microsoft's MSDN forums
This entry was posted in enmsdn, Microsoft, Visual C++, Visual Studio and tagged , , , . Bookmark the permalink.

3 Responses to How to restart Windows Explorer programmatically using Restart manager

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

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

    • Jorma Rebane says:

      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.

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s