问题
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.
回答1:
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;
回答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;
}
来源:https://stackoverflow.com/questions/23965895/how-to-close-windows-explorer-windows-with-opened-folders-from-a-certain-drive