Skip to content

Commit

Permalink
Add new WMI Light component and associated extension methods
Browse files Browse the repository at this point in the history
- Created `WindowsDeviceIdBuilderExtensions.cs` to add extension methods for adding device components based on WMI Light.
- Implemented `WmiLightDeviceIdComponent` to retrieve data from a WMI class using WMI Light.
- Implemented `WmiLightMacAddressDeviceIdComponent` for retrieving MAC addresses using WMI Light, with options to exclude wireless and non-physical adapters.
- Implemented `WmiLightSystemDriveSerialNumberDeviceIdComponent` to retrieve the system drive's serial number using WMI Light.
- Updated `_InternalsVisibleTo.cs` to include `DeviceId.Windows.WmiLight` in the `InternalsVisibleTo` attributes.
- Added performance tests for `WmiLightSystemDriveSerialNumberDeviceIdComponent` in `WmiAndMmiDriveSerialNumberPerfTests.cs`.
  • Loading branch information
Koncord authored and MatthewKing committed May 24, 2024
1 parent 4e5e503 commit 5a78ae3
Show file tree
Hide file tree
Showing 9 changed files with 380 additions and 0 deletions.
7 changes: 7 additions & 0 deletions DeviceId.sln
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceId.Tests", "test\Devi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceId.SqlServer", "src\DeviceId.SqlServer\DeviceId.SqlServer.csproj", "{678F5EAE-C13E-4EEC-A2F3-17F43CE099FE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceId.Windows.WmiLight", "src\DeviceId.Windows.WmiLight\DeviceId.Windows.WmiLight.csproj", "{FA9327EF-BC7C-4237-8D6E-C81233362CEF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -64,6 +66,10 @@ Global
{678F5EAE-C13E-4EEC-A2F3-17F43CE099FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{678F5EAE-C13E-4EEC-A2F3-17F43CE099FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{678F5EAE-C13E-4EEC-A2F3-17F43CE099FE}.Release|Any CPU.Build.0 = Release|Any CPU
{FA9327EF-BC7C-4237-8D6E-C81233362CEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA9327EF-BC7C-4237-8D6E-C81233362CEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA9327EF-BC7C-4237-8D6E-C81233362CEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA9327EF-BC7C-4237-8D6E-C81233362CEF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -77,6 +83,7 @@ Global
{865D5271-A21F-4F4B-8F57-7278FEC051AA} = {BD122A91-437F-497E-80F1-F21BAD62C51F}
{C706C3B5-4912-41E6-9EF3-B8560464680A} = {5D030E54-9156-468B-9288-498DE74D8C61}
{678F5EAE-C13E-4EEC-A2F3-17F43CE099FE} = {BD122A91-437F-497E-80F1-F21BAD62C51F}
{FA9327EF-BC7C-4237-8D6E-C81233362CEF} = {BD122A91-437F-497E-80F1-F21BAD62C51F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {702DF5F2-A40A-46BC-A155-7CEAF7F7AA93}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Collections.Generic;
using WmiLight;

namespace DeviceId.Windows.WmiLight.Components;

/// <summary>
/// An implementation of <see cref="IDeviceIdComponent"/> that retrieves data from a WMI class.
/// </summary>
/// <param name="className">The class name.</param>
/// <param name="propertyName">The property name.</param>
public class WmiLightDeviceIdComponent(string className, string propertyName) : IDeviceIdComponent
{
/// <summary>
/// Gets the component value.
/// </summary>
/// <returns>The component value.</returns>
public string GetValue()
{
var values = new List<string>();

try
{
using var wmiConnection = new WmiConnection();
foreach (var wmiObject in wmiConnection.CreateQuery($"SELECT * FROM {className}"))
{
try
{
if (wmiObject[propertyName] is string value)
{
values.Add(value);
}
}
finally
{
wmiObject.Dispose();
}
}
}
catch
{
// Ignore exceptions
}

values.Sort();

return values.Count > 0
? string.Join(",", values.ToArray())
: null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using System.Collections.Generic;
using WmiLight;
using DeviceId.Components;
using DeviceId.Internal;

namespace DeviceId.Windows.WmiLight.Components;

/// <summary>
/// An implementation of <see cref="IDeviceIdComponent"/> that uses the MAC Address of the PC.
/// This improves upon the basic <see cref="MacAddressDeviceIdComponent"/> by using WMI
/// to get better information from either MSFT_NetAdapter or Win32_NetworkAdapter.
/// </summary>
/// <param name="excludeWireless">A value determining whether wireless devices should be excluded.</param>
/// <param name="excludeNonPhysical">A value determining whether non-physical devices should be excluded.</param>
public class WmiLightMacAddressDeviceIdComponent(bool excludeWireless, bool excludeNonPhysical) : IDeviceIdComponent
{
/// <summary>
/// Gets the component value.
/// </summary>
/// <returns>The component value.</returns>
public string GetValue()
{
// First, try to get a value using MSFT_NetAdapter:
try
{
return GetValueUsingMsftNetAdapter(excludeWireless, excludeNonPhysical);
}
catch { }

// Next, try using Win32_NetworkAdapter:
try
{
return GetValueUsingWin32NetworkAdapter(excludeWireless, excludeNonPhysical);
}
catch { }

// Finally, try the fallback component:
var fallback = new MacAddressDeviceIdComponent(excludeWireless);
return fallback.GetValue();
}

/// <summary>
/// Gets the component value using MSFT_NetAdapter.
/// </summary>
/// <param name="excludeWireless">A value determining whether wireless devices should be excluded.</param>
/// <param name="excludeNonPhysical">A value determining whether non-physical devices should be excluded.</param>
/// <returns>The component value.</returns>
private static string GetValueUsingMsftNetAdapter(bool excludeWireless, bool excludeNonPhysical)
{
var values = new List<string>();

values.Sort();

using var wmiConnection = new WmiConnection(@"\\.\root\StandardCimv2");

foreach (var wmiObject in wmiConnection.CreateQuery("SELECT * FROM MSFT_NetAdapter"))
{
try
{
// Skip non-physical adapters if instructed to do so.
if (wmiObject["ConnectorPresent"] is bool isPhysical)
{
if (excludeNonPhysical && !isPhysical)
{
continue;
}
}

// Skip wireless adapters if instructed to do so.
if (wmiObject["NdisPhysicalMedium"] is uint ndisPhysicalMedium)
{
if (excludeWireless && ndisPhysicalMedium == 9) // Native802_11
{
continue;
}
}
if (wmiObject["PermanentAddress"] is string permanentAddress)
{
// Ensure the hardware addresses are formatted as MAC addresses if possible.
// This is a discrepancy between the MSFT_NetAdapter and Win32_NetworkAdapter interfaces.
values.Add(MacAddressFormatter.FormatMacAddress(permanentAddress));
}
}
finally
{
wmiObject.Dispose();
}
}

return values.Count > 0
? string.Join(",", values.ToArray())
: null;
}

/// <summary>
/// Gets the component value using Win32_NetworkAdapter.
/// </summary>
/// <param name="excludeWireless">A value determining whether wireless devices should be excluded.</param>
/// <param name="excludeNonPhysical">A value determining whether non-physical devices should be excluded.</param>
/// <returns>The component value.</returns>
private static string GetValueUsingWin32NetworkAdapter(bool excludeWireless, bool excludeNonPhysical)
{
var values = new List<string>();

using var wmiConnection = new WmiConnection();
var wmiQuery = wmiConnection.CreateQuery("SELECT MACAddress, AdapterTypeID, PhysicalAdapter FROM Win32_NetworkAdapter");
foreach (var managementObject in wmiQuery)
{
try
{
// Skip non-physical adapters if instructed to do so.
if (managementObject["PhysicalAdapter"] is bool isPhysical)
{
if (excludeNonPhysical && !isPhysical)
{
continue;
}
}

// Skip wireless adapters if instructed to do so.
if (managementObject["AdapterTypeID"] is ushort adapterTypeId)
{
if (excludeWireless && adapterTypeId == 9)
{
continue;
}
}

if (managementObject["MACAddress"] is string macAddress)
{
if (!string.IsNullOrEmpty(macAddress))
{
values.Add(macAddress);
}
}
}
finally
{
managementObject.Dispose();
}
}

values.Sort();

return values.Count > 0
? string.Join(",", values.ToArray())
: null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using WmiLight;

namespace DeviceId.Windows.WmiLight.Components;

/// <summary>
/// An implementation of <see cref="IDeviceIdComponent"/> that uses the system drive's serial number.
/// </summary>
public class WmiLightSystemDriveSerialNumberDeviceIdComponent() : IDeviceIdComponent
{
/// <summary>
/// Gets the component value.
/// </summary>
/// <returns>The component value.</returns>
public string GetValue()
{
var systemDirectory = Environment.GetFolderPath(Environment.SpecialFolder.System);
var systemLogicalDiskDeviceId = systemDirectory.Substring(0, 2);

// SystemDirectory can sometimes be null or empty.
// See: https://github.com/dotnet/runtime/issues/21430 and https://github.com/MatthewKing/DeviceId/issues/64
if (string.IsNullOrEmpty(systemDirectory) || systemDirectory.Length < 2)
{
return null;
}

try
{
using var wmiConnection = new WmiConnection();

foreach (var logicalDisk in wmiConnection.CreateQuery($"ASSOCIATORS OF {{Win32_LogicalDisk.DeviceID=\"{systemLogicalDiskDeviceId}\"}} WHERE ResultClass = Win32_DiskPartition"))
{
try
{
if (logicalDisk.Class != "Win32_DiskPartition") continue;
if (logicalDisk["DeviceId"] is not string diskPartitionDeviceId) continue;
foreach (var diskPartitionAssociator in wmiConnection.CreateQuery(
$"ASSOCIATORS OF {{Win32_DiskPartition.DeviceID=\"{diskPartitionDeviceId}\"}}"))
{
if (diskPartitionAssociator.Class == "Win32_DiskDrive"
&& diskPartitionAssociator["SerialNumber"] is string diskDriveSerialNumber)
{
return diskDriveSerialNumber;
}
}
}
finally
{
logicalDisk.Dispose();
}
}
}
catch
{
// Swallow exceptions.
}

return null;
}
}
24 changes: 24 additions & 0 deletions src/DeviceId.Windows.WmiLight/DeviceId.Windows.WmiLight.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>DeviceId.Windows.WmiLight</PackageId>
<Title>DeviceId (Windows / WmiLight)</Title>
<Description>Provides extra Windows-specific components (using WmiLight) for the DeviceId package.</Description>
<VersionPrefix>6.6.0</VersionPrefix>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;.Net7.0;.Net8.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\DeviceId.Windows\DeviceId.Windows.csproj" />
<ProjectReference Include="..\DeviceId\DeviceId.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="WmiLight" Version="6.0.0" />
</ItemGroup>

</Project>
76 changes: 76 additions & 0 deletions src/DeviceId.Windows.WmiLight/WindowsDeviceIdBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.ComponentModel;
using DeviceId.Windows.WmiLight.Components;

// ReSharper disable once CheckNamespace
namespace DeviceId;

/// <summary>
/// Extension methods for <see cref="WindowsDeviceIdBuilder"/>.
/// </summary>
public static class WindowsDeviceIdBuilderExtensions
{
/// <summary>
/// Adds the MAC address to the device identifier, optionally excluding wireless adapters and/or non-physical adapters.
/// </summary>
/// <param name="builder">The <see cref="WindowsDeviceIdBuilder"/> to add the component to.</param>
/// <param name="excludeWireless">A value indicating whether wireless adapters should be excluded.</param>
/// <param name="excludeNonPhysical">A value indicating whether non-physical adapters should be excluded.</param>
/// <returns>The <see cref="WindowsDeviceIdBuilder"/> instance.</returns>
public static WindowsDeviceIdBuilder AddMacAddressFromWmi(this WindowsDeviceIdBuilder builder, bool excludeWireless, bool excludeNonPhysical)
{
return builder.AddComponent("MACAddress", new WmiLightMacAddressDeviceIdComponent(excludeWireless, excludeNonPhysical));
}

/// <summary>
/// Adds the processor ID to the device identifier.
/// </summary>
/// <param name="builder">The <see cref="WindowsDeviceIdBuilder"/> to add the component to.</param>
/// <returns>The <see cref="WindowsDeviceIdBuilder"/> instance.</returns>
public static WindowsDeviceIdBuilder AddProcessorId(this WindowsDeviceIdBuilder builder)
{
return builder.AddComponent("ProcessorId", new WmiLightDeviceIdComponent("Win32_Processor", "ProcessorId"));
}

/// <summary>
/// Adds the motherboard serial number to the device identifier.
/// </summary>
/// <param name="builder">The <see cref="WindowsDeviceIdBuilder"/> to add the component to.</param>
/// <returns>The <see cref="WindowsDeviceIdBuilder"/> instance.</returns>
public static WindowsDeviceIdBuilder AddMotherboardSerialNumber(this WindowsDeviceIdBuilder builder)
{
return builder.AddComponent("MotherboardSerialNumber", new WmiLightDeviceIdComponent("Win32_BaseBoard", "SerialNumber"));
}

/// <summary>
/// Adds the system UUID to the device identifier.
/// </summary>
/// <param name="builder">The <see cref="WindowsDeviceIdBuilder"/> to add the component to.</param>
/// <returns>The <see cref="WindowsDeviceIdBuilder"/> instance.</returns>
public static WindowsDeviceIdBuilder AddSystemUuid(this WindowsDeviceIdBuilder builder)
{
return builder.AddComponent("SystemUUID", new WmiLightDeviceIdComponent("Win32_ComputerSystemProduct", "UUID"));
}

/// <summary>
/// Adds the system serial drive number to the device identifier.
/// </summary>
/// <param name="builder">The <see cref="WindowsDeviceIdBuilder"/> to add the component to.</param>
/// <returns>The <see cref="WindowsDeviceIdBuilder"/> instance.</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method name was a typo. Use AddSystemDriveSerialNumber instead.")]
public static WindowsDeviceIdBuilder AddSystemSerialDriveNumber(this WindowsDeviceIdBuilder builder)
{
return builder.AddComponent("SystemDriveSerialNumber", new WmiLightSystemDriveSerialNumberDeviceIdComponent());
}

/// <summary>
/// Adds the system serial drive number to the device identifier.
/// </summary>
/// <param name="builder">The <see cref="WindowsDeviceIdBuilder"/> to add the component to.</param>
/// <returns>The <see cref="WindowsDeviceIdBuilder"/> instance.</returns>
public static WindowsDeviceIdBuilder AddSystemDriveSerialNumber(this WindowsDeviceIdBuilder builder)
{
return builder.AddComponent("SystemDriveSerialNumber", new WmiLightSystemDriveSerialNumberDeviceIdComponent());
}
}
1 change: 1 addition & 0 deletions src/DeviceId/_InternalsVisibleTo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
[assembly: InternalsVisibleTo("DeviceId.Windows, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5bb1a0001be3481cbd50b4a86a99d4ce1f71eff5631fbacae0016ecc5273209aa9ab743f14cf1d370e63a039b9079326a35de058cc1f5f40ba86faf4ac8679ecc04241da2edc94e20582d00455cefbd484a124a1ecde382ff5281f6375c3efd96efdea6c6da248c1daa4ab8a4db0a325afd531668a67d5617d1bd0ad7c40dda")]
[assembly: InternalsVisibleTo("DeviceId.Windows.Mmi, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5bb1a0001be3481cbd50b4a86a99d4ce1f71eff5631fbacae0016ecc5273209aa9ab743f14cf1d370e63a039b9079326a35de058cc1f5f40ba86faf4ac8679ecc04241da2edc94e20582d00455cefbd484a124a1ecde382ff5281f6375c3efd96efdea6c6da248c1daa4ab8a4db0a325afd531668a67d5617d1bd0ad7c40dda")]
[assembly: InternalsVisibleTo("DeviceId.Windows.Wmi, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5bb1a0001be3481cbd50b4a86a99d4ce1f71eff5631fbacae0016ecc5273209aa9ab743f14cf1d370e63a039b9079326a35de058cc1f5f40ba86faf4ac8679ecc04241da2edc94e20582d00455cefbd484a124a1ecde382ff5281f6375c3efd96efdea6c6da248c1daa4ab8a4db0a325afd531668a67d5617d1bd0ad7c40dda")]
[assembly: InternalsVisibleTo("DeviceId.Windows.WmiLight, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5bb1a0001be3481cbd50b4a86a99d4ce1f71eff5631fbacae0016ecc5273209aa9ab743f14cf1d370e63a039b9079326a35de058cc1f5f40ba86faf4ac8679ecc04241da2edc94e20582d00455cefbd484a124a1ecde382ff5281f6375c3efd96efdea6c6da248c1daa4ab8a4db0a325afd531668a67d5617d1bd0ad7c40dda")]
[assembly: InternalsVisibleTo("DeviceId.Linux, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5bb1a0001be3481cbd50b4a86a99d4ce1f71eff5631fbacae0016ecc5273209aa9ab743f14cf1d370e63a039b9079326a35de058cc1f5f40ba86faf4ac8679ecc04241da2edc94e20582d00455cefbd484a124a1ecde382ff5281f6375c3efd96efdea6c6da248c1daa4ab8a4db0a325afd531668a67d5617d1bd0ad7c40dda")]
[assembly: InternalsVisibleTo("DeviceId.Mac, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5bb1a0001be3481cbd50b4a86a99d4ce1f71eff5631fbacae0016ecc5273209aa9ab743f14cf1d370e63a039b9079326a35de058cc1f5f40ba86faf4ac8679ecc04241da2edc94e20582d00455cefbd484a124a1ecde382ff5281f6375c3efd96efdea6c6da248c1daa4ab8a4db0a325afd531668a67d5617d1bd0ad7c40dda")]
Loading

0 comments on commit 5a78ae3

Please sign in to comment.