Search code examples
c++windowswinapiwindows-shellwindows-explorer

How to close Windows Explorer windows with opened folders from a certain drive


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.


Solution

  • 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;