|

Introduction
Although removing a USB stick using the Windows shell is quite easy, it tends to be quite difficult programmatically. There are a lot of underlying notions, at the frontier of kernel driver development, one has to understand to achieve what may seem a simple task. When I started this work, I really did not think where it would lead me to. I sure did not think I would have to switch between kernel driver control codes, the Windows setup and Configuration Manager APIs, WMI, etc...
Well, this is the main reason for this article, I think the subject really deserves one. The result is a demo .NET Winforms project that includes reusable source code.
And by the way, as a side benefit, I will also explain how to read the hardware serial number of those disks, a recurring question on newsgroups and forums.
Background
First of all, let's talk about the various Windows API involved here, apart from Win32, and there are many:
- The Windows DDK (Driver Development Kit): A driver development kit that provides a build environment, tools, driver samples, and documentation to support driver development for Windows.
- The
Setup API: An underused and not very well known Windows API. One of the reasons it is underused is because it is part of the DDK, but in fact, these are general Setup routines, which are used in installation applications, and there are many very interesting gems in there, like the CM_Request_Device_Eject function, which is the heart of the UsbEject project.
- The
DeviceIoControl function: The "Open, Sesame" user mode function to the depths of kernel mode cave. Tons of things (a large part of it is undocumented, or not well document) can be done using this.
- WMI (Windows Management Instrumentation): The
UsbEject project does not actually uses WMI because WMI does not handle device ejection, as far as I know. I could have used a hybrid approach using WMI for disk management and the rest for device Ejection. I will leave this as an exercise to the reader.
- Windows Messages: The
WM_DEVICECHANGE Window message is used in this sample GUI application to visually refresh the tree when something happens to the device manager tree.
Now, let's introduce a few terms. The definitions here are my own, not official ones (it is actually quite hard to find official definitions for all these).
- Physical Disks: Well, as the name implies, it is the real piece of hardware an end user manipulates. In the case of a USB disk, it is the stick itself.
- Volumes: There are actually two types of volumes: volumes as they are understood by Windows, and volumes as we know them, that is, drive letters, from A: to Z:, also called logical disks. A volume can span multiple physical disks.
- Devices: The Windows DDK defines both volumes and physical disks as being devices. The code reflects this. Device is a base type, and Volume derives from it.
Using the code
The project is a Visual Studio 2005 (.NET 2.0) Windows form project. There is a folder called Library that contains the heart of it, an object oriented API to handle USB disks. The classes there are described in the following class diagram:

As you can see, there are 5 main classes (each class is defined in its own respective .cs file):
DeviceClass: An abstract class that represents a physical device class. It has a list of devices in this class
DiskDeviceClass: Represents all disk devices in the system
VolumeDeviceClass: Represents all volume devices in the system
Device: Represents a generic device of any type (disk, volume, etc...). Note there is no Disk class, because in this project, a Disk has no specific property, compared to a Device. Note also the code has been designed so it could be extended to other devices, not only volumes and disks
Volume: Represents a Windows volume. A volume has a LogicalDrive property which is filled if it has a drive letter assigned
This is how you can eject all USB volumes for example:
VolumeDeviceClass volumeDeviceClass = new VolumeDeviceClass();
foreach (Volume device in volumeDeviceClass.Devices)
{
if (!device.IsUsb)
continue;
if ((device.LogicalDrive == null) || (device.LogicalDrive.Length == 0))
continue;
device.Eject(true);
}
Points of Interest
CM_Request_Device_Eject function
This is the SetupApi function that ejects a device (any device that can be ejected). It takes a device instance handle (or devInst) as input. The function behaves differently if the second argument pVetoType is provided or not. If it is provided, the Windows shell will not present the end user with any dialog, and the eject operation will either succeed or fail silently (with an error code, called the Veto). If not, the Windows shell may display the traditional messages or dialog boxes, or balloon windows to inform the end user (Note: Windows 2000 may always display a message under certain circumstances, even when the second argument is not provided) that something interesting has happened.
The main problem is therefore "For a given disk letter, what is the device to eject?"
The device to eject must be a Disk device, not a Volume device (at least for USB disks). So the general algorithm is the following:
- Determine all available logical disks, using .NET's
Environment.GetLogicalDrives.
- For each logical disk (drive letter), determine the real volume name, using Win32's
GetVolumeNameForVolumeMountPoint.
- For each volume device, determine if there is a matching logical disk.
- For each volume device, determine physical disk devices composing (using their number) the volume device (as there may be more than one physical disk per volume), using the DDK's
IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS IO control code.
- Because the Plug-And-Play (PNP) Configuration Manager builds a device hierarchy, a physical disk device is not in general the device to eject, so for each physical disk device, determine what is the device to eject among its ascendant devices. The device to eject is the first one in the hierarchy with the
CM_DEVCAP_REMOVABLE capability.
GetVolumeNameForVolumeMountPoint function
To match Windows volumes with logical disks (algorithm step 3), there is a trick to know. First of all, let me say that all Windows volume can be uniquely addressed with a specific syntax of the form "\\?\Volume{GUID}\" where GUID is the GUID that identifies the volume.
When enumerating volumes devices (using the Volume Device Class Guid GUID_DEVINTERFACE_VOLUME, do not worry, the VolumeDeviceClass wraps it), the volume device path can be used directly in a call to GetVolumeNameForVolumeMountPoint, if "\" is appended to it, although it is not strictly speaking a volume name.
Enumerating all devices for a given class
I have reproduced here a code snippet that shows how .NET P/Invoke interop is used to enumerate all physical devices for a given type.
int index = 0;
while (true)
{
SP_DEVICE_INTERFACE_DATA interfaceData = new SP_DEVICE_INTERFACE_DATA();
if (!SetupDiEnumDeviceInterfaces(
_deviceInfoSet, null, ref _classGuid, index, interfaceData))
{
int error = Marshal.GetLastWin32Error();
if (error != Native.ERROR_NO_MORE_ITEMS)
throw new Win32Exception(error);
break;
}
SP_DEVINFO_DATA devData = new SP_DEVINFO_DATA();
int size = 0;
if (!SetupDiGetDeviceInterfaceDetail(
_deviceInfoSet, interfaceData, IntPtr.Zero, 0, ref size, devData))
{
int error = Marshal.GetLastWin32Error();
if (error != Native.ERROR_INSUFFICIENT_BUFFER)
throw new Win32Exception(error);
}
IntPtr buffer = Marshal.AllocHGlobal(size);
SP_DEVICE_INTERFACE_DETAIL_DATA detailData =
new SP_DEVICE_INTERFACE_DETAIL_DATA();
detailData.cbSize = Marshal.SizeOf(
typeof(Native.SP_DEVICE_INTERFACE_DETAIL_DATA));
Marshal.StructureToPtr(detailData, buffer, false);
if (!SetupDiGetDeviceInterfaceDetail(
_deviceInfoSet, interfaceData, buffer, size, ref size, devData))
{
Marshal.FreeHGlobal(buffer);
throw new Win32Exception(Marshal.GetLastWin32Error());
}
IntPtr pDevicePath = (IntPtr)((int)buffer + Marshal.SizeOf(typeof(int)));
string devicePath = Marshal.PtrToStringAuto(pDevicePath);
Marshal.FreeHGlobal(buffer);
index++;
}
Refreshing the UI when a disk is inserted or removed
There are multiple ways of doing this. I have chosen the most simple one: capture the WM_DEVICECHANGE Windows message. You just have to override the default Window procedure of any Winform in your application, like this:
protected override void WndProc(ref Message m)
{
if (m.Msg == Native.WM_DEVICECHANGE)
{
if (!_loading)
{
LoadItems();
}
}
base.WndProc(ref m);
}
A word on Serial Numbers
This is not strictly speaking related to the Eject subject, but I have seen many questions about this too, so I will talk a bit about hard disk's "serial numbers". When speaking about Windows volumes and disks, there are actually (at least) two serial numbers:
- A volume software serial number, assigned during the formatting process. This 32bits value can be read easily using the
GetVolumeInformation regular Win32 function.
- A disk vendor's hardware serial number: This serial number is setup by the vendor during the manufacturing process. It's a string. Of course, it cannot be changed. Unfortunately, you have to know that serial numbers are optional for USB devices, so USB storage sticks may not have one, and indeed, many do not.
Ok, so the question is "how do I read the hardware serial number?". There are at least two ways:
- WMI: By far, the easiest way, although it looks like black magic first. Here is an example of such a code (not provided in the project .zip package).
foreach(ManagementObject drive in new ManagementObjectSearcher(
"select * from Win32_DiskDrive where InterfaceType='USB'").Get())
{
foreach(ManagementObject partition in new ManagementObjectSearcher(
"ASSOCIATORS OF {Win32_DiskDrive.DeviceID='" + drive["DeviceID"]
+ "'} WHERE AssocClass =
Win32_DiskDriveToDiskPartition").Get())
{
Console.WriteLine("Partition=" + partition["Name"]);
foreach(ManagementObject disk in new ManagementObjectSearcher(
"ASSOCIATORS OF {Win32_DiskPartition.DeviceID='"
+ partition["DeviceID"]
+ "'} WHERE AssocClass =
Win32_LogicalDiskToPartition").Get())
{
Console.WriteLine("Disk=" + disk["Name"]);
}
}
Console.WriteLine("Serial="
+ new ManagementObject("Win32_PhysicalMedia.Tag='"
+ drive["DeviceID"] + "'")["SerialNumber"]);
}
- Using raw techniques described here. I have not ported this to C# although it is perfectly feasible.
Remarks
- The source code has been designed using the .NET Framework 2.0, but should work on the .NET framework 1.1, although this has not been tested. I do not think it can work on 64 bits CLR as is because of interop structs size that may vary. It could be ported quite easily though.
- The code has been tested on Windows XP SP2, and Windows Server 2003, not on Windows 2000, but it should work. I don't think the code can work or can be ported on Windows 9x.
- I have not researched the security implications of all this. In particular, the Windows user calling the API must obviously have some rights to be able to access physical devices. I have left these experiments as an exercise for the reader.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 49 (Total in Forum: 49) (Refresh) | FirstPrevNext |
|
|
 |
|
|
Brilliant example code and reusable code! I was horrified when it would not run on my win xp pro 64bit machine, but it works if you set the compile options to x84.
Any idea why it fails to work with 64bit?
not suitable for idiots
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Is there a why to rediscover (mount) a usb drive that has been ejected, but is still inserted into a usb port?
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Hi,
I don't want to use Device struct for ejecting. Please tell me know how can I eject using drive letter ? Cab you show me source demo about that ?
Thanks !
|
| Sign In·View Thread·PermaLink | 1.00/5 (2 votes) |
|
|
|
 |
|
|
 |
|
|
I have problem with floppic drive. When I'm getting devices, my floppic drive try to destroy silent arround my computer).
Using your standart code for ejecting all usb devices.
Do you have same problem?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi
I tested the application with Win XP Sp.2. It works great. But It doesn't work with Windows Vista. Why?
|
| Sign In·View Thread·PermaLink | 1.00/5 (2 votes) |
|
|
|
 |
|
|
This is quite a feat of programming - well done. Question though - when you click on a node of the tree, the code goes through all of the same steps done originally to get the "friendly name" of the device again. Why do that when you already have it's friendly name displayed on the tree?
Just curious... thanx
chasbabb
|
| Sign In·View Thread·PermaLink | 1.00/5 (2 votes) |
|
|
|
 |
|
|
We really needed to be able to programmatically remove a USB drive safely and found very little that was reliable on the subject. This is ideal for us - being in C# etc. Thanks you for putting the effort into this and publishing it. Also thanks to CodeProject for one again being the place to find such good quality articles. Many Thanks.
Chris
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
it does not detect the usb device. the .exe is detecting the flash drive as a temp disk is created.
but when i try to insert my usb camera which does not mount a disk - the .exe does not detect
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello Simon,
thank you for your great article. But I think, I have found a mistake. I don't know, how I can resolve this. In this code:
VolumeDeviceClass volumeDeviceClass = new VolumeDeviceClass(); foreach (Volume device in volumeDeviceClass.Devices) { ... }
memory is occupied, but is not no more released. Why? Where is the problem ? You can see this in the Windows Task-Manager, when you repeatedly click on the Refresh.
Thank you for your help and sorry for my poor English...
Greetings Patrick
-- modified at 6:33 Thursday 19th July, 2007
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
I want to detect multiple usb cameras but this code gives me only usb storage devices .. can anybody throw any light on this ?
Cheers DJ
"Technical Skill is the mastery of complexity, while Creativity is the master of presence of mind"
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I found your article to very helpful. I have recently been working with USB virtual com ports. They have a tendency to hold onto the com port after being pulled from the cable port. Can your process be adapted to eject the USB virtual com port?
Thanks alot in advance.
dwlyman
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Its a nice article...
I have another qurey.... How can i enable & disable USB ports from a .NET application, which runs under a non administrator login..
i have developed a desktop based application using C#.NET, which require access to some USB based devices.
I have to use this application on target PCs in which the USB port is disabled by the system administrator.
From the application, i wants to enable & disable the USB drives programatically, so that no other application can use the USB ports, as per configured by the system administrator.
ie, enable the USB port before the application require access to the USB port it & disable it after the use. I need to achieve the same when the application is running in a login, who doesn't have administrative rights.
Is it possible to achieve the same using .NET framework? Do any one knows how to implement the same. If so, please inform me.
Best Regards, Abhilash Chandran
|
| Sign In·View Thread·PermaLink | 2.17/5 (5 votes) |
|
|
|
 |
|
|
great work and I was searching for the opposite for this process and it's the way windows detect MMC or SD or Flash disk and bring up a dialog that gives you options to open folder or in windows media player..etc. ...
I want to build application that make the same...
I had made an application that detect any change in disks generally but for example ( I'm intereted in MMC ) my application can detect the memory reader when it's plugged or unplugged but when I insert a memory stick in the reader it doesnot detect any change ...should I make timer that watch the memory reader all the time!! I think it's not good way..so any one has any solution for this??? and this dialog that windows brings up is from shell32.dll so any one knows how to know the function and its parameters??
thanks in advance
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I would like to say thanks and appreciate the work ....of the Author of "Eject USB disks using C#" , This article is really helpful to the beginners as well as as for the ...Professionals
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
http://www.codeproject.com/useritems/usbeject.asp[^]
I have downloaded code for USB Drive detection and ejection from the above link.This code works fine with Windows XP Professional,but not working on Windows XP Embedded. It gets WM_DEVICECHANGE message but can not find the device or logical drive for Removable Disk(USB Drive).
I am not able to know cause of the problem.Solve my query asap.
Varsha
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
first of all, i liked the code, it works great, but it has to be modified a bit for it to work under .net version 1.1, at least i did.
i know you have to be under Administrator for it to be able to eject devices, but isnt there a way for ordinary users in a network to be able to eject USB drives, without adminsitratve priveleges? i mean it is possible to do it from the tray icon.
thanks in advance.
[dose]
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
I don't know the answer to that question . I know it's possible to do it from the tray icon, but still can you do it w/o admin priviledge? Have you tried to log on as a regular user and from there used successfully the tray icon? Is it even there?
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
Hello, Thanks for sharing your code with us.
I tried to further extend your sample to get the possibility to turn a disk drive off and on... Sorry it's not directly related with what you do here but as you seem to know WMI and the Win32 API pretty well I thought you might know the answer 
I'm using GetDevicePowerState to get the current disk status but it looks like the SetDevicePowerState function is only available on CE... Waking up a drive is just a matter of trying to access it but I can't find a way to turn off a drive...
Would you have an idea on how to achieve this?
Thanks a lot in advance, John.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Sorry for 2 posts ( désole d'avoir posté 2 fois ):
L'article est très bien fait et très instructif.
J'ai remarqué que l'éjection d'une clé usb via un service windows ne fonctionne pas, Access denied erreur hexa 33. Comment est-il possible de pallier à ce problème ? J'ai cherché dans la documentation microsoft mais je n'ai pas réussi à en tires quelque chose qui puisse m'aider. Une idée ?
Merci
Vincent
Very good code, so, when I want eject usb with windows service it is not ok, access denied error hexa 33. How can I eject usb in service windows ? is it possible ? an idea ?
thanks
Vincent 
Native.CM_Request_Device_Eject_NoUi ===> CR_ACCESS_DENIED = 0x33 if windows service
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
It's certainly possible to use the code in a Windows Service. I have not tested it however.
I think you have to run your service under an account that as sufficient privilege to do so. I suggest you first try Administrator. May be you actually *need* to be an Administrator for this.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
thanks for answer
when I use Device.Eject(false) exeption is : (in french sorry )
ordinateur distant non disponible ( computer not disponible ?)
An idea
Vincent
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
L'article est très bien fait et très instructif.
J'ai remarqué que l'éjection d'une clé usb via un service windows ne fonctionne pas, Access denied erreur hexa 33. Comment est-il possible de pallier à ce problème ? J'ai cherché dans la documentation microsoft mais je n'ai pas réussi à en tires quelque chose qui puisse m'aider. Une idée ?
Merci
Vincent
Very good code, so, when I want eject usb with windows service it is not ok, access denied error hexa 33. How can I eject usb in service windows ? is it possible ? an idea ?
thanks
Vincent
Native.CM_Request_Device_Eject_NoUi ===> CR_ACCESS_DENIED = 0x33 if windows service
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Sorry to say it, but I think, example how to read Physical USB number serial can not work well, I have played with, it is possible to read from FROM Win32_USBHub, or in case of Win32_DiskDrive, the right property is PNPDeviceId, no DeviceId. The following C# example can be used:
public static void usbReport() { ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_USBHub"); foreach (ManagementObject dusmi in searcher.Get()) { Console.WriteLine(dusmi["DeviceID"].ToString()); Console.WriteLine(); }
or:
ManagementObjectSearcher searcher1 = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive"); foreach (ManagementObject dm in searcher1.Get()) {
Console.WriteLine(dm["PNPDeviceID"].ToString()); //usb serial number }
Regards
|
| Sign In·View Thread·PermaLink | 2.25/5 (4 votes) |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|