none
CM_Request_Device_Eject 実行時に"コンピュータから安全に取り外すことができます。"が表示されない。 RRS feed

  • 質問

  • 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


    2021年7月3日 2:37

回答

  • 面白そうなのでちょっとデバッグしてみました。
    タスクトレイ アイコンからの「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:36

すべての返信

  • Microsoftのアナウンスのページがなかなか分かり難いので、この件で日本語で直ぐに出て来た Gigazine のページを載せます。

    つまりこの件は、ご存知でしょうか?ということです。

    最新のバージョン1809では外部記憶メディアの取り外しに関するポリシーが変更されており、これまで行ってきた「ハードウェアの安全な取り外し」作業は不要になることが明らかになっています。

    Microsoftが「ハードウェアの安全な取り外し」を行わなくてもUSBを取り外してOKと認める

    https://gigazine.net/news/20190409-microsoft-windows-remove-usb/

    2021年7月4日 6:02
  • そのプログラム実行後、デバイス マネージャ上での該当デバイスの表示は、どうなっているのでしょうか?
    2021年7月5日 1:29
  • Microsoft の公式情報はこれですね。

    Windows 10 default media removal policy - Windows Client Management | Microsoft Docs

    当該の外部デバイスのポリシーは「クイック削除」「高パフォーマンス」いずれになっているでしょう?


    Hebikuzure aka Murachi Akira


    2021年7月5日 1:51
  • "クイック取り外し(既定)"が選択された状態になっていました。
    ですが、"高パフォーマンス"に変更しても
    タスクトレイ アイコンから"XXX の取り外し"で取り外した時は通知が表示され、
    CM_Request_Device_Eject を呼び出した時は通知が表示されないようです。
    2021年7月5日 11:27
  • プログラムを実行すると、"ディスク ドライブ"から該当のUSBデバイスの表示が消えます。
    "ユニバーサル シリアル バス コントローラー"ツリーの"USB 大容量記憶装置"が
    警告マーク(黄色い三角に感嘆符)が付いた状態になり、プロパティを見ると"デバイスの状態"が
    "このハードウェア デバイスをコンピューターから取り外す準備が行われているがまだ取り外されていないため、そのデバイスを使用できません。"
    と表示された状態になります。

    これは、タスクトレイ アイコンから"XXX の取り外し"で取り外した時も同じ状態になります。

    2021年7月5日 11:29
  • スクリーンショットです。デバイス マネージャー


    2021年7月5日 11:30
  • 面白そうなのでちょっとデバッグしてみました。
    タスクトレイ アイコンからの「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:36
  • 目から鱗です。

    Windows 7ではエクスプローラが状態を監視しているのでは?と推測していましたが、

    隠しAPIを経由していたんですね。

    ありがとうございました。


    2021年7月7日 9:23
  • >最新のバージョン1809では外部記憶メディアの取り外しに関するポリシーが変更されており、
    >これまで行ってきた「ハードウェアの安全な取り外し」作業は不要になることが明らかになっています。

    そのアナウンスが出た後、リムーバブルメディアで、安全な取り外しを行わなくて、
    データが消えるケースが多発しているそうなので、過信できないようです。
    (恐らく、外付けメディアなのに、内蔵ハードディスクとして分類されるケースがある)
    2022年6月7日 6:55