Safely Remove Hardware

1. Invoke the Safely Remove Hardware Dialog:
RunDll32.exe shell32.dll,Control_RunDLL hotplug.dll
2. First issue CM_Query_And_Remove_SubTree on the device node, and then follow up with CM_Request_Device_Eject on the device node. Note: use the device node for the USB storage device enumerated by the USB hub, not the volume device enumerated via USBSTOR.

Approach overview

1. Open the device via the drive letter (\\.\d:) and issue a IOCTL_MOUNTDEV_QUERY_UNIQUE_ID to obtain the unique ID for the device.

2. Use the SetupDi calls to enumerate all mounted devices. Open each device (via the interfaces) and issue IOCTL_MOUNTDEV_QUERY_UNIQUE_ID calls to find the mounted device that has a matching unique ID.

3. Use the SetupDi calls to obtain the device node ID for the matching device. This can then be used by the CM_ calls to initiate a safe removal operation.Details1. Use CreateFile to open the drive ("\\.\d:"). Use FILE_SHARE_READ  FILE_SHARE_WRITE and GENERIC_READ.2. Issue IOCTL_MOUNTDEV_QUERY_UNIQUE_ID with an output buffer size of sizeof(MOUNTDEV_UNIQUE_ID). The request will fail, but will return a copy of the MOUNTDEV_UNIQUE_ID structure with the UniqueIdLength field filled in. Check the count of bytes returned (should be >= sizeof MOUNTDEV_UNIQUE_ID) to be sure that the required buffer size was returned.3. Allocate a new buffer of size sizeof(MOUNTDEV_UNIQUE_ID) plus the value of UniqueIdLength returned by the previous call.

4. Issue IOCTL_MOUNTDEV_QUERY_UNIQUE_ID with the new output buffer and size. The call should succeed.

5. Call SetupDiGetClassDevs( &MOUNTDEV_MOUNTED_DEVICE_GUID, NULL, NULL, DIGCF_DEVICEINTERFACE) to obtain a device info object representing mounted storage devices.

6. Repeatedly call SetupDiEnumDeviceInterfaces( dev_info, NULL, &MOUNTDEV_MOUNTED_DEVICE_GUID, index, &interface_data) to examine each interface.a. Call SetupDiGetDeviceInterfaceDetail to obtain the interface detail, including a device path that can be passed to CreateFile.b. Use CreateFile to open the device (interface_detail->DevicePath).c. Issue IOCTL_MOUNTDEV_QUERY_UNIQUE_ID with an output buffer size of sizeof(MOUNTDEV_UNIQUE_ID). The request will fail, but will return a copy of the MOUNTDEV_UNIQUE_ID structure with the UniqueIdLength field filled in. Check the count of bytes returned (should be >= sizeof MOUNTDEV_UNIQUE_ID) to be sure that the required buffer size was returned.d. Allocate a new buffer of size sizeof(MOUNTDEV_UNIQUE_ID) plus the value of UniqueIdLength returned by the previous call.e. Issue IOCTL_MOUNTDEV_QUERY_UNIQUE_ID with the new output buffer and size. The call should succeed.f. Compare the unique ID of the device with the unique ID found in step 4. If they match, we found the SetupDi device instance equivalent of the drive letter.

7. Call SetupDiGetDeviceInstanceId to obtain the device instance ID of the device, which can then be used in calls to the CM_ APIs.Additional items1. When initiating a safe removal for a USB storage device, the removal should be done for the root of the device, which generally seems to be of class "USB" (as opposed to USB store). The function CM_Get_DevNode_Registry_Property can be used to query the device class. If the device is not of class USB, then navigate to the parent using CM_Get_Parent. When a device instance of class "USB" is reached, that can be used as the target for a safe removal operation.2. Alternatively, rather than checking device classes, the app can check for known good hardware IDs or compatible IDs, and keep walking up the tree until it finds one it can work with.

Comments

  • Anonymous
    January 17, 2014
    I realize you are targeting USB, but assuming I have an HDD with an eject button, is there any way I can notify any applications with open file handles, to close them, so the safely remove can succeed? thanks! Pat