Check for Device Change (Add/Remove) Events

Check for device change (add/remove) events

If you have a window in your application, you can use something like this:

using System;
using System.Runtime.InteropServices;

internal static class UsbNotification
{
public const int DbtDevicearrival = 0x8000; // system detected a new device
public const int DbtDeviceremovecomplete = 0x8004; // device is gone
public const int WmDevicechange = 0x0219; // device change event
private const int DbtDevtypDeviceinterface = 5;
private static readonly Guid GuidDevinterfaceUSBDevice = new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); // USB devices
private static IntPtr notificationHandle;

/// <summary>
/// Registers a window to receive notifications when USB devices are plugged or unplugged.
/// </summary>
/// <param name="windowHandle">Handle to the window receiving notifications.</param>
public static void RegisterUsbDeviceNotification(IntPtr windowHandle)
{
DevBroadcastDeviceinterface dbi = new DevBroadcastDeviceinterface
{
DeviceType = DbtDevtypDeviceinterface,
Reserved = 0,
ClassGuid = GuidDevinterfaceUSBDevice,
Name = 0
};

dbi.Size = Marshal.SizeOf(dbi);
IntPtr buffer = Marshal.AllocHGlobal(dbi.Size);
Marshal.StructureToPtr(dbi, buffer, true);

notificationHandle = RegisterDeviceNotification(windowHandle, buffer, 0);
}

/// <summary>
/// Unregisters the window for USB device notifications
/// </summary>
public static void UnregisterUsbDeviceNotification()
{
UnregisterDeviceNotification(notificationHandle);
}

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags);

[DllImport("user32.dll")]
private static extern bool UnregisterDeviceNotification(IntPtr handle);

[StructLayout(LayoutKind.Sequential)]
private struct DevBroadcastDeviceinterface
{
internal int Size;
internal int DeviceType;
internal int Reserved;
internal Guid ClassGuid;
internal short Name;
}
}

Here's how you use it from a WPF Window (Windows Forms is similar):

    protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);

// Adds the windows message processing hook and registers USB device add/removal notification.
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
if (source != null)
{
windowHandle = source.Handle;
source.AddHook(HwndHandler);
UsbNotification.RegisterUsbDeviceNotification(windowHandle);
}
}

/// <summary>
/// Method that receives window messages.
/// </summary>
private IntPtr HwndHandler(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
{
if (msg == UsbNotification.WmDevicechange)
{
switch ((int)wparam)
{
case UsbNotification.DbtDeviceremovecomplete:
Usb_DeviceRemoved(); // this is where you do your magic
break;
case UsbNotification.DbtDevicearrival:
Usb_DeviceAdded(); // this is where you do your magic
break;
}
}

handled = false;
return IntPtr.Zero;
}

Here's the use example for Windows Forms (even simpler):

public Form1()
{
InitializeComponent();
UsbNotification.RegisterUsbDeviceNotification(this.Handle);
}

protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == UsbNotification.WmDevicechange)
{
switch ((int)m.WParam)
{
case UsbNotification.DbtDeviceremovecomplete:
Usb_DeviceRemoved(); // this is where you do your magic
break;
case UsbNotification.DbtDevicearrival:
Usb_DeviceAdded(); // this is where you do your magic
break;
}
}
}

Check for device change (add/remove) events

If you have a window in your application, you can use something like this:

using System;
using System.Runtime.InteropServices;

internal static class UsbNotification
{
public const int DbtDevicearrival = 0x8000; // system detected a new device
public const int DbtDeviceremovecomplete = 0x8004; // device is gone
public const int WmDevicechange = 0x0219; // device change event
private const int DbtDevtypDeviceinterface = 5;
private static readonly Guid GuidDevinterfaceUSBDevice = new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); // USB devices
private static IntPtr notificationHandle;

/// <summary>
/// Registers a window to receive notifications when USB devices are plugged or unplugged.
/// </summary>
/// <param name="windowHandle">Handle to the window receiving notifications.</param>
public static void RegisterUsbDeviceNotification(IntPtr windowHandle)
{
DevBroadcastDeviceinterface dbi = new DevBroadcastDeviceinterface
{
DeviceType = DbtDevtypDeviceinterface,
Reserved = 0,
ClassGuid = GuidDevinterfaceUSBDevice,
Name = 0
};

dbi.Size = Marshal.SizeOf(dbi);
IntPtr buffer = Marshal.AllocHGlobal(dbi.Size);
Marshal.StructureToPtr(dbi, buffer, true);

notificationHandle = RegisterDeviceNotification(windowHandle, buffer, 0);
}

/// <summary>
/// Unregisters the window for USB device notifications
/// </summary>
public static void UnregisterUsbDeviceNotification()
{
UnregisterDeviceNotification(notificationHandle);
}

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags);

[DllImport("user32.dll")]
private static extern bool UnregisterDeviceNotification(IntPtr handle);

[StructLayout(LayoutKind.Sequential)]
private struct DevBroadcastDeviceinterface
{
internal int Size;
internal int DeviceType;
internal int Reserved;
internal Guid ClassGuid;
internal short Name;
}
}

Here's how you use it from a WPF Window (Windows Forms is similar):

    protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);

// Adds the windows message processing hook and registers USB device add/removal notification.
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
if (source != null)
{
windowHandle = source.Handle;
source.AddHook(HwndHandler);
UsbNotification.RegisterUsbDeviceNotification(windowHandle);
}
}

/// <summary>
/// Method that receives window messages.
/// </summary>
private IntPtr HwndHandler(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
{
if (msg == UsbNotification.WmDevicechange)
{
switch ((int)wparam)
{
case UsbNotification.DbtDeviceremovecomplete:
Usb_DeviceRemoved(); // this is where you do your magic
break;
case UsbNotification.DbtDevicearrival:
Usb_DeviceAdded(); // this is where you do your magic
break;
}
}

handled = false;
return IntPtr.Zero;
}

Here's the use example for Windows Forms (even simpler):

public Form1()
{
InitializeComponent();
UsbNotification.RegisterUsbDeviceNotification(this.Handle);
}

protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == UsbNotification.WmDevicechange)
{
switch ((int)m.WParam)
{
case UsbNotification.DbtDeviceremovecomplete:
Usb_DeviceRemoved(); // this is where you do your magic
break;
case UsbNotification.DbtDevicearrival:
Usb_DeviceAdded(); // this is where you do your magic
break;
}
}
}

Detecting USB Insertion / Removal Events in Windows using C++

Create a dummy window that does nothing but wait for WM_DEVICECHANGE and register that window using RegisterDeviceNotification. WMI is an overkill here, IMHO.

Cannot eject safely removed a USB memory stick when running a FileSystemWatcher on the drive

After several days looking for an answer to the planted problem I found the solution.

This article was very helpful to solve the problem and this.

The solution changes if we are using a service or a windows form application. My answer is based on a service.

Services have a control handler which receives all messages from Windows. These might include codes to stop or pause the service, or as in our case, device events. We need to register our own service handler, so we could catch device events. This would disable all callbacks like OnStop except OnStart, which is called before we tell Windows to use our handler.

The Windows API function for this is RegisterServiceCtrlHandlerEx, which accepts a service name and a callback function to call when a message is received. We will call it in the OnStart function in our service.

The service control handler's signature is like this:

public delegate int ServiceControlHandlerEx(int control,int eventType,
IntPtr eventData, IntPtr context);

public partial class Service1 : ServiceBase{

private FileSystemWatcher fileSystemWatcher;
private IntPtr deviceNotifyHandle;
private IntPtr deviceEventHandle;
private IntPtr directoryHandle;
private Win32.ServiceControlHandlerEx myCallback;

public Service1()
{
InitializeComponent();
}

protected override void OnStart(string[] args)
{
base.OnStart(args);
//
RegisterDeviceNotification();

fileSystemWatcher = new FileSystemWatcher();
fileSystemWatcher.Created += new System.IO.FileSystemEventHandler(fileSystemWatcher_Created);
fileSystemWatcher.Deleted += new System.IO.FileSystemEventHandler(fileSystemWatcher_Deleted);
fileSystemWatcher.Changed += new System.IO.FileSystemEventHandler(fileSystemWatcher_Changed);
fileSystemWatcher.Renamed += new System.IO.RenamedEventHandler(fileSystemWatcher_Renamed);
}

}

Register for Device Notifications

In the OnStart method, apart from registering for a control handler, we will register for device notifications by using the Win32 API function RegisterDeviceNotification. We give it our service's handle, a pointer to a DEV_BROADCAST_DEVICEINTERFACE struct (telling the function to register for a class of devices), and some flags, among which is the DEVICE_NOTIFY_SERVICE_HANDLE, which specifies that the caller is a service and not a window, for example. It returns a handle, which we must preserve in order to unregister when we don't need device messages anymore (for example, we could do this in the SERVICE_CONTROL_STOP event).

Using this function allows us to capture the DBT_DEVICEARRIVAL and DBT_DEVICEREMOVECOMPLETE event types. We get them through the eventType parameter of our Service Control Handler. There, we can handle the SERVICE_CONTROL_DEVICEEVENT and do anything we like.

  public void RegisterDeviceNotification()
{
InitArrayDevNotifyHandle();

myCallback = new Win32.ServiceControlHandlerEx(ControlHandler);
Win32.RegisterServiceCtrlHandlerEx(service.ServiceName, myCallback, IntPtr.Zero);

if (service.GetServiceHandle() == IntPtr.Zero)
{
// TODO handle error
}

Win32.DEV_BROADCAST_DEVICEINTERFACE deviceInterface = new Win32.DEV_BROADCAST_DEVICEINTERFACE();
int size = Marshal.SizeOf(deviceInterface);
deviceInterface.dbcc_size = size;
deviceInterface.dbcc_devicetype = Win32.DBT_DEVTYP_DEVICEINTERFACE;
IntPtr buffer = default(IntPtr);
buffer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(deviceInterface, buffer, true);
deviceEventHandle = Win32.RegisterDeviceNotification(service.GetServiceHandle(), buffer, Win32.DEVICE_NOTIFY_SERVICE_HANDLE | Win32.DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);

if (deviceEventHandle == IntPtr.Zero)
{
// TODO handle error
}

}

DBT_DEVICEQUERYREMOVE - The Solution

Here is the main thing to answer the question
I needed notifies to application of a change to the hardware configuration of a device. Specifically a USB memory change before SO will remove the memory. Thanks to @ZdeněkJelínek I was able to find the event DBT_DEVICEQUERYREMOVE that permission is requested to remove a device or piece of media. This event is being held just before a device is about to be removed.

The solution is to create a handle to the device itself, use it in a DEV_BROADCAST_HANDLE structure, and register with it to our Service Control Handler. In order to accomplish all this, it takes a couple of things:

Find which drive letter the device. I was able using the ManagementEventWatcher class, which allowed me to subscribe to the device insertion event and it provides me with information about the inserted device.
All this was done with the in order to get a device handle using the CreateFileHandle function. Only after that are we able to get the DBT_DEVICEQUERYREMOVE event, disable the FileSystemWatcher, and allow the USB to be freely removed.

private int ControlHandler(int control, int eventType, IntPtr eventData, IntPtr context)
{
if (control == Win32.SERVICE_CONTROL_STOP || control == Win32.SERVICE_CONTROL_SHUTDOWN)
{
UnregisterHandles();
Win32.UnregisterDeviceNotification(deviceEventHandle);
base.Stop();
}
else if (control == Win32.SERVICE_CONTROL_DEVICEEVENT)
{
string c;
switch (eventType)
{
case Win32.DBT_DEVICEARRIVAL:
//This is an example ... I do not use the DBT_DEVICEARRIVAL event, but it can be used. Instead use the ManagementEventWatcher class to detect when a device arrives and driveLetter.
RegisterForHandle(driveLetter);
fileSystemWatcher.Path = driveLetter + ":\\";
fileSystemWatcher.EnableRaisingEvents = true;
break;
case Win32.DBT_DEVICEQUERYREMOVE:

Win32.DEV_BROADCAST_HDR hdrR;
Win32.DEV_BROADCAST_HANDLE dbhdl;
hdrR = (Win32.DEV_BROADCAST_HDR)
Marshal.PtrToStructure(eventData, typeof(Win32.DEV_BROADCAST_HDR));

if (hdrR.dbcc_devicetype == Win32.DBT_DEVTYP_HANDLE)
{
dbhdl = (Win32.DEV_BROADCAST_HANDLE)
Marshal.PtrToStructure(eventData, typeof(Win32.DEV_BROADCAST_HANDLE));

UnregisterHandles();
fileSystemWatcher.EnableRaisingEvents = false;
fileSystemWatcher = null;
}

break;
}
}

return 0;
}

private void UnregisterHandles()
{
if (directoryHandle != IntPtr.Zero)
{
Win32.CloseHandle(directoryHandle);
directoryHandle = IntPtr.Zero;
}
if (deviceNotifyHandle != IntPtr.Zero)
{
Win32.UnregisterDeviceNotification(deviceNotifyHandle);
deviceNotifyHandle = IntPtr.Zero;
}
}

private void RegisterForHandle(char c)
{
Win32.DEV_BROADCAST_HANDLE deviceHandle = new Win32.DEV_BROADCAST_HANDLE();
int size = Marshal.SizeOf(deviceHandle);
deviceHandle.dbch_size = size;
deviceHandle.dbch_devicetype = Win32.DBT_DEVTYP_HANDLE;
directoryHandle = CreateFileHandle(c + ":\\");
deviceHandle.dbch_handle = directoryHandle;
IntPtr buffer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(deviceHandle, buffer, true);
deviceNotifyHandle = Win32.RegisterDeviceNotification(this.ServiceHandle, buffer, Win32.DEVICE_NOTIFY_SERVICE_HANDLE);
if (deviceNotifyHandle == IntPtr.Zero)
{
// TODO handle error
}
}

public static IntPtr CreateFileHandle(string driveLetter)
{
// open the existing file for reading
IntPtr handle = Win32.CreateFile(
driveLetter,
Win32.GENERIC_READ,
Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE,
0,
Win32.OPEN_EXISTING,
Win32.FILE_FLAG_BACKUP_SEMANTICS | Win32.FILE_ATTRIBUTE_NORMAL,
0);

if (handle == Win32.INVALID_HANDLE_VALUE)
{
return IntPtr.Zero;
}
else
{
return handle;
}
}

public class Win32
{
public const int DEVICE_NOTIFY_SERVICE_HANDLE = 1;
public const int DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 4;

public const int SERVICE_CONTROL_STOP = 1;
public const int SERVICE_CONTROL_DEVICEEVENT = 11;
public const int SERVICE_CONTROL_SHUTDOWN = 5;

public const uint GENERIC_READ = 0x80000000;
public const uint OPEN_EXISTING = 3;
public const uint FILE_SHARE_READ = 1;
public const uint FILE_SHARE_WRITE = 2;
public const uint FILE_SHARE_DELETE = 4;
public const uint FILE_ATTRIBUTE_NORMAL = 128;
public const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
public static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

public const int DBT_DEVTYP_DEVICEINTERFACE = 5;
public const int DBT_DEVTYP_HANDLE = 6;

public const int DBT_DEVICEARRIVAL = 0x8000;
public const int DBT_DEVICEQUERYREMOVE = 0x8001;
public const int DBT_DEVICEREMOVECOMPLETE = 0x8004;

public const int WM_DEVICECHANGE = 0x219;

public delegate int ServiceControlHandlerEx(int control, int eventType, IntPtr eventData, IntPtr context);

[DllImport("advapi32.dll", SetLastError = true)]
public static extern IntPtr RegisterServiceCtrlHandlerEx(string lpServiceName, ServiceControlHandlerEx cbex, IntPtr context);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetVolumePathNamesForVolumeNameW(
[MarshalAs(UnmanagedType.LPWStr)]
string lpszVolumeName,
[MarshalAs(UnmanagedType.LPWStr)]
string lpszVolumePathNames,
uint cchBuferLength,
ref UInt32 lpcchReturnLength);

[DllImport("kernel32.dll")]
public static extern bool GetVolumeNameForVolumeMountPoint(string
lpszVolumeMountPoint, [Out] StringBuilder lpszVolumeName,
uint cchBufferLength);

[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr RegisterDeviceNotification(IntPtr IntPtr, IntPtr NotificationFilter, Int32 Flags);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern uint UnregisterDeviceNotification(IntPtr hHandle);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateFile(
string FileName, // file name
uint DesiredAccess, // access mode
uint ShareMode, // share mode
uint SecurityAttributes, // Security Attributes
uint CreationDisposition, // how to create
uint FlagsAndAttributes, // file attributes
int hTemplateFile // handle to template file
);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DEV_BROADCAST_DEVICEINTERFACE
{
public int dbcc_size;
public int dbcc_devicetype;
public int dbcc_reserved;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 16)]
public byte[] dbcc_classguid;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
public char[] dbcc_name;
}

[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_HDR
{
public int dbcc_size;
public int dbcc_devicetype;
public int dbcc_reserved;
}

[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_HANDLE
{
public int dbch_size;
public int dbch_devicetype;
public int dbch_reserved;
public IntPtr dbch_handle;
public IntPtr dbch_hdevnotify;
public Guid dbch_eventguid;
public long dbch_nameoffset;
public byte dbch_data;
public byte dbch_data1;
}
}


Related Topics



Leave a reply



Submit