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:
protected override void 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:
switch (m.Msg)
{
case WM_DEVICECHANGE:
break;
}
base.WndProc(ref m);
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:
HDEVNOTIFY RegisterDeviceNotification(HANDLE hRecipient,
LPVOID NotificationFilter,DWORD Flags);
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:
- Add the source code (DriveDetector.cs) to your project and add
using Dolinay;
directive to your Form's source code. - Create an instance of the
DriveDetector
class in your form. See example below. - Add handlers for the events exposed by
DriveDetector,
that is for DeviceArrived
, DeviceRemoved
and QueryRemove
.
public partial class Form1 : Form
{
private DriveDetector driveDetector = null;
public Form1()
{
InitializeComponent();
driveDetector = new DriveDetector();
driveDetector.DeviceArrived += new DriveDetectorEventHandler(
OnDriveArrived);
driveDetector.DeviceRemoved += new DriveDetectorEventHandler(
OnDriveRemoved);
driveDetector.QueryRemove += new DriveDetectorEventHandler(
OnQueryRemove);
...
- Implement these handlers as shown here. You can simply copy and paste this block of source code to your
Form
class.
private void OnDriveArrived(object sender, DriveDetectorEventArgs e)
{
e.HookQueryRemove = true;
}
private void OnDriveRemoved(object sender, DriveDetectorEventArgs e)
{
}
private void OnQueryRemove(object sender, DriveDetectorEventArgs e)
{
if (MessageBox.Show("Allow remove?", "Query remove",
MessageBoxButtons.YesNo, MessageBoxIcon.Question) ==
DialogResult.Yes)
e.Cancel = false;
else
e.Cancel = true;
}
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.
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (driveDetector != null)
{
driveDetector.WndProc(ref m);
}
}
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.
History
- March 19, 2007 - this article first published.
- October 31, 2007 - article updated (opening root directory and hidden form)