Link to home
Start Free TrialLog in
Avatar of douggreen
douggreen

asked on

Shutdown/Restart Explorer Programatically on WinXP and do NOT rerun startup group

I am attempting to restart the Windows Shell Explorer programmatically.  Microsoft’s answer in Q137572 was written for Windows 95 appears incomplete for WinXP.  The knowledge base article says to send a WM_QUIT message to progman then restart explorer.exe.

If I expand this to both progman and Shell_TrayWnd (https://www.experts-exchange.com/questions/10507038/stop-explorer-cleanly.html), I get pretty close.  The one problem is that when I restart explorer.exe, programs in the startup menu always re-run.  I refer to this as my-shutdown.

If I shutdown explorer the safe interactive way (by selecting “Start/Turn Off Computer” and holding down CTRL-ALT-DEL simultaneously while pressing “Cancel”) it shuts down in what looks almost identical to my-shutdown, however when I re-start explorer now, the start menu items do not re-run themselves.  I refer to this as the clean-shutdown.

I used Spy to snoop the messages for these two windows.  Shell_TrayWnd actually received a WM_CLOSE just before the WM_QUIT, but adding this didn’t solve anything.  The Expert Exchange article actually says to use an lParam of 1 on the progman quit, but this is a mistake.  WM_QUIT only takes a wParam, and Spy shows that it is not sent on the clean-shutdown.

I used ntregmon from SysInternals to snoop the registry.  There are several differences, but I couldn’t detect anything specific to use.

To avoid the obvious question, why am I doing this, let me answer.  I am attempting to auto-install a software update over the web, and I can’t install the new DLL because the DLL provides some COM services to the shell, in this case a context-menu-handler.  At this point in the process, the silent auto-update has failed, and the user has selected that they want to install interactively and restart the shell.  I believe that this is a better solution than forcing a reboot.

Here is what I have.  I have a handle to the explorer.exe process before calling this method, so there is no need to find it.

BOOL CShell32::ShellRestart(HANDLE hProcess)
{
        if (hProcess)
        {
               // get the window handles of the program manager and system tray
               HWND hwndProgman = FindWindow("Progman", NULL);
               HWND hwndShell = FindWindow("Shell_TrayWnd", NULL);
               if (hwndProgman && hwndShell)
               {
                       // TODO: there is something else that must be done
                       // to keep explorer from running the startup group
                       // when it restarts, but I don't know what it is!!!
                       //
                       // Is it a registry setting?
 
                       // request the shell to stop
                       PostMessage(hwndProgman, WM_QUIT, 0, 0);
                       PostMessage(hwndShell, WM_QUIT, 0, 0);
 
                       // wait for these windows to shutdown
                       DWORD dwNow = GetTickCount();
                       while (TRUE)
                       {
                              _Module.YieldMessageLoop();
 
                              // wait until the system tray is running and initialized
                              if (!IsProcess(hProcess))
                              {
                                              // restart explorer
                                      STARTUPINFO si;
                                      PROCESS_INFORMATION pi;
                                      ZeroMemory(&si, sizeof si);
                                      si.cb = sizeof si;
                                      ZeroMemory(&pi, sizeof pi);
                                      if (CreateProcess(NULL, "Explorer.EXE", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
                                      {
                                             CloseHandle(pi.hProcess);
                                             return TRUE;
                                      }
                                      break;
                              }
 
                              // wait at most 30 seconds
                              if (GetTickCount() - dwNow > 30000)
                                      break;
                       }
               }
        }
        return FALSE;
}
 
BOOL CShell32::IsProcess(HANDLE hProcess) const
{
        DWORD dwExitCode;
        if (GetExitCodeProcess(hProcess, &dwExitCode) && dwExitCode == STILL_ACTIVE)
               return TRUE;
        return FALSE;
}
Avatar of _nn_
_nn_

Small investigation : I found this key in my W2K registry

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\000000000000b3a6\StartupHasBeenRun

1. I tried your code : at the moment explorer.exe dies, the key disappears. Soon after the new process is created, the key is recreated (and yes, startup is rerun)

2. I grossly killed explorer.exe with the taskmanager : the key doesn't disappear. Starting a new instance from the task manager does not trigger startup

So, I think that if you recreate that key between killing explorer.exe and starting a new one, you should be fine. Warning : this key is a volatile one, just pay attention when using RegCreateKey(Ex). I trust you can deal with it, but in case you would experience a problem, just drop a note :)
Avatar of douggreen

ASKER

Great try!  This seems to be the key, but I can't rewrite it.  Independent from everything else done above, just use regedit and try to create a key, any key, under SessionInfo or under 000000000000b3a6.  I always get an error.  

When I try to do it programatically, the only time I don't get an error, is if the key already exists.  Even if I try writing the key before I've shutdown explorer, I get the error.

BOOL CShell32::GetSessionInfoKey(CString& sKey)
{
      HKEY hKey;
      if (RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SessionInfo", 0, KEY_READ, &hKey) == ERROR_SUCCESS)
      {
            char szKey[64];
            DWORD dwSize = sizeof szKey;
            if (RegEnumKeyEx(hKey, 0, szKey, &dwSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
                  sKey = szKey;
            RegCloseKey(hKey);
      }
      return sKey.IsEmpty() ? FALSE : TRUE;
}

BOOL CShell32::RewriteStartupHasBeenRunKey(LPCSTR lpszKey)
{
      BOOL bStatus = FALSE;
      HKEY hKey;
      if (RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SessionInfo", 0, KEY_WRITE, &hKey) == ERROR_SUCCESS)
      {
            HKEY hkeySession;
            if (RegCreateKeyEx(hKey, lpszKey, 0, NULL, 0, KEY_WRITE, NULL, &hkeySession, NULL) == ERROR_SUCCESS)
            {
                  HKEY hkeyStartupHasBeenRun;
                  if (RegCreateKeyEx(hkeySession, "StartupHasBeenRun", 0, NULL, 0, KEY_WRITE, NULL, &hkeyStartupHasBeenRun, NULL) == ERROR_SUCCESS)
                  {
                        bStatus = TRUE;
                        RegCloseKey(hkeyStartupHasBeenRun);
                  }
                  RegCloseKey(hkeySession);
            }
            RegCloseKey(hKey);
      }
      return bStatus;
}
ASKER CERTIFIED SOLUTION
Avatar of _nn_
_nn_

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Excellent!  I'm the lazy one.  I didn't catch your warning about volatile keys because I didn't understand them until now.

For any future readers, the trick is that this key gets removed by the OS soon AFTER explorer exits, because explorer is the creator of the volatile key.  So the trick is to not only wait for the explorer process to end, but to also wait for the volatile SessionInfo key to dissappear, before resetting the key.  Also, wait to exit your process (which is now the volatile key creator) until after a new Progman and Shell_SysTray are created.  Otherwise, the key that we just set, could disappear before explorer has a chance to read it.

It works in my code.  Did I get the explanation right?
Wow.... I wasn't suspecting these caveats. Congrats ! great job, really.
And thanks for the points, glad I could help :)
One more question disguised as two questions... How did you initially determine that this key was disappearing and how did you know that it is a volatile key?  I'd like to learn how I could of self-diagnosed this for the future.  I downloaded and used SysInternals ntregmon a few days ago, which looks like a good program.  Is this what you used, or did you use another program?
ntregmon is indeed an excellent tool, which helped me a number of times in the past. A problem though : it's still difficult,  even using the built-in filters, to evaluate the output (which is often huge) when you don't know exactly what you're looking for. I would certainly have tried it, but it happened something before : luck ! ;o)

Actually, I rapidly looked into the explorer subkeys and found this one almost per inadvertance. Obviously, I thought "with such a name..." ;) So, I went to experiment a bit.

The procedure was then fairly straight forward :
- start the task manager and both regedit and regedt32
  (I like the second one for managing security related stuff)
- adapt, compile your code in msdev and run it step by step under debugger

I paused the program after the WM_QUIT messages were sent. The task bar disappeared, so I knew explorer.exe had died. With alt-tab quick switch to regedit and refresh the view : oh, the key disappeared ;) return to msdev, let run, explorer starts, my usual bunch of startup sh*t as well (I should really think about cleaning up btw), switch back to the reg editor, oh, the key has reappeared.

Then, same experience without running your code, but just by "fragging" the explorer process and starting a new one with the task manager, resulting in noticeable differences : reg key kept untouched and no startup programs.

Ok, I thought "let's see what happens if I create the key myself". Of course, I stumbled on the same errors as you did. I believe it was regedt32 who told me that it couldn't create a "normal" static key on top of a volatile one. I had heard of these, but it was the first time I had to deal with it. Check the MSDN docs, looks not too tough.

Then, (sorry again) my natural laziness got me : your post was clearly hinting at you're being knowledgable (obviously over average), and I checked your profile which confirmed it. So instead of coding/testing it myself, I decided to provide you with the informations I had collected and go drink a beer ;p


cheers

PS: Sorry for the shortcuts, the syntactic and grammatic mistakes, english isn't my primary tongue, but I'm sure you've already noticed ;)