トップ回答者
CM_Request_Device_Eject 実行時に"コンピュータから安全に取り外すことができます。"が表示されない。

質問
-
Windows 7では、USBドライブを装着した状態で CM_Request_Device_Eject を実行した場合、
pVetoType, pszVetoName, ulNameLength に NULL(0) を渡すと、
タスクトレイ アイコンのバルーンより"'USB大容量記憶装置' はコンピューターから安全に取り外すことができます。"と表示されます。
Windows 10でも、デバイスのタスクトレイ アイコンを右クリックし、"XXX の取り外し"をクリックすることで
同様の通知が表示されますが、CM_Request_Device_Eject で同じ呼び出しをした場合、通知が表示されません。
(Windows 8, 8.1 は未確認)
タスクトレイ アイコンから取り外した時のように通知を表示させることはできますか?
Windows 7で実行した時も同様にバルーンが表示されると良いのですが。
Windows 10 Pro 64-bit 20H2
ビルド: 19042.1052
// C++ コンソール プロジェクト #include <tchar.h> #include <iostream> #include <Windows.h> #include <SetupAPI.h> #include <cfgmgr32.h> #pragma comment(lib, "setupapi.lib") #if defined (UNICODE) #define tstring std::wstring #else #define tstring std::string #endif DWORD GetDeviceNumber(HANDLE hVolume); DEVINST GetDevInst(const DWORD dwDeviceNumber, const int nDriveType, const TCHAR* pszDosDeviceName); int RemoveDevice(const TCHAR driveLetter, const unsigned nRetryCount = 3, const int nRetryInterval = 3000); int _tmain() { if (__argc < 2) { return -1; } try { return RemoveDevice(*__targv[1]); } catch (int nException) { return nException; } } DWORD GetDeviceNumber(HANDLE hVolume) { STORAGE_DEVICE_NUMBER storageDeviceNumber; DWORD dwBytesReturned = 0; DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, nullptr, 0, &storageDeviceNumber, sizeof(storageDeviceNumber), &dwBytesReturned, nullptr); if (dwBytesReturned < 1) { throw GetLastError(); } return storageDeviceNumber.DeviceNumber; } DEVINST GetDevInst(const DWORD dwDeviceNumber, const int nDriveType, const TCHAR* pszDosDeviceName) { GUID guid; switch (nDriveType) { case DRIVE_REMOVABLE: guid = GUID_DEVINTERFACE_DISK; break; case DRIVE_FIXED: guid = GUID_DEVINTERFACE_DISK; break; case DRIVE_CDROM: guid = GUID_DEVINTERFACE_CDROM; break; default: return -1; } HDEVINFO hDevInfo = SetupDiGetClassDevs(&guid, 0, nullptr, (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)); if (hDevInfo == INVALID_HANDLE_VALUE) { SetupDiDestroyDeviceInfoList(hDevInfo); throw GetLastError(); } for (int index = 0; ; ++index) { SP_DEVICE_INTERFACE_DATA interfaceData{ 0 }; interfaceData.cbSize = sizeof(interfaceData); interfaceData.InterfaceClassGuid = guid; if (!SetupDiEnumDeviceInterfaces(hDevInfo, nullptr, &guid, index, &interfaceData)) { int nError = GetLastError(); SetupDiDestroyDeviceInfoList(hDevInfo); throw nError; } SP_DEVINFO_DATA devInfoData{ 0 }; devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); devInfoData.ClassGuid = guid; DWORD dwRequiredSize = 0; SetupDiGetDeviceInterfaceDetail(hDevInfo, &interfaceData, nullptr, 0, &dwRequiredSize, &devInfoData); if (dwRequiredSize < 1) { int nError = GetLastError(); SetupDiDestroyDeviceInfoList(hDevInfo); throw nError; } BYTE* buffer = new BYTE[dwRequiredSize]{ 0 }; SP_DEVICE_INTERFACE_DETAIL_DATA* pDetailData = (SP_DEVICE_INTERFACE_DETAIL_DATA*)buffer; pDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(hDevInfo, &interfaceData, pDetailData, dwRequiredSize, &dwRequiredSize, &devInfoData)) { int nError = GetLastError(); delete[] buffer; SetupDiDestroyDeviceInfoList(hDevInfo); throw nError; } HANDLE hVolume = CreateFile(pDetailData->DevicePath, 0, (FILE_SHARE_READ | FILE_SHARE_WRITE), nullptr, OPEN_EXISTING, 0, nullptr); delete[] buffer; if (hVolume == INVALID_HANDLE_VALUE) { continue; } DWORD dwDeviceNumber_; try { dwDeviceNumber_ = GetDeviceNumber(hVolume); } catch (...) { CloseHandle(hVolume); SetupDiDestroyDeviceInfoList(hDevInfo); throw; } if (dwDeviceNumber_ == dwDeviceNumber) { CloseHandle(hVolume); SetupDiDestroyDeviceInfoList(hDevInfo); return devInfoData.DevInst; } CloseHandle(hVolume); } } int RemoveDevice(const TCHAR driveLetter, const unsigned nRetryCount, const int nRetryInterval) { tstring strVolumeName; strVolumeName += driveLetter; strVolumeName += _T(':'); tstring strFileName; strFileName += _T("\\\\.\\"); strFileName += strVolumeName; HANDLE hVolume = CreateFile(strFileName.c_str(), 0, (FILE_SHARE_READ | FILE_SHARE_WRITE), nullptr, OPEN_EXISTING, 0, nullptr); if (hVolume == INVALID_HANDLE_VALUE) { return GetLastError(); } DWORD dwDeviceNumber; try { dwDeviceNumber = GetDeviceNumber(hVolume); } catch (...) { CloseHandle(hVolume); throw; } CloseHandle(hVolume); tstring strRootPathName; strRootPathName = strVolumeName; strRootPathName += _T('\\'); UINT uiDriveType = GetDriveType(strRootPathName.c_str()); TCHAR szDeviceName[MAX_PATH] = { _T('\0') }; DWORD dwResultOfQuery = QueryDosDevice(strVolumeName.c_str(), szDeviceName, MAX_PATH); if (dwResultOfQuery == 0) { throw GetLastError(); } DEVINST devInst; devInst = GetDevInst(dwDeviceNumber, uiDriveType, szDeviceName); if (devInst < 0) { return -1; } DEVINST devInstParent = 0; CM_Get_Parent(&devInstParent, devInst, 0); int nResult = 0; for (unsigned i = 0; i < nRetryCount; ++i) { if (i != 0) { Sleep(nRetryInterval); } TCHAR szVetoName[MAX_PATH]{ _T('\0') }; PNP_VETO_TYPE vetoType; #if true nResult = CM_Request_Device_Eject(devInstParent, nullptr, nullptr, 0, 0); #else nResult = CM_Request_Device_Eject(devInstParent, &vetoType, szVetoName, MAX_PATH, 0); #endif if (nResult == 0) { return 0; } } return -1; }
使用例:
C:\>RemoveDevice.exe D
- 編集済み Yuki Nishina 2021年7月9日 15:05
回答
-
面白そうなのでちょっとデバッグしてみました。
タスクトレイ アイコンからの「XXX の取り外し」をクリックした場合、Explorer プロセス内から cfgmgr32!CM_Request_Device_EjectW ルーチンが呼び出されますが、このコールは hotplug!HotPlugEjectDeviceBase ルーチン経由で呼び出されていました。0:109> k # Child-SP RetAddr Call Site 00 00000000`0c74f6a8 00007ff8`6ca3a64e cfgmgr32!CM_Request_Device_EjectW 01 00000000`0c74f6b0 00007ff8`682f5ac7 cfgmgr32!CM_Request_Device_Eject_ExW+0x2e 02 00000000`0c74f6f0 00007ff8`682f5cb3 hotplug!HotPlugEjectDeviceBase+0x7f 03 00000000`0c74f9f0 00007ff8`6ea47034 hotplug!HotPlugEjectDeviceStub+0x13 04 00000000`0c74fa20 00007ff8`6eb82651 KERNEL32!BaseThreadInitThunk+0x14 05 00000000`0c74fa50 00000000`00000000 ntdll!RtlUserThreadStart+0x21
hotplug!HotPlugEjectDeviceStub は、hotplug!HotPlugEjectDeviceEx ルーチン コール経由で生成される非同期処理用のスレッドらしく、どうやらデバイスの取り外しを行った際のバルーン メッセージの表示/非表示は、hotplug モジュール内で制御されているようです。
hotplug!HotPlugEjectDeviceEx は非公開 API なので、マイクロソフトからの公式ドキュメントはありませんが、HotPlugEjectDevice API に関しては下記サイトに「それらしい」情報がありました。
--------------------------
Eject SSD from c++ code
https://docs.microsoft.com/en-us/answers/questions/353483/eject-ssd-from-c-code.html
--------------------------
で、質問で提示されているコードを基に、cfgmgr32!CM_Request_Device_EjectW コール部分を hotplug!HotPlugEjectDevice コールに改造したところ、見事にバルーン メッセージが表示されました。
(ちなみに質問で提示されているコードでは例外ハンドラの部分がまともに動かなったので、この部分は全部削除したコードで試しました。)
- 編集済み お馬鹿 2021年7月7日 8:37
- 回答としてマーク Yuki Nishina 2021年7月7日 9:18
すべての返信
-
Microsoftのアナウンスのページがなかなか分かり難いので、この件で日本語で直ぐに出て来た Gigazine のページを載せます。
つまりこの件は、ご存知でしょうか?ということです。
最新のバージョン1809では外部記憶メディアの取り外しに関するポリシーが変更されており、これまで行ってきた「ハードウェアの安全な取り外し」作業は不要になることが明らかになっています。
Microsoftが「ハードウェアの安全な取り外し」を行わなくてもUSBを取り外してOKと認める
https://gigazine.net/news/20190409-microsoft-windows-remove-usb/
-
Microsoft の公式情報はこれですね。
Windows 10 default media removal policy - Windows Client Management | Microsoft Docs
当該の外部デバイスのポリシーは「クイック削除」「高パフォーマンス」いずれになっているでしょう?
Hebikuzure aka Murachi Akira
- 編集済み Hebikuzure aka Murachi AkiraMVP 2021年7月5日 1:58
-
-
-
面白そうなのでちょっとデバッグしてみました。
タスクトレイ アイコンからの「XXX の取り外し」をクリックした場合、Explorer プロセス内から cfgmgr32!CM_Request_Device_EjectW ルーチンが呼び出されますが、このコールは hotplug!HotPlugEjectDeviceBase ルーチン経由で呼び出されていました。0:109> k # Child-SP RetAddr Call Site 00 00000000`0c74f6a8 00007ff8`6ca3a64e cfgmgr32!CM_Request_Device_EjectW 01 00000000`0c74f6b0 00007ff8`682f5ac7 cfgmgr32!CM_Request_Device_Eject_ExW+0x2e 02 00000000`0c74f6f0 00007ff8`682f5cb3 hotplug!HotPlugEjectDeviceBase+0x7f 03 00000000`0c74f9f0 00007ff8`6ea47034 hotplug!HotPlugEjectDeviceStub+0x13 04 00000000`0c74fa20 00007ff8`6eb82651 KERNEL32!BaseThreadInitThunk+0x14 05 00000000`0c74fa50 00000000`00000000 ntdll!RtlUserThreadStart+0x21
hotplug!HotPlugEjectDeviceStub は、hotplug!HotPlugEjectDeviceEx ルーチン コール経由で生成される非同期処理用のスレッドらしく、どうやらデバイスの取り外しを行った際のバルーン メッセージの表示/非表示は、hotplug モジュール内で制御されているようです。
hotplug!HotPlugEjectDeviceEx は非公開 API なので、マイクロソフトからの公式ドキュメントはありませんが、HotPlugEjectDevice API に関しては下記サイトに「それらしい」情報がありました。
--------------------------
Eject SSD from c++ code
https://docs.microsoft.com/en-us/answers/questions/353483/eject-ssd-from-c-code.html
--------------------------
で、質問で提示されているコードを基に、cfgmgr32!CM_Request_Device_EjectW コール部分を hotplug!HotPlugEjectDevice コールに改造したところ、見事にバルーン メッセージが表示されました。
(ちなみに質問で提示されているコードでは例外ハンドラの部分がまともに動かなったので、この部分は全部削除したコードで試しました。)
- 編集済み お馬鹿 2021年7月7日 8:37
- 回答としてマーク Yuki Nishina 2021年7月7日 9:18
-
目から鱗です。
Windows 7ではエクスプローラが状態を監視しているのでは?と推測していましたが、
隠しAPIを経由していたんですね。
ありがとうございました。
- 編集済み Yuki Nishina 2021年7月7日 9:26