使用Windows服务和C#检测USB驱动器的插入和拔出

77

我正在研究制作一个USB分布式应用程序的可能性,
该程序将在插入USB时自动启动,并在拔出USB时关闭。

将使用.Net和C#。
寻求建议,如何使用C#实现这一点?


更新:两种可能的解决方案可以将其实现为服务。
- 覆盖WndProc
或者
- 使用带有ManagementEventWatcher的WMI查询


1
关于服务捕获此事件的好问题。我的第一个想法是将您的服务标记为“允许与桌面交互”,然后创建一个隐藏窗口。更安全的选择可能是创建一个在启动时运行的Windows应用程序-它可以创建窗口,然后与服务进行通信。 - Mike Marshall
相关:https://dev59.com/3W025IYBdhLWcg3wcli- - DuckMaestro
2
我制作了一个 NuGet 包,可以在 Windows、MacOS 和 Linux 上运行:https://github.com/Jinjinov/Usb.Events - Jinjinov
10个回答

80

你可以使用WMI,它很容易并且比使用服务的WndProc解决方案效果要好。

这里是一个简单的例子:

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();

7
可以,但是我该如何获取插入的USB驱动器盘符? - Never Quit
这篇文章似乎是在Powershell中获取此信息。将其翻译成C#应该不难。 - VitalyB
7
在你的事件处理程序中,e.NewEvent.Properties["DriveName"].Value.ToString() - lambinator
1
查询明显是关于音量更改的,与USB添加/移除无关。它甚至不能用于音量更改,至少在我的系统上。 - spy
1
使用上述查询会消耗CPU资源,有什么想法为什么WMI会引起这个问题? - stackmalux
这会立即触发事件还是会在一段时间内检查?/ 另一个程序/用户是否可以在事件触发之前从驱动器中读取/写入文件? - leumasme

57

这对我很有效,而且你可以找到更多有关此设备的信息。

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);
}

3
完美运作。与其他答案不同,插入/删除时不会触发多个事件。这应该是被采纳的答案。 - samuelesque
1
我同意@samuelAndThe的观点,这似乎是最好的方法。 如果你想要检测硬盘驱动器的变化而不仅仅是USB驱动器,你可以使用“Win32_DiskDrive”类。 - helder.tavares.silva
2
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e),为什么要加上(object sender, DoWorkEventArgs e)?(我已经提交了一份编辑建议。) - ch271828n
1
这个能在Mono上运行吗?只是问一下,因为我最近才开始使用它,而且WMI是微软的纯粹产物。 - icbytes
一切都很棒,但请删除那个Thread.Sleep ^_^ - Pavlin Marinov
显示剩余3条评论

26

在VitalyB的帖子中补充一点。

为了触发任何USB设备插入事件,请使用以下代码:

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();

每当插入USB设备时,此代码将触发事件。它甚至可以用于我正在尝试自动检测的National Instruments DAQ。


@Lee Taylor,这个可以正常工作,但是我如何获取插入的USB驱动器盘符呢? - Never Quit
@NeverQuit - 我只是编辑了问题,请问 @Syn!另外,如果您有新的问题,请随意创建一个。 - Lee Taylor
@Syn 这个可以正常工作,但是我怎么获取插入的USB的驱动器号? - Never Quit
嗨,它在USB插入方面运行得很好! 上述评论与“SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2” 我不知道为什么,但对我来说它不起作用。 我希望该事件能够同时运行插入和拔出操作。 您是否知道哪个字符串可以实现这一点? 或者我在哪里可以看到wqleventQuery接收的所有字符串? 它没有列在MSDN wqleventQuery CTOR页面上.. - Amit Lipman
3
要进行删除操作,只需执行 WHERE EventType = 2 OR EventType = 3EventType 的值为 2 表示添加,3 表示删除,请参考 https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-devicechangeevent#members。 - Callum Rogers

21

VitalyB的回答未涉及到设备的移除。我稍作修改,以便在介质插入移除时同时触发事件,并获取插入介质的驱动器号码的代码。

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();
        }
    }
}

1
这很好,但由于某种原因,每次我插入或拔出设备时它都会触发多次事件 - 你知道这可能是为什么,以及如何防止它吗? - komodosp
由于您没有发布您的代码,所以很难确定,但可能是您多次附加了事件。 - Ashkan Mobayen Khiabani
这不是完整的代码!!!因此,您需要替换VitalyB答案中的几行。 - ch271828n
1
很棒的增强功能,Ashkan。当驱动器被弹出时知道这件事情也很不错。 - Fidel

6
对以上所有答案进行了一些编辑:
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();
    }
}

2
在 e.NewEven.Properties 中找不到 DeviceName。 - Kasun Koswattha

5

您还可以使用WMI来检测插入事件。这比监视WM_CHANGEDEVICE消息要复杂一些,但不需要窗口句柄,如果您作为服务在后台运行,则可能会很有用。


2
@John Conrad:+1 WMI是一个不错的选择。我还在stackoverflow上找到了一个相关话题: https://dev59.com/2kXRa4cB1Zd3GeqPohDs - Kb.
实际上,WMI是一个更简单的解决方案。我将其作为另一种解决方案发布在下面。 - VitalyB

4

4

我的完整答案可以在这里的Gist中找到。

我从这个问题/答案中找到了确定序列号驱动器字母的答案:如何使用WMI获取USB设备的驱动器字母

我修改了Phil Minor的代码,使其具有响应性:

   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);
        }

3

以下是我们在WPF应用程序下使用C# .Net 4.0所做的。我们仍在寻找“如何告诉插入/移除了哪种设备类型”的答案,但这是一个开始:

    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;
    }

}

2
有没有更好的方法来确定插入了哪个设备? - Kcvin
@Kevin这个列表可以很容易地在其他地方找到。这是我首先得到的完整解决方案。只有WM_DEVICECHANGE适用于我。 https://social.msdn.microsoft.com/Forums/vstudio/en-US/ea183afd-d070-4abd-8e00-a1784fdfedc5/detecting-usb-device-insertion-and-removal?forum=csharpgeneral - CularBytes

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();

       }





}

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接