3

I'm writing a small app that will allow a user to eject (or safely remove) a USB drive. My app works fine, except the situation when a folder on the USB drive (or several folders) are opened in Windows Explorer. In that case the eject function fails as the drive appears to be locked.

So I'm curious, since the user is issuing a command through my app to eject the USB drive, is there any way to make Explorer close those open windows from the USB drive?

PS. Note that I don't want to close all processes belonging to the Windows Explorer, but only the ones that opened folders on a specific drive.

c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • Tricky, this. The Windows Explorer windows all have the same process ID (normally), and you don't want to stop that. However, when you eject a USB key safely using the default Windows notification area icon, it does close any relevant Explorer windows -- perhaps you can figure out what API functions are being called for that? – Cameron May 31 '14 at 02:55
  • @Cameron: Well, that's what I'm trying to do here. – c00000fd May 31 '14 at 03:23
  • I think the only way you can programmatically destroy a window belonging to another process is to ask that process to destroy it itself. The notification area icon is presumably under the control of explorer.exe, so it's able to kill those windows. You need a way to talk directly to explorer.exe and ask it to kill the window, or you could possibly synthesize a click of the close button on the window itself. – ooga May 31 '14 at 03:50
  • 2
    You can use the [ShellWindows object](http://msdn.microsoft.com/en-us/library/windows/desktop/bb773974(v=vs.85).aspx) and enumerate through the open windows. The ones open to folders you don't like, you can navigate them to My Computer. – Raymond Chen May 31 '14 at 05:44
  • Could this question provide any help in regards to some WinAPI you could use: http://stackoverflow.com/questions/85649/safe-remove-usb-drive-using-win32-api ?? – txtechhelp May 31 '14 at 06:08
  • @RaymondChen: Hey, Raymond, how about linking to your own article for a change :) http://blogs.msdn.com/b/oldnewthing/archive/2004/07/20/188696.aspx – c00000fd May 31 '14 at 21:34

2 Answers2

3
procedure ProcessExplorerWindows(ADriveLetter: WideChar; AClose: Boolean);
var
  ShellWindows: IShellWindows;
  i: Integer;
  Dispatch: IDispatch;
  WebBrowser2: IWebBrowser2;
  ServiceProvider: IServiceProvider;
  ShellBrowser: IShellBrowser;
  ShellView: IShellView;
  FolderView: IFolderView;
  PersistFolder2: IPersistFolder2;
  ItemIDList: PItemIDList;
  ShellFolder: IShellFolder;
  ChildItem: PItemIDList;
  Attr: DWORD;
  StrRet: TStrRet;
  FileName: UnicodeString;
  DesktopItemIDList: PItemIDList;
begin
  OleCheck(CoCreateInstance(CLASS_ShellWindows, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IShellWindows, ShellWindows));
  try
    for i := ShellWindows.Count - 1 downto 0 do
      begin
        Dispatch := ShellWindows.Item(i);
        try
          OleCheck(Dispatch.QueryInterface(IWebBrowser2, WebBrowser2));
          try
            OleCheck(Dispatch.QueryInterface(IServiceProvider, ServiceProvider));
            try
              OleCheck(ServiceProvider.QueryService(SID_STopLevelBrowser, IShellBrowser, ShellBrowser));
              try
                OleCheck(ShellBrowser.QueryActiveShellView(ShellView));
                try
                  OleCheck(ShellView.QueryInterface(IFolderView, FolderView));
                  try
                    OleCheck(FolderView.GetFolder(IPersistFolder2, PersistFolder2));
                    try
                      OleCheck(PersistFolder2.GetCurFolder(ItemIDList));
                      try
                        OleCheck(SHBindToParent(ItemIDList, IShellFolder, Pointer(ShellFolder), ChildItem));
                        try
                          Attr := SFGAO_FILESYSTEM;
                          OleCheck(ShellFolder.GetAttributesOf(1, ChildItem, Attr));
                          if Attr and SFGAO_FILESYSTEM = SFGAO_FILESYSTEM then
                            begin
                              OleCheck(ShellFolder.GetDisplayNameOf(ChildItem, SHGDN_FORPARSING, StrRet));
                              FileName := '';
                              case StrRet.uType of
                                STRRET_WSTR:
                                  begin
                                    FileName := StrRet.pOleStr;
                                    CoTaskMemFree(StrRet.pOleStr);
                                  end;
                                STRRET_OFFSET:
                                  if Assigned(ChildItem) then
                                    begin
                                      Inc(PByte(ChildItem), StrRet.uOffset);
                                      FileName := UnicodeString(PAnsiChar(ChildItem));
                                    end;
                                STRRET_CSTR:
                                  FileName := UnicodeString(AnsiString(StrRet.cStr));
                              end;
                              if ExtractFileDrive(FileName) = ADriveLetter + ':' then
                                if AClose then
                                  WebBrowser2.Quit
                                else
                                  begin
                                    SHGetFolderLocation(0, CSIDL_DESKTOP, 0, 0, DesktopItemIDList);
                                    try
                                      OleCheck(ShellBrowser.BrowseObject(DesktopItemIDList, SBSP_SAMEBROWSER or SBSP_DEFMODE or SBSP_ABSOLUTE));
                                    finally
                                      CoTaskMemFree(DesktopItemIDList);
                                    end;
                                  end;
                            end;
                        finally
                          ShellFolder := nil;
                        end;
                      finally
                        CoTaskMemFree(ItemIDList);
                      end;
                    finally
                      PersistFolder2 := nil
                    end;
                  finally
                    FolderView := nil;
                  end;
                finally
                  ShellView := nil;
                end;
              finally
                ShellBrowser := nil;
              end;
            finally
              ServiceProvider := nil;
            end;
          finally
            WebBrowser2 := nil;
          end;
        finally
          Dispatch := nil;
        end;
      end;
  finally
    ShellWindows := nil;
  end;
end;
Denis Anisimov
  • 3,297
  • 1
  • 10
  • 18
2

Here's @DenisAnisimov's method re-written for C++:

HRESULT CloseWindowsExplorerWindowsForDrive(LPCTSTR pStrPath)
{
    //Closes all Windows Explorer windows for a specific drive
    //'pStrPath' = path somewhere on the drive
    //RETURN:
    //      = S_OK if done
    CoInitialize(NULL);

    HRESULT hr;

    TCHAR buffVolPath[MAX_PATH];
    buffVolPath[0] = 0;
    if(GetVolumePathName(pStrPath, buffVolPath, MAX_PATH))
    {
        int nLnVolPath = lstrlen(buffVolPath);

        IShellWindows* pISW = NULL;

        hr = CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
            IID_IShellWindows, (void**) &pISW);
        if (SUCCEEDED(hr))
        {
            long nCount;
            if(SUCCEEDED(hr = pISW->get_Count(&nCount)))
            {
                for(int i = nCount - 1; i >= 0; i--)
                {
                    CComPtr<IDispatch> pIDisp;
                    CComVariant v_i(i);
                    if(SUCCEEDED(hr = pISW->Item(v_i, &pIDisp)))
                    {
                        CComPtr<IWebBrowser2> pIWB2;
                        if(SUCCEEDED(pIDisp->QueryInterface(IID_IWebBrowser2, (void**)&pIWB2)))
                        {
                            CComPtr<IServiceProvider> pISP;
                            if(SUCCEEDED(pIDisp->QueryInterface(IID_IServiceProvider, (void**)&pISP)))
                            {
                                CComPtr<IShellBrowser> pIShBrswr;
                                if(SUCCEEDED(hr= pISP->QueryService(SID_STopLevelBrowser, IID_IShellBrowser, (void**)&pIShBrswr)))
                                {
                                    CComPtr<IShellView> pIShView;
                                    if(SUCCEEDED(hr = pIShBrswr->QueryActiveShellView(&pIShView)))
                                    {
                                        CComPtr<IFolderView> pIFV;
                                        if(SUCCEEDED(hr = pIShView->QueryInterface(IID_IFolderView, (void**)&pIFV)))
                                        {
                                            CComPtr<IPersistFolder2> pIPF2;
                                            if(SUCCEEDED(hr = pIFV->GetFolder(IID_IPersistFolder2, (void**)&pIPF2)))
                                            {
                                                LPITEMIDLIST pidlFolder = NULL;
                                                if(SUCCEEDED(hr = pIPF2->GetCurFolder(&pidlFolder)))
                                                {
                                                    LPCITEMIDLIST pidlChild = NULL;
                                                    CComPtr<IShellFolder> pIShFldr;
                                                    if(SUCCEEDED(::SHBindToParent(pidlFolder, IID_IShellFolder, (void**)&pIShFldr, &pidlChild)))
                                                    {
                                                        ULONG attrs = SFGAO_FILESYSTEM;
                                                        if(SUCCEEDED(hr = pIShFldr->GetAttributesOf(1, &pidlChild, &attrs)))
                                                        {
                                                            if(attrs & SFGAO_FILESYSTEM)
                                                            {
                                                                STRRET srt;
                                                                if(SUCCEEDED(hr = pIShFldr->GetDisplayNameOf(pidlChild, SHGDN_FORPARSING, &srt)))
                                                                {
                                                                    LPWSTR pStrFileName = NULL;
                                                                    if(SUCCEEDED(hr = StrRetToStr(&srt, pidlChild, &pStrFileName)))
                                                                    {
                                                                        //Compare to our path
                                                                        if(lstrlen(pStrFileName) >= nLnVolPath &&
                                                                            ::CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
                                                                            buffVolPath, nLnVolPath,
                                                                            pStrFileName, nLnVolPath) == CSTR_EQUAL)
                                                                        {
                                                                            //Close it
                                                                            hr = pIWB2->Quit();
                                                                        }
                                                                    }

                                                                    if(pStrFileName)
                                                                    {
                                                                        CoTaskMemFree(pStrFileName);
                                                                        pStrFileName = NULL;
                                                                    }

                                                                    //Free mem (if StrRetToStr() hasn't done it)
                                                                    if(srt.pOleStr)
                                                                    {
                                                                        CoTaskMemFree(srt.pOleStr);
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }

                                                //No need to free pidlChild!
                                                }

                                                if(pidlFolder)
                                                {
                                                    CoTaskMemFree(pidlFolder);
                                                    pidlFolder = NULL;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            pISW->Release();
        }
    }
    else
        hr = E_INVALIDARG;

    CoUninitialize();

    return hr;
}
c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • 1
    You don't really need the SFGAO_FILESYSTEM check. You can use SHGetNameFromIDList with SIGDN_DESKTOPABSOLUTEPARSIN that's easier to use than STRRET archaic stuff. Otherwise I suggest you use the cool IID_PPV_ARGS macro for all calls with an IID and a void**, like this: `pIShView->QueryInterface(IID_PPV_ARGS(&pIFV))`. Last thing, sometimes the windows are busy (the user may be playing with it or they just opened - there may be race conditions). You can check that with IWebBrowser2->get_Busy. I usually add a loop that does like X retries before returning a hard error. – Simon Mourier Jun 01 '14 at 05:45
  • This works only on desktop for me. Is there a way how to make function CoCreateInstance() work also on Windows CE? If I copy this code, the function returns: REGDB_E_CLASSNOTREG and if I do some changes, errors are: CO_E_NOTINITIALIZED, E_NOINTERFACE – Racky Nov 08 '16 at 11:43