ejecting via Shell COM Object

This commit is contained in:
David Kocik 2023-04-17 10:14:56 +02:00
parent bb94e386d8
commit 9d34998ac3
2 changed files with 57 additions and 57 deletions

View File

@ -18,6 +18,10 @@
#include <devpropdef.h>
#include <devpkey.h>
#include <usbioctl.h>
#include <atlbase.h>
#include <atlcom.h>
#include <shldisp.h>
#else
// unix, linux & OSX includes
#include <errno.h>
@ -80,7 +84,7 @@ std::vector<DriveData> RemovableDriveManager::search_for_removable_drives() cons
namespace {
#if 0
// From https://github.com/microsoft/Windows-driver-samples/tree/main/usb/usbview
typedef struct _STRING_DESCRIPTOR_NODE
{
@ -581,6 +585,57 @@ void eject_alt(std::string path, wxEvtHandler* callback_evt_handler, DriveData d
if (callback_evt_handler)
wxPostEvent(callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(drive_data), true)));
}
#endif // 0
// C++ equivavalent of PowerShell script:
// $driveEject = New - Object - comObject Shell.Application
// $driveEject.Namespace(17).ParseName("E:").InvokeVerb("Eject")
// from https://superuser.com/a/1750403
bool eject_inner(const std::string& path)
{
std::wstring wpath = boost::nowide::widen(path);
CoInitialize(nullptr);
CComPtr<IShellDispatch> pShellDisp;
HRESULT hr = pShellDisp.CoCreateInstance(CLSID_Shell, nullptr, CLSCTX_INPROC_SERVER);
if (!SUCCEEDED(hr)) {
BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Attempt to get Shell pointer has failed.", path);
CoUninitialize();
return false;
}
CComPtr<Folder> pFolder;
VARIANT vtDrives;
VariantInit(&vtDrives);
vtDrives.vt = VT_I4;
vtDrives.lVal = ssfDRIVES;
hr = pShellDisp->NameSpace(vtDrives, &pFolder);
if (!SUCCEEDED(hr)) {
BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Attempt to create Namespace has failed.", path);
CoUninitialize();
return false;
}
CComPtr<FolderItem> pItem;
hr = pFolder->ParseName(static_cast<BSTR>(const_cast<wchar_t*>(wpath.c_str())), &pItem);
if (!SUCCEEDED(hr)) {
BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Attempt to Parse name has failed.", path);
CoUninitialize();
return false;
}
VARIANT vtEject;
VariantInit(&vtEject);
vtEject.vt = VT_BSTR;
vtEject.bstrVal = SysAllocString(L"Eject");
hr = pItem->InvokeVerb(vtEject);
if (!SUCCEEDED(hr)) {
BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Attempt to Invoke Verb has failed.", path);
VariantClear(&vtEject);
CoUninitialize();
return false;
}
BOOST_LOG_TRIVIAL(debug) << "Ejecting via InvokeVerb has succeeded.";
VariantClear(&vtEject);
CoUninitialize();
return true;
}
} // namespace
// Called from UI therefore it blocks the UI thread.
@ -597,27 +652,19 @@ void RemovableDriveManager::eject_drive()
BOOST_LOG_TRIVIAL(info) << "Ejecting started";
std::scoped_lock<std::mutex> lock(m_drives_mutex);
auto it_drive_data = this->find_last_save_path_drive_data();
#if 1
if (it_drive_data != m_current_drives.end()) {
if (!eject_inner(m_last_save_path)) {
if (eject_inner(m_last_save_path)) {
// success
BOOST_LOG_TRIVIAL(info) << "Ejecting has succeeded.";
assert(m_callback_evt_handler);
if (m_callback_evt_handler)
wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(*it_drive_data), true)));
} else {
if (m_eject_thread.joinable())
m_eject_thread.join();
m_eject_thread = boost::thread(eject_alt, m_last_save_path, m_callback_evt_handler, std::move(*it_drive_data));
// failed to eject
// this should not happen, throwing exception might be the way here
/*
BOOST_LOG_TRIVIAL(error) << "Ejecting has failed.";
assert(m_callback_evt_handler);
if (m_callback_evt_handler)
wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair<DriveData, bool>(*it_drive_data, false)));
*/
}
} else {
// drive not found in m_current_drives
@ -626,47 +673,6 @@ void RemovableDriveManager::eject_drive()
if (m_callback_evt_handler)
wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair<DriveData, bool>({"",""}, false)));
}
#endif
#if 0
// Implementation used until 2.5.x version
// Some usb drives does not eject properly (still visible in file explorer). Some even does not write all content and eject.
if (it_drive_data != m_current_drives.end()) {
// get handle to device
std::string mpath = "\\\\.\\" + m_last_save_path;
mpath = mpath.substr(0, mpath.size() - 1);
HANDLE handle = CreateFileW(boost::nowide::widen(mpath).c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
BOOST_LOG_TRIVIAL(error) << "Ejecting " << mpath << " failed (handle == INVALID_HANDLE_VALUE): " << GetLastError();
assert(m_callback_evt_handler);
if (m_callback_evt_handler)
wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair<DriveData, bool>(*it_drive_data, false)));
return;
}
DWORD deviceControlRetVal(0);
//these 3 commands should eject device safely but they dont, the device does disappear from file explorer but the "device was safely remove" notification doesnt trigger.
//sd cards does trigger WM_DEVICECHANGE messege, usb drives dont
BOOL e1 = DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr);
BOOST_LOG_TRIVIAL(error) << "FSCTL_LOCK_VOLUME " << e1 << " ; " << deviceControlRetVal << " ; " << GetLastError();
BOOL e2 = DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr);
BOOST_LOG_TRIVIAL(error) << "FSCTL_DISMOUNT_VOLUME " << e2 << " ; " << deviceControlRetVal << " ; " << GetLastError();
// some implemenatations also calls IOCTL_STORAGE_MEDIA_REMOVAL here with FALSE as third parameter, which should set PreventMediaRemoval
BOOL error = DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr);
if (error == 0) {
CloseHandle(handle);
BOOST_LOG_TRIVIAL(error) << "Ejecting " << mpath << " failed (IOCTL_STORAGE_EJECT_MEDIA)" << deviceControlRetVal << " " << GetLastError();
assert(m_callback_evt_handler);
if (m_callback_evt_handler)
wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair<DriveData, bool>(*it_drive_data, false)));
return;
}
CloseHandle(handle);
BOOST_LOG_TRIVIAL(info) << "Ejecting finished";
assert(m_callback_evt_handler);
if (m_callback_evt_handler)
wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(*it_drive_data), true)));
m_current_drives.erase(it_drive_data);
}
#endif // 0
}
std::string RemovableDriveManager::get_removable_drive_path(const std::string &path)

View File

@ -106,12 +106,6 @@ private:
#endif /* _WIN32 */
#endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
#ifdef _WIN32
// Another worker thread, used only to perform alt_eject method (external SD cards only).
// Does not share data with m_thread
boost::thread m_eject_thread;
#endif /* _WIN32 */
// Called from update() to enumerate removable drives.
std::vector<DriveData> search_for_removable_drives() const;