76

Looking into possibility of making an USB distributed application
that will autostart on insertion of an USB stick and shutdown when removing the stick

Will use .Net and C#.
Looking for suggestion how to approach this using C#?


Update: Two possible solutions implementing this as a service.
- override WndProc
or
- using WMI query with ManagementEventWatcher
Kb.
  • 7,240
  • 13
  • 56
  • 75
  • 1
    Good question on the service trapping this event. My first thought is you have to mark your service as "allow to interact with desktop" and then create a hidden window. Safer option is probably to create a windows app that runs at startup - it can create the window and then communicate to the svc – Mike Marshall Mar 06 '09 at 20:22
  • Related: http://stackoverflow.com/questions/6003822/how-to-detect-a-usb-drive-has-been-plugged-in – DuckMaestro Jul 01 '16 at 18:33
  • 2
    I made a NuGet packet that works on Windows, MacOS and Linux: https://github.com/Jinjinov/Usb.Events – Jinjinov Apr 28 '20 at 06:26

10 Answers10

79

You can use WMI, it is easy and it works a lot better than WndProc solution with services.

Here is a simple example:

using System.Management;

ManagementEventWatcher watcher = new ManagementEventWatcher();
WqlEventQuery query = new WqlEventQuery("SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2");
watcher.EventArrived += new EventArrivedEventHandler(watcher_EventArrived);
watcher.Query = query;
watcher.Start();
watcher.WaitForNextEvent();
VitalyB
  • 12,397
  • 9
  • 72
  • 94
  • 7
    That works fine but How can I get drive letter of inserted USB? – Never Quit May 11 '13 at 10:26
  • [This article](http://www.ravichaganti.com/blog/monitoring-volume-change-events-in-powershell-using-wmi/) seems to be getting this information in Powershell. Shouldn't be too hard to translate that to C#. – VitalyB Aug 16 '14 at 18:15
  • 7
    In your event handler, `e.NewEvent.Properties["DriveName"].Value.ToString()` – lambinator May 18 '16 at 18:10
  • 1
    the query is clearly for volume changes, which has nothing to do usb added/removed. It doesn't even work for volume changes, at least on my system – spy Apr 13 '19 at 18:05
  • 1
    using the above query is eating cpu resources, any idea why wmi causes this issue? – stackmalux Jul 22 '19 at 09:01
  • Does this instantly trigger the event or does it check in an interval? / Is it possible for another Program / the user to read/write files from the drive before the event fires? – leumasme Dec 28 '19 at 17:06
57

This works well for me, plus you can find out more information about the device.

using System.Management;

private void DeviceInsertedEvent(object sender, EventArrivedEventArgs e)
{
    ManagementBaseObject instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];
    foreach (var property in instance.Properties)
    {
        Console.WriteLine(property.Name + " = " + property.Value);
    }
}

private void DeviceRemovedEvent(object sender, EventArrivedEventArgs e)
{
    ManagementBaseObject instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];
    foreach (var property in instance.Properties)
    {
        Console.WriteLine(property.Name + " = " + property.Value);
    }
}            

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    WqlEventQuery insertQuery = new WqlEventQuery("SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_USBHub'");

    ManagementEventWatcher insertWatcher = new ManagementEventWatcher(insertQuery);
    insertWatcher.EventArrived += new EventArrivedEventHandler(DeviceInsertedEvent);
    insertWatcher.Start();

    WqlEventQuery removeQuery = new WqlEventQuery("SELECT * FROM __InstanceDeletionEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_USBHub'");
    ManagementEventWatcher removeWatcher = new ManagementEventWatcher(removeQuery);
    removeWatcher.EventArrived += new EventArrivedEventHandler(DeviceRemovedEvent);
    removeWatcher.Start();

    // Do something while waiting for events
    System.Threading.Thread.Sleep(20000000);
}
Phil Minor
  • 571
  • 4
  • 2
  • 3
    Works perfectly. Doesn't fire multiple events like some of the other answers here on insertion/removal. This should be the accepted answer. – samuelesque Sep 16 '14 at 12:51
  • 1
    I agree with @samuelAndThe, this seems the best aproach. If you are looking for also detect changes to hard drives and not only usb drives you can use the 'Win32_DiskDrive' class – helder.tavares.silva Jul 29 '16 at 09:52
  • 2
    `private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)`, why do you have `(object sender, DoWorkEventArgs e)`???(I committed an edit suggestion for this.) – ch271828n Jul 05 '17 at 06:03
  • 1
    Will this run on mono? Just asking, because started recently with that, and wmi is ms pure. – icbytes Nov 12 '17 at 15:23
  • Everything is awesome but please remove that Thread.Sleep ^_^ – Pavlin Marinov Jan 04 '19 at 12:29
  • What's the purpose of Thread.Sleep? The DeviceInsertedEvent still fires event after the BackgroundWorker call ends after this sleep times out – Aditya Sep 03 '19 at 13:46
  • I think that Thread.Sleep is there to simulate a long application code. You can simply remove it. – AmirSina Mashayekh May 18 '20 at 07:52
  • Is there any way to detect **Any** USB device in this method? As you know other answers fire multiple events which is not good. – AmirSina Mashayekh May 18 '20 at 08:02
25

Adding to VitalyB's post.

To raise an event where ANY USB device is inserted, use the following:

var watcher = new ManagementEventWatcher();
var query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
watcher.EventArrived += new EventArrivedEventHandler(watcher_EventArrived);
watcher.Query = query;
watcher.Start();

This will raise an event whenever a USB device is plugged. It even works with a National Instruments DAQ that I'm trying to auto-detect.

Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
Syn
  • 411
  • 5
  • 3
  • @Lee Taylor That works fine but How can I get drive letter of inserted USB? – Never Quit May 11 '13 at 10:26
  • @NeverQuit - I only edited the question, ask @Syn! Also, if you have a new question then feel free to create one. – Lee Taylor May 11 '13 at 14:05
  • @Syn That works fine but How can I get drive letter of inserted USB? – Never Quit May 14 '13 at 17:48
  • Hi it works great for USB insertion! the above comment with "SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2" I don't know why but it doesn't work for me. I want the event to run both for insertion and removal. Do you now the line string which will make it happen? or where I can see all the string which the wqleventQuery receive? It's not listed on the MSDN wqleventQuery CTOR page .. – Amit Lipman Jul 02 '15 at 14:31
  • 3
    For removal, you just need to do `WHERE EventType = 2 OR EventType = 3`. `EventType` of `2` means addition, `3` means removal, as per https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-devicechangeevent#members – Callum Rogers Mar 22 '20 at 21:04
21

VitalyB's answer does't cover remove of the device. I changed it a bit to trigger the event both when media is inserted and removed and also code to get the drive letter of the inserted media.

using System;
using System.Management;

namespace MonitorDrives
{
    class Program
    {
        public enum EventType
        {
            Inserted = 2,
            Removed = 3
        }

        static void Main(string[] args)
        {
            ManagementEventWatcher watcher = new ManagementEventWatcher();
            WqlEventQuery query = new WqlEventQuery("SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2 or EventType = 3");

            watcher.EventArrived += (s, e) =>
            {
                string driveName = e.NewEvent.Properties["DriveName"].Value.ToString();
                EventType eventType = (EventType)(Convert.ToInt16(e.NewEvent.Properties["EventType"].Value));

                string eventName = Enum.GetName(typeof(EventType), eventType);

                Console.WriteLine("{0}: {1} {2}", DateTime.Now, driveName, eventName);
            };

            watcher.Query = query;
            watcher.Start();

            Console.ReadKey();
        }
    }
}
Fidel
  • 7,027
  • 11
  • 57
  • 81
Ashkan Mobayen Khiabani
  • 33,575
  • 33
  • 102
  • 171
6

A little bit edit on all above answer:

using System.Management;

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();

        bgwDriveDetector.DoWork += bgwDriveDetector_DoWork;
        bgwDriveDetector.RunWorkerAsync();
    }

    private void DeviceInsertedEvent(object sender, EventArrivedEventArgs e)
    {
        string driveName = e.NewEvent.Properties["DriveName"].Value.ToString();
        MessageBox.Show(driveName + " inserted");
    }

    private void DeviceRemovedEvent(object sender, EventArrivedEventArgs e)
    {
        string driveName = e.NewEvent.Properties["DriveName"].Value.ToString();
        MessageBox.Show(driveName + " removed");
    }

    void bgwDriveDetector_DoWork(object sender, DoWorkEventArgs e)
    {
        var insertQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
        var insertWatcher = new ManagementEventWatcher(insertQuery);
        insertWatcher.EventArrived += DeviceInsertedEvent;
        insertWatcher.Start();

        var removeQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
        var removeWatcher = new ManagementEventWatcher(removeQuery);
        removeWatcher.EventArrived += DeviceRemovedEvent;
        removeWatcher.Start();
    }
}
lzutao
  • 409
  • 5
  • 13
5

You can also use WMI to detect insertion events. It's a little bit more complicated than monitoring for WM_CHANGEDEVICE messages, but it does not require a window handle which may be useful if you are running in the background as a service.

John Conrad
  • 305
  • 1
  • 2
  • 7
  • 2
    @John Conrad: +1 WMI is a good choice. Also found a SO topic on this: http://stackoverflow.com/questions/39704/wmi-and-win32devicechangeevent-wrong-event-type-returned – Kb. Mar 07 '09 at 08:20
  • Actually WMI is much simpler solution. I'm posting it below as another solution. – VitalyB Jun 07 '10 at 10:06
4

My complete answer can be found here as a gist

I found the answer to determining the drive letter from the serial # from this question/answer How to get the drive letter of USB device using WMI

And I modified Phil Minor's code to make it reactive:

   public class UsbDetector : IUsbDetector
    {
        private const string Query = "SELECT * FROM {0} WITHIN 2 WHERE TargetInstance ISA 'Win32_USBHub'";
        private const string CreationEvent = "__InstanceCreationEvent";
        private const string DeletionEvent = "__InstanceDeletionEvent";
        private const int ReplayNumber = 1;

        private readonly Subject<USBDeviceInfo> adds = new Subject<USBDeviceInfo>();
        private readonly Subject<USBDeviceInfo> removes = new Subject<USBDeviceInfo>();

        public UsbDetector()
        {
            var bgwDriveDetector = new BackgroundWorker();
            bgwDriveDetector.DoWork += DoWork;
            bgwDriveDetector.RunWorkerAsync();
        }

        public IObservable<USBDeviceInfo> Adds => adds.AsObservable();

        public IObservable<USBDeviceInfo> Removes => removes.AsObservable();


        private void DoWork(object sender, DoWorkEventArgs e)
        {
            SubscribeToEvent(CreationEvent, adds);
            SubscribeToEvent(DeletionEvent, removes);
        }

        private static void SubscribeToEvent(string eventType, IObserver<USBDeviceInfo> observer)
        {
            WqlEventQuery wqlEventQuery = new WqlEventQuery(string.Format(Query, eventType));
            ManagementEventWatcher insertWatcher = new ManagementEventWatcher(wqlEventQuery);

            var observable = Observable.FromEventPattern<EventArrivedEventHandler, EventArrivedEventArgs>(
                h => insertWatcher.EventArrived += h,
                h => insertWatcher.EventArrived -= h).Replay(ReplayNumber);

            observable.Connect();
            observable.Select(a => a.EventArgs).Select(MapEventArgs).Subscribe(observer);
            insertWatcher.Start();
        }


        private static USBDeviceInfo MapEventArgs(EventArrivedEventArgs e)
        {
            ManagementBaseObject instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];

            string deviceId = (string)instance.GetPropertyValue("DeviceID");
            string serialNr = deviceId.Substring(deviceId.LastIndexOf('\\')).Replace("\\", "");
            char driveLetter = GetDriveLetter(serialNr).First();

            return new USBDeviceInfo(deviceId, serialNr, driveLetter);
        }
dtwk2
  • 75
  • 6
4

Try WM_CHANGEDEVICE handling.

Mike Marshall
  • 7,788
  • 4
  • 39
  • 63
3

Here is what we did with C# .Net 4.0 under a WPF app. We are still searching for an answer to "how to tell WHICH device type was inserted/removed", but this is a start:

    using System.Windows.Interop;
...
public partial class MainWindow : Window
 {
    ...
    public MainWindow()
    {
    ...
    }

    //============================================================
    // WINDOWS MESSAGE HANDLERS
    // 

    private const int WM_DEVICECHANGE = 0x0219;  // int = 537
    private const int DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 0x00000004; 

    /// <summary>
    ///
    /// </summary>
    /// <param name="e"></param>
    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
        source.AddHook(WndProc);
    }

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == WM_DEVICECHANGE)
        {
            ReadDongleHeader();
        }
        return IntPtr.Zero;
    }

}
Lance Cleveland
  • 3,098
  • 1
  • 33
  • 36
  • 2
    Any improvements as to figuring out which device was inserted? – Kcvin Jun 26 '13 at 14:49
  • @Kevin this can easily be found elsewhere to get the list of devices. Here is a full solution which I got to first. Only WM_DEVICECHANGE is fired for me. https://social.msdn.microsoft.com/Forums/vstudio/en-US/ea183afd-d070-4abd-8e00-a1784fdfedc5/detecting-usb-device-insertion-and-removal?forum=csharpgeneral – CularBytes Mar 10 '16 at 22:41
2
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Management;
using System.ComponentModel;

namespace ConsoleApplication4
{
  public  class usbState
    {
       public usbState()
        {

        }

   private void DeviceInsertedEvent(object sender, EventArrivedEventArgs e)
   {
       ManagementBaseObject instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];
       foreach (var property in instance.Properties)
       {
           Console.WriteLine(property.Name + " = " + property.Value);
       }
   }

   private void DeviceRemovedEvent(object sender, EventArrivedEventArgs e)
   {
       ManagementBaseObject instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];
       foreach (var property in instance.Properties)
       {
           Console.WriteLine(property.Name + " = " + property.Value);
       }
   } 

    public  void bgwDriveDetector_DoWork(object sender, DoWorkEventArgs e)
    {
        WqlEventQuery insertQuery = new WqlEventQuery("SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_USBHub'");

        ManagementEventWatcher insertWatcher = new ManagementEventWatcher(insertQuery);
        insertWatcher.EventArrived += new EventArrivedEventHandler(DeviceInsertedEvent);
        insertWatcher.Start();

        WqlEventQuery removeQuery = new WqlEventQuery("SELECT * FROM __InstanceDeletionEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_USBHub'");
        ManagementEventWatcher removeWatcher = new ManagementEventWatcher(removeQuery);
        removeWatcher.EventArrived += new EventArrivedEventHandler(DeviceRemovedEvent);
        removeWatcher.Start();
    }

}



class Class1
{
       private static void Main(string[] args)
      {
          usbState  usb= new usbState();



          BackgroundWorker bgwDriveDetector = new BackgroundWorker();
          bgwDriveDetector.DoWork += usb.bgwDriveDetector_DoWork;
          bgwDriveDetector.RunWorkerAsync();
          bgwDriveDetector.WorkerReportsProgress = true;
          bgwDriveDetector.WorkerSupportsCancellation = true;

         // System.Threading.Thread.Sleep(100000);
           Console.ReadKey();

       }





}

}
Milind Morey
  • 2,100
  • 19
  • 15