11

I'm a WinForms developer and I already knew how to monitor the USB's that connects or disconnects using WMI, but time ago I'd discovered the DeviceWatcher class for Modern Windows Apps, that Class has interested at first time 'cause seems like a very improved and efficient alternative to replace all those 'old' WMI codes that explains how to monitor drives over Internet, but until yesterday (thanks to this post) I haven't idea about how to use the DeviceWatcher in a WinForms project, but now I'm using the DeviceWatcher in a WinForms project.

The problem is that maybe I'm wrong but I think that this is not really what I expected, just I can't find any kind of documentation about the DeviceWatcher (only the MSDN example above) and I can't find the way to retrieve the necessary information to monitor the drive events, I've tried to handle all the events of the DeviceWatcher to print out in the Debug console all the data contained in the arguments with the hope to find something that could help me ...but not, I'm very stuck with the usage of the DeviceWatcher Class and I can't imagine how to procceed.

When I connect or disconnect an USB I just see two things, the Hardware ID and the 'InterfaceEnabled' property (that I don't know if that determines the Device Availability), nothing interesting more.

What I have accomplished:

· Retrieve the Hardware Device ID's.

What I would like to accomplish:

· Retrieve the Device type (to difference between USB and other kind of devices) when the device is connected, disconnecting, and disconnected.

· Retrieve the Device Availability (I mean whether the device is accessible to read/write data on it) when the device is connected, disconnecting, and disconnected.

· Retrieve the Device Letter when the device is connected, disconnecting, and disconnected.

· Retrieve the Device Label-Description when the device is connected, disconnecting, and disconnected.

The code:

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
Community
  • 1
  • 1
ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • Is this a Phone App or Store App? – Ňɏssa Pøngjǣrdenlarp Jul 25 '14 at 00:04
  • None of they, it's a WindowsForm project (desktop app) as I've explained, thanks for comment! – ElektroStudios Jul 25 '14 at 01:17
  • I asked because all the docs for this are under Phone and StoreApps on MSDN (or I found the wrong ones). For WinForms, you could use WMI, no? – Ňɏssa Pøngjǣrdenlarp Jul 25 '14 at 01:18
  • Yes but my intentions is to replace the WMI usage for a modern and improved way like DeviceWatcher (maybe I'm wrong but I think is better) – ElektroStudios Jul 25 '14 at 01:20
  • 1
    System.Devices.InterfaceEnabled will give you availability. To get the type of device, you can use the DeviceInformation.CreateFromIdAsync method and specify an additional "System.Devices.InterfaceClassGuid" property name (last parameter). An USB device will have GUID_DEVINTERFACE_USB_DEVICE for this property. For the rest (volume, letter, disk, etc.), the DeviceWatcher will not help you much. WMI is much simpler for this (ex: http://www.codeproject.com/Articles/13530/Eject-USB-disks-using-C). Not sure why you want to change from WMI to this poor W8/JS thing... – Simon Mourier Jul 28 '14 at 13:30
  • WMI is a better choice (no matter the age) if DeviceWatcher does not provide the information you want. As an analogy consider `FileSystemWatcher` it simply tells you when a file has been added, changed, deleted etc. It is outside its scope to also tell you the size, creation date, last modified etc; all it tells you is the name. Since WM appears to be primarily for phones and such, they likely keep the the footprint small purposely. Finally, you assume that even if it were available, that MS would write a whole new API rather than resuse what they have in WMI. – Ňɏssa Pøngjǣrdenlarp Aug 06 '14 at 18:47

3 Answers3

6

This will watch for the Arrival and Removal of USB drives, and report the DriveLetter, Volume Label and device serial number. It raises 2 Events for your convenience:

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

This only watches for a new drive added/removed, it will not detect things like a CD being ejected or inserted, or inserting media into a card reader slot.

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

How to Implement 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

The Device Watcher runs on a different thread so if you want to post a notice to a control, you will have to use a delegate:

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

Then from the Device Added event, for example:

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 would be the same except "Removed" as the third param in the message text.

USBWatcher also has a StopWatching method to turn off the watcher for a time, and StartWatching to start and restart it. Your app should call StopWatching when the app ends to prevent COM errors; just add watcher.StopWatching in the form close event.

This does what you want - raise events when removable media is inserted and return the information about them - just not exactly how you wanted it. It uses tried and tested WMI rather than the Win8 method, which is as yet has only sparse documention on MSDN and would only work on Win8.

Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
  • Excellent class!, I wonder if [this post](https://stackoverflow.com/a/4396692/7735285) can be used to improve? Can we add this line? `If col.ContainsKey(mo("DeviceID").ToString) = False And (CType(mo("DriveType"), IO.DriveType) = IO.DriveType.Removable) Then: Console.WriteLine(mo("DriveType")) :End If` – wpcoder Aug 08 '17 at 13:10
4

you can use WMI to detect, if pen drive is inserted. You have to add reference to the System.Management dll of dotnet framework.

you can get Devide Details as :

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
Er Mayank
  • 1,017
  • 2
  • 16
  • 39
  • 2
    WMI is better option for detecting changes in Win32_logicalDisks –  Jul 28 '14 at 09:26
  • @Er Mayank as I've explained in my question I know how to implement the WMI usage to do all related things about drive insertion/ejection, is not necessary to show an example, but thanks anyways. – ElektroStudios Jul 29 '14 at 07:25
  • @Master User If you could consider to write a very detailed answer explaining the points, giving reference urls, and determining the differences between WMI Classes usage and DeviceWatcher usage to demostrate whether the old decade WMI usage is better and why is better than a modern solution of this decade like DeviceWatcher is, then I could consider to mark an answer like that as the accepted one (only if could demostrate that i'm loosing time wth DeviceWatcher 'cause WMI is a better choice). thanks for comment. – ElektroStudios Jul 29 '14 at 07:29
4

You can get PnPObject from the interface ID which will give you more device specific information.

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

seems pretty interesting fellah. You have few different options to specfy for PnpObjectType,

DeviceInterface | deviceInterface - The PnpObject represents a device interface.

DeviceContainer | deviceContainer - The PnpObject represents a device container.

Device | device -The PnpObject represents a device.

DeviceInterfaceClass | deviceInterfaceClass - The PnpObject represents a device interface class.

The id actually is interface id, which you already received.

Also note that you have list of properties you can ask for(requstedProperties argument): http://msdn.microsoft.com/en-us/library/hh464997.aspx#ListOfCanonicalProperties.

It's true that there's not too much for you, but don't be scared. There's a lot more of them: http://msdn.microsoft.com/en-us/library/ff553416.aspx

If you check devpkey.h, you can find interesting properties, such as:

  • DEVPKEY_Device_DevType
  • DEVPKEY_Storage_Removable_Media
  • DEVPKEY_Storage_Portable
  • -

Some of the things are device specfc, some of not. You can get even more properties through devmgmt.msc(Details->Property). There's also bunch of stuff in the registry. The Unified device property model discusses this: http://msdn.microsoft.com/en-us/library/ff553515(v=vs.85).aspx

PnP api has following methods:

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, ...)

You can read this article: http://www.codeproject.com/Articles/458550/Device-enumeration-in-Windows

It has everything, source code, keys, and shows how to dump all the properties from devices into file. This way you can see what properties your USB stick has.

Erti-Chris Eelmaa
  • 25,338
  • 6
  • 61
  • 78
  • I don't understand what you've tried to say me, how to get that "PnpObject" using VB.NET code?. I've tried all the ways with the pnp class of devicewaatcher but there is no documentantion, only for javascript and I really don't understand how to do it, also itsupposed that is necesary an asynchronous operation here?. – ElektroStudios Aug 06 '14 at 12:53
  • I've tried all the property names from the urls that you've provided, none "exists" for my devices, I can't get the path/letter of a device when its connected, I can't get nothing but the interface ID and the friendlyname. – ElektroStudios Aug 06 '14 at 12:55
  • I have added a link that has example code in VB. It should help you. It's way too long to cite here though. I don't have win8 nor any VB skills, that's all I can tell you for now :-) – Erti-Chris Eelmaa Aug 08 '14 at 08:27