使用 DeviceWatcher 监控 USB 驱动器并检索设备信息?

Posted

技术标签:

【中文标题】使用 DeviceWatcher 监控 USB 驱动器并检索设备信息?【英文标题】:Monitor USB drives and retrieve device Info using a DeviceWatcher? 【发布时间】:2014-09-16 16:00:19 【问题描述】:

我是 WinForms 开发人员,我已经知道如何使用 WMI 监控连接或断开连接的 USB,但不久前我发现了现代 Windows 应用程序的 DeviceWatcher 类,它班级第一次感兴趣,因为似乎是一个非常改进和有效的替代方案,可以替代所有解释如何通过 Internet 监控驱动器的“旧”WMI 代码,但直到昨天(感谢this post)我还不知道如何在 WinForms 项目中使用 DeviceWatcher,但现在我在 WinForms 项目中使用 DeviceWatcher。

问题是,也许我错了,但我认为这并不是我所期望的,只是我找不到任何关于 DeviceWatcher 的文档(只有上面的 MSDN 示例)而且我找不到检索必要信息以监视驱动器事件的方法,我尝试处理 DeviceWatcher 的所有事件,以在 Debug 控制台中打印出参数中包含的所有数据,希望能找到对我有帮助的东西。 ..但不是,我非常坚持使用 DeviceWatcher 类,我无法想象如何进行。

当我连接或断开 USB 时,我只看到两件事,硬件 ID 和“InterfaceEnabled”属性(我不知道这是否决定了设备可用性),没有什么有趣的了。

我的成就:

· 检索硬件设备 ID。

我想要完成的事情:

· 在设备连接、断开和断开时检索设备类型(以区分 USB 和其他类型的设备)。

· 在设备连接、断开和断开连接时检索设备可用性(我的意思是设备是否可以在其上读取/写入数据)。

· 在设备连接、断开和断开连接时检索设备字母。

· 在设备连接、断开和断开时检索设备标签描述。

代码:

Public Class DeviceWatcher_Test

    Private WithEvents dw As DeviceWatcher = DeviceInformation.CreateWatcher

    ' It's suposed that these properties should exist in the "e.properties" on the "dw_updated" event?, not in my case.
    ' Dim props As String() = "System.ItemNameDisplay", "System.Devices.ModelName", "System.Devices.Connected"

    Private Sub Test() Handles MyBase.Load

        dw.Start()

    End Sub

    Private Sub dw_Added(ByVal sender As DeviceWatcher, ByVal e As DeviceInformation) _
    Handles dw.Added

        Dim sb As New System.Text.StringBuilder

        With sb
            .AppendLine("dw_added")
            .AppendLine("********")
            .AppendLine(String.Format("Interface ID.: 0", e.Id))
            .AppendLine(String.Format("Friendly Name: 0", e.Name))
            .AppendLine(String.Format("Is Enabled?..: 0", e.IsEnabled))
        End With

        Debug.WriteLine(sb.ToString)

    End Sub

    Private Sub dw_Removed(ByVal sender As DeviceWatcher, ByVal e As DeviceInformationUpdate) _
    Handles dw.Removed

        Dim sb As New System.Text.StringBuilder

        With sb
            .AppendLine("dw_Removed")
            .AppendLine("**********")
            .AppendLine(String.Format("Interface ID:0", e.Id))

            For Each item As KeyValuePair(Of String, Object) In e.Properties
                .AppendLine(String.Format("TKey:0, TVal:1 (TVal Type:2)",
                                          item.Key, item.Value.ToString, item.Value.GetType.Name))
            Next

        End With

        Debug.WriteLine(sb.ToString)

    End Sub

    Private Sub dw_Updated(ByVal sender As DeviceWatcher, ByVal e As DeviceInformationUpdate) _
    Handles dw.Updated

        Dim sb As New System.Text.StringBuilder

        With sb
            .AppendLine("dw_Updated")
            .AppendLine("**********")
            .AppendLine(String.Format("Interface ID: 0", e.Id))

            For Each item As KeyValuePair(Of String, Object) In e.Properties

                If item.Key.EndsWith("InterfaceEnabled", StringComparison.OrdinalIgnoreCase) Then
                    Dim Result As Boolean = CBool(item.Value)
                    ' I'm not sure whether the 'Result' value really determines this:
                    .AppendLine(String.Format("The device is accessible?:0", CStr(Result)))

                Else
                    .AppendLine(String.Format("TKwy:0, TVal:1 (TVal Type:2)",
                                              item.Key, item.Value.ToString, item.Value.GetType.Name))

                End If

            Next

        End With

        Debug.WriteLine(sb.ToString)

    End Sub

    Private Sub dw_Stopped(ByVal sender As DeviceWatcher, ByVal e As Object) _
    Handles dw.Stopped

        Dim sb As New System.Text.StringBuilder

        With sb
            .AppendLine("dw_Stopped")
            .AppendLine("**********")
            .AppendLine(String.Format("e:1 (e Type:2)",
                                      e.ToString, e.GetType.Name))

        End With

        Debug.WriteLine(sb.ToString)

    End Sub

    Private Sub dw_EnumerationCompleted(ByVal sender As DeviceWatcher, ByVal e As Object) _
    Handles dw.EnumerationCompleted

        If e IsNot Nothing Then

            Dim sb As New System.Text.StringBuilder

            With sb
                .AppendLine("EnumerationCompleted")
                .AppendLine("********************")
                .AppendLine(String.Format("e:1 (e Type:2)",
                                          e.ToString, e.GetType.Name))

            End With

            Debug.WriteLine(sb.ToString)

        End If

    End Sub

End Class

【问题讨论】:

这是手机应用还是商店应用? 都不是,我已经解释过,这是一个 WindowsForm 项目(桌面应用程序),感谢您的评论! 我之所以问,是因为所有相关文档都在 MSDN 上的 Phone 和 StoreApps 下(或者我找到了错误的文档)。对于 WinForms,你可以使用 WMI,不是吗? 是的,但我的意图是用现代和改进的方式替换 WMI 的使用,比如 DeviceWatcher(也许我错了,但我认为更好) System.Devices.InterfaceEnabled 将为您提供可用性。要获取设备类型,您可以使用 DeviceInformation.CreateFromIdAsync 方法并指定附加的“System.Devices.InterfaceClassGuid”属性名称(最后一个参数)。 USB 设备将具有此属性的 GUID_DEVINTERFACE_USB_DEVICE。对于其余部分(卷、字母、磁盘等),DeviceWatcher 对您帮助不大。 WMI 对此要简单得多(例如:codeproject.com/Articles/13530/Eject-USB-disks-using-C)。不知道为什么要从 WMI 更改为这个糟糕的 W8/JS 东西... 【参考方案1】:

这将监视 USB 驱动器的到达和移除,并报告 DriveLetter、卷标和设备序列号。为了您的方便,它引发了 2 个事件:

Public Event DeviceAdded(sender As Object, e As USBWatcherEventArgs)
Public Event DeviceRemoved(sender As Object, e As USBWatcherEventArgs)

这只监视添加/删除的新驱动器,它不会检测诸如 CD 弹出或插入,或将媒体插入读卡器插槽之类的事情。

Imports System.Management

Public Class USBWatcher

    ' alternate method to use one event with an extra
    ' event property to report the action
    'Public Enum WatcherActions
    '    DriveInserted = 1
    '    DriveRemoved = 2
    'End Enum

    ' USB device added/removed args
    Public Class USBWatcherEventArgs
        Inherits EventArgs

        'Public Property WatcherAction As WatcherActions
        Public Property DriveLetter As String
        Public Property VolumeName As String
        Public Property VolumeSerial As String

        Friend Sub New(drv As String, vol As String, ser As String)
            DriveLetter = drv
            VolumeName = vol
            VolumeSerial = ser
            'WatcherAction = act
        End Sub

    End Class

    Private WithEvents Watcher As ManagementEventWatcher

    Public Event DeviceAdded(sender As Object, e As USBWatcherEventArgs)
    Public Event DeviceRemoved(sender As Object, e As USBWatcherEventArgs)

    Private pnpCol As Dictionary(Of String, Management.ManagementObject)

    Public Sub New()

    End Sub

    Public Sub StartWatching()

        ' get USBs currently attached
        pnpCol = GetUSBDevices()

        Dim arriveQuery = New WqlEventQuery("Select * from Win32_DeviceChangeEvent")
        Watcher = New ManagementEventWatcher(arriveQuery)

        ' we are watching you
        Watcher.Start()

    End Sub

    Public Sub StopWatching()
        Watcher.Stop()

    End Sub

    Private Function GetUSBDevices() As Dictionary(Of String,
                        Management.ManagementObject)

        Dim col As New Dictionary(Of String, Management.ManagementObject)

        Dim moSearch As New Management.ManagementObjectSearcher("Select * from Win32_LogicalDisk")
        Dim moReturn As Management.ManagementObjectCollection = moSearch.Get


        For Each mo As Management.ManagementObject In moReturn
            'Console.WriteLine("====")
            'DebugProperties(mo)

            ' some USB external drives report as DriveType 3 (local disk), but are
            ' in fact removable.  So monitor all disk drives.
            If col.ContainsKey(mo("DeviceID").ToString) = False Then
                col.Add(mo("DeviceID").ToString, mo)
            End If

        Next

        Return col
    End Function

    Private inEvent As Boolean = False

    Private Sub arrive_EventArrived(ByVal sender As Object, 
                    ByVal e As System.Management.EventArrivedEventArgs) _
                    Handles Watcher.EventArrived

        If inEvent Then Exit Sub
        inEvent = True

        Dim col As Dictionary(Of String, Management.ManagementObject) = GetUSBDevices()

        Select Case col.Count
            Case Is > pnpCol.Count
                ' device arrived
                ProcessArrival(col)
            Case Is < pnpCol.Count
                ' device removed
                ProcessRemoval(col)
            Case Is = pnpCol.Count
                ' noise...this is a chatty rascal
        End Select

        inEvent = False

    End Sub

    Private Sub ProcessArrival(col As Dictionary(Of String,
               Management.ManagementObject))
        For Each kvp As KeyValuePair(Of String, 
                 Management.ManagementObject) In col
            If pnpCol.ContainsKey(kvp.Key) = False Then

                'Console.WriteLine("0 1 ", kvp.Key, kvp.Value)
                'DebugProperties(kvp.Value)

                Dim ea As New USBWatcherEventArgs(kvp.Value("DeviceID").ToString,
                                      SafeString(kvp.Value("VolumeName")),
                                      SafeString(kvp.Value("VolumeSerialNumber")))

                RaiseEvent DeviceAdded(Me, ea)

                'rebuild baseline for next event
                pnpCol = col

            End If
        Next

    End Sub

    Private Sub ProcessRemoval(col As Dictionary(Of String,
                    Management.ManagementObject))
        For Each kvp As KeyValuePair(Of String, 
                          Management.ManagementObject) In pnpCol
            If col.ContainsKey(kvp.Key) = False Then

                'Console.WriteLine("0 1 ", kvp.Key, kvp.Value)
                'DebugProperties(kvp.Value)

                Dim ea As New USBWatcherEventArgs(kvp.Value("DeviceID").ToString,
                                      SafeString(kvp.Value("VolumeName")),
                                      SafeString(kvp.Value("VolumeSerialNumber")))

                RaiseEvent DeviceRemoved(Me, ea)

                'rebuild baseline for next event
                pnpCol = col

            End If
        Next

    End Sub

    ' lots of things can be NOTHING depending on the manufacturer's
    ' attention to detail.  try to avoid NRE
    Private Function SafeString(obj As Object) As String

        If obj.GetType Is GetType(String) Then
            Return CType(obj, String)
        Else
            If obj IsNot Nothing Then
                Return obj.ToString
            Else
                Return "???"
            End If
        End If

    End Function

    ' debug tool to poll a management object to get the properties and values
    Private Sub DebugProperties(mo As Management.ManagementObject)

        For Each pd As PropertyData In mo.Properties
            If pd.Value IsNot Nothing Then
                Console.WriteLine("0 1", pd.Name,
                                  If(pd.Value IsNot Nothing,
                                     pd.Value.ToString,
                                     "Nothing"))
            End If

        Next
    End Sub

End Class

如何实现 USBWatcher

' local variable to catch events
Private WithEvents watcher As USBWatcher

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    watcher = New USBWatcher
    watcher.StartWatching()     ' or add to a button click
    ' I DONT have it starting automatically when you create the watcher

End Sub

Device Watcher 在不同的线程上运行,因此如果您想向控件发布通知,则必须使用委托:

Delegate Sub AddListItem(text As String)
Private myDelegate As AddListItem = New AddListItem(AddressOf AddNewListItem)

Private Sub AddNewListItem(text As String)
    myListBox.Items.Add(text)
End Sub

然后从设备添加事件,例如:

Private Sub watcher_DeviceAdded(sender As Object, 
            e As USBWatcher.USBWatcherEventArgs) Handles watcher.DeviceAdded

    Console.Beep()

    Dim msg As String = String.Format("Drive 0 (1)   2", 
                                       e.DriveLetter,
                                       e.VolumeName, "Inserted")
    If myListBox.InvokeRequired Then
        myListBox.Invoke(myDelegate, New Object() msg)
    Else
        myListBox.Items.Add(msg)
    End If

End Sub

DeviceRemoved 与消息文本中的第三个参数“Removed”不同。

USBWatcher还有一个StopWatching方法可以关闭watcher一段时间,StartWatching可以启动和重启。当应用程序结束时,您的应用程序应调用StopWatching 以防止 COM 错误;只需在表单关闭事件中添加watcher.StopWatching

这可以满足您的要求 - 在插入可移动媒体时引发事件并返回有关它们的信息 - 只是不完全是如何您想要的。它使用久经考验的 WMI 而不是 Win8 方法,该方法目前在 MSDN 上只有稀疏文档,并且只能在 Win8 上运行。

【讨论】:

优秀的课!不知this post能不能提高?我们可以添加这一行吗? If col.ContainsKey(mo("DeviceID").ToString) = False And (CType(mo("DriveType"), IO.DriveType) = IO.DriveType.Removable) Then: Console.WriteLine(mo("DriveType")) :End If【参考方案2】:

您可以使用 WMI 来检测是否插入了笔式驱动器。 您必须添加对 dotnet 框架的 System.Management dll 的引用。

您可以将详细信息获取为:

Public Function SerialNO(ByVal DriveNameWithColon As String) As String
    Dim disk As ManagementObject = New ManagementObject("win32_logicaldisk.deviceid='" + DriveNameWithColon + "'")
    disk.Get()
    Return disk("VolumeSerialNumber").ToString()
End Function

【讨论】:

WMI 是检测 Win32_logicalDisks 更改的更好选择 @Er Mayank 正如我在我的问题中所解释的那样,我知道如何实现 WMI 使用来执行有关驱动器插入/弹出的所有相关事情,没有必要展示一个示例,但无论如何都要感谢。 @Master User 如果您可以考虑写一个非常详细的答案,解释要点,提供参考网址,并确定 WMI Classes 使用和 DeviceWatcher 使用之间的差异,以演示旧十年 WMI 使用是否更好以及为什么比这十年的现代解决方案(如 DeviceWatcher)更好,然后我可以考虑将这样的答案标记为已接受的答案(只有当可以证明我正在使用 DeviceWatcher 浪费时间,因为 WMI 是更好的选择) .感谢您的评论。【参考方案3】:

您可以从接口 ID 中获取 PnPObject,这将为您提供更多设备特定信息。

public static IAsyncOperation<PnpObject> CreateFromIdAsync(
  PnpObjectType type, 
  string id, 
  IEnumerable<string> requestedProperties
)

看起来很有趣的家伙。您可以为 PnpObjectType,

指定几个不同的选项

设备接口 | deviceInterface - PnpObject 表示设备接口。

设备容器 | deviceContainer - PnpObject 代表一个设备容器。

设备 | device - PnpObject 代表一个设备。

设备接口类 | deviceInterfaceClass - PnpObject 代表一个设备接口类。

id实际上是你已经收到的接口id。

另请注意,您有可以请求的属性列表(requstedProperties 参数):http://msdn.microsoft.com/en-us/library/hh464997.aspx#ListOfCanonicalProperties。

对你来说确实没有太多,但不要害怕。还有很多:http://msdn.microsoft.com/en-us/library/ff553416.aspx

如果你检查 devpkey.h,你会发现有趣的属性,例如:

DEVPKEY_Device_DevType DEVPKEY_Storage_Removable_Media DEVPKEY_Storage_Portable -

有些东西是设备规格的,有些不是。您可以通过devmgmt.msc(Details->Property) 获得更多属性。注册表中还有很多东西。统一设备属性模型对此进行了讨论:http://msdn.microsoft.com/en-us/library/ff553515(v=vs.85).aspx

PnP api 有以下方法:

Await DeviceInformation.FindAllAsync()
Await Pnp.PnpObject.FindAllAsync(Pnp.PnpObjectType.DeviceContainer, ...)
Await Pnp.PnpObject.FindAllAsync(Pnp.PnpObjectType.Device, ...)
Await Pnp.PnpObject.FindAllAsync(Pnp.PnpObjectType.DeviceInterface, ...) 
Await Pnp.PnpObject.FindAllAsync(Pnp.PnpObjectType.DeviceInterfaceClass, ...)  
Await Pnp.PnpObject.CreateFromIdAsync(..., id, ...)

您可以阅读这篇文章:http://www.codeproject.com/Articles/458550/Device-enumeration-in-Windows

它包含所有内容、源代码、密钥,并展示了如何将设备中的所有属性转储到文件中。这样您就可以看到您的 U 盘具有哪些属性。

【讨论】:

我不明白你想说什么,如何使用 VB.NET 代码获取“PnpObject”?我已经尝试了 devicewaatcher 的 pnp 类的所有方法,但没有文档,仅适用于 javascript,我真的不明白该怎么做,还假设这里需要异步操作?。 我已经尝试了您提供的 url 中的所有属性名称,我的设备没有“存在”,连接时我无法获取设备的路径/字母,我除了接口 ID 和友好名称之外什么也得不到。 我添加了一个链接,其中包含 VB 中的示例代码。它应该可以帮助你。不过这里引用的时间太长了。我没有win8也没有VB技能,我现在只能告诉你:-)

以上是关于使用 DeviceWatcher 监控 USB 驱动器并检索设备信息?的主要内容,如果未能解决你的问题,请参考以下文章

技巧.在虚拟机Vmware中使用HID设备(如USB免驱键盘)

在通用 Windows 平台 C# 中使用 DeviceWatcher 填充 ComboBox

在人脸识别机器视觉中USB免驱摄像头拍照颜色不对怎么办?大影30~1600万像素多种摄像头方案通过一次白平衡解决偏色问题。

UVC采集卡是啥?

USB开发——内核USB驱动+libusb开发方法

win7无法自动更新,xbox one手柄怎么驱