How do you gracefully close Explorer programmatically?
By that I mean, how do you invoke this function programmatically:
Edit: Typo in the picture, it should say "Ctrl-Shift-Right-Click" instead of "Shift-Click".
I debugged this out of curiosity. All it does is post a message to one of explorer's windows:
BOOL ExitExplorer() { HWND hWndTray = FindWindow(_T("Shell_TrayWnd"), NULL); return PostMessage(hWndTray, 0x5B4, 0, 0); }
Of course this is an undocumented WM_USER message so the behavior could quite possibly change in the future.
@Luke: first of all, thanks for the detailed analysis and the hint about the 0x5B4 user message to the Shell_TrayWnd!
Unfortunately, the method has two drawbacks; First, it uses an undocumented user message, which may change in future Windows versions, and second, it does not work under Windows XP, since the 'magic procedure' to exit windows is different (open the shutdown dialog, then cancel it pressing SHIFT-CTRL-ALT-ESC) and no message posting is involved there.
It would be nice to have a reliable and portable way to terminate explorer cleanly from another process regardless of the windows version. So I continued debugging into the disassembly of the code which terminates explorer cleanly in order to find a hint about how I could achieve this. I still don't have the perfect solution but I made some interesting observations (on Windows 7 and Windows XP) which I want to share with whoever might be interested:
Windows 7
The 0x5B4-message is eventually handled by the method CTray::_DoExitExplorer. If you have symbol server enabled, then you can set a breakpoint in
{,,explorer.exe}CTray::_DoExitExplorer
(visual studio syntax)
resp.
explorer!CTray::_DoExitExplorer
(windbg syntax)
Windows XP
In WinXP, you have to set your breakpoint at
{,,explorer.exe}CTray::_ExitExplorerCleanly
(visual studio syntax)
resp.
explorer!CTray::_ExitExplorer
(windbg syntax)
before you enter the 'magic keystrokes' (SHIFT-CTRL-ALT-ESC) at the shutdown dialog. Both methods are very similar, as you can see from the disassembly (see followup post). The pseudo code is
if (bUnnamedVariable == FALSE) { g_fFakeShutdown = TRUE; // (1) PostMessage(hWndProgMan, WM_QUIT, 0, TRUE); // (2) if (PostMessage(hWndTray, WM_QUIT, 0, 0)) { // (3) bUnnamedVariable = TRUE; } }
Note that the first PostMessage() call passes TRUE as lParam, which is officially unused by WM_QUIT. The meaning of the lParam seems to be bShutdown == TRUE.
Of course it is impossible (or not feasible) to set g_fFakeShutdown from another application. So I tested different combinations of PostMessage(hWndProgMan, WM_QUIT, 0, TRUE/FALSE) followed or not by PostMessage(hWndTray, WM_QUIT, 0, FALSE). It seems that explorer shows different behaviour under Windows XP and Windows 7.
The following two methods seem to be good candidates to terminate explorer under windows XP. Unfortunately they don't work under Windows 7:
BOOL ExitExplorer1() { HWND hWndProgMan = FindWindow(_T("Progman"), NULL); PostMessage(hWndProgMan, WM_QUIT, 0, TRUE); // <= lParam == TRUE ! HWND hWndTray = FindWindow(_T("Shell_TrayWnd"), NULL); PostMessage(hWndTray, WM_QUIT, 0, 0); return TRUE; } BOOL ExitExplorer2() { HWND hWndProgMan = FindWindow(_T("Progman"), NULL); PostMessage(hWndProgMan, WM_QUIT, 0, FALSE); // <= lParam == FALSE ! return TRUE; }
Behaviour in Windows XP
In both cases the shell (explorer.exe) terminates and before terminating it sets the registry key
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\CleanShutdown = TRUE
as can be observed using Sysinternals Process Monitor, or by setting a breakpoint at {,,explorer}_WriteCleanShutdown@4 (resp. explorer!_WriteCleanShutdown).
Behaviour in Windows 7
Both methods don't work: although it appears that the shell terminated, the explorer.exe process is still running.
Remark
If I only post a WM_QUIT to hWndProgMan with lParam = TRUE without posting a message to hWndTray, i.e.,
BOOL ExitExplorer3() { HWND hWndProgMan = FindWindow(_T("Progman"), NULL); PostMessage(hWndProgMan, WM_QUIT, 0, TRUE); return TRUE; }
then I get an interesting behaviour (both Win7 and WinXP): The shutdown dialog appears. If you cancel it, everything appears to be normal, but after two or three (!) seconds, explorer terminates.
Conclusion
Maybe the best solution is to use ExitExplorer() with the undocumented WM_USER function for Windows 7 and either ExitExplorer1() or ExitExplorer2() for Windows XP. Does any one of the two XP-methods have advantages over the other? I don't know.
Appendix
Disassembly of CTray::_DoExitExplorer (Windows 7) and CTray::_ExitExplorerCleanly (Windows XP)
Windows 7
{,,explorer.exe}CTray::_DoExitExplorer: explorer!CTray::_DoExitExplorer: 00fdde24 833df027020100 cmp dword ptr [explorer!g_fInSizeMove+0x4 (010227f0)],0 ds:0023:010227f0=00000000 00fdde2b 53 push ebx 00fdde2c 8bd9 mov ebx,ecx 00fdde2e 7535 jne explorer!CTray::_DoExitExplorer+0x41 (00fdde65) 00fdde30 56 push esi 00fdde31 8b35ec14f700 mov esi,dword ptr [explorer!_imp__PostMessageW (00f714ec)] 00fdde37 57 push edi 00fdde38 33ff xor edi,edi 00fdde3a 47 inc edi 00fdde3b 57 push edi 00fdde3c 6a00 push 0 00fdde3e 6a12 push 12h 00fdde40 ff35e8000201 push dword ptr [explorer!v_hwndDesktop (010200e8)] 00fdde46 893ddc270201 mov dword ptr [explorer!g_fFakeShutdown (010227dc)],edi 00fdde4c ffd6 call esi 00fdde4e 6a00 push 0 00fdde50 6a00 push 0 00fdde52 6a12 push 12h 00fdde54 ff7304 push dword ptr [ebx+4] 00fdde57 ffd6 call esi 00fdde59 85c0 test eax,eax 00fdde5b 7406 je explorer!CTray::_DoExitExplorer+0x3f (00fdde63) 00fdde5d 893df0270201 mov dword ptr [explorer!g_fInSizeMove+0x4 (010227f0)],edi 00fdde63 5f pop edi 00fdde64 5e pop esi 00fdde65 a1f0270201 mov eax,dword ptr [explorer!g_fInSizeMove+0x4 (010227f0)] 00fdde6a 5b pop ebx 00fdde6b c3 ret
('bUnnamedVariable' is a module global variable at address g_fInSizeMove+4)
Windows XP
{,,explorer.exe}CTray::_ExitExplorerCleanly: 01031973 8B FF mov edi,edi 01031975 57 push edi 01031976 8B F9 mov edi,ecx 01031978 83 BF 40 04 00 00 00 cmp dword ptr [edi+440h],0 0103197F 75 35 jne CTray::_ExitExplorerCleanly+43h (10319B6h) 01031981 53 push ebx 01031982 56 push esi 01031983 8B 35 94 17 00 01 mov esi,dword ptr [__imp__PostMessageW@16 (1001794h)] 01031989 33 DB xor ebx,ebx 0103198B 43 inc ebx 0103198C 53 push ebx 0103198D 6A 00 push 0 0103198F 6A 12 push 12h 01031991 FF 35 8C 60 04 01 push dword ptr [_v_hwndDesktop (104608Ch)] 01031997 89 1D 48 77 04 01 mov dword ptr [_g_fFakeShutdown (1047748h)],ebx 0103199D FF D6 call esi 0103199F 6A 00 push 0 010319A1 6A 00 push 0 010319A3 6A 12 push 12h 010319A5 FF 77 04 push dword ptr [edi+4] 010319A8 FF D6 call esi 010319AA 85 C0 test eax,eax 010319AC 74 06 je CTray::_ExitExplorerCleanly+41h (10319B4h) 010319AE 89 9F 40 04 00 00 mov dword ptr [edi+440h],ebx 010319B4 5E pop esi 010319B5 5B pop ebx 010319B6 8B 87 40 04 00 00 mov eax,dword ptr [edi+440h] 010319BC 5F pop edi 010319BD C3 ret
('bUnnamedVariable' seems to be a member of CTray at relative offset 440h)
Remark It seems that WM_QUIT is used here in a very non-standard way, compare the following excerpt from MSDN WM_QUIT on MSDN
This message does not have a return value because it causes the message loop to terminate before the message is sent to the application's window procedure.
Remarks The WM_QUIT message is not associated with a window and therefore will never be received through a window's window procedure. It is retrieved only by the GetMessage or PeekMessage functions.
Do not post the WM_QUIT message using the PostMessage function; use PostQuitMessage.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With