Search code examples
windowswinapiusbusb-drive

How to eject USB drive on Windows 10 (IOCTL_STORAGE_EJECT_MEDIA no longer enough)


Convention wisdom to eject a USB drive on Windows is the following sequence:

CreateFile (drive letter, with read/write rights, file share read and write)
DeviceIoControl(FSCTL_LOCK_VOLUME) 
DeviceIoControl(FSCTL_DISMOUNT_VOLUME) 
DeviceIoControl(IOCTL_STORAGE_MEDIA_REMOVAL) PreventMediaRemoval = FALSE 
DeviceIoControl(IOCTL_STORAGE_EJECT_MEDIA)

This worked fine until a recent change in Windows 10 (not sure when). Now the drive is still properly ejected, but then Windows immediately remounts the drive.

What needs to be done to eject the drive until the user removes it and puts it in again?


Solution

  • Using CM_Request_Device_EjectW API works for me. You can have a try.

    The following is the complete code I tested and it from "How to Prepare a USB Drive for Safe Removal" at codeproject.

    (Here the "F" is my USB drive letter. Replace it using your own one.)

    #include <stdio.h>
    #include <windows.h>
    #include <Setupapi.h>
    #include <winioctl.h>
    #include <winioctl.h>
    #include <cfgmgr32.h>
    
    //-------------------------------------------------
    DEVINST GetDrivesDevInstByDeviceNumber(long DeviceNumber, UINT DriveType, char* szDosDeviceName);
    //-------------------------------------------------
    
    //-------------------------------------------------
    int main()
    {
        char DriveLetter = 'F';
        DriveLetter &= ~0x20; // uppercase
    
        if (DriveLetter < 'A' || DriveLetter > 'Z') {
            return 1;
        }
    
        char szRootPath[] = "F:\\";   // "X:\"  -> for GetDriveType
        szRootPath[0] = DriveLetter;
    
        char szDevicePath[] = "F:";   // "X:"   -> for QueryDosDevice
        szDevicePath[0] = DriveLetter;
    
        char szVolumeAccessPath[] = "\\\\.\\F:";   // "\\.\X:"  -> to open the volume
        szVolumeAccessPath[4] = DriveLetter;
    
        long DeviceNumber = -1;
    
        // open the storage volume
        HANDLE hVolume = CreateFile(szVolumeAccessPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
        if (hVolume == INVALID_HANDLE_VALUE) {
            return 1;
        }
    
        // get the volume's device number
        STORAGE_DEVICE_NUMBER sdn;
        DWORD dwBytesReturned = 0;
        long res = DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL);
        if (res) {
            DeviceNumber = sdn.DeviceNumber;
        }
        CloseHandle(hVolume);
    
        if (DeviceNumber == -1) {
            return 1;
        }
    
        // get the drive type which is required to match the device numbers correctely
        UINT DriveType = GetDriveType(szRootPath);
    
        // get the dos device name (like \device\floppy0) to decide if it's a floppy or not - who knows a better way?
        char szDosDeviceName[MAX_PATH];
        res = QueryDosDevice(szDevicePath, szDosDeviceName, MAX_PATH);
        if (!res) {
            return 1;
        }
    
        // get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number
        DEVINST DevInst = GetDrivesDevInstByDeviceNumber(DeviceNumber, DriveType, szDosDeviceName);
    
        if (DevInst == 0) {
            return 1;
        }
    
        PNP_VETO_TYPE VetoType = PNP_VetoTypeUnknown;
        WCHAR VetoNameW[MAX_PATH];
        VetoNameW[0] = 0;
        bool bSuccess = false;
    
        // get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives!
        DEVINST DevInstParent = 0;
        res = CM_Get_Parent(&DevInstParent, DevInst, 0);
    
        for (long tries = 1; tries <= 3; tries++) { // sometimes we need some tries...
    
            VetoNameW[0] = 0;
    
            // CM_Query_And_Remove_SubTree doesn't work for restricted users
            //res = CM_Query_And_Remove_SubTreeW(DevInstParent, &VetoType, VetoNameW, MAX_PATH, CM_REMOVE_NO_RESTART); // CM_Query_And_Remove_SubTreeA is not implemented under W2K!
            //res = CM_Query_And_Remove_SubTreeW(DevInstParent, NULL, NULL, 0, CM_REMOVE_NO_RESTART);  // with messagebox (W2K, Vista) or balloon (XP)
    
            res = CM_Request_Device_EjectW(DevInstParent, &VetoType, VetoNameW, MAX_PATH, 0);
            //res = CM_Request_Device_EjectW(DevInstParent, NULL, NULL, 0, 0); // with messagebox (W2K, Vista) or balloon (XP)
    
            bSuccess = (res == CR_SUCCESS && VetoType == PNP_VetoTypeUnknown);
            if (bSuccess) {
                break;
            }
    
            Sleep(500); // required to give the next tries a chance!
        }
    
        if (bSuccess) {
            printf("Success\n\n");
            return 0;
        }
    
        printf("failed\n");
    
        printf("Result=0x%2X\n", res);
    
        if (VetoNameW[0]) {
            printf("VetoName=%ws)\n\n", VetoNameW);
        }
        return 1;
    }
    //-----------------------------------------------------------
    
    
    
    //----------------------------------------------------------------------
    // returns the device instance handle of a storage volume or 0 on error
    //----------------------------------------------------------------------
    DEVINST GetDrivesDevInstByDeviceNumber(long DeviceNumber, UINT DriveType, char* szDosDeviceName)
    {
        bool IsFloppy = (strstr(szDosDeviceName, "\\Floppy") != NULL); // who knows a better way?
    
        GUID* guid;
    
        switch (DriveType) {
        case DRIVE_REMOVABLE:
            if (IsFloppy) {
                guid = (GUID*)&GUID_DEVINTERFACE_FLOPPY;
            }
            else {
                guid = (GUID*)&GUID_DEVINTERFACE_DISK;
            }
            break;
        case DRIVE_FIXED:
            guid = (GUID*)&GUID_DEVINTERFACE_DISK;
            break;
        case DRIVE_CDROM:
            guid = (GUID*)&GUID_DEVINTERFACE_CDROM;
            break;
        default:
            return 0;
        }
    
        // Get device interface info set handle for all devices attached to system
        HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    
        if (hDevInfo == INVALID_HANDLE_VALUE) {
            return 0;
        }
    
        // Retrieve a context structure for a device interface of a device information set
        DWORD dwIndex = 0;
        long res;
    
        BYTE Buf[1024];
        PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf;
        SP_DEVICE_INTERFACE_DATA         spdid;
        SP_DEVINFO_DATA                  spdd;
        DWORD                            dwSize;
    
        spdid.cbSize = sizeof(spdid);
    
        while (true) {
            res = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, guid, dwIndex, &spdid);
            if (!res) {
                break;
            }
    
            dwSize = 0;
            SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL); // check the buffer size
    
            if (dwSize != 0 && dwSize <= sizeof(Buf)) {
    
                pspdidd->cbSize = sizeof(*pspdidd); // 5 Bytes!
    
                ZeroMemory(&spdd, sizeof(spdd));
                spdd.cbSize = sizeof(spdd);
    
                long res = SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd, dwSize, &dwSize, &spdd);
                if (res) {
    
                    // in case you are interested in the USB serial number:
                    // the device id string contains the serial number if the device has one,
                    // otherwise a generated id that contains the '&' char...
                    /*
                    DEVINST DevInstParent = 0;
                    CM_Get_Parent(&DevInstParent, spdd.DevInst, 0);
                    char szDeviceIdString[MAX_PATH];
                    CM_Get_Device_ID(DevInstParent, szDeviceIdString, MAX_PATH, 0);
                    printf("DeviceId=%s\n", szDeviceIdString);
                    */
    
                    // open the disk or cdrom or floppy
                    HANDLE hDrive = CreateFile(pspdidd->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
                    if (hDrive != INVALID_HANDLE_VALUE) {
                        // get its device number
                        STORAGE_DEVICE_NUMBER sdn;
                        DWORD dwBytesReturned = 0;
                        res = DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL);
                        if (res) {
                            if (DeviceNumber == (long)sdn.DeviceNumber) {  // match the given device number with the one of the current device
                                CloseHandle(hDrive);
                                SetupDiDestroyDeviceInfoList(hDevInfo);
                                return spdd.DevInst;
                            }
                        }
                        CloseHandle(hDrive);
                    }
                }
            }
            dwIndex++;
        }
    
        SetupDiDestroyDeviceInfoList(hDevInfo);
    
        return 0;
    }