2

I'm trying to detect USB device insertion and remove with WinForm desktop C# application:

 public Form1()
    {
        InitializeComponent();
        USB();
    }

then:

private void USB()
{
     WqlEventQuery weqQuery = new WqlEventQuery();
     weqQuery.EventClassName = "__InstanceOperationEvent";
     weqQuery.WithinInterval = new TimeSpan(0, 0, 3);
     weqQuery.Condition = @"TargetInstance ISA 'Win32_DiskDrive'";  
     var m_mewWatcher = new ManagementEventWatcher(weqQuery);
     m_mewWatcher.EventArrived += new EventArrivedEventHandler(m_mewWatcher_EventArrived);
     m_mewWatcher.Start();           
}

and:

static void m_mewWatcher_EventArrived(object sender, EventArrivedEventArgs e)
    {
        bool bUSBEvent = false;
        foreach (PropertyData pdData in e.NewEvent.Properties)
        {
            ManagementBaseObject mbo = (ManagementBaseObject)e.NewEvent.Properties["TargetInstance"].Value;
           // ManagementBaseObject mbo = (ManagementBaseObject)pdData.Value;
            if (mbo != null)
            {
                foreach (PropertyData pdDataSub in mbo.Properties)
                {
                    if (pdDataSub.Name == "InterfaceType" && pdDataSub.Value.ToString() == "USB")
                    {
                        bUSBEvent = true;
                        break;
                    }
                }

                if (bUSBEvent)
                {
                    if (e.NewEvent.ClassPath.ClassName == "__InstanceCreationEvent")
                    {
                        MessageBox.Show ("USB was plugged in");
                    }
                    else if (e.NewEvent.ClassPath.ClassName == "__InstanceDeletionEvent")
                    {
                        MessageBox.Show("USB was plugged out");
                    }
                }
            }
        }
    }

But I got exception with ManagementBaseObject mbo = (ManagementBaseObject)pdData.Value; when detects USB change:

An exception of type 'System.InvalidCastException' occurred in Controller.exe but was not handled in user code

Additional information: Unable to cast object of type 'System.UInt64' to type 'System.Management.ManagementBaseObject'.

edit:

using System;
using System.Windows.Forms;
using System.Management;

namespace test
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            WqlEventQuery query = new WqlEventQuery()
            {
                EventClassName = "__InstanceOperationEvent",
                WithinInterval = new TimeSpan(0, 0, 3),
                Condition = @"TargetInstance ISA 'Win32_DiskDrive'"
            };

            using (ManagementEventWatcher MOWatcher = new ManagementEventWatcher(query))
            {
                MOWatcher.EventArrived += new EventArrivedEventHandler(DeviceInsertedEvent);
                MOWatcher.Start();
            }
        }

        private void DeviceInsertedEvent(object sender, EventArrivedEventArgs e)
        {
            using (ManagementBaseObject MOBbase = (ManagementBaseObject)e.NewEvent.Properties["TargetInstance"].Value)
            {
                bool DriveArrival = false;
                string EventMessage = string.Empty;
                string oInterfaceType = MOBbase.Properties["InterfaceType"]?.Value.ToString();

                if (e.NewEvent.ClassPath.ClassName.Equals("__InstanceDeletionEvent"))
                {
                    DriveArrival = false;
                    EventMessage = oInterfaceType + " Drive removed";
                }
                else
                {
                    DriveArrival = true;
                    EventMessage = oInterfaceType + " Drive inserted";
                }
                EventMessage += ": " + MOBbase.Properties["Caption"]?.Value.ToString();
                this.BeginInvoke((MethodInvoker)delegate { this.UpdateUI(DriveArrival, EventMessage); });
            }
        }

        private void UpdateUI(bool IsDriveInserted, string message)
        {
            if (IsDriveInserted)
            {
                this.label1.Text = message;
            }             
            else
            {
                this.label1.Text = message;
            }                
        }
    }
}
  • `pdData.Value` can be: `UInt16` for `EventType`, `UInt8` for `SECURITY_DESCRIPTOR` and `UInt64` for `TIME_CREATED`. These are the 3 properties that `e.NewEvent` exposes. You, of course, can't cast any of these to `ManagementBaseObject`. You could cast `e.NewEvent`, but it already derives from that base object, so you won't get another "view" of it. – Jimi Oct 01 '18 at 20:40
  • @Jimi I'm not sure if I get it right, you mean I have to parse, like this `ManagementBaseObject mbo = (ManagementBaseObject)UInt16.Parse(pdData.Value);`, but how if it is object to string. So I'm still not sure how to get desired result –  Oct 01 '18 at 20:57
  • Ah, no, sorry. I didn't notice you had changed the WMI watcher method. Your base class is `TargetInstance` now. That is a full `ManagementObject`. The property name of course is "TargetInstance", you can parse it as usual. It will contain the `Win32_DiskDrive` base informations. – Jimi Oct 01 '18 at 21:09
  • Well, to be more explicit, you can cast it this way: `ManagementBaseObject MOBbase = (ManagementBaseObject)e.NewEvent.Properties["TargetInstance"].Value;`. Then, you have 51 new properties, which map to a `Win32_DiskDrive` MO type with the usual properties "Caption", DeviceID", "Capabilities" etc. – Jimi Oct 01 '18 at 21:18
  • @Jimi I' ve changed it from `ManagementBaseObject mbo = (ManagementBaseObject)pdData.Value;` to `ManagementBaseObject mbo = (ManagementBaseObject)e.NewEvent.Properties["TargetInstance"].Value;`, now it doesn't even detects plug and unplug event –  Oct 01 '18 at 21:27
  • Update your question's code, otherwise it's unclear what's happening. You don't need any other code in the `EventArrived` other than the `(...) (ManagementBaseObject)e.NewEvent (...)` cast and a `.BeginInvoke` if you need to update the UI. Eliminate the `BackGroundWorker` if you still have one. This procedure is already async. – Jimi Oct 01 '18 at 21:32
  • @Jimi Code updated, it is the same, but without `BackGroundWorker` and with`ManagementBaseObject mbo = (ManagementBaseObject)e.NewEvent.Properties["TargetInstance"].Value;` instead of `ManagementBaseObject mbo = (ManagementBaseObject)pdData.Value;` –  Oct 01 '18 at 21:46
  • Eliminate the first `foreach`. That will cause the procedure to crash right after. Then, you can just write `string oInterfaceType = mbo.Properties["InterfaceType"]?.Value.ToString()` `string oDriveCaption = mbo.Properties["Caption"]?.Value.ToString()` etc. Note that not all properties return a string. You'll have to check the actual value type. Use the previously linked code: [Get serial number of usb storage device (...)](https://stackoverflow.com/questions/49118708/get-serial-number-of-usb-storage-device-in-net-core-2-1?answertab=active#tab-top) as a blueprint for a property storage. – Jimi Oct 01 '18 at 22:07
  • @Jimi well, I don't know, I can't checked this part because I have same `System.InvalidCastException` with `ManagementBaseObject mbo = (ManagementBaseObject)pdData.Value;` and nothing with `ManagementBaseObject mbo = (ManagementBaseObject)e.NewEvent.Properties["TargetInstance"].Value;` so to see what happens if I remove `foreach (PropertyData pdDataSub in mbo.Properties)` with `string oInterfaceType = mbo.Properties["InterfaceType"]?.Value.ToString();` instead, first I have to figure out with exception. All I need is to know if USB was plugged or unplugged –  Oct 01 '18 at 22:12
  • Edit your event handler to just contain: `ManagementBaseObject mbo = (ManagementBaseObject)e.NewEvent.Properties["TargetInstance"].Value;` `string oInterfaceType = mbo.Properties["InterfaceType"]?.Value.ToString();` `Console.WriteLine(oInterfaceType);`. And nothing else. – Jimi Oct 01 '18 at 22:18
  • @Jimi Ok, now I add only your code in `m_mewWatcher_EventArrived`, with first unplug of USB, I see message box with text "IDE" and Ok button, then if I plug or unplug USB again, nothing happens, appears, only once. My goal is to get condition "USB was plugged in" and "USB was plugged out" each time it is changed –  Oct 01 '18 at 22:26
  • What `MessageBox`? Did you put a `MessageBox` in the event handler? If so, cut it out ASAP. You can't show that kind of window in a Thread other then the UI Thread. The event is not raised in the UI Thread. Use `BeginInvoke` with `MethodInvoker`, as shown, to update a control in your Form. – Jimi Oct 01 '18 at 22:30
  • @Jimi I've added message box there to check if value appears, I need this condition for video camera, but still I can get how this works –  Oct 01 '18 at 22:39
  • Well, you can't have a `MessageBox` there. It will disrupt the thread context. You can invoke a method in the UI thread and open a `MessageBox` this way, if really needed. But you should use a non-modal `Form` to show a message. A custom secondary `Form` opened with `Show(this);` will do. – Jimi Oct 01 '18 at 22:43
  • @Jimi, I don't need message box there, all I need is to get this condition with USB change event `if (e.NewEvent.ClassPath.ClassName == "__InstanceCreationEvent") { MessageBox.Show ("USB was plugged in"); } else if (e.NewEvent.ClassPath.ClassName == "__InstanceDeletionEvent") { MessageBox.Show("USB was plugged out"); }` but can't figure out how –  Oct 01 '18 at 22:47
  • Allright, give a moment and I'll write it down. – Jimi Oct 01 '18 at 22:47
  • OK, see if this solves the problem. If you have questions, comment under the answer section. – Jimi Oct 01 '18 at 23:38

1 Answers1

0

Here, the ManagementEventWatcher is initialized on a Button.Click() event. Of course, you can initialize it elsewhere. In Form.Load() for example.

The ManagementEventWatcher.EventArrived event is subscribed, setting the delegate to private void DeviceInsertedEvent(). The watching procedure is the started using ManagementEventWatcher.Start() (MOWatcher.Start();)

When an event is notified, the EventArrivedEventArgs e.NewEvent.Properties["TargetInstance"].Value will be set to a ManagementBaseObject, which will reference a Win32_DiskDrive WMI/CIM class.
Read the documentation to see what informations are available in this class.

This event is not rasised in the UI Thread. To notify the main UI interface of the nature of the event, we need to .Invoke() a method in that thread.

this.BeginInvoke((MethodInvoker)delegate { this.UpdateUI(DriveArrival, EventMessage); });

Here, the private void UpdateUI() method is called, delegating the UI update to a method which is executed in the UI thread.

Update:
Added RegisterWindowMessage() to register QueryCancelAutoPlay, to prevent the AutoPlay window from popping up in front of our Form, stealing the focus.

A visual sample of the results:

WMI_EventWatcher

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern uint RegisterWindowMessage(string lpString);

private uint CancelAutoPlay = 0;

private void button1_Click(object sender, EventArgs e)
{
    WqlEventQuery query = new WqlEventQuery() {
        EventClassName = "__InstanceOperationEvent",
        WithinInterval = new TimeSpan(0, 0, 3),
        Condition = @"TargetInstance ISA 'Win32_DiskDrive'"
    };

    ManagementScope scope = new ManagementScope("root\\CIMV2");
    using (ManagementEventWatcher MOWatcher = new ManagementEventWatcher(query))
    {
        MOWatcher.Options.Timeout = ManagementOptions.InfiniteTimeout;
        MOWatcher.EventArrived += new EventArrivedEventHandler(DeviceChangedEvent);
        MOWatcher.Start();
    }
}

private void DeviceChangedEvent(object sender, EventArrivedEventArgs e)
{
    using (ManagementBaseObject MOBbase = (ManagementBaseObject)e.NewEvent.Properties["TargetInstance"].Value)
    {
        bool DriveArrival = false;
        string EventMessage = string.Empty;
        string oInterfaceType = MOBbase.Properties["InterfaceType"]?.Value.ToString();

        if (e.NewEvent.ClassPath.ClassName.Equals("__InstanceDeletionEvent"))
        {
            DriveArrival = false;
            EventMessage = oInterfaceType + " Drive removed";
        }
        else
        {
            DriveArrival = true;
            EventMessage = oInterfaceType + " Drive inserted";
        }
        EventMessage += ": " + MOBbase.Properties["Caption"]?.Value.ToString();
        this.BeginInvoke((MethodInvoker)delegate { this.UpdateUI(DriveArrival, EventMessage); });
    }
}


private void UpdateUI(bool IsDriveInserted, string message)
{
    if (IsDriveInserted)
        this.lblDeviceArrived.Text = message;
    else
        this.lblDeviceRemoved.Text = message;
}


[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
protected override void WndProc(ref Message m)
{
    base.WndProc(ref m);

    if (CancelAutoPlay == 0)
        CancelAutoPlay = RegisterWindowMessage("QueryCancelAutoPlay");

    if ((int)m.Msg == CancelAutoPlay) { m.Result = (IntPtr)1; }
}
Jimi
  • 29,621
  • 8
  • 43
  • 61
  • Well, I've tried your code without any changing, including button. With first plug, it shows `IDE Drive inserted: HGST HTS541010A9E680` in UI label, and same with first unplug, by some reason in both cases "Drive inserted", but what is even more important, it detects USB change event only once, not even plug and unplug detection in one debug session, only once after load, then nothing with plug and unplug of USB. It is equal code, without any change, I'm not sure what I'm doing wrong =( –  Oct 02 '18 at 00:21
  • Well, let's find out :) First thing, what Framework version are you using (FW 4.7.1 here, VS 15.8.4)? Then, see that you don't have `Prefer 32-bit` in your project properties. Post your last code in your question. The whole form you are using (except the designer, of course). – Jimi Oct 02 '18 at 00:28
  • Code is added, `.NET Framework 4.5.2`, `C#` version `v4.0.30319` and yes, it was `32-bit` in project properties build, I have unchecked `Prefer 32-bit`, but result is same, this does not affect particular problem –  Oct 02 '18 at 00:46
  • I can't reproduce the problem. I've copied your form1 Form, created a project with .Net 4.5.2. It runs like mine. Each time a USB device is inserted (the same or a different one), the event is notified and the AutoRun window is shown. In my project I had it disabled, but it doesn't change the result. Try to CleanSolution/Rebuild Solution (not project) and restart. You current code is working. Also, check out the USB drives you're testing. After many insertions, they might have a (classic) problem. It appears when Windows, sometimes, asks you to fix the device and you don't. – Jimi Oct 02 '18 at 01:05
  • Well, I have clean, rebuild, and created new project with this equal code, nothing helps. I see it just now, it shows `Drive inserted` and newer `Drive removed` and provides event detection only once without any action with USB, but by itself after 3 seconds with click on button (I'm not keeping the button pressed), and then nothing –  Oct 02 '18 at 01:48
  • Do you hear the classic sound which signals that the USB controller has changed state (has been updated)? -- Also, try to modify the `WithinInterval = new TimeSpan(0, 0, 3)` to different value (try 5 seconds, or 1, to make it more responsive). Try inserting `MOWatcher.Options.Timeout = EnumerationOptions.InfiniteTimeout;` in the ManagementEventWatcher constructor, too. – Jimi Oct 02 '18 at 02:11
  • I forgot to mention. Do you want to try this overriding `WnpProc`, to get the `WM_DEVICECHANGE` notification, lets say, manually? It's more or less the same thing, except you need to find out what drive has arrived in different way. – Jimi Oct 02 '18 at 08:12
  • Hello, sorry for later response to your last answers. With 3, 5 or 1 second in `WithinInterval = new TimeSpan` detection appiers exectly in 5 or 1 second without any connection with change in USB. Same with `MOWatcher.Options.Timeout = EnumerationOptions.InfiniteTimeout;` in `using (ManagementEventWatcher MOWatcher = new ManagementEventWatcher(query))` –  Oct 02 '18 at 12:35
  • Also I have tested this .exe on another PC with Windows 10, and got same result. After 3, 1 or 5 second, as seen, specifically depending on TimeSpan value, I have authomatic detection of `IDE Drive inserted: HGST HTS541010A9E680`, I don't know what this name means, particular tested USB device is `Logitech Webcam C210` mause and kayboard, nothing provokes detection –  Oct 02 '18 at 12:35
  • My complete goal without flaws and excesses should be the following, detection of change event on USB connection with PC for both as condition `Drive removed` and `Drive inserted`, and as perfect result, would be fine, if I can detect any device name on change, or at least, prescribed name for particular camera or for example USB hub connection, this last condition suits me on the grounds that I will use same USB device model for assembly, but detection the name of any device, however, seems more attractive –  Oct 02 '18 at 12:37
  • Anyway for now I have nothing. And I'm not sure, what is solution, I've tried several ways, but only those that was discussed in both posts, including [Detect USB device remove or insertion System.Management.ManagementException](https://stackoverflow.com/questions/52594027/detect-usb-device-remove-or-insertion-system-management-managementexception), somehow reacts to USB change, but based on the latest results, I can not say, that the last code works for me, at least in some way, especially after test on another computer. I don't understand why =( –  Oct 02 '18 at 12:57