Introduction

This article describes my C# class called DriveDetector which will allow your program to receive a notification when a removable drive (such as a flash drive) is inserted or removed. It also supports notifications about pending removal of such a device and cancelling this removal.

This is an updated version of the article first published in March 2007. The class is now easier to use — there is no need to specify a file to open on the flash drive or override WndProc in your code. For details please see the What's new section at the bottom of this page.

Note that this class uses .NET Framework 2.0. It will not work with older versions. Also, it will only work in applications which have a window (not in console applications).

Recently, I wrote a program which allows the user to encrypt data on his/her flash drive. The program should decrypt and encrypt the data transparently when flash drive is inserted/removed. For this I needed to be informed when a flash drive is plugged in and when the user decides to remove it. I am quite new to C# so I started searching the internet for some solution. It didn't take me long to find out how to detect when a removable drive is inserted or removed, but I had a hard time trying to figure out how to get notified when the drive is about to be removed (when user clicks the remove hardware icon in system notification area). After making it all work I decided to put the code into a simple-to-use class and make it available to everyone. I hope you will find it useful. It's far from perfect but it works.

Background

In this section I will describe some of the principles on which the removable drive notifications work. If you just need to use this class without spending much time learning how it works, feel free to skip to the "Using the Code" section.

Windows will send WM_DEVICECHANGE message to all applications whenever some hardware change occurs, including when a flash drive (or other removable device) is inserted or removed. The WParam parameter of this message contains code which specifies exactly what event occurred. For our purpose only the following events are interesting:

DBT_DEVICEARRIVAL - sent after a device or piece of media has been inserted. Your program will receive this message when the device is ready for use, at about the time when Explorer displays the dialog which lets you choose what to do with the inserted media.

DBT_DEVICEQUERYREMOVE - sent when the system requests permission to remove a device or piece of media. Any application can deny this request and cancel the removal. This is the important event if you need to perform some action on the flash drive before it is removed, e.g. encrypt some files on it. Your program can deny this request which will cause Windows to display the well-known message saying that the device cannot be removed now.

DBT_DEVICEREMOVECOMPLETE - sent after a device has been removed. When your program receives this event, the device is no longer available — at the time when Windows display its "device has been removed" bubble to the user.

To handle these events in your program you need to be able to process the WM_DEVICECHANGE messages. In Windows Forms applications, you can override the WndProc function which is available in any class derived from Windows.Forms.Control. Since the Control class is the "great-grand-father" of your Form class, overriding it is simply a matter of adding these lines to the Form1.cs file in your project:

protectedoverridevoid WndProc(ref Message m)
{
base.WndProc(ref m);
}

This implementation does nothing, just calls the base class. But by adding some code, we can easily check out the messages which we receive in the ref Message m argument. Processing the messages is as easy as this:

Now maybe you are thinking if it is as easy as this, what's all this about. Well, there are two problems:

You will not only want to know that some device arrived, but also what device it is, if it's a removable drive what drive letter it got assigned and so on. For this you have to dig into the Win API structure, a pointer to which you receive in the m.LParam.

To make things more complicated, the most interesting event for us, DBT_DEVICEQUERYREMOVE, is not automatically sent to all applications — unlike DBT_DEVICEARRIVAL and DBT_DEVICEREMOVECOMPLETE. If you try the above code and test the m.WParam it will never have the value DBT_DEVICEQUERYREMOVE. To also receive the DBT_DEVICEQUERYREMOVE event, you need to register with the system using RegisterDeviceNotification API. This function is defined as follows in the platform SDK:

Calling native API is not too hard but the tricky part for me was the NotificationFilter parameter about which SDK documentation says it is "pointer to a block of data... This block always begins with the DEV_BROADCAST_HDR structure. The data following this header is dependent on the value of the dbch_devicetype member...". Sounds scary, doesn't it? Well, the thing is if you want to receive the DBT_DEVICEQUERYREMOVE event for a removable drive, you need to open a file on the drive and pass a handle to this file to the RegisterDeviceNotification function. If you want to see the resulting code in C#, look at RegisterForDeviceChange in DriveDetector.cs.

To sum it all up, when a flash drive is inserted, Windows will send WM_DEVICECHANGE to your program with the wParam equal to DBT_DEVICEARRIVAL. Now you can open a file on the flash drive and call RegisterDeviceNotification native API function passing the handle to it. Only if you did this will your program will receive the DBT_DEVICEQUERYREMOVE event when the flash drive is about to be removed and you can respond to this. At least you have to close the file which you opened otherwise the removal attempt will fail and Windows will display a message saying that the drive cannot be removed now.

The DriveDetector class is intended to do most of the above-described work for you. It provides events which your program can handle for the device arrival, removal and query remove. All you have to do is to override WndProc in your program and call DriveDetector's WndProc method from there. The usable fields and methods of this class are described at the end of this article. Let's now look at simple example of use.

Using the Code

Here are the steps needed to add this functionality into your program without worrying about how it works:

Implement these handlers as shown here. You can simply copy and paste this block of source code to your Form class.

// Called by DriveDetector when removable device in insertedprivatevoid OnDriveArrived(object sender, DriveDetectorEventArgs e)
{
// e.Drive is the drive letter, e.g. "E:\\"// If you want to be notified when drive is being removed (and be// able to cancel it),// set HookQueryRemove to true
e.HookQueryRemove = true;
}
// Called by DriveDetector after removable device has been unpluggedprivatevoid OnDriveRemoved(object sender, DriveDetectorEventArgs e)
{
// TODO: do clean up here, etc. Letter of the removed drive is in// e.Drive;
}
// Called by DriveDetector when removable drive is about to be removedprivatevoid OnQueryRemove(object sender, DriveDetectorEventArgs e)
{
// Should we allow the drive to be unplugged?if (MessageBox.Show("Allow remove?", "Query remove",
MessageBoxButtons.YesNo, MessageBoxIcon.Question) ==
DialogResult.Yes)
e.Cancel = false; // Allow removalelse
e.Cancel = true; // Cancel the removal of the device
}

That's all. Your event handlers should now be called whenever a flash drive is inserted or removed.

In this default implementation DriveDetector will create a hidden form, which it will use to receive notification messages from Windows. You could also pass your form to DriveDetector's constructor to avoid this extra (although invisible) form. To do this, just create your DriveDetector object using the second constructor:

driveDetector = new DriveDetector(this);

But then you also have to override WndPRoc method in your Form's code and call DriveDetector's WndProc from there. Again, you can just copy-paste the code below to your Form1.cs.

Even though DriveDetector hides all the background from you, it may be helpful if you know the following things:

Your program must register to receive a message from Windows when the drive is about to be removed (DBT_DEVICEQUERYREMOVE message). DriveDetector does this automatically if you set the HookQueryRemove flag to true in the DetectorEventArgs argument of your DeviceArrived handler (e.HookQueryRemove) — as is shown in the sample code above.

For this registration handle to a file located on the removable drive is required.

If you do not provide the name of the file to be opened, DriveDetector opens the root directory of the flash drive (e.g. E:\). This should work fine in most cases. If you for any reason want to specify your own file to be opened to obtain the notification handle, you can do so either in a DriveDetector constructor or using the EnableQueryRemove method as described below. Specify the file to be opened either in the DriveDetector constructor or using the EnableQueryRemove method as described below.

The demo project "Simple Detector" illustrates using the DriveDetector class. When a removable drive is inserted it will report this event in the list. If you check the "Ask me before drive can be disconnected" box, a message box will pop out asking whether the removal should be allowed. The code of this demo is pretty much the same as in the examples above. The event handler for the "Ask me..." checkbox demonstrates using EnableQueryRemove method to register for DBT_DEVICEQUERYREMOVE as an alternative to setting the e.HookQueryRemove flag in DeviceArrived event handler.

What's New

I decided to update Drivedetector after trying to use it in some real project and finding out that I needed something more usable. So I took the suggestions from the posts for this article and implemented two major improvements:

DriveDetector can now open the root directory instead of a file to obtain the handle required for query remove notification. You do not have to specify any file and you can be sure all files you need on the flash drive are accessible.

DriveDetector can now create a hidden form instead of relying on a Control object passed to the constructor. As a result it's not necessary to override WndProc in the client code.

The class should be backward compatible, so if you are using it in your project, you should be able to simply replace the DriveDetector.cs file and have it work as before. But if you decide to update your code, you will probably have some reason. Perhaps the same I had — opening some file on the flash drive to be notified about pending removal is annoying and causes trouble with sharing when you need to update the opened file. This new version which opens the root directory is much easier to use.

So what needs to be changed? Simply do not specify any file. If your code uses the constructor with file name, just replace it with the constructor without it. If you specify a file in the EnableQueryRemove call, use only the drive instead of your file path. That's all.

DriveDetector Quick Reference

Here is a summary of the fields and methods DriveDetector offers.

Events Signaled by the DriveDetector Class

DeviceArrived - removable drive has just been inserted.

DeviceRemoved - removable drive has just been removed.

QueryRemove - removable drive is about to be removed. Note that to receive this event you must set the HookQueryRemove flag to true in the argument in your DeviceArrived handler (e.HookQueryRemove) or call EnableQueryRemove method. See the sample code above.

All these events are of type DriveDetectorEventHandler and the event handlers receive DriveDetectorEventArgs argument which contains following fields:

string Drive - in the DeviceArrived and DeviceRemoved event handlers this string contains the drive letter of the drive which has been inserted or removed, e.g. "E:\\". In the QueryRemove handler this field will read an empty string.

bool HookQueryRemove - set to true in your DeviceArrived event handler if you want to receive the QueryRemove event for the drive which just arrived.

bool Cancel - set to true in your QueryRemove handler if you want to cancel the removal of the drive.

Public Properties

bool IsQueryHooked - Is true if any drive is currently "hooked" for the DBT_DEVICEQUERYREMOVE event. If this property is true, it means your QueryRemove event handler will be called. The drive which is hooked can be obtained from HookedDrive property.

string HookedDrive - drive letter of the drive which is currently hooked, e.g. "E:\\". Empty string if no drive is hooked.

FileStream OpenedFile - FileStream object for the file which is currently opened (and its handle used for DBT_DEVICEQUERYREMOVE notification). Note that this is normally null as DriveDetector opens the root directory of the flash drive unless you specify a valid file in the constructor or in call to EnableQueryRemove.

Public Methods

bool EnableQueryRemove(string fileOnDrive) - Hooks drive specified in the fileOnDrive argument to receive notification when it is being removed. This can also be achieved by setting e.HookQueryRemove to true in your DeviceArrived event handler. The fileOnDrive argument is drive letter or relative path to a file on the removable drive which should be opened (opening the file is required to obtain notifications about removal). If you specify drive letter only, DriveDetector will open the root directory of the drive and use its handle.

DisableQueryRemove() - Unregisters from the notification for currently registered drive, if any.

Possible Improvements

Extend the class to also handle the DBT_DEVICEQUERYREMOVEFAILED event. In the current version, if another program cancels the removal of a drive which we've already allowed, we will lose track of this drive.

Improve the class so that it can monitor several drives for query remove. Currently only one drive can be registered for this notification so if a new drive arrives and QueryRemove is requested for it, DriveDetector will unregister the previous one.

Points of Interest

There are actually two ways of denying removal of a removable drive. You can simply keep some file open on the drive in your program and system will not allow the drive to be removed. But if your program also returns the proper value in response to DBT_DEVICEQUERYREMOVE event, system will display a nice message including the name of the application which denies the removal. DriveDetector does set the proper response value in the message structure and that is why the DriveDetector's WndProc function is called only after the base class call in the sample code. If you called the base class last, it would reset the response in the message structure.