Gracefully Exit Explorer (Programmatically)
Asked Answered
H

4

37

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".

Halfassed answered 16/4, 2011 at 22:1 Comment(15)
Why do you want to do this? Are you uninstalling shell extensions? If so, remember that shell extensions can be loaded into any application...Ribal
@Anders: Because sometimes Explorer gets buggy and I need to close it, modify some files, and reopen it.Halfassed
Out of curiosity, what happens when you choose "Exit Explorer"? Does explorer totally go away (including the start menu, taskbar, etc? You could probably attach windbg to explorer.exe and put a breakpoint on TrackPopupMenu(Ex) and work your way back to see what the code actually does.Reconstitute
@Luke: Yes, Explorer completely goes away. (Try it! Just hold Ctrl-Shift and right-click on the start menu.) Oooooh good idea about TrackPopupMenu, I'll try it, thanks! :)Halfassed
@Luke: I tried debugging Explorer, but everything froze. :(Halfassed
@Downvoter: I'd love to know why you downvoted...Halfassed
This shift-clicking doesn't work in Windows 7, does it?Delwyn
@Andreas: It's Ctrl-Shift, not Shift. :) And yeah, it works on Windows 7, as my screenshot shows. :PHalfassed
@Mehrdad: Your taskbar is atypical; it looks like this (more or less) in Windows Vista, though. But the start menu is from Windows 7... Oh, yes, it isn't Shift+Click but Ctrl+Shift+Right click. Subtle difference... ;) Yes, it works!Delwyn
@Andreas: I just went to the taskbar's Properties and said Use Small Icons, since the normal taskbar is humongous and wastes a lot of screen space. :P But yeah, it looks like Vista too I guess.Halfassed
@Mehrdad: And you don't hide the labels...Delwyn
@Andreas: Oh yeah, that too. I don't see what the point of it is... if I have more than one window open, why should I go through a couple extra clicks to figure out which one I need? :\Halfassed
I just do pskill explorer when I need to do this!Logarithmic
@David: The whole point of the question was kinda to avoid that... :PHalfassed
NB: for Windows 10, ctrl+shift+right-click the taskbarParcel
R
56

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.

Reconstitute answered 18/4, 2011 at 16:31 Comment(3)
This answer needs lots of upvotes! Thanks a lot for taking the time to find this! :)Halfassed
@Luke: thanks for the detailed analysis and the hint about the 0x5B4 user message to the Shell_TrayWnd! I posted a related answer that might interest you. (don't know if you already noticed. I previously did not have the privilege to comment everywhere)Linnealinnean
NOTE1: This works perfectly on Windows 7. But on Windows 10 Microsoft implemented a delay of 3 seconds. This means that you send this message and all Explorer windows are hidden immediately but Explorer.exe keeps on running invisibly in the background. You can call IsWindow(hWndTray) in a loop to wait until Explorer has finally exited. NOTE2: If you want to restart Explorer later, you cannot use ShellExecute() which would open a new Explorer without Taskbar. To restart Explorer correctly you must use CreateProcess() instead.Alloy
L
33

@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.

Linnealinnean answered 5/6, 2011 at 22:3 Comment(8)
+1 this is a very good post, thanks for sharing it. I wish I could upvote it more, haha.. :)Halfassed
The original Windows 7 disassembly taken from a Windows x64 installation. I replaced it by a disassembly from Windows 7 32 bit. Now the similarity between Windows 7 and Windows XP is more obvious.Linnealinnean
@Mehrdad: It seems that WM_QUIT is used here in a very non-standard way, compare the following excerpt from msdn link:Linnealinnean
@Mehrdad: Sorry, my last comment was incomplete. I hit the return key prematurely. (It appears that I'm too stupid to use this forum correctly;-) Instead, I added a Remark about WM_QUIT at the end of my original Answer.Linnealinnean
msp, please do you have any idea how to do this using windows batch file? I need to turn off explorer before other program is started and I'm tired of doing this manually.Compelling
tomas: the best solution would probably be to write a small C Program which you can call from your batch file. Read the post by alex about the RestartManager API, that sounds promising.Linnealinnean
I've been trying to get this to work in XP to no avail. When you go through the ctrl+alt+shift+Cancel route, it Magically Works. When you post WM_QUIT as per the above, it works, but when Explorer is started up again, it proceeds to execute everything in Startup/Run! ctrl+alt+shift+Cancel sets some sort of magic flag somewhere to indicate that the session isn't being terminated, while WM_QUIT tricks it into believing that the session terminated. I can reproduce this over and over, yet Process Monitor doesn't give any clear indication of what this flag might be and where it might be. Any ideas?Alarise
On Windows 2000 ExitExplorer1 causes the session to log off. ExitExplorer2 works OK-ish but an error message will pop up if you start Explorer again.Ribal
G
11

On Windows Vista and above, you can use RestartManager API to gracefully shutdown explorer.

In pseudocode it will look like this:

RmStartSession(...); 
RM_UNIQUE_PROCESS[] processes = GetProcesses("explorer.exe"); // get special handles to process you want to close    
RmRegisterResources(processes); // register those processes with restart manager session    
RmShutdown(RM_SHUTDOWN_TYPE.RmForceShutdown);     
RmRestart(...); // restart them back, optionally    
RmEndSession(...); 
Goodygoody answered 14/8, 2012 at 14:38 Comment(3)
This is the only method that works if there are multiple explorer.exe processes which can be done automatically with the setting "Launch folder windows in a separate process, with a shift-right-click on a folder and choosing to open in a new process, or with the /separate command line option. The 0x5B4 message is apparently not supported by non-Shell_TrayWnd windows and it only shuts the main explorer.exe process down, not the other ones.Wachtel
API page: msdn.microsoft.com/en-us/library/windows/desktop/… | How to use explained: msdn.microsoft.com/en-us/library/windows/desktop/…Condensation
This is some really evil pseudocode if you aren't well-acquainted with WinAPIs xD Line 1 is fine, but 2 and 3 are a PITACondensation
P
0

I don't think explorer can be closed "Gracefully". EnumProcesses -> compare path -> TerminateProcess

Edit: Try to send WM_CLOSE/WM_QUIT (http://support.microsoft.com/kb/178893) or EndTask

Phatic answered 16/4, 2011 at 22:13 Comment(5)
@cprogrammer: There's gotta be a way... that's what the menu item does.Halfassed
@Mehrdad: Why do you suppose that the menu item closes Explorer "gracefully"?Prosimian
@Cody: Because (1) closing explorer takes a bit longer than a kill, and (2) because I doubt MS would go through all this trouble of hiding a menu item that would kill explorer forcefully, when the user can just kill it in task manager.Halfassed
@cprogrammer: What window should I send it to? I tried sending it to GetDesktopWindow() but it doesn't do anything...Halfassed
EnumProcesses or FindWindow to find Explorer window then dwThreadId=GetWindowThreadProcessId then ::PostThreadMessage(dwThreadId, WM_QUIT/WM_CLOSE, NULL, NULL). or use Endtask on hwndPhatic

© 2022 - 2024 — McMap. All rights reserved.