Click here to Skip to main content
Click here to Skip to main content

How to Prepare a USB Drive for Safe Removal

By , 9 Mar 2012
 

Introduction

Removing a USB drive using the Windows tray icon is easy, especially if you single left-click it, but sometimes, it's useful to do it from your program.

Background

There are some samples around, but the ones I saw were searching for the volume and then calling CM_Get_Parent twice to get the USB device to eject. This approach works only with drives which claim to have removable media. Such drives (drive type: DRIVE_REMOVABLE) are handled differently from basic disks (DRIVE_FIXED) under W2K and XP. Removable drives have a one-to-one relation between the volume and the disk, where the disk is the parent device of the volume. This is true for CDROM drives too.

USB drives without removable media are handled like basic disks, so they can have multiple partitions, and the volume's parent device is not the disk! Under Vista, this is the case for removable drives too, but multiple partitions are still not allowed. By the way, there are more differences resulting from the type of the USB disk. Here is some information.

The magic link between storage volumes and their disk is the device number. You can get it via DeviceIoControl called with IOCTL_STORAGE_GET_DEVICE_NUMBER. This call works with handles to storage volumes on one side, and disk, floppy, and CDROM drives on the other side.

Storage volumes can be spread over multiple disks. So, it is possible that for safe removal of a storage volume, more than one disk device has to be prepared for safe removal. Getting the list of device numbers of such a storage volume is done by IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS. I have ignored this because I think that using a RAID on external drives is a strange idea.

The device number is unique within a device class only. When dealing with drive letters, we have to distinguish between the device interfaces GUID_DEVINTERFACE_DISK, GUID_DEVINTERFACE_FLOPPY, and GUID_DEVINTERFACE_CDROM. The floppies were not considered here until end of October 2006, so a USB floppy screwed up everything, in theory. In real life, a USB floppy has usually device number 0, while any other USB drive has a higher number, so there were no real problems.

By the way: in W2K and XP, legacy floppies are not part of the GUID_DEVINTERFACE_FLOPPY enumeration.

The Sample

This sample is a simplified version of my command-line tool RemoveDrive. It expects the drive letter as a parameter to prepare for safe removal. It opens the volume and gets its device number:

// "X:\"    -> for GetDriveType
char szRootPath[] = "X:\\";
szRootPath[0] = DriveLetter;

// "X:"     -> for QueryDosDevice
char szDevicePath[] = "X:";
szDevicePath[0] = DriveLetter;

// "\\.\X:" -> to open the volume
char szVolumeAccessPath[] = "\\\\.\\X:";
szVolumeAccessPath[4] = DriveLetter;

long DeviceNumber = -1;

HANDLE hVolume = CreateFile(szVolumeAccessPath, 0,
                    FILE_SHARE_READ | FILE_SHARE_WRITE,
                    NULL, OPEN_EXISTING, 0, NULL);
if (hVolume == INVALID_HANDLE_VALUE) {
  return 1;
}

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;
}

UINT DriveType = GetDriveType(szRootPath);

// get the dos device name (like \device\floppy0)
// to decide if it's a floppy or not
char szDosDeviceName[MAX_PATH];
res = QueryDosDevice(szDevicePath, szDosDeviceName, MAX_PATH);
if ( !res ) {
  return 1;
}

DEVINST DevInst = GetDrivesDevInstByDeviceNumber(DeviceNumber,
                  DriveType, szDosDeviceName);
if ( ! DevInst ) {
  return 1;
}

Depending on the volume's drive type and the DOS device name, it then enumerates either all disks, floppies, or CD-ROMs, using the setup API. The device numbers of the drives are matched with the device number mentioned above in order to get the device instance of the correct drive:

//---------------------------------------------------------
DEVINST GetDrivesDevInstByDeviceNumber(long DeviceNumber,
          UINT DriveType, char* szDosDeviceName)
{
  bool IsFloppy = (strstr(szDosDeviceName,
       "\\Floppy") != NULL); // is there 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;
  BOOL bRet = FALSE;

  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 )  {
    bRet = SetupDiEnumDeviceInterfaces(hDevInfo, NULL,
           guid, dwIndex, &spdid);
    if ( !bRet ) {
      break;
    }

    dwSize = 0;
    SetupDiGetDeviceInterfaceDetail(hDevInfo,
      &spdid, NULL, 0, &dwSize, NULL);

    if ( dwSize!=0 && dwSize<=sizeof(Buf) ) {
      pspdidd->cbSize = sizeof(*pspdidd); // 5 Bytes!

      ZeroMemory((PVOID)&spdd, sizeof(spdd));
      spdd.cbSize = sizeof(spdd);

      long res =
        SetupDiGetDeviceInterfaceDetail(hDevInfo, &
                                        spdid, pspdidd,
                                        dwSize, &dwSize,
                                        &spdd);
      if ( res ) {
        HANDLE hDrive = CreateFile(pspdidd->DevicePath,0,
                      FILE_SHARE_READ | FILE_SHARE_WRITE,
                      NULL, OPEN_EXISTING, 0, NULL);
        if ( hDrive != INVALID_HANDLE_VALUE ) {
          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 ) {
              CloseHandle(hDrive);
              SetupDiDestroyDeviceInfoList(hDevInfo);
              return spdd.DevInst;
            }
          }
          CloseHandle(hDrive);
        }
      }
    }
    dwIndex++;
  }

  SetupDiDestroyDeviceInfoList(hDevInfo);

  return 0;
}
//---------------------------------------------------------

The parent device of the disk, floppy, or CD-ROM is the USB device to eject. CM_Request_Device_Eject shall be used for devices which have the SurpriseRemovalOK flag only. Otherwise, CM_Query_And_Remove_SubTree shall be used. See MSDN here and here.

As a result, the removed device is still present but has a problem code which is usually 47 (CM_PROB_HELD_FOR_EJECT). But if CM_Query_And_Remove_SubTree is used for a USB device (which has the SurpriseRemovalOK flag) then the problem code is 21 (CM_PROB_WILL_BE_REMOVED). This seems to be "safe" too since the USB device's drive and volume are gone then. The big difference here is that such a device can be reactivated! I have made a tool for this: RestartSrDev.

However, CM_Query_And_Remove_SubTree doesn't work for restricted users; it returns CR_ACCESS_DENIED in these cases, while the non-suggested CM_Request_Device_Eject works fine for restricted users. Surprisingly, CM_Request_Device_Eject does not work in a service or a GINA, here CM_Query_And_Remove_SubTree is the right choice.

When using CM_Query_And_Remove_SubTree, we have to add the flag CM_REMOVE_NO_RESTART because otherwise the just-removed device may be immediately redetected. This happens under Vista, but is also reported to happen under W2K and XP sometimes. It is documented as "Beginning with Windows XP" but the flag works (and is required) under W2K with SP4 too.

I take the easy way, and now use CM_Request_Device_Eject exclusively in this sample.

Discussion

If you use it for PATA drives, then both master and slave drives are removed! However, both can be brought back with a DEVCON RESCAN.

If the functions are called with NULL/0 for the veto parameters, then XP shows the "it's safe now" balloon tip, W2K shows a message box, and Vista shows nothing. As McCoy once said: "I know engineers. They love to change things."

I remember that I've seen CM_Query_And_Remove_SubTree and CM_Request_Device_Eject returning CR_SUCCESS even when the call failed with a veto under XP. I cannot reproduce it, but I'm sure I've seen this, maybe it was under XP RTM or SP1. Therefore, it seems to be better to check the veto values the functions return.

Under Windows 2000, the ANSI versions of both functions are not implemented. They return CR_CALL_NOT_IMPLEMENTED, so we use the Unicode versions instead.

Both functions usually take several seconds until they return, so it's a good idea to put them into their own thread. In fact, the delay can be up to 30 seconds under XP, and up to 15 seconds since Vista. This happens when a process has registered for receiving the removal request and does not return it. To keep this sample simple, there is no extra thread created for calling CM_Request_Device_Eject:

ULONG Status = 0;
ULONG ProblemNumber = 0;
PNP_VETO_TYPE VetoType = PNP_VetoTypeUnknown;
WCHAR VetoNameW[MAX_PATH];
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;

  res = CM_Request_Device_EjectW(DevInstParent,
          &VetoType, VetoNameW, MAX_PATH, 0);

  bSuccess = ( res==CR_SUCCESS &&
                    VetoType==PNP_VetoTypeUnknown );
  if ( bSuccess )  {
    break;
  }

  Sleep(500); // required to give the next tries a chance!
}

It's often seen that the removal fails on the first attempt but works on the second attempt. Therefore, I just try it three times.

What Makes the Removal Fail

The preparation for safe removal fails as long as there is one open handle to the disk or to the storage volume. And, of course, you cannot run this EXE from the drive to remove. To do that, you would need a temporary copy on another drive. ProcessExplorer is great for discovering which process holds an open handle to a drive. Press Ctrl+F and enter the drive letter, like U:. It's often seen that it cannot resolve drive letters, so you have to search for the DOS device name of the drive. It should be something like \Device\Harddisk4\DP(1)0-0+11. A significant part, such as 'disk4', is usually good enough. On occasions, however, even the driver-driven ProcessExplorer isn't able to find the nasty handle.

Reactivate a USB Drive after Safe Removal

When prepared for safe removal and having the problem code 47 (CM_PROB_HELD_FOR_EJECT), a USB device cannot be reactivated. The only way out is to deactivate and then reactivate the USB hub which it is connected to. This works with both standard hubs and root hubs. Of course, this cycles all USB devices attached to this hub.

The Demo Project

The demo project is made with VS6. It requires LIBs and headers from the Microsoft Windows Platform SDK and DDK/WDK. If you are using a newer Visual Studio, just try to compile it. If it complains about missing includes or LIBs, just get the latest SDK. If someting is still missing, it might come with the Windows Driver Kit (WDK): here.

Using Visual Studio 6.0

Many users still love to use VS6, me too. This is because it is slim, snappy, and installed in no time. In fact, for simple Win32 applications it's good enough to copy its folder and import its Registry settings. The disadvantages are the old compiler, missing x64 support, and the incompatibility with the new SDKs and DDKs/WDKs.

The latest SDK version that integrates and works perfectly with VS6 is from February 2003. It's called "Platform SDK for Windows Server 2003" (February 2003 Edition, Build 3790.0), and is still available for download at Microsoft, see here. Unfortunately, cfg.h and cfgmgr32.lib do not come with this SDK, even cfgmgr32.h has an include for cfg.h. cfg.h and the also missing cfgmgr32.lib are found in the Windows DDK. Both can be used from any DDK or WDK, even new versions, e.g., from WDK Build 6000. When building more complex projects, you will run into trouble since other header and lib files from newer SDKs and DDKs/WDKs will not work with VS6! The oldest DDK available for download is the "Windows Server 2003 SP1 DDK" (Build 3790.1830): 1830_usa_ddk.iso. Most files are compatible with VS6, but some lib files are not when compiling debug versions, for instance, uuid.lib. uuid.lib is no problem, since a compatible one comes with VS6. In such cases, just rename the new file to make VS6 use the old one from its own LIB folder. The right DDK for VS6 is the "Windows XP SP1 DDK" (Build 2600.1106), but this is not available for download at Microsoft.

If you don't want to download a whole DDK for a single file, then you can use CFG.h from the ReactOS project, which is at least compatible for this demo. Just put the downloaded cfg.h into the folder where the cfgmgr32.h is found.

The integration of the SDK+DDK headers and libs is usually done manually by entering them into the include and lib folder list.

Screenshots from a German VS6: Includes and Libs.

History

  • 15 Jan 2007 - Updated download
  • 27 Jan 2007 - Updated article and download; CM_Query_And_Remove_SubTree isn't used anymore
  • 16 May 2007 - Updated article and download; removed SetupDiEnumInterfaceDevice because it's just a define for SetupDiEnumDeviceInterfaces which has been called some lines before
  • 7 Nov 2007 - Fixed the hints about the required SDK for using VS6
  • 21 Jan 2009 - Some minor changes to the article
  • 30 March 2009 - Fixed the hints about the required SDK and DDK for using VS6
  • 15 Feb 2010 - Some minor changes to the article
  • 13 May 2010 - Some minor changes to the article
  • 16 May 2010 - Added some information about the resulting device problem codes
  • 6 April 2011 - Updated links to old SDK and DDK

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Uwe_Sieber
Software Developer
Germany Germany
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5membergndnet29 Oct '12 - 22:18 
good
QuestionI trying to use the sample code for HID GUID interface, but it not working!membernandikantir14 Jul '12 - 18:56 
I am trying to use the same sample code for HID GUID interface, but it not working.
I am trying to map drive letter to device number and then trying to get Driverkey Name of USB mass storage HID device, Do I need to do make any code changes to the above sample to make it work for USB HID Devices.
 
Thanks
AnswerRe: I trying to use the sample code for HID GUID interface, but it not working!memberUwe_Sieber15 Jul '12 - 21:39 
So, you have an USB device which is mass storage and HID device?
Then the partent/child relations probably look like this:
 
USB device
-> DISK
-> HID
 
Use the existing code for getting the USB device. Then do an additional enumertion for HID devices, get the DevInst of each, call CM_Get_Parent to get their parents's DevInst. Match this with the USB device's DevInst.
GeneralRe: I trying to use the sample code for HID GUID interface, but it not working!membernandikantir16 Jul '12 - 12:47 
Thanks for the reply,
In my case parent is a rack with number of slots, So parent is common to all the devices I plugin. I can plug in a USB device which is a mass storage and HID device,
and also additionally other HID/USB devices.
The only input parameter I am getting is drive letter, using this I need to get driverkey name of the plugin device.
QuestionGetting different driver key names for each of these guidsmembernandikantir14 Jul '12 - 1:51 
I am trying to get the USB driver keyname, but why I am getting differerent
driverkey names when change for following guid types, why?

guid = &GUID_DEVINTERFACE_VOLUME;
guid = &GUID_DEVINTERFACE_DISK;
HidD_GetHidGuid(&hidGuid);
 
My purpose is to get DriverKey name of the USB plugged in
 
I am getting drive letter as a parameter,
using that I get device number using IOCTL_STORAGE_GET_DEVICE_NUMBER.
 
it then enumerates all disks, using the setup APIs.
The device numbers of the drives are matched with the device number mentioned above in order to get the device instance
of the correct drive.

Using SP_DEVINFO_DATA and hDevInfo I call SetupDiGetDeviceRegistryProperty to get DriverKey name.
 
I getting different driver key names for each of these guids
 
guid = &GUID_DEVINTERFACE_VOLUME;
guid = &GUID_DEVINTERFACE_DISK;
HidD_GetHidGuid(&hidGuid);
 
Application is using HidD_GetHidGuid(&hidGuid) when they are adding device object to the currentDevice object list.
When I use one of these guids, the driver key name I am getting does not match with one in currentDevice object list
guid = &GUID_DEVINTERFACE_VOLUME;
guid = &GUID_DEVINTERFACE_DISK;
AnswerRe: Getting different driver key names for each of these guidsmemberUwe_Sieber15 Jul '12 - 21:30 
I've dealed with existing devices only.
 
Maybe enumeating for all devices it the right choice here, like so:
 
 
int res;
CONFIGRET cret;
char szProperty[512];
SP_DEVINFO_DATA devInfoData;
HDEVINFO hDevInfo = SetupDiGetClassDevs(NULL, 0, 0, DIGCF_ALLCLASSES | DIGCF_PRESENT);
 
devInfoData.cbSize = sizeof(devInfoData);
 
for ( int idx=0; true; idx++ ) {
 
  res = SetupDiEnumDeviceInfo(hDevInfo, idx, &devInfoData);
  if ( !res ) {
    break;  // Exit For
  }
 
  res = CM_Get_Device_ID(devInfoData.DevInst, szProperty, sizeof(szProperty), 0);
  if ( res == CR_SUCCESS ) {
    printf("Device ID          = %s\n", szDeviceIdString);
  }
 

  res = SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfoData,
                                         SPDRP_DRIVER, &dwType,
                                         (PBYTE)szProperty, sizeof(szProperty),
                                         NULL);
  if ( res ) {
    printf("SPDRP_DRIVER       = %s\n", szProperty);
  }
}
SetupDiDestroyDeviceInfoList(hDevInfo);
 


QuestionWhy I am getting differerent driverkey names when I change guid type?membernandikantir13 Jul '12 - 17:07 
I am trying to get the USB driver keyname, but why I am getting differerent
driverkey names when change for following guid types, why?
 
guid = &GUID_DEVINTERFACE_VOLUME;
guid = &GUID_DEVINTERFACE_DISK;
HidD_GetHidGuid(&hidGuid);
AnswerRe: Why I am getting differerent driverkey names when I change guid type?memberUwe_Sieber15 Jul '12 - 21:08 
Each device has a unique drive key name. If you enumerate for a different device interface then you get differenct devices which of course have their own driver key names.
QuestionNicememberCIDev6 Jun '12 - 3:00 
Nicely written and useful article.
Just because the code works, it doesn't mean that it is good code.

Questionvery goodmembercheryXP16 Apr '12 - 22:12 
very good
Questionn2012 comes, in order to thank everyone, characteristic, novel style, varieties, low price and good quality, and the low sale price. Thank everyone ==== ( http://www.fullmalls.com ) ===== ==== ( http://www.fullmalls.com ) ===== $33 True Religiongroupkjlfdgehe9 Mar '12 - 15:28 
n2012 comes, in order to thank everyone, characteristic, novel style, varieties, low price and good quality, and the low sale price. Thank everyone
 

==== ( http://www.fullmalls.com ) =====
 
==== ( http://www.fullmalls.com ) =====
 

$33 True Religion jeans, Ed Hardy jeans,LV,Coogi jeans,Affliction jeans
 
$30 Air Jordan shoes,Shox shoes,Gucci,LV shoes
 
50%Discount winter fashion :Sandle,t-shirt,caps,jerseys,handbag and brand watches!!!
 
$15 Ed Hardy ,LV ,Gucci Bikini
 
$15 Polo, Ed Hardy, Gucci, LV, Lacoste T-shirts
 
$25 Coach,Gucci,LV,Prada,Juicy,Chanel handbag,
 
$10 Gucci,Ed Hardy sunglasses
 
$9 New Era caps.
 
give you the unexpected harvest
 
==== ( http://www.fullmalls.com ) =====
 
==== ( http://www.fullmalls.com ) =====
 
==== ( http://www.fullmalls.com ) =====
 
==== ( http://www.fullmalls.com ) =====
 
==== ( http://www.fullmalls.com ) =====
 
==== ( http://www.fullmalls.com ) =====
GeneralMy vote of 5memberJacques Abada8 Feb '12 - 8:36 
Very useful code dealing with poorly documented Windows functions.
QuestionReConnect Usb [modified]membersabareesh.t17 Aug '11 - 19:34 
Hello
How can reconnect the usb drive based on the given DeviceId;
 

modified on Friday, August 19, 2011 2:41 AM

AnswerRe: ReConnect UsbmemberUwe_Sieber17 Aug '11 - 20:06 
Check out how the Microsoft devcon tool performs its restart command.
QuestionCM_Query_And_Remove_SubTree with messagebox (W2K, Vista) or balloon (XP) [modified]membersabareesh.t17 Aug '11 - 6:40 
I ejected usb using CM_Query_And_Remove_SubTree but the MessageBox is not displaying("Safe to remove").
I am using this method for later i want to re-enable the device.
 
How can i use CM_Query_And_Remove_SubTree method with MessageBox
 

 
-- Modified Friday, August 19, 2011 1:25 AM
AnswerRe: CM_Query_And_Remove_SubTree with messagebox (W2K, Vista) or balloon (XP)memberUwe_Sieber17 Aug '11 - 9:28 
Works for me as described. What is your OS and your code?
GeneralRe: CM_Query_And_Remove_SubTree with messagebox (W2K, Vista) or balloon (XP) [modified]tingmembersabareesh.t17 Aug '11 - 17:35 
My operating system is XP.
 
My code as follows.
 
 

			// USBデバイスを取り外します。(1回試す)
			for(INT nCount = 0; nCount < 1; nCount++)
			{
				// with messagebox (W2K, Vista) or balloon (XP)
				cr = CM_Query_And_Remove_SubTree(objDevInfoData.DevInst, NULL, NULL, 0, 0);
				if(cr == CR_SUCCESS)
				{
					bUSBEjected = TRUE;
					break;
				}
 
				Sleep(SLEEP);
			}
 

modified on Thursday, August 18, 2011 12:04 AM

GeneralRe: CM_Query_And_Remove_SubTree with messagebox (W2K, Vista) or balloon (XP) [modified]tingmemberUwe_Sieber17 Aug '11 - 19:52 
I had tried it with my tool RemoveDrive and here it worked. I've checked the code and there is indeed a trick involved. First perform the silent removal, then call CM_Request_Device_Eject to get the balloon. The device's problem code remains 21, so reactivating still works.
 
res = CM_Query_And_Remove_SubTreeW(DevInstRemovable, &VetoType, VetoNameW, MAX_PATH, ulFlags);
if ( bShowBalloon && res==CR_SUCCESS && VetoType==PNP_VetoTypeUnknown ) {
    res = CM_Request_Device_EjectW(DevInstRemovable, NULL, NULL, 0, 0); // let Windows show the balloon
}

GeneralRe: CM_Query_And_Remove_SubTree with messagebox (W2K, Vista) or balloon (XP) [modified]tingmemberSarath.tirur18 Aug '11 - 22:42 
Hello,
My code as follows. i applied like this but still Problem 47 is coming............
 
PNP_VETO_TYPE VetoType;
CHAR VetoName[MAX_PATH];
 
cr = CM_Query_And_Remove_SubTree(objDevInfoData.DevInst,&VetoType,VetoName,sizeof VetoName, 0);
 
if(cr == CR_SUCCESS && VetoType==PNP_VetoTypeUnknown)
{
cr = CM_Request_Device_EjectW(objDevInfoData.DevInst, NULL, NULL, 0, 0);
bUSBEjected = TRUE;
break;
}
Questionwhy the code is not working under WIN7membermarkfilan20 Jul '11 - 21:06 
hi Uwe:
I used the codes you posted and made some necessary changes,but i found out that under WIN7 X64 is not working ,the error message i got is:the "drect   memory access controller" device   is not removable and can not be ejected or unplugged.why?can you give me a basic direction about why is message appeared?thanks ~
FYI: the device i want to eject is a calculaor.
AnswerRe: why the code is not working under WIN7memberUwe_Sieber21 Jul '11 - 10:52 
Finally it's the device's driver which grants or rejects the request, so the driver of your device just does not support safe removal. I think it is not specific to Win7 x64, it is specific to the driver of the device.
 
How do you get this error string?
 

Uwe Sieber
GeneralRe: why the code is not working under WIN7membermarkfilan21 Jul '11 - 17:31 
i change the parameters in the cm_request_device_eject like this:
cr = CM_Request_Device_EjectW(DeviceInfoData.DevInst,&amp;pnpvietotype,NULL,0,0);
wheni start to do eject the device ,it pop a messagebox include the message i said before .so that is not a peoblem of system, it's the driver ,i need to make the driver can suppot safe removal .but how to ?can you give me a example ?I'm a new learner of usb field.thank very very much~
GeneralHow to list processes that locked USB?memberivanmrk2 May '11 - 5:50 
It would be very convenient to tell user which processes block safely removal. Sometimes let say Firefox opens some file and disable safely removal.
 
Btw. I had problems translating your code in Delphi cause lots of functions in setupapi.pas (interface) file are not good mapped. I thought that my code is not working, but actually both code worked excellent and thanks to your sample I found out problems!!
 
If someone knows if it's possible to map processes that locked files or entire drive I would be very thankful.
 
Ivan
GeneralRe: How to list processes that locked USB?memberUwe_Sieber2 May '11 - 8:48 
I've tried this in my tools RemoveDrive and EjectMedia using some undocumented ntdll.dll calls.
 
Google for NtQuerySystemInformation, NtQueryObject.
 
The main problem is that you don't know the type of handle before calling NtQueryObject. But NtQueryObject does not return if you hit a handle of a waiting sync object e.g. a mutex. If this happens then the calling thread is stuck in the kernel, TermitateThread cannot kill it, even killing the process does not work.
 
Tools as the Sysinternal ProcessExplorer or Cedrick Collomb's Unlocker use a kernel driver to get around this problem.
 

Uwe
GeneralRe: How to list processes that locked USB?memberivanmrk3 May '11 - 4:57 
On MSND stands this:
[NtQuerySystemInformation may be altered or unavailable in future versions of Windows. Applications should use the alternate functions listed in this topic.]
 
But that is not the problem, but idea, cause I don't know where to start and what to do. How to get handle list of all processes that effect on safely remove etc.
 
>> If this happens then the calling thread is stuck in the kernel, TermitateThread cannot kill it, even killing the process does not work.
 
This could be a problem but I will try to solve this somehow.
 
Sincerely,
Ivan
GeneralRe: How to list processes that locked USB?memberUwe_Sieber4 May '11 - 20:47 
Google gives some nice hits:
http://www.google.de/search?q=NtQuerySystemInformation+NtQueryObject+delphi
 
Especially this looks interesting for Delphi developers:
http://sites.google.com/site/delphibasics/home/delphibasicssnippets/listallopenhandlesfromopenprocesses
 
Match the name of all found handles with
- the drive's drive letter, e.g. X:\
- the drive's NTFS mountpoints, e.g. C:\USB-Drives\Kingston Mini
- the drive's volume name, e.g. \\?\Volume{8c65aac8-5385-11e0-ba8e-001279a2959a}\
- the drive's volume DosDeviceName, e.g. \Device\Harddisk4\DP(1)0-0+45
 
Uwe
GeneralMy vote of 5memberGAG [Jaguar]12 Apr '11 - 0:38 
Nice explanation with details and analysis
GeneralMy vote of 5membercamilohe8 Apr '11 - 3:41 
Very good explanation with references. Several updates to cover gotchas. Working code sample with build instructions.
Questionhow to determine if a drive is a floppy [modified]memberlomo7431 Mar '11 - 23:20 
Hi Uwe,
thank you for this great article. Digging into the problem of determining if a removable drive is a floppy, I found this thread:
http://www.techtalkz.com/microsoft-device-drivers/244257-how-determine-removable-drive-floppy-drive.html[^]
I'm going to test the IOCTL_DISK_GET_DRIVE_GEOMETRY DeviceIoControl call --
cheers from Italy - Lorenzo -
 
-- EDIT --
well, seems it works... if you were looking for a better way to detect a floppy, here it is.
works even if floppy drive is empty or media is badly formatted or corrupted --
 
bool VolumeIsFloppy(HANDLE hVolume)
{
DISK_GEOMETRY geom;
DWORD dwBytesReturned = 0;
 
long res = DeviceIoControl(hVolume, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &geom, sizeof(geom), &dwBytesReturned, NULL);
 
if ( res )
return ( geom.MediaType != RemovableMedia ) && ( geom.MediaType != FixedMedia );
else
return GetLastError() == ERROR_NOT_READY;
}

modified on Friday, April 1, 2011 6:03 AM

AnswerRe: how to determine if a drive is a floppymemberUwe_Sieber5 Apr '11 - 21:03 
Interesting idea. Have you tried it? My concern is that the drive is really accessed, so a floppy drive would "rattle"...
 
Uwe
GeneralRe: how to determine if a drive is a floppymemberlomo745 Apr '11 - 21:17 
Yes, I tried it, and it works fine for me.
The drive does not rattle, but I don't know if some sort of "cache" is involved in this...
I'm not exactly a "Windows internals" expert... but my opinion is that the call is actually querying the geometry of the *drive* rather than the geometry of the *disk*.
I'm looking for some old 720k single sided floppy, I'm sure I have some in my loft. Let's see what the OS reports: this should tell us if the disk is phisically accesed or not, do you agree?
ciao - Lorenzo -
GeneralRe: how to determine if a drive is a floppymemberlomo746 Apr '11 - 22:44 
ok I made some tests with a 720K diskette.
1) the call to IOCTL_DISK_GET_DRIVE_GEOMETRY reports MediaType = F3_720_512 so it is reporting the geometry of the disk, not that of the drive (what on earth is "drive geometry", afterall? maybe I drank too much whisky WTF | :WTF: );
2) the drive is phisically accessed only if it wasn't accessed previously; I think there is some sensor in the drive that detects media change, so phisical access is unnecessary unless disk geometry wasn't read yet since the last media change;
3) ok, the drive actually "rattles" in the described circumstances;
4) anyway, I observe that Windows itself has the same behaviour when asked to remove the USB floppy through the tray icon.
in conclusion, I think we're on the right track.
by the way: I'm (also) a Delphi fan, so I ported your code to Delphi. are you interested in publishing it alongside the C++ one?
ciao - Lorenzo -
GeneralCM_Query_And_Remove_SubTreeW returns PNP_VetoOutstandingOpen when tried to remove the device.membersahayajonijilna21 Mar '11 - 20:06 
I have used the above code to remove the PCI drive and found the following errors.
1. If CM_Query_And_Remove_SubTreeW function is used , it returns PNP_VetoOutstandingOpen when tried to remove the device.
2. If CM_Request_Device_EjectW function is used , it returns PNP_VetoIllegalDeviceRequest when tried to remove the device.
 
Please tell me how to solve these issues
GeneralRe: CM_Query_And_Remove_SubTreeW returns PNP_VetoOutstandingOpen when tried to remove the device.memberUwe_Sieber21 Mar '11 - 20:41 
What is a "PCI drive"?
 
PNP_VetoOutstandingOpen means there is at least one open handle, and
PNP_VetoIllegalDeviceRequest "The device does not support the specified operation".
 
http://msdn.microsoft.com/en-gb/library/ff549728.aspx
 
So, whatever it is, I suspect that the driver of the drive's parent device just does not support safe removal.
 
You can try to prepare the drive itself for safe removal (don't call CM_Get_Parent, just use the drive's DevInst).
 

Uwe Sieber
GeneralRe: CM_Query_And_Remove_SubTreeW returns PNP_VetoOutstandingOpen when tried to remove the device.membersahayajonijilna21 Mar '11 - 22:37 
1. I tried CM_Query_And_Remove_SubTreeW with drive's DevInst and observed the following state.

The drive has been disappeared from the Disk Management. But in DeviceManager the drive is displayed with yellow icon(the properties of the drive displays that "Windows is uninstalling this device. (Code 21)"). What to do to completely remove the drive from DeviceManager.
 
2. I tried CM_Request_Device_EjectW with drive's DevInst, it displays the PNP_VetoIllegalDeviceRequest "The device does not support the specified operation". What has to be done from driver to support safe removal ?
GeneralMy vote of 5memberRobertREvans7619 Jan '11 - 6:35 
Very complete; information difficult to derive from MSDN documentation [sic]
GeneralWindows 7membermegraham@msn.com13 Sep '10 - 11:35 
You use CM_Request_Device_Eject to eject the drive, which should logically and physically eject the drive.
 
It physically ejects the drive in XP, but not in Windows 7.   The drive cable must be manually removed for the USB device to register a disconnect.   How can I send a physical disconnect in Windows 7 to the USB drive?
GeneralRe: Windows 7memberUwe_Sieber21 Mar '11 - 20:47 
Sorry, seems I missed the notification about your message...
 

I think you refer to the new behaviour since Vista, which leaves USB devices powered after safe removal.
 
This can be changed to the old behaviour either system wide or device specific:
http://support.microsoft.com/kb/2401954
 

Uwe Sieber
QuestionVery nice! But how to forcefully remove a USB flash drive?memberahmd098 May '10 - 10:59 
Thanks alot for this nice code!!!
 
But I have a question. Very often something gets corrupted in the system or in one of the programs that uses this USB device and no matter what you do it won't be ejected, CM_Request_Device_Eject returns CR_REMOVE_VETOED.
 
I'm curious, is there any way to forcefully remove a USB Flash Drive (well, aside from physically yanking it out of the slot), via some kernel API call? Maybe some undocumented one? And if no, is there any way to know what process is holding a reference to this device that it preventing it from being ejected?
AnswerRe: Very nice! But how to forcefully remove a USB flash drive?memberUwe_Sieber9 May '10 - 7:18 
The returned veto name gives a hint if it is the volume or the disk which prevents the removal.
Mapping this to its "dos device name" you can use the SysInternals ProcessExplorer to search for an open handle.
 
To deal with foreign processes and their handles properly you have to go into the kernel mode as the ProcessExplorer or the UnLocker tool do.
I have no experience with that...
 
Uwe
GeneralRe: Very nice! But how to forcefully remove a USB flash drive?memberSergey Podobry7 Jun '10 - 23:23 
I think you can try to force dismount a volume on the flash drive. Use FSCTL_DISMOUNT_VOLUME for that.
GeneralRe: Very nice! But how to forcefully remove a USB flash drive?memberUwe_Sieber21 Mar '11 - 20:49 
FSCTL_DISMOUNT_VOLUME dismounts the file system, open handles are invalidated but they are not closed, so, safe removal will still fail.
 
Uwe Sieber
QuestionIs there an easy way to get that cfg.h?memberdc_20006 May '10 - 8:53 
The link given above gives the contents of the cfg.h file on the screen with line numbers in it. Why not just include it along with the .lib file in this download???
AnswerRe: Is there an easy way to get that cfg.h?memberUwe_Sieber6 May '10 - 19:37 
An SDK is required anyway and the newer SDK (since Build 6000) come with the cfg.h.
Using it from ReactOS is the final fallback only for developers who are using VS6 and never have downloaded a newer SDK, which everyone should have at least to have a look on it...
The VS6 editor supports column blocks. So just paste it with the line numbers, place the cursor at the begin, press and hold the Alt key, click and drag over the numbers and the space column, release mouse and Alt key, press delete, save as cfg.h. Easy enough Smile | :)
 
Uwe Sieber
GeneralGood ArticlememberAnt210015 Feb '10 - 1:05 
Hey,
 
Thanks, this is a good article =)
 
Anthony
Check out my unit conversion software for Windows - Universal Converter

Free Toolbar for all your needs! - Universal Converter Toolbar

GeneralDifferences between RemoveDrive and RemoveDriveByLettermemberMatt Chessum13 Dec '09 - 20:13 
Hello Uwe
 
You have released some excellent code and it is helping me a lot. I found a difference in your two programs, where RemoveDrive can remove a SATA device, but RemoveDriveByLetter cannot.
 
Could you please tell me what the difference is between the two programs?
 
Thank you! I have included the logs below to show what is going on.
 

 
D:\RemoveDriveByLetter\Debug>RemoveDriveByLetter.exe f:
failed
Result=0x17
VetoName=PCIIDE\IDEChannel\4&3adfead3&0&0)
 

 
C:\RemoveDrive\x64>removedrive f:
 
RemoveDrive V1.9 (x64) - prepares drives for safe removal
Freeware by Uwe Sieber - www.uwe-sieber.de
Removing Drive 'F:\' (WDC WD7500AACS-65D6B0 ATA Device)
 
success
GeneralRe: Differences between RemoveDrive and RemoveDriveByLettermemberUwe_Sieber18 Dec '09 - 8:28 
RemoveDrive checks if the drive's parent device is removable. If not then it removes the drive itself instead of its parent device.
The check for beeing removable:
 
CM_Get_DevNode_Status(&Status, &ProblemNumber, DevInst, 0);
IsRemovable = ((Status & DN_REMOVABLE) != 0);
 

Uwe
QuestionDriveCleanupmemberR-Zett15 Jul '09 - 22:27 
Hi Uwe
 
This smal sample is a powerful help for my private work. Thanks for sharing it. Checking the other discussions and your home page I found a additional very helpfull tool called DriveCleanup. I my special case it make sense to use both, so do you also share the code of this DriveCleanup tool. Than I'm able to combine it in one software I'm just building for my private use. Will be great.
 
Greetings from Germany
 
Raimund
AnswerRe: DriveCleanupmemberUwe_Sieber19 Jul '09 - 4:44 
No, I'm sorry.
 
Uwe
GeneralFlaw -- according to Microsoftmembercodeproject4sba22 Jun '09 - 3:28 
Hi Uwe,
 
While discussing an issue related to drive *insertion* sometimes requiring a reboot with a Microsoft support engineer, I was supplied with the following information:
 
QUOTE
I think the DriveCleanup tool (http://www.uwe-sieber.de/drivetools_e.html ) may have the defect that it doesn't call co-installer to uninstall driver, hence sometimes when re-installing USB storage device, device vetoes removal failed, in the end the installation finished with marking DI_NEEDREBOOT flag.
UNQUOTE
 
QUOTE
DriverCleanup calls CM_xxx API's to remove device, such as CM_Query_And_Remove_SubTree. This is bad, because CM_XXX API's bypass class/co-installers.
 
Co-installer sets Flags member of the SP_DEVINSTALL_PARAMS structure for the device with the DI_NEEDREBOOT flag, and the installer also notifies the user that a system restart is required.
 

The correct method is to invoke DIF_REMOVE, which is shown in Devcon. You may check the codes in WDK(\WinDDK\6001.18002\src\setup\devcon -> cmds.cpp->
RemoveCallback())
UNQUOTE
 
What do you think?
 
Grüße;
 

P.S. I'll share the final word on "why does XP sometimes prompt for a reboot" and how to avoid it.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 9 Mar 2012
Article Copyright 2006 by Uwe_Sieber
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid