Skip to content

Instantly share code, notes, and snippets.

@dtwk2
Last active November 15, 2021 16:26
Show Gist options
  • Save dtwk2/42d3aa1d6f42b6b106228c472272b966 to your computer and use it in GitHub Desktop.
Save dtwk2/42d3aa1d6f42b6b106228c472272b966 to your computer and use it in GitHub Desktop.
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Management;
using System.Reactive.Linq;
using System.Reactive.Subjects;
/// <summary>
/// <a href="https://stackoverflow.com/questions/620144/detecting-usb-drive-insertion-and-removal-using-windows-service-and-c-sharp"/>
/// </summary>
public class UsbDetector
{
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 appDataPath = System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string deviceId = (string)instance.GetPropertyValue("DeviceID");
string serialNr = deviceId.Substring(deviceId.LastIndexOf('\\')).Replace("\\", "");
char? driveLetter = GetDriveLetter(serialNr)?.First();
return new USBDeviceInfo(deviceId, serialNr, driveLetter);
}
public struct USBDeviceInfo
{
public USBDeviceInfo(string deviceId, string SerialNr, char? driveLetter)
{
this.DeviceId = deviceId;
this.SerialNr = SerialNr;
DriveLetter = driveLetter;
}
public string DeviceId { get; }
public string SerialNr { get; }
public char? DriveLetter { get; }
public override string ToString() => $"DeviceId: {DeviceId}; SerialNr: {SerialNr}; DriveLetter: {DriveLetter}";
}
/// <summary>
/// https://stackoverflow.com/questions/31938686/how-to-get-the-drive-letter-of-usb-device-using-wmi
/// </summary>
/// <returns></returns>
static IEnumerable<(string deviceId, string pnpDeviceId, string? driveLetter)> SelectDeviceInformation()
{
foreach (ManagementObject device in SelectDevices())
{
var deviceId = (string)device.GetPropertyValue("DeviceID");
var pnpDeviceId = (string)device.GetPropertyValue("PNPDeviceID");
var driveLetter = (string?)SelectPartitions(device).SelectMany(SelectDisks).Select(disk => disk["Name"]).SingleOrDefault();
yield return (deviceId, pnpDeviceId, driveLetter);
}
static IEnumerable<ManagementObject> SelectDevices() => new ManagementObjectSearcher(
@"SELECT * FROM Win32_DiskDrive WHERE InterfaceType LIKE 'USB%'").Get()
.Cast<ManagementObject>();
static IEnumerable<ManagementObject> SelectPartitions(ManagementObject device) => new ManagementObjectSearcher(
"ASSOCIATORS OF {Win32_DiskDrive.DeviceID=" +
"'" + device.Properties["DeviceID"].Value + "'} " +
"WHERE AssocClass = Win32_DiskDriveToDiskPartition").Get()
.Cast<ManagementObject>();
static IEnumerable<ManagementObject> SelectDisks(ManagementObject partition) => new ManagementObjectSearcher(
"ASSOCIATORS OF {Win32_DiskPartition.DeviceID=" +
"'" + partition["DeviceID"] + "'" +
"} WHERE AssocClass = Win32_LogicalDiskToPartition").Get()
.Cast<ManagementObject>();
}
private static string? GetDriveLetter(string serialNr)
{
return SelectDeviceInformation()
.SingleOrDefault(a =>
a.pnpDeviceId.Remove(a.pnpDeviceId.Length - 2).Substring(a.pnpDeviceId.LastIndexOf('\\')).Replace("\\", "") ==(serialNr)).driveLetter;
}
}
@elmazzun
Copy link

elmazzun commented Jun 18, 2021

Thanks for this Gist.

You should change Single() to SingleOrDefault() though because I got System.InvalidOperationException:
as I read here:

You're calling .Single(), which means you're asserting that there is always exactly one record returned in the result.
The error is telling you that no records were returned in the result, so the call to .Single() fails.

Since it's possible for no records to be returned in the result, instead use
.SingleOrDefault() and check the result for null.

@dtwk2
Copy link
Author

dtwk2 commented Aug 17, 2021

@elmazzun - thanks -replaced the instances of Single with SingleOrDefault

@duchdtran
Copy link

How to use it? Sorry, I am newbie

@elmazzun
Copy link

@duchdtran here's a repository I made as minimal working example.
It is a Visual Studio solution.

@duchdtran
Copy link

@elmazzun Thank you very much

@elmazzun
Copy link

You are welcome!
I am not a C# expert but you should be able to get something working out of my repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment