Click here to Skip to main content
15,881,866 members
Articles / Programming Languages / C#
Tip/Trick

How to Detect When Your USB Device Has Been Plugged In

Rate me:
Please Sign up or sign in to vote.
4.91/5 (7 votes)
10 May 2017CPOL2 min read 31.1K   19   2
This short article shows how to automatically detect when a given USB device is plugged in.

Introduction

I needed to automatically connect to a USB scanner whenever it was connected to the PC. The simplest approach would have been to run a timer, which on expiry queried for the presence of the device. But this is inefficient, and unresponsive unless the timer repeat period is short which would tie up the CPU. A better solution is to automatically detect when the device is plugged in. Fortunately, that is easy to do as I shall demonstrate.

Background

You will need to understand C#, and have a basic knowledge of Windows message processing, threading, and USB devices.

Using the Code

The example code detects when a device with a specific VID (Vendor ID) and PID (Process ID) connects, and then restarts it.

The first step is to intercept the WM_DEVICECHANGE Windows message which is sent whenever the device configuration changes, e.g., a USB device is plugged in. This is easily done by hooking a Windows procedure to an existing window as follows:

C#
var helper = new System.Windows.Interop.WindowInteropHelper(_hiddenWindow); 
IntPtr hWnd = helper.Handle; 
System.Windows.Interop.HwndSource src = System.Windows.Interop.HwndSource.FromHwnd(hWnd); 
src.AddHook(WndProc);

The _hiddenWindow variable is an instance of the Systems.Windows.Window class.

The WndProc method processes the window messages:

C#
private const int WM_DEVICECHANGE = 537; 
private System.Threading.Tasks.Task _restartDeviceTask = null;

private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    if (msg == WM_DEVICECHANGE)
    {
        if (_configData.AutomaticStartup && 
        (Status == ReaderStatus.Stopped) && (_restartDeviceTask == null))
        {
            // This must be done asynchronously otherwise it screws the message pump

            _restartDeviceTask = new System.Threading.Tasks.Task((Action)delegate 
            { 
                RestartUSBDevice(); 
                _restartDeviceTask = null; 
            });
            _restartDeviceTask.Start();
        }
    }

    handled = false; 
    return 0;
}

On receipt of a WM_DEVICECHANGE message, the method checks whether or not we need to restart our device. If so, it creates a new task to invoke the RestartUSBDevice method. The background task ensures that the method does not block execution of the windows message pump, which would hang our application.

This line simply works out if we need to start our device, and makes sure a task is not already running:

C#
if (_configData.AutomaticStartup && 
(Status == ReaderStatus.Stopped) && (_restartDeviceTask == null))

The task handle is set to null by the task after the device has been restarted.

The RestartUSBDevice method searches for loaded USB devices with our Vendor ID, and one of our Process IDs and if it finds one of interest, it starts it:

C#
private void RestartUSBDevice()
{
     if (Status != ReaderStatus.Stopped)
     {
         return;
     }
            
     const string VID_OURCOMPANY = "VID_0DB5";
     using (var searcher = new ManagementObjectSearcher_
           (@"SELECT * FROM Win32_PnPEntity WHERE DeviceID LIKE '%" + VID_OURCOMPANY + "%'"))
     {
         const string PID_MERCURY_READER = "PID_3001";
         const string PID_JUPITER_READER = "PID_2003";

         try
         {
             ManagementObjectCollection deviceCollection = searcher.Get();
             foreach (var device in deviceCollection)
             {
                 string deviceID = (string)device.GetPropertyValue("DeviceID");
                 if (
                         deviceID.Contains(PID_MERCURY_READER) ||
                         deviceID.Contains(PID_JUPITER_READER)
                    )
                 {
                     StartUSBDevice();
                     break;
                 }
             }
         }
         catch (Exception)
         {
         }
     }
}

Note that the code uses string constants for each VID and PID rather than using "PID_3001" and "PID_2003" directly. This is to make the code more readable, as it tells us the meaning of each string.

Note also that the RestartUSBDevice method uses API functions which will not work unless the Windows message pump is working, hence the reason why the windows procedure invokes the method from a background task.

Points of Interest

As already pointed out, you must be careful what you do in the Windows procedure, as you might inadvertently block the Windows message processing.

History

  • 4th May, 2017: First version

License

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


Written By
United Kingdom United Kingdom
C#/WPF/C++ Windows developer

Comments and Discussions

 
GeneralFor those looking for a demo... Pin
Member 128056369-May-19 11:05
Member 128056369-May-19 11:05 
QuestionIs there a file to download? Pin
SFC Donner10-Jul-18 4:25
SFC Donner10-Jul-18 4:25 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.