Get the serial number of USB storage devices in .Net Core 2.1
Asked Answered
L

2

8

How can I get the serial number of USB storage devices in .Net Core 2.1?

I found different solutions, but sadly they don't work due the lack of Windows registry and WMI support in .Net Core.

In Powershell it's really simple, but I wasn't able to find an implementation in Powershell Core.

PS C:\> Get-Disk | Select-Object SerialNumber

SerialNumber
------------
0008_0D02_0021_9852.

I prefer a solution with no extra installation requirements on the clients (Win, Linux, Mac).

Limonite answered 5/3, 2018 at 20:15 Comment(12)
This looks useful nuget.org/packages/CoreCompat.LibUsbDotNetLoftin
@Loftin this package uses WinUSB, LibUsb-Win32, and libusb-1.0. I would prefer a method without such dependencies.Limonite
Down vote and no comment? thank you!Limonite
.NET Core can use WMI with System.Management packageOutport
Using a low-level USB access library will not help you, a drive serial number is a property of a drive, not of USB. The USB bus is merely the data vehicle, it does nothing to implement the file system. Finding a library that works well to query drives and can handle dozens of file systems in common use and works on all operating systems supported by .NETCore is very tough shopping. Beyond the required porting effort, this is just not a common need. You might want to focus a bit on the underlying reason you need this to work to have any hope of getting a usable answer.Autonomous
Guessing at it a bit, do keep in mind that drive serial numbers are useless for copy protection or license verification. They are too easy to change.Autonomous
@HansPassant I know it is easy to change, but it was implemented this way and for backwards compatibility it need to work this way after the net core migration.Limonite
Hmm, so I guessed right. Given that the check is useless, just fall back to checking that the drive exists. If you can insist that the USB stick has an encrypted file with secret data that must match then you can check that.Autonomous
@HansPassant I'm aware of the design flaw. But at this point, where I'm porting the code I need to keep this.Limonite
If you don't have the authority to fix this then it is absolutely crucial that you talk to somebody that does. Employees can easily be fired for not disclosing info that affects the company's bottom line. Windows users can generally be counted on to be inept enough to not figure out how to duplicate the USB stick content, but that doesn't work the same way in the Unix world. First command they'll try is dd in the terminal, shazam, run everywhere without a license. Do talk to your supervisor about this.Autonomous
@Hans Passant Have you by chance ever tested the Windows Compatibility Pack on some Linux OS or Mac OS, in relation to WMI support? I know Linux will have support for WIM/CIM but, as of now, what compatibility can actually exist? Is it an Infrastructure support (in real world throwing a NonImplementedException or the like.)?Feinberg
No. But no point in trying to use the Windows compatibility pack on a *nix version. It is there only to backfill the Windows-specific classes that were omitted in .NETCore because they are not portable. System.Management is rockhard Windows onlyAutonomous
F
12

This Class performs a series of queries on the WMI Win32_DiskDrive class and its associators: Win32_DiskDriveToDiskPartition and CIM_LogicalDiskBasedOnPartition, to retrieve informations on the active USB Drives on a System (local or remote).

It might seem redundant (probably because it is), since you just asked for the USB Drives Serial Number. But, you never know what you will need next, and it could be useful to someone else.

It requires Microsoft .Net System.Management 4.5 for .Net Core 2.1 (NuGet Package)
Can be easily found and installed using the Visual Studio NuGet Package Manager.
About Linux support, read here:
Windows Management Instrumentation Now A Formal Bus With Linux 4.13

Also, keep an eye out for Windows Compatibility Pack for .NET Core.
New cross-platform assemblies are constantly added and updated.

The main class implements all the required functionalities and it has quite a simple structure.
The WMI queries use the Associator syntax, a method to correlate WMI class objects related to each other.
The Class properties meaning is self-explanatory.

Can be instantiated this way:
SystemUSBDrives systemUSBDrives = new SystemUSBDrives("[Computer Name]");

When [Computer Name] is null or empty, it uses the Local Machine name.

To get the List of USB Devices and their properties, call the GetUSBDrivesInfo() method:

var USBDrivesEnum = systemUSBDrives.GetUSBDrivesInfo([UserName], [Password], [Domain]);

[UserName], [Password], [Domain] are used to connect to a NT Domain.
These parameters, if not needed, can be null or an empty string.

Sample class instantiation and function call (Local Machine, no authentication):

SystemUSBDrives systemUSBDrives = new SystemUSBDrives(null);
var USBDrivesEnum = systemUSBDrives.GetUSBDrivesInfo(null, null, null);

Tested on:
Visual Studio Pro 15.7.6 - 15.9.35
.Net Core 2.1 / .Net Framework 4.8
C# 6.0 -> 7.3
.Net System.Management 4.5

using System.Management;

public class SystemUSBDrives
{
    string m_ComputerName = string.Empty;
    public SystemUSBDrives(string ComputerName) {
        this.m_ComputerName = string.IsNullOrEmpty(ComputerName)
                            ? Environment.MachineName
                            : ComputerName;
    }

    private static EnumerationOptions GetEnumerationOptions(bool DeepScan)
    {
        var mOptions = new EnumerationOptions()
        {
            Rewindable = false,        //Forward only query => no caching
            ReturnImmediately = true,  //Pseudo-async result
            DirectRead = true,
            EnumerateDeep = DeepScan
        };
        return mOptions;
    }

    private static ConnectionOptions GetConnectionOptions() => GetConnectionOptions("", "", "");

    private static ConnectionOptions GetConnectionOptions(string userName, string password, string domain)
    {
        var connOptions = new ConnectionOptions()
        {
            EnablePrivileges = true,
            Timeout = ManagementOptions.InfiniteTimeout,
            Authentication = AuthenticationLevel.PacketPrivacy,
            Impersonation = ImpersonationLevel.Impersonate,
            Username = userName,
            Password = password,
            Authority = !string.IsNullOrEmpty(Domain) ? $"NTLMDOMAIN:{domain}" : string.Empty  //Authority = "NTLMDOMAIN:[domain]"
        };
        return connOptions;
    }

    public List<USBDriveInfo> GetUSBDrivesInfo(string userName, string password, string domain)
    {
        var wmiQueryResult = new List<USBDriveInfo>();
        ConnectionOptions connOptions = GetConnectionOptions(userName, password, domain);
        EnumerationOptions mOptions = GetEnumerationOptions(false);
        var mScope = new ManagementScope($@"\\{this.m_ComputerName}\root\CIMV2", connOptions);
        var selQuery = new SelectQuery("SELECT * FROM Win32_DiskDrive WHERE InterfaceType='USB'");
        mScope.Connect();

        using (var moSearcher = new ManagementObjectSearcher(mScope, selQuery, mOptions)) {
            foreach (ManagementObject moDiskDrive in moSearcher.Get()) {
                var usbInfo = new USBDriveInfo();
                usbInfo.GetDiskDriveInfo(moDiskDrive);

                var relQuery = new RelatedObjectQuery(
                    $"Associators of {{Win32_DiskDrive.DeviceID='{moDiskDrive.Properties["DeviceID"].Value}'}} " +
                    $"WHERE AssocClass = Win32_DiskDriveToDiskPartition");
                using (var moAssocPart = new ManagementObjectSearcher(mScope, relQuery, mOptions)) {
                    foreach (ManagementObject moAssocPartition in moAssocPart.Get()) {

                        usbInfo.GetDiskPartitionInfo(moAssocPartition);
                        relQuery = new RelatedObjectQuery(
                            $"Associators of {{Win32_DiskPartition.DeviceID='{moAssocPartition.Properties["DeviceID"].Value}'}} " +
                            $"WHERE AssocClass = CIM_LogicalDiskBasedOnPartition");

                        using (var moLogDisk = new ManagementObjectSearcher(mScope, relQuery, mOptions)) {
                            foreach (ManagementObject moLogDiskEnu in moLogDisk.Get()) {
                                usbInfo.GetLogicalDiskInfo(moLogDiskEnu);
                                moLogDiskEnu.Dispose();
                            }
                        }
                        moAssocPartition.Dispose();
                    }
                    wmiQueryResult.Add(usbInfo);
                }
                moDiskDrive.Dispose();
            }
            return wmiQueryResult;
        }
    }   //GetUSBDrivesInfo()

    public class USBDriveInfo
    {
        private int m_PartionsCount = 0;
        public USBDriveInfo() => this.Partitions = new List<Partition>(1);
        public string Caption { get; private set; }
        public string DeviceID { get; private set; }
        public string FirmwareRevision { get; private set; }
        public ulong FreeSpace { get; private set; }
        public string InterfaceType { get; private set; }
        public bool MediaLoaded { get; private set; }
        public string MediaType { get; private set; }
        public string Model { get; private set; }
        public uint NumberOfPartitions { get; private set; }
        public List<Partition> Partitions { get; private set; }
        public string PNPDeviceID { get; private set; }
        public string SerialNumber { get; private set; }
        public ulong Size { get; private set; }
        public string Status { get; private set; }
        public ulong TotalCylinders { get; private set; }
        public uint TotalHeads { get; private set; }
        public ulong TotalSectors { get; private set; }
        public ulong TotalTracks { get; private set; }
        public uint TracksPerCylinder { get; private set; }

        public class Partition
        {
            public Partition() => this.LogicalDisks = new List<LogicalDisk>();
            public bool Bootable { get; internal set; }
            public bool BootPartition { get; internal set; }
            public uint DiskIndex { get; internal set; }
            public List<LogicalDisk> LogicalDisks { get; internal set; }
            public ulong PartitionBlockSize { get; internal set; }
            public ulong PartitionNumberOfBlocks { get; internal set; }
            public ulong PartitionStartingOffset { get; internal set; }
            public bool PrimaryPartition { get; internal set; }
        }

        public class LogicalDisk
        {
            public ulong FreeSpace { get; internal set; }
            public string FileSystem { get; internal set; }
            public string LogicalDiskVolume { get; internal set; }
            public bool SupportsDiskQuotas { get; internal set; }
            public string VolumeName { get; internal set; }
            public string VolumeSerialNumber { get; internal set; }
        }

        internal void GetDiskDriveInfo(ManagementObject DiskDrive)
        {
            this.Caption = DiskDrive.GetPropertyValue("Caption")?.ToString();
            this.DeviceID = DiskDrive["DeviceID"]?.ToString();
            this.FirmwareRevision = DiskDrive["FirmwareRevision"]?.ToString();
            this.InterfaceType = DiskDrive["InterfaceType"]?.ToString();
            this.MediaLoaded = (bool?)DiskDrive["MediaLoaded"] ?? false;
            this.MediaType = DiskDrive["MediaType"]?.ToString();
            this.Model = DiskDrive["Model"]?.ToString();
            this.NumberOfPartitions = (uint?)DiskDrive["Partitions"] ?? 0;
            this.PNPDeviceID = DiskDrive["PNPDeviceID"]?.ToString();
            this.SerialNumber = DiskDrive["SerialNumber"]?.ToString();
            this.Size = (ulong?)DiskDrive["Size"] ?? 0L;
            this.Status = DiskDrive["Status"]?.ToString();
            this.TotalCylinders = (ulong?)DiskDrive["TotalCylinders"] ?? 0;
            this.TotalHeads = (uint?)DiskDrive["TotalHeads"] ?? 0U;
            this.TotalSectors = (ulong?)DiskDrive["TotalSectors"] ?? 0;
            this.TotalTracks = (ulong?)DiskDrive["TotalTracks"] ?? 0;
            this.TracksPerCylinder = (uint?)DiskDrive["TracksPerCylinder"] ?? 0;
        }

        internal void GetDiskPartitionInfo(ManagementObject Partitions)
        {
            m_PartionsCount += 1;
            this.Partitions.Add(new Partition()
            {
                DiskIndex = (uint?)Partitions["DiskIndex"] ?? 0,
                PartitionBlockSize = (ulong?)Partitions["BlockSize"] ?? 0,
                Bootable = (bool?)Partitions["Bootable"] ?? false,
                BootPartition = (bool?)Partitions["BootPartition"] ?? false,
                PartitionNumberOfBlocks = (ulong?)Partitions["NumberOfBlocks"] ?? 0,
                PrimaryPartition = (bool?)Partitions["PrimaryPartition"] ?? false,
                PartitionStartingOffset = (ulong?)Partitions["StartingOffset"] ?? 0
            });
        }

        internal void GetLogicalDiskInfo(ManagementObject LogicalDisk)
        {
            if (m_PartionsCount == 0) return;
            this.Partitions[m_PartionsCount - 1].LogicalDisks.Add(new LogicalDisk()
            {
                FileSystem = LogicalDisk["FileSystem"]?.ToString(),
                FreeSpace = (ulong?)LogicalDisk["FreeSpace"] ?? 0,
                LogicalDiskVolume = LogicalDisk["DeviceID"]?.ToString(),
                SupportsDiskQuotas = (bool?)LogicalDisk["SupportsDiskQuotas"] ?? false,
                VolumeName = LogicalDisk["VolumeName"]?.ToString(),
                VolumeSerialNumber = LogicalDisk["VolumeSerialNumber"]?.ToString()
            });
            //Linq's Sum() does not sum ulong(s)
            foreach(Partition p in this.Partitions)
            {
                foreach (LogicalDisk ld in p.LogicalDisks) {
                    this.FreeSpace += ld.FreeSpace;
                }
            }
        }
    }
} // SystemUSBDrives
Feinberg answered 12/8, 2018 at 6:18 Comment(3)
On linux you need to install x86 platform driver and mac is not included. I think I really need to write platform aware code the get the serial number of a specific drive.Limonite
Have you tested the Windows Compatibility Pack (I didn't)? WMI is included and MAC OS supported (on the paper). See: Using the Windows Compatibility Pack. The NuGet Package.Feinberg
@dh_cgn It's most probably like that. I asked Hans about the "Compatibility" pack, but of course is not "physically" compatible. If I find out something that can be useful in this context (some OpenSource stuff or an on-going project), I'll let you know.Feinberg
E
1

Not sure if this is exactly what you're looking for, but here's some code I've used in the past.

using System.Management;

public class USBDeviceInfo
{
    public string Availability { get; set; }
    public string Caption { get; set; }
    public string ClassCode { get; set; }
    public uint ConfigManagerErrorCode { get; set; }
    public bool ConfigManagerUserConfig { get; set; }
    public string CreationClassName { get; set; }
    public string CurrentAlternateSettings { get; set; }
    public string CurrentConfigValue { get; set; }
    public string Description { get; set; }
    public string DeviceID { get; set; }
    public string ErrorCleared { get; set; }
    public string ErrorDescription { get; set; }
    public string GangSwitched { get; set; }
    public string InstallDate { get; set; }
    public string LastErrorCode { get; set; }
    public string Name { get; set; }
    public string NumberOfConfigs { get; set; }
    public string NumberOfPorts { get; set; }
    public string PNPDeviceID { get; set; }
    public string PowerManagementCapabilities { get; set; }
    public string PowerManagementSupported { get; set; }
    public string ProtocolCode { get; set; }
    public string Status { get; set; }
    public string StatusInfo { get; set; }
    public string SubclassCode { get; set; }
    public string SystemCreationClassName { get; set; }
    public string SystemName { get; set; }
    public string USBVersion { get; set; }
}

public static List<USBDeviceInfo> GetUSBDevices()
{
    ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"Select * From Win32_USBHub");
    ManagementObjectCollection collection = searcher.Get();

    List<USBDeviceInfo> devices = new List<USBDeviceInfo>();
    foreach (var device in collection)
    {
        USBDeviceInfo deviceInfo = new USBDeviceInfo();
        deviceInfo.Availability = (string)device.GetPropertyValue("Availability");
        deviceInfo.Caption = (string)device.GetPropertyValue("Caption");
        deviceInfo.ClassCode = (string)device.GetPropertyValue("ClassCode");
        deviceInfo.ConfigManagerErrorCode = (uint)device.GetPropertyValue("ConfigManagerErrorCode");
        deviceInfo.ConfigManagerUserConfig = (bool)device.GetPropertyValue("ConfigManagerUserConfig");
        deviceInfo.CreationClassName = (string)device.GetPropertyValue("CreationClassName");
        deviceInfo.CurrentAlternateSettings = (string)device.GetPropertyValue("CurrentAlternateSettings");
        deviceInfo.CurrentConfigValue = (string)device.GetPropertyValue("CurrentConfigValue");
        deviceInfo.Description = (string)device.GetPropertyValue("Description");
        deviceInfo.DeviceID = (string)device.GetPropertyValue("DeviceID");
        deviceInfo.ErrorCleared = (string)device.GetPropertyValue("ErrorCleared");
        deviceInfo.ErrorDescription = (string)device.GetPropertyValue("ErrorDescription");
        deviceInfo.GangSwitched = (string)device.GetPropertyValue("GangSwitched");
        deviceInfo.InstallDate = (string)device.GetPropertyValue("InstallDate");
        deviceInfo.LastErrorCode = (string)device.GetPropertyValue("LastErrorCode");
        deviceInfo.Name = (string)device.GetPropertyValue("Name");
        deviceInfo.NumberOfConfigs = (string)device.GetPropertyValue("NumberOfConfigs");
        deviceInfo.NumberOfPorts = (string)device.GetPropertyValue("NumberOfPorts");
        deviceInfo.PNPDeviceID = (string)device.GetPropertyValue("PNPDeviceID");
        deviceInfo.PowerManagementCapabilities = (string)device.GetPropertyValue("PowerManagementCapabilities");
        deviceInfo.PowerManagementSupported = (string)device.GetPropertyValue("PowerManagementSupported");
        deviceInfo.ProtocolCode = (string)device.GetPropertyValue("ProtocolCode");
        deviceInfo.Status = (string)device.GetPropertyValue("Status");
        deviceInfo.StatusInfo = (string)device.GetPropertyValue("StatusInfo");
        deviceInfo.SubclassCode = (string)device.GetPropertyValue("SubclassCode");
        deviceInfo.SystemCreationClassName = (string)device.GetPropertyValue("SystemCreationClassName");
        deviceInfo.SystemName = (string)device.GetPropertyValue("SystemName");
        deviceInfo.USBVersion = (string)device.GetPropertyValue("USBVersion");
        devices.Add(deviceInfo);
    }

    collection.Dispose();
    searcher.Dispose();
    return devices;
}
Ellingston answered 12/3, 2018 at 14:31 Comment(1)
Surely it's not exactly what the question needs, because you print information about hubs, not mass storage devices. But it might be a good start for adaptation.Huneycutt

© 2022 - 2024 — McMap. All rights reserved.