From 25d025f3ccf3d809c0e9cbc0a4be478b5e65fa09 Mon Sep 17 00:00:00 2001 From: Shahar Zini Date: Tue, 14 Apr 2020 21:10:58 +1000 Subject: [PATCH 01/26] Add support for Linux. Add FileDeviceIdComponent, RegistryValueDeviceIdComponent and UnsupportedDeviceIdComponent. --- .../Components/FileDeviceIdComponent.cs | 89 +++++++++++++++++++ .../NetworkAdapterDeviceIdComponent.cs | 20 ++++- .../RegistryValueDeviceIdComponent.cs | 56 ++++++++++++ .../UnsupportedDeviceIdComponent.cs | 41 +++++++++ src/DeviceId/DeviceId.csproj | 13 +++ src/DeviceId/DeviceIdBuilderExtensions.cs | 69 +++++++++++++- 6 files changed, 285 insertions(+), 3 deletions(-) create mode 100644 src/DeviceId/Components/FileDeviceIdComponent.cs create mode 100644 src/DeviceId/Components/RegistryValueDeviceIdComponent.cs create mode 100644 src/DeviceId/Components/UnsupportedDeviceIdComponent.cs diff --git a/src/DeviceId/Components/FileDeviceIdComponent.cs b/src/DeviceId/Components/FileDeviceIdComponent.cs new file mode 100644 index 0000000..2c3470f --- /dev/null +++ b/src/DeviceId/Components/FileDeviceIdComponent.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace DeviceId.Components +{ + /// + /// An implementation of that retrieves its value from a file. + /// + public class FileDeviceIdComponent : IDeviceIdComponent + { + /// + /// Gets the name of the component. + /// + public string Name { get; } + + /// + /// The paths of file we should look at + /// + private readonly string[] _paths; + + /// + /// Should the contents of the file be hashed? relevant for sources such as /proc/cpuinfo + /// + private readonly bool _shouldHashContents; + + /// + /// Value to use when a result is not obtainable + /// + private const string NoValue = "NoValue"; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the component. + /// The path of the file holding the componentID. + /// Whether the file contents should be hashed. + public FileDeviceIdComponent(string name, string path, bool shouldHashContents = false) : this(name, new string[] { path }, shouldHashContents) { } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the component. + /// The paths of the files holding the componentID. + /// Whether the file contents should be hashed. + public FileDeviceIdComponent(string name, string[] paths, bool shouldHashContents = false) + { + Name = name; + _paths = paths; + _shouldHashContents = shouldHashContents; + } + + /// + /// Gets the component value. + /// + /// The component value. + public string GetValue() + { + foreach(string path in _paths) + { + if (!File.Exists(path)) + continue; + + try + { + string contents; + + using(var file = File.OpenText(path)) + { + contents = file.ReadToEnd(); // File.ReadAllBytes() fails for special files such as /sys/class/dmi/id/product_uuid + } + contents = contents.Trim(); + + if (!_shouldHashContents) + return contents; + + var hasher = MD5.Create(); + byte[] hash = hasher.ComputeHash(ASCIIEncoding.ASCII.GetBytes(contents)); + return BitConverter.ToString(hash).Replace("-", "").ToUpper(); + } + catch(System.UnauthorizedAccessException){ }// can fail because we have no permissions to access the file + + } + + return NoValue; + } + } +} diff --git a/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs b/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs index efbb00b..af34ad6 100644 --- a/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs +++ b/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Management; +using System.Net.NetworkInformation; namespace DeviceId.Components { @@ -18,6 +19,11 @@ public class NetworkAdapterDeviceIdComponent : IDeviceIdComponent /// public string Name { get; } = "MACAddress"; + /// + /// Value to use when a result is not obtainable + /// + private const string NoValue = "NoValue"; + /// /// A value indicating whether non-physical adapters should be excluded. /// @@ -53,6 +59,7 @@ public string GetValue() { List values; +#if Windows try { // First attempt to retrieve the addresses using the CIMv2 interface. @@ -72,7 +79,18 @@ public string GetValue() throw; } } - +#else + try + { + NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces(); + values = interfaces.Where(inter => !_excludeWireless || inter.NetworkInterfaceType != NetworkInterfaceType.Wireless80211).Select(inter => inter.GetPhysicalAddress().ToString()).ToList(); + } + catch // can fail on weird systems, such as WSL + { + return NoValue; + } +#endif + values = values.Where(mac => mac != "000000000000").ToList(); return string.Join(",", values); } diff --git a/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs b/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs new file mode 100644 index 0000000..0df0d9f --- /dev/null +++ b/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using Microsoft.Win32; + +namespace DeviceId.Components +{ + /// + /// An implementation of that retrieves its value from the Windows registry. + /// + public class RegistryValueDeviceIdComponent : IDeviceIdComponent + { + /// + /// Gets the name of the component. + /// + public string Name { get; } + + /// + /// The path of the registry key to look at + /// + private readonly string _key; + + /// + /// The name of the registry value + /// + private readonly string _valueName; + + /// + /// Value to use when a result is not obtainable + /// + private const string NoValue = "NoValue"; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the component. + /// The path of the registry key to look at. + /// the name of the registry value + public RegistryValueDeviceIdComponent(string name, string key, string valueName) + { + Name = name; + _key = key; + _valueName = valueName; + } + + /// + /// Gets the component value. + /// + /// The component value. + public string GetValue() + { + return Registry.GetValue(_key, _valueName, NoValue).ToString(); + } + } +} diff --git a/src/DeviceId/Components/UnsupportedDeviceIdComponent.cs b/src/DeviceId/Components/UnsupportedDeviceIdComponent.cs new file mode 100644 index 0000000..14c66f5 --- /dev/null +++ b/src/DeviceId/Components/UnsupportedDeviceIdComponent.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace DeviceId.Components +{ + /// + /// An implementation of that returns a constant string to indicate this type of component is not supported + /// + class UnsupportedDeviceIdComponent : IDeviceIdComponent + { + /// + /// String value to use when data cannot be obtained in the current system + /// + public const string ValueUnavailable = "ValueUnavailable"; + + /// + /// Gets the name of the component. + /// + public string Name { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the component. + public UnsupportedDeviceIdComponent(string name) + { + Name = name; + } + + /// + /// Gets the component value. + /// + /// The component value. + public string GetValue() + { + return ValueUnavailable; + } + } +} diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index df2fa1b..cafae7b 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -18,6 +18,7 @@ net40;netstandard2.0 latest true + true @@ -28,4 +29,16 @@ + + + 3.1.3 + + + 4.7.0 + + + 4.3.0 + + + diff --git a/src/DeviceId/DeviceIdBuilderExtensions.cs b/src/DeviceId/DeviceIdBuilderExtensions.cs index 5e1e3f2..a93c146 100644 --- a/src/DeviceId/DeviceIdBuilderExtensions.cs +++ b/src/DeviceId/DeviceIdBuilderExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using DeviceId.Components; namespace DeviceId @@ -59,7 +60,11 @@ public static DeviceIdBuilder AddMachineName(this DeviceIdBuilder builder) /// The instance. public static DeviceIdBuilder AddOSVersion(this DeviceIdBuilder builder) { +#if NETFRAMEWORK return builder.AddComponent(new DeviceIdComponent("OSVersion", Environment.OSVersion.ToString())); +#else + return builder.AddComponent(new DeviceIdComponent("OSVersion", Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystem + " " + Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemVersion)); // examples are "Ubuntu 18.04" +#endif } /// @@ -69,7 +74,14 @@ public static DeviceIdBuilder AddOSVersion(this DeviceIdBuilder builder) /// The instance. public static DeviceIdBuilder AddMacAddress(this DeviceIdBuilder builder) { +#if NETFRAMEWORK return builder.AddComponent(new WmiDeviceIdComponent("MACAddress", "Win32_NetworkAdapterConfiguration", "MACAddress")); +#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return builder.AddComponent(new WmiDeviceIdComponent("MACAddress", "Win32_NetworkAdapterConfiguration", "MACAddress")); + + return builder.AddComponent(new NetworkAdapterDeviceIdComponent(true, true)); +#endif } /// @@ -91,27 +103,54 @@ public static DeviceIdBuilder AddMacAddress(this DeviceIdBuilder builder, bool e /// The instance. public static DeviceIdBuilder AddProcessorId(this DeviceIdBuilder builder) { +#if NETFRAMEWORK return builder.AddComponent(new WmiDeviceIdComponent("ProcessorId", "Win32_Processor", "ProcessorId")); +#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return builder.AddComponent(new WmiDeviceIdComponent("ProcessorId", "Win32_Processor", "ProcessorId")); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return builder.AddComponent(new FileDeviceIdComponent("ProcessorId", "/proc/cpuinfo", true)); + + return builder.AddComponent(new UnsupportedDeviceIdComponent("ProcessorId")); +#endif } /// - /// Adds the motherboard serial number to the device identifier. + /// Adds the motherboard serial number to the device identifier. On Linux, this requires root privilege. /// /// The to add the component to. /// The instance. public static DeviceIdBuilder AddMotherboardSerialNumber(this DeviceIdBuilder builder) { +#if NETFRAMEWORK return builder.AddComponent(new WmiDeviceIdComponent("MotherboardSerialNumber", "Win32_BaseBoard", "SerialNumber")); +#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return builder.AddComponent(new WmiDeviceIdComponent("MotherboardSerialNumber", "Win32_BaseBoard", "SerialNumber")); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return builder.AddComponent(new FileDeviceIdComponent("MotherboardSerialNumber", "/sys/class/dmi/id/board_serial")); + + return builder.AddComponent(new UnsupportedDeviceIdComponent("MotherboardSerialNumber")); +#endif } /// - /// Adds the system UUID to the device identifier. + /// Adds the system UUID to the device identifier. On Linux, this requires root privilege. /// /// The to add the component to. /// The instance. public static DeviceIdBuilder AddSystemUUID(this DeviceIdBuilder builder) { +#if NETFRAMEWORK return builder.AddComponent(new WmiDeviceIdComponent("SystemUUID", "Win32_ComputerSystemProduct", "UUID")); +#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return builder.AddComponent(new WmiDeviceIdComponent("SystemUUID", "Win32_ComputerSystemProduct", "UUID")); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return builder.AddComponent(new FileDeviceIdComponent("SystemUUID", "/sys/class/dmi/id/product_uuid")); + + return builder.AddComponent(new UnsupportedDeviceIdComponent("SystemUUID")); +#endif } /// @@ -121,9 +160,35 @@ public static DeviceIdBuilder AddSystemUUID(this DeviceIdBuilder builder) /// The instance. public static DeviceIdBuilder AddSystemDriveSerialNumber(this DeviceIdBuilder builder) { +#if NETFRAMEWORK return builder.AddComponent(new SystemDriveSerialNumberDeviceIdComponent()); +#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return builder.AddComponent(new SystemDriveSerialNumberDeviceIdComponent()); + + return builder.AddComponent(new UnsupportedDeviceIdComponent("SystemDriveSerialNumber")); +#endif } + /// + /// Adds the an identifier tied to the installation of the OS + /// + /// The to add the component to. + /// The instance. + public static DeviceIdBuilder AddOSInstallationID(this DeviceIdBuilder builder) + { +#if NETFRAMEWORK + return builder.AddComponent(new RegistryValueDeviceIdComponent("OSInstallationID", @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography", "MachineGuid")); +#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return builder.AddComponent(new RegistryValueDeviceIdComponent("OSInstallationID", @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography", "MachineGuid")); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return builder.AddComponent(new FileDeviceIdComponent("OSInstallationID", new string[] {"/var/lib/dbus/machine-id", "/etc/machine-id"})); + + return builder.AddComponent(new UnsupportedDeviceIdComponent("OSInstallationID")); +#endif + } + /// /// Adds a file-based token to the device identifier. /// From 6f18344cd21668f0404c394e5c585f56c1cf5fc4 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sun, 19 Apr 2020 09:28:36 +0800 Subject: [PATCH 02/26] Tweak some of the OS-specific functionality. --- .../Components/FileDeviceIdComponent.cs | 35 +++-- .../NetworkAdapterDeviceIdComponent.cs | 55 ++++---- .../RegistryValueDeviceIdComponent.cs | 14 +- .../UnsupportedDeviceIdComponent.cs | 13 +- src/DeviceId/DeviceId.csproj | 15 +-- src/DeviceId/DeviceIdBuilderExtensions.cs | 120 ++++++++---------- src/DeviceId/OS.cs | 43 +++++++ 7 files changed, 159 insertions(+), 136 deletions(-) create mode 100644 src/DeviceId/OS.cs diff --git a/src/DeviceId/Components/FileDeviceIdComponent.cs b/src/DeviceId/Components/FileDeviceIdComponent.cs index 2c3470f..89227a9 100644 --- a/src/DeviceId/Components/FileDeviceIdComponent.cs +++ b/src/DeviceId/Components/FileDeviceIdComponent.cs @@ -16,17 +16,17 @@ public class FileDeviceIdComponent : IDeviceIdComponent public string Name { get; } /// - /// The paths of file we should look at + /// The paths of file we should look at. /// private readonly string[] _paths; /// - /// Should the contents of the file be hashed? relevant for sources such as /proc/cpuinfo + /// Should the contents of the file be hashed? (Relevant for sources such as /proc/cpuinfo) /// private readonly bool _shouldHashContents; /// - /// Value to use when a result is not obtainable + /// Value to use when a result is not obtainable. /// private const string NoValue = "NoValue"; @@ -34,7 +34,7 @@ public class FileDeviceIdComponent : IDeviceIdComponent /// Initializes a new instance of the class. /// /// The name of the component. - /// The path of the file holding the componentID. + /// The path of the file holding the component ID. /// Whether the file contents should be hashed. public FileDeviceIdComponent(string name, string path, bool shouldHashContents = false) : this(name, new string[] { path }, shouldHashContents) { } @@ -42,7 +42,7 @@ public class FileDeviceIdComponent : IDeviceIdComponent /// Initializes a new instance of the class. /// /// The name of the component. - /// The paths of the files holding the componentID. + /// The paths of the files holding the component ID. /// Whether the file contents should be hashed. public FileDeviceIdComponent(string name, string[] paths, bool shouldHashContents = false) { @@ -57,30 +57,37 @@ public FileDeviceIdComponent(string name, string[] paths, bool shouldHashContent /// The component value. public string GetValue() { - foreach(string path in _paths) + foreach (var path in _paths) { if (!File.Exists(path)) + { continue; + } try { - string contents; + var contents = default(string); - using(var file = File.OpenText(path)) + using (var file = File.OpenText(path)) { contents = file.ReadToEnd(); // File.ReadAllBytes() fails for special files such as /sys/class/dmi/id/product_uuid } + contents = contents.Trim(); if (!_shouldHashContents) + { return contents; - - var hasher = MD5.Create(); - byte[] hash = hasher.ComputeHash(ASCIIEncoding.ASCII.GetBytes(contents)); - return BitConverter.ToString(hash).Replace("-", "").ToUpper(); - } - catch(System.UnauthorizedAccessException){ }// can fail because we have no permissions to access the file + } + using var hasher = MD5.Create(); + var hash = hasher.ComputeHash(Encoding.ASCII.GetBytes(contents)); + return BitConverter.ToString(hash).Replace("-", "").ToUpper(); + } + catch (UnauthorizedAccessException) + { + // Can fail if we have no permissions to access the file. + } } return NoValue; diff --git a/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs b/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs index af34ad6..bc7bd20 100644 --- a/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs +++ b/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs @@ -57,40 +57,45 @@ public NetworkAdapterDeviceIdComponent(bool excludeNonPhysical, bool excludeWire /// The component value. public string GetValue() { - List values; + List values = null; -#if Windows - try + if (OS.IsWindows) { - // First attempt to retrieve the addresses using the CIMv2 interface. - values = GetMacAddressesUsingCimV2(); - } - catch (ManagementException ex) - { - // In case we are notified of an invalid namespace, attempt to lookup the adapters using WMI. - // Could avoid this catch by manually checking for the CIMv2 namespace. - - if (ex.ErrorCode == ManagementStatus.InvalidNamespace) + try { - values = GetMacAddressesUsingWmi(); + // First attempt to retrieve the addresses using the CIMv2 interface. + values = GetMacAddressesUsingCimV2(); } - else + catch (ManagementException ex) { - throw; + // In case we are notified of an invalid namespace, attempt to lookup the adapters using WMI. + // Could avoid this catch by manually checking for the CIMv2 namespace. + + if (ex.ErrorCode == ManagementStatus.InvalidNamespace) + { + values = GetMacAddressesUsingWmi(); + } } } -#else - try - { - NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces(); - values = interfaces.Where(inter => !_excludeWireless || inter.NetworkInterfaceType != NetworkInterfaceType.Wireless80211).Select(inter => inter.GetPhysicalAddress().ToString()).ToList(); - } - catch // can fail on weird systems, such as WSL + + // If we're on a non-Windows OS, or if the above two methods failed, we have the following fallback: + if (values == null) { - return NoValue; + try + { + values = NetworkInterface.GetAllNetworkInterfaces() + .Where(x => !_excludeWireless || x.NetworkInterfaceType != NetworkInterfaceType.Wireless80211) + .Select(x => x.GetPhysicalAddress().ToString()) + .Where(x => x != "000000000000") + .Select(x => FormatMacAddress(x)) + .ToList(); + } + catch + { + return NoValue; + } } -#endif - values = values.Where(mac => mac != "000000000000").ToList(); + return string.Join(",", values); } diff --git a/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs b/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs index 0df0d9f..9e8d73f 100644 --- a/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs +++ b/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs @@ -1,8 +1,4 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using Microsoft.Win32; +using Microsoft.Win32; namespace DeviceId.Components { @@ -17,17 +13,17 @@ public class RegistryValueDeviceIdComponent : IDeviceIdComponent public string Name { get; } /// - /// The path of the registry key to look at + /// The path of the registry key to look at. /// private readonly string _key; /// - /// The name of the registry value + /// The name of the registry value. /// private readonly string _valueName; /// - /// Value to use when a result is not obtainable + /// Value to use when a result is not obtainable. /// private const string NoValue = "NoValue"; @@ -36,7 +32,7 @@ public class RegistryValueDeviceIdComponent : IDeviceIdComponent /// /// The name of the component. /// The path of the registry key to look at. - /// the name of the registry value + /// the name of the registry value. public RegistryValueDeviceIdComponent(string name, string key, string valueName) { Name = name; diff --git a/src/DeviceId/Components/UnsupportedDeviceIdComponent.cs b/src/DeviceId/Components/UnsupportedDeviceIdComponent.cs index 14c66f5..911a05e 100644 --- a/src/DeviceId/Components/UnsupportedDeviceIdComponent.cs +++ b/src/DeviceId/Components/UnsupportedDeviceIdComponent.cs @@ -1,17 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace DeviceId.Components +namespace DeviceId.Components { /// - /// An implementation of that returns a constant string to indicate this type of component is not supported + /// An implementation of that returns a constant string to indicate this type of component is not supported. /// - class UnsupportedDeviceIdComponent : IDeviceIdComponent + internal sealed class UnsupportedDeviceIdComponent : IDeviceIdComponent { /// - /// String value to use when data cannot be obtained in the current system + /// String value to use when data cannot be obtained in the current system. /// public const string ValueUnavailable = "ValueUnavailable"; diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index cafae7b..d9126ea 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -26,19 +26,10 @@ + + - - - - - 3.1.3 - - - 4.7.0 - - - 4.3.0 - + diff --git a/src/DeviceId/DeviceIdBuilderExtensions.cs b/src/DeviceId/DeviceIdBuilderExtensions.cs index a93c146..b460217 100644 --- a/src/DeviceId/DeviceIdBuilderExtensions.cs +++ b/src/DeviceId/DeviceIdBuilderExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.InteropServices; using DeviceId.Components; namespace DeviceId @@ -60,28 +59,7 @@ public static DeviceIdBuilder AddMachineName(this DeviceIdBuilder builder) /// The instance. public static DeviceIdBuilder AddOSVersion(this DeviceIdBuilder builder) { -#if NETFRAMEWORK - return builder.AddComponent(new DeviceIdComponent("OSVersion", Environment.OSVersion.ToString())); -#else - return builder.AddComponent(new DeviceIdComponent("OSVersion", Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystem + " " + Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemVersion)); // examples are "Ubuntu 18.04" -#endif - } - - /// - /// Adds the MAC address to the device identifier. - /// - /// The to add the component to. - /// The instance. - public static DeviceIdBuilder AddMacAddress(this DeviceIdBuilder builder) - { -#if NETFRAMEWORK - return builder.AddComponent(new WmiDeviceIdComponent("MACAddress", "Win32_NetworkAdapterConfiguration", "MACAddress")); -#else - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return builder.AddComponent(new WmiDeviceIdComponent("MACAddress", "Win32_NetworkAdapterConfiguration", "MACAddress")); - - return builder.AddComponent(new NetworkAdapterDeviceIdComponent(true, true)); -#endif + return builder.AddComponent(new DeviceIdComponent("OSVersion", OS.Version)); } /// @@ -103,16 +81,18 @@ public static DeviceIdBuilder AddMacAddress(this DeviceIdBuilder builder, bool e /// The instance. public static DeviceIdBuilder AddProcessorId(this DeviceIdBuilder builder) { -#if NETFRAMEWORK - return builder.AddComponent(new WmiDeviceIdComponent("ProcessorId", "Win32_Processor", "ProcessorId")); -#else - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (OS.IsWindows) + { return builder.AddComponent(new WmiDeviceIdComponent("ProcessorId", "Win32_Processor", "ProcessorId")); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + } + else if (OS.IsLinux) + { return builder.AddComponent(new FileDeviceIdComponent("ProcessorId", "/proc/cpuinfo", true)); - - return builder.AddComponent(new UnsupportedDeviceIdComponent("ProcessorId")); -#endif + } + else + { + return builder.AddComponent(new UnsupportedDeviceIdComponent("ProcessorId")); + } } /// @@ -122,16 +102,18 @@ public static DeviceIdBuilder AddProcessorId(this DeviceIdBuilder builder) /// The instance. public static DeviceIdBuilder AddMotherboardSerialNumber(this DeviceIdBuilder builder) { -#if NETFRAMEWORK - return builder.AddComponent(new WmiDeviceIdComponent("MotherboardSerialNumber", "Win32_BaseBoard", "SerialNumber")); -#else - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (OS.IsWindows) + { return builder.AddComponent(new WmiDeviceIdComponent("MotherboardSerialNumber", "Win32_BaseBoard", "SerialNumber")); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - return builder.AddComponent(new FileDeviceIdComponent("MotherboardSerialNumber", "/sys/class/dmi/id/board_serial")); - - return builder.AddComponent(new UnsupportedDeviceIdComponent("MotherboardSerialNumber")); -#endif + } + else if (OS.IsLinux) + { + return builder.AddComponent(new FileDeviceIdComponent("MotherboardSerialNumber", "/sys/class/dmi/id/board_serial")); + } + else + { + return builder.AddComponent(new UnsupportedDeviceIdComponent("MotherboardSerialNumber")); + } } /// @@ -141,16 +123,18 @@ public static DeviceIdBuilder AddMotherboardSerialNumber(this DeviceIdBuilder bu /// The instance. public static DeviceIdBuilder AddSystemUUID(this DeviceIdBuilder builder) { -#if NETFRAMEWORK - return builder.AddComponent(new WmiDeviceIdComponent("SystemUUID", "Win32_ComputerSystemProduct", "UUID")); -#else - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (OS.IsWindows) + { return builder.AddComponent(new WmiDeviceIdComponent("SystemUUID", "Win32_ComputerSystemProduct", "UUID")); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - return builder.AddComponent(new FileDeviceIdComponent("SystemUUID", "/sys/class/dmi/id/product_uuid")); - - return builder.AddComponent(new UnsupportedDeviceIdComponent("SystemUUID")); -#endif + } + else if (OS.IsLinux) + { + return builder.AddComponent(new FileDeviceIdComponent("SystemUUID", "/sys/class/dmi/id/product_uuid")); + } + else + { + return builder.AddComponent(new UnsupportedDeviceIdComponent("SystemUUID")); + } } /// @@ -160,34 +144,36 @@ public static DeviceIdBuilder AddSystemUUID(this DeviceIdBuilder builder) /// The instance. public static DeviceIdBuilder AddSystemDriveSerialNumber(this DeviceIdBuilder builder) { -#if NETFRAMEWORK - return builder.AddComponent(new SystemDriveSerialNumberDeviceIdComponent()); -#else - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (OS.IsWindows) + { return builder.AddComponent(new SystemDriveSerialNumberDeviceIdComponent()); - - return builder.AddComponent(new UnsupportedDeviceIdComponent("SystemDriveSerialNumber")); -#endif + } + else + { + return builder.AddComponent(new UnsupportedDeviceIdComponent("SystemDriveSerialNumber")); + } } /// - /// Adds the an identifier tied to the installation of the OS + /// Adds the identifier tied to the installation of the OS. /// /// The to add the component to. /// The instance. public static DeviceIdBuilder AddOSInstallationID(this DeviceIdBuilder builder) { -#if NETFRAMEWORK - return builder.AddComponent(new RegistryValueDeviceIdComponent("OSInstallationID", @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography", "MachineGuid")); -#else - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (OS.IsWindows) + { return builder.AddComponent(new RegistryValueDeviceIdComponent("OSInstallationID", @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography", "MachineGuid")); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - return builder.AddComponent(new FileDeviceIdComponent("OSInstallationID", new string[] {"/var/lib/dbus/machine-id", "/etc/machine-id"})); - - return builder.AddComponent(new UnsupportedDeviceIdComponent("OSInstallationID")); -#endif - } + } + else if (OS.IsLinux) + { + return builder.AddComponent(new FileDeviceIdComponent("OSInstallationID", new string[] { "/var/lib/dbus/machine-id", "/etc/machine-id" })); + } + else + { + return builder.AddComponent(new UnsupportedDeviceIdComponent("OSInstallationID")); + } + } /// /// Adds a file-based token to the device identifier. diff --git a/src/DeviceId/OS.cs b/src/DeviceId/OS.cs new file mode 100644 index 0000000..592139d --- /dev/null +++ b/src/DeviceId/OS.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; +#if NETSTANDARD +using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment; +#endif + +namespace DeviceId +{ + /// + /// Provides helper methods relating to the OS. + /// + internal static class OS + { + /// + /// Gets a value indicating whether this is a Windows OS. + /// + public static bool IsWindows { get; } +#if NETFRAMEWORK + = true; +#elif NETSTANDARD + = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); +#endif + + /// + /// Gets a value indicating whether this is a Linux OS. + /// + public static bool IsLinux { get; } +#if NETFRAMEWORK + = false; +#elif NETSTANDARD + = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); +#endif + + public static string Version { get; } +#if NETFRAMEWORK + = Environment.OSVersion.ToString(); +#elif NETSTANDARD + = IsWindows + ? Environment.OSVersion.ToString() + : string.Concat(RuntimeEnvironment.OperatingSystem, " ", RuntimeEnvironment.OperatingSystemVersion); +#endif + } +} From 64404ab4c24d7695151d76384188873e91de1e30 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sun, 19 Apr 2020 11:02:11 +0800 Subject: [PATCH 03/26] Add strong naming. --- src/DeviceId/DeviceId.csproj | 8 ++++++++ src/DeviceId/DeviceId.snk | Bin 0 -> 596 bytes src/DeviceId/InternalsVisibleTo.cs | 2 +- test/DeviceId.Tests/DeviceId.Tests.csproj | 8 ++++++++ test/DeviceId.Tests/DeviceId.Tests.snk | Bin 0 -> 596 bytes 5 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/DeviceId/DeviceId.snk create mode 100644 test/DeviceId.Tests/DeviceId.Tests.snk diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index d9126ea..7b84e6d 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -19,6 +19,8 @@ latest true true + true + DeviceId.snk @@ -32,4 +34,10 @@ + + + false + + + diff --git a/src/DeviceId/DeviceId.snk b/src/DeviceId/DeviceId.snk new file mode 100644 index 0000000000000000000000000000000000000000..e77f51dc3e8d702554dd759afe64e6c71f9c505e GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50098CyBYuizBGZ$)eB07shv#W_a6UNG5f5p z02b`UCo&1DnzuvoO!3oj<~jqLka;p{HQoh`!S(bDsBf=I$Y-9+1SB1z?a59cSS=L>X$U@Tf;PQ$U_ z8_4rBx|pRL7n|+7o9l^p^SC>ra3O}4g(Cv_RB4nP{z;jA^>4Ydx?S;Ah1&)3m#({e z*fjuCuq0sZi5$=EZ7#C5M{#3%G4JZD>-)bMq;27y_9inx;JTsfkW<^taWJ*xxlbF% z_s7xeWCd3hf^TGA%1MrZAL~yv^nPT3!l;Y8uBi485k9U&jW3T-bDKd4|KJaaoJ*?PWs?alS;)oY7~Z3&|L#|DEF<{F_d9oh)!E)vuGh zpVwX?#=sJKFmd(uLPe7X51UZg9jX?@xM82ar_w$@zULAZjJ~P2$c`ynv!fcly{3|z z5Tbr(gjY`q@hqY2BB5z_kUL?)YNnCf4Ht)HD~Z_nlJck?vX|RwQHnB4!ZQ_Cg9`(R z>>cUWVWLGM!fC^+QJHKq`el}b(&0`7ESlJjDwJ~T+f>3QX4PazomkE22V@%JiEds{ iY5zUgpe^|$YBN7VK1_OO5Scb)R;5xxpzG08FQlRO? netcoreapp2.0 false + true + DeviceId.Tests.snk @@ -20,4 +22,10 @@ + + + false + + + diff --git a/test/DeviceId.Tests/DeviceId.Tests.snk b/test/DeviceId.Tests/DeviceId.Tests.snk new file mode 100644 index 0000000000000000000000000000000000000000..260b0587c5be9e2d5d960bc94292a093f2c962ca GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097r2GZrJEpll*{nAS*`NQg5r5SR*bY~>5 z?}}vr3Ld-2itbe8$-}<%oDPF`0v8UMk(!Hub;5!)>FYZawyeW2R3vKY^6nSNDF3`qoUa-(&78-=JYF(uQ-Z zmw^&;R}tuM*m?3V{mEiVPUOrf{NM&!m_LBlWUWOqpIKtRw86r#cHjO;<=e_b+$nYv zjEkhKD%@99EnOQv6rr%E%yAM1 z+8$|O(LnKyMA3ZRy20V#w&~Wco=4^orYgW%%c4(*7SAh&Mp2$oFGL1% z8&;?ee3wbnei9($sozj0W)W;oQ$vBiU3E_e7$}4)keSS;%L3KjzS6O8!Nj>A_qQGl zFC8p#!EdESP5r6e~c>&x~6(kF7Zy!(Q ikX9DL-m`_vd@2tFPljbLDPKa=)uk-L)sR3Elq7F`b0RkY literal 0 HcmV?d00001 From 1909037bd77cbe49de6b87f59feb238b8d98cd22 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sun, 19 Apr 2020 11:17:13 +0800 Subject: [PATCH 04/26] Update readme. Update copyright years. --- readme.md | 32 +++++++++++++++++++++++--------- src/DeviceId/DeviceId.csproj | 4 ++-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index c937d4e..cfcfef5 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,8 @@ -DeviceId -======== +# DeviceId A simple library providing functionality to generate a 'device ID' that can be used to uniquely identify a computer. -Quickstart ----------- +## Quickstart ### Building a device identifier @@ -31,6 +29,7 @@ The following extension methods are available out of the box to suit some common * `AddMotherboardSerialNumber()` adds the motherboard serial number to the device ID. * `AddSystemDriveSerialNumber()` adds the system drive's serial number to the device ID. * `AddSystemUUID()` adds the system UUID to the device ID. +* `AddOSInstallationID` adds the OS installation ID. * `AddFileToken(path)` adds a token stored at the specified path to the device ID. * `AddComponent(component)` adds a custom component (see below) to the device ID. @@ -68,8 +67,24 @@ There are a number of encoders that can be used customize the formatter. These i * [Base64ByteArrayEncoder](/src/DeviceId/Encoders/Base64ByteArrayEncoder.cs) - Encodes a byte array as a base 64 string. * [Base64UrlByteArrayEncoder](/src/DeviceId/Encoders/Base64UrlByteArrayEncoder.cs) - Encodes a byte array as a base 64 url-encoded string. -Installation ------------- +## Cross-platform support + +The following cross-platform support is available: + +| Component | Windows | Linux | +| -------------------------- | ------- | ------- | +| User name | **Yes** | **Yes** | +| Machine name | **Yes** | **Yes** | +| OS version | **Yes** | **Yes** | +| Processor ID | **Yes** | **Yes** | +| MAC address | **Yes** | **Yes** | +| Motherboard serial number | **Yes** | **Yes** | +| System drive serial number | **Yes** | No | +| System UUID | **Yes** | **Yes** | +| OS installation ID | **Yes** | **Yes** | +| File token | **Yes** | **Yes** | + +## Installation Just grab it from [NuGet](https://www.nuget.org/packages/DeviceId/) @@ -81,8 +96,7 @@ PM> Install-Package DeviceId $ dotnet add package DeviceId ``` -License and copyright ---------------------- +## License and copyright -Copyright Matthew King 2015-2019. +Copyright Matthew King 2015-2020. Distributed under the [MIT License](http://opensource.org/licenses/MIT). Refer to license.txt for more information. diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index 7b84e6d..fb87b6e 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -5,7 +5,7 @@ DeviceId Provides functionality to generate a 'device ID' that can be used to uniquely identify a computer. Matthew King - Copyright 2015-2019 Matthew King. + Copyright Matthew King. MIT https://github.com/MatthewKing/DeviceId https://github.com/MatthewKing/DeviceId @@ -31,7 +31,7 @@ - + From d8f3c436b5d6c70e7747b1f51f731a7945b67177 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sun, 19 Apr 2020 11:29:27 +0800 Subject: [PATCH 05/26] Add registry value extension method. --- readme.md | 2 + .../RegistryValueDeviceIdComponent.cs | 4 +- src/DeviceId/DeviceIdBuilderExtensions.cs | 37 +++++++++++++------ 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/readme.md b/readme.md index cfcfef5..9bae696 100644 --- a/readme.md +++ b/readme.md @@ -31,6 +31,7 @@ The following extension methods are available out of the box to suit some common * `AddSystemUUID()` adds the system UUID to the device ID. * `AddOSInstallationID` adds the OS installation ID. * `AddFileToken(path)` adds a token stored at the specified path to the device ID. +* `AddRegistryValue()` adds a value from the registry. * `AddComponent(component)` adds a custom component (see below) to the device ID. Custom components can be built by implementing `IDeviceIdComponent`. There is also a simple `DeviceIdComponent` class that allows you to specify an arbitrary component value to use, and a `WmiDeviceIdComponent` class that uses a specified WMI property (example: `new WmiDeviceIdComponent("MACAddress", "Win32_NetworkAdapterConfiguration", "MACAddress"`). @@ -82,6 +83,7 @@ The following cross-platform support is available: | System drive serial number | **Yes** | No | | System UUID | **Yes** | **Yes** | | OS installation ID | **Yes** | **Yes** | +| Registry value | **Yes** | No | | File token | **Yes** | **Yes** | ## Installation diff --git a/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs b/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs index 9e8d73f..f11cf6b 100644 --- a/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs +++ b/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs @@ -31,8 +31,8 @@ public class RegistryValueDeviceIdComponent : IDeviceIdComponent /// Initializes a new instance of the class. /// /// The name of the component. - /// The path of the registry key to look at. - /// the name of the registry value. + /// The full path of the registry key. + /// The name of the registry value. public RegistryValueDeviceIdComponent(string name, string key, string valueName) { Name = name; diff --git a/src/DeviceId/DeviceIdBuilderExtensions.cs b/src/DeviceId/DeviceIdBuilderExtensions.cs index b460217..d40377f 100644 --- a/src/DeviceId/DeviceIdBuilderExtensions.cs +++ b/src/DeviceId/DeviceIdBuilderExtensions.cs @@ -117,40 +117,40 @@ public static DeviceIdBuilder AddMotherboardSerialNumber(this DeviceIdBuilder bu } /// - /// Adds the system UUID to the device identifier. On Linux, this requires root privilege. + /// Adds the system drive's serial number to the device identifier. /// /// The to add the component to. /// The instance. - public static DeviceIdBuilder AddSystemUUID(this DeviceIdBuilder builder) + public static DeviceIdBuilder AddSystemDriveSerialNumber(this DeviceIdBuilder builder) { if (OS.IsWindows) { - return builder.AddComponent(new WmiDeviceIdComponent("SystemUUID", "Win32_ComputerSystemProduct", "UUID")); - } - else if (OS.IsLinux) - { - return builder.AddComponent(new FileDeviceIdComponent("SystemUUID", "/sys/class/dmi/id/product_uuid")); + return builder.AddComponent(new SystemDriveSerialNumberDeviceIdComponent()); } else { - return builder.AddComponent(new UnsupportedDeviceIdComponent("SystemUUID")); + return builder.AddComponent(new UnsupportedDeviceIdComponent("SystemDriveSerialNumber")); } } /// - /// Adds the system drive's serial number to the device identifier. + /// Adds the system UUID to the device identifier. On Linux, this requires root privilege. /// /// The to add the component to. /// The instance. - public static DeviceIdBuilder AddSystemDriveSerialNumber(this DeviceIdBuilder builder) + public static DeviceIdBuilder AddSystemUUID(this DeviceIdBuilder builder) { if (OS.IsWindows) { - return builder.AddComponent(new SystemDriveSerialNumberDeviceIdComponent()); + return builder.AddComponent(new WmiDeviceIdComponent("SystemUUID", "Win32_ComputerSystemProduct", "UUID")); + } + else if (OS.IsLinux) + { + return builder.AddComponent(new FileDeviceIdComponent("SystemUUID", "/sys/class/dmi/id/product_uuid")); } else { - return builder.AddComponent(new UnsupportedDeviceIdComponent("SystemDriveSerialNumber")); + return builder.AddComponent(new UnsupportedDeviceIdComponent("SystemUUID")); } } @@ -175,6 +175,19 @@ public static DeviceIdBuilder AddOSInstallationID(this DeviceIdBuilder builder) } } + /// + /// Adds a registry value to the device identifier. + /// + /// The to add the component to. + /// The name of the component. + /// The full path of the registry key. + /// The name of the registry value. + /// The instance. + public static DeviceIdBuilder AddRegistryValue(this DeviceIdBuilder builder, string name, string key, string valueName) + { + return builder.AddComponent(new RegistryValueDeviceIdComponent(name, key, valueName)); + } + /// /// Adds a file-based token to the device identifier. /// From 0c446e2c3da5ebc324fb39d736d37f593647928f Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sun, 19 Apr 2020 11:37:21 +0800 Subject: [PATCH 06/26] Add strong naming disclaimer. --- readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readme.md b/readme.md index 9bae696..d16535c 100644 --- a/readme.md +++ b/readme.md @@ -98,6 +98,10 @@ PM> Install-Package DeviceId $ dotnet add package DeviceId ``` +## Strong naming + +From version 5 onwards, the assemblies in this package are strong named for the convenience of those users who require strong naming. Please note, however, that the key files are checked in to this repository. This means that anyone can compile their own version and strong name it with the original keys. This is a common practice with open source projects, but it does mean that you shouldn't use the strong name as a guarantee of security or identity. + ## License and copyright Copyright Matthew King 2015-2020. From 41c1893997efbc0cf08d6fdfd0e4224874170cb9 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Mon, 20 Apr 2020 19:20:04 +0800 Subject: [PATCH 07/26] Make return values more consistent. * Ensure multi-values are sorted. * Return null rather than empty string or "NoValue" when no value is present. * Treat null values as empty strings when encoding. --- src/DeviceId/Components/FileDeviceIdComponent.cs | 16 +++++++--------- .../Components/FileTokenDeviceIdComponent.cs | 9 +++++---- .../NetworkAdapterDeviceIdComponent.cs | 11 +++++++++-- .../Components/RegistryValueDeviceIdComponent.cs | 15 +++++++++------ .../Components/UnsupportedDeviceIdComponent.cs | 7 +------ src/DeviceId/Components/WmiDeviceIdComponent.cs | 4 +++- .../Encoders/HashDeviceIdComponentEncoder.cs | 2 +- .../PlainTextDeviceIdComponentEncoder.cs | 2 +- .../HashDeviceIdComponentEncoderTests.cs | 9 +++++++++ .../PlainTextDeviceIdComponentEncoderTests.cs | 9 +++++++++ .../Formatters/HashDeviceIdFormatterTests.cs | 13 +++++++++++++ .../Formatters/StringDeviceIdFormatterTests.cs | 13 +++++++++++++ .../Formatters/XmlDeviceIdFormatterTests.cs | 13 +++++++++++++ 13 files changed, 93 insertions(+), 30 deletions(-) diff --git a/src/DeviceId/Components/FileDeviceIdComponent.cs b/src/DeviceId/Components/FileDeviceIdComponent.cs index 89227a9..8d1e3da 100644 --- a/src/DeviceId/Components/FileDeviceIdComponent.cs +++ b/src/DeviceId/Components/FileDeviceIdComponent.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Security.Cryptography; using System.Text; @@ -25,18 +27,14 @@ public class FileDeviceIdComponent : IDeviceIdComponent /// private readonly bool _shouldHashContents; - /// - /// Value to use when a result is not obtainable. - /// - private const string NoValue = "NoValue"; - /// /// Initializes a new instance of the class. /// /// The name of the component. /// The path of the file holding the component ID. /// Whether the file contents should be hashed. - public FileDeviceIdComponent(string name, string path, bool shouldHashContents = false) : this(name, new string[] { path }, shouldHashContents) { } + public FileDeviceIdComponent(string name, string path, bool shouldHashContents = false) + : this(name, new string[] { path }, shouldHashContents) { } /// /// Initializes a new instance of the class. @@ -44,10 +42,10 @@ public class FileDeviceIdComponent : IDeviceIdComponent /// The name of the component. /// The paths of the files holding the component ID. /// Whether the file contents should be hashed. - public FileDeviceIdComponent(string name, string[] paths, bool shouldHashContents = false) + public FileDeviceIdComponent(string name, IEnumerable paths, bool shouldHashContents = false) { Name = name; - _paths = paths; + _paths = paths.ToArray(); _shouldHashContents = shouldHashContents; } @@ -90,7 +88,7 @@ public string GetValue() } } - return NoValue; + return null; } } } diff --git a/src/DeviceId/Components/FileTokenDeviceIdComponent.cs b/src/DeviceId/Components/FileTokenDeviceIdComponent.cs index 74b7de1..d6fe671 100644 --- a/src/DeviceId/Components/FileTokenDeviceIdComponent.cs +++ b/src/DeviceId/Components/FileTokenDeviceIdComponent.cs @@ -41,14 +41,13 @@ public FileTokenDeviceIdComponent(string name, string path) /// The component value. public string GetValue() { - var value = Guid.NewGuid().ToString().ToUpper(); - if (File.Exists(_path)) { try { var bytes = File.ReadAllBytes(_path); - value = Encoding.ASCII.GetString(bytes); + var value = Encoding.ASCII.GetString(bytes); + return value; } catch { } } @@ -56,13 +55,15 @@ public string GetValue() { try { + var value = Guid.NewGuid().ToString().ToUpper(); var bytes = Encoding.ASCII.GetBytes(value); File.WriteAllBytes(_path, bytes); + return value; } catch { } } - return value; + return null; } } } diff --git a/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs b/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs index bc7bd20..70bee40 100644 --- a/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs +++ b/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs @@ -92,11 +92,18 @@ public string GetValue() } catch { - return NoValue; + } } - return string.Join(",", values); + if (values != null) + { + values.Sort(); + } + + return (values != null && values.Count > 0) + ? string.Join(",", values) + : null; } /// diff --git a/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs b/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs index f11cf6b..bd6ab27 100644 --- a/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs +++ b/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs @@ -22,11 +22,6 @@ public class RegistryValueDeviceIdComponent : IDeviceIdComponent /// private readonly string _valueName; - /// - /// Value to use when a result is not obtainable. - /// - private const string NoValue = "NoValue"; - /// /// Initializes a new instance of the class. /// @@ -46,7 +41,15 @@ public RegistryValueDeviceIdComponent(string name, string key, string valueName) /// The component value. public string GetValue() { - return Registry.GetValue(_key, _valueName, NoValue).ToString(); + try + { + var value = Registry.GetValue(_key, _valueName, null); + return value?.ToString(); + } + catch + { + return null; + } } } } diff --git a/src/DeviceId/Components/UnsupportedDeviceIdComponent.cs b/src/DeviceId/Components/UnsupportedDeviceIdComponent.cs index 911a05e..f6a6e85 100644 --- a/src/DeviceId/Components/UnsupportedDeviceIdComponent.cs +++ b/src/DeviceId/Components/UnsupportedDeviceIdComponent.cs @@ -5,11 +5,6 @@ /// internal sealed class UnsupportedDeviceIdComponent : IDeviceIdComponent { - /// - /// String value to use when data cannot be obtained in the current system. - /// - public const string ValueUnavailable = "ValueUnavailable"; - /// /// Gets the name of the component. /// @@ -30,7 +25,7 @@ public UnsupportedDeviceIdComponent(string name) /// The component value. public string GetValue() { - return ValueUnavailable; + return null; } } } diff --git a/src/DeviceId/Components/WmiDeviceIdComponent.cs b/src/DeviceId/Components/WmiDeviceIdComponent.cs index c889e69..f6c4cbc 100644 --- a/src/DeviceId/Components/WmiDeviceIdComponent.cs +++ b/src/DeviceId/Components/WmiDeviceIdComponent.cs @@ -64,7 +64,9 @@ public string GetValue() values.Sort(); - return string.Join(",", values); + return (values != null && values.Count > 0) + ? string.Join(",", values) + : null; } } } diff --git a/src/DeviceId/Encoders/HashDeviceIdComponentEncoder.cs b/src/DeviceId/Encoders/HashDeviceIdComponentEncoder.cs index 6a80f1c..51c729a 100644 --- a/src/DeviceId/Encoders/HashDeviceIdComponentEncoder.cs +++ b/src/DeviceId/Encoders/HashDeviceIdComponentEncoder.cs @@ -37,7 +37,7 @@ public HashDeviceIdComponentEncoder(Func hashAlgorithm, IByteArra /// The component encoded as a string. public string Encode(IDeviceIdComponent component) { - var value = component.GetValue(); + var value = component.GetValue() ?? string.Empty; var bytes = Encoding.UTF8.GetBytes(value); using var algorithm = _hashAlgorithm.Invoke(); var hash = algorithm.ComputeHash(bytes); diff --git a/src/DeviceId/Encoders/PlainTextDeviceIdComponentEncoder.cs b/src/DeviceId/Encoders/PlainTextDeviceIdComponentEncoder.cs index 8ddd2b7..25b9f08 100644 --- a/src/DeviceId/Encoders/PlainTextDeviceIdComponentEncoder.cs +++ b/src/DeviceId/Encoders/PlainTextDeviceIdComponentEncoder.cs @@ -17,7 +17,7 @@ public PlainTextDeviceIdComponentEncoder() { } /// The component encoded as a string. public string Encode(IDeviceIdComponent component) { - return component.GetValue(); + return component.GetValue() ?? string.Empty; } } } diff --git a/test/DeviceId.Tests/Encoders/HashDeviceIdComponentEncoderTests.cs b/test/DeviceId.Tests/Encoders/HashDeviceIdComponentEncoderTests.cs index 821abdf..4a9d283 100644 --- a/test/DeviceId.Tests/Encoders/HashDeviceIdComponentEncoderTests.cs +++ b/test/DeviceId.Tests/Encoders/HashDeviceIdComponentEncoderTests.cs @@ -84,5 +84,14 @@ public void Encode_MD5_Base64Url_ReturnsHashedComponentValue() encoder.Encode(component).Should().Be("aJICQJ5IdDuRRxP5bZOUfA"); } + + [Fact] + public void Encode_ValueIsNull_TreatItAsAnEmptyString() + { + var encoder = new HashDeviceIdComponentEncoder(() => MD5.Create(), new Base64UrlByteArrayEncoder()); + var expected = encoder.Encode(new DeviceIdComponent("Name", string.Empty)); + var actual = encoder.Encode(new DeviceIdComponent("Name", default(string))); + actual.Should().Be(expected); + } } } diff --git a/test/DeviceId.Tests/Encoders/PlainTextDeviceIdComponentEncoderTests.cs b/test/DeviceId.Tests/Encoders/PlainTextDeviceIdComponentEncoderTests.cs index ac1e4a6..c469cde 100644 --- a/test/DeviceId.Tests/Encoders/PlainTextDeviceIdComponentEncoderTests.cs +++ b/test/DeviceId.Tests/Encoders/PlainTextDeviceIdComponentEncoderTests.cs @@ -16,5 +16,14 @@ public void Encode_ReturnsPlainTextComponentValue() encoder.Encode(component).Should().Be("Value"); } + + [Fact] + public void Encode_ValueIsNull_TreatItAsAnEmptyString() + { + var encoder = new PlainTextDeviceIdComponentEncoder(); + var expected = encoder.Encode(new DeviceIdComponent("Name", string.Empty)); + var actual = encoder.Encode(new DeviceIdComponent("Name", default(string))); + actual.Should().Be(expected); + } } } diff --git a/test/DeviceId.Tests/Formatters/HashDeviceIdFormatterTests.cs b/test/DeviceId.Tests/Formatters/HashDeviceIdFormatterTests.cs index 3afdcc4..6c49bd4 100644 --- a/test/DeviceId.Tests/Formatters/HashDeviceIdFormatterTests.cs +++ b/test/DeviceId.Tests/Formatters/HashDeviceIdFormatterTests.cs @@ -59,5 +59,18 @@ public void GetDeviceId_ComponentsAreValid_ReturnsDeviceId() deviceId.Should().Be("b02f4481c190173f05192bc08a1b14bc"); } + + [Fact] + public void GetDeviceId_ComponentReturnsNull_ReturnsDeviceId() + { + var formatter = new HashDeviceIdFormatter(() => MD5.Create(), new HexByteArrayEncoder()); + + var deviceId = formatter.GetDeviceId(new IDeviceIdComponent[] + { + new DeviceIdComponent("Test1", default(string)), + }); + + deviceId.Should().Be("d41d8cd98f00b204e9800998ecf8427e"); + } } } diff --git a/test/DeviceId.Tests/Formatters/StringDeviceIdFormatterTests.cs b/test/DeviceId.Tests/Formatters/StringDeviceIdFormatterTests.cs index bc94d9d..4914d31 100644 --- a/test/DeviceId.Tests/Formatters/StringDeviceIdFormatterTests.cs +++ b/test/DeviceId.Tests/Formatters/StringDeviceIdFormatterTests.cs @@ -51,5 +51,18 @@ public void GetDeviceId_ComponentsAreValid_ReturnsDeviceId() deviceId.Should().Be("e1b849f9631ffc1829b2e31402373e3c.c454552d52d55d3ef56408742887362b"); } + + [Fact] + public void GetDeviceId_ComponentReturnsNull_ReturnsDeviceId() + { + var formatter = new StringDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder())); + + var deviceId = formatter.GetDeviceId(new IDeviceIdComponent[] + { + new DeviceIdComponent("Test1", default(string)), + }); + + deviceId.Should().Be("d41d8cd98f00b204e9800998ecf8427e"); + } } } diff --git a/test/DeviceId.Tests/Formatters/XmlDeviceIdFormatterTests.cs b/test/DeviceId.Tests/Formatters/XmlDeviceIdFormatterTests.cs index f2bdcd5..5c6cffb 100644 --- a/test/DeviceId.Tests/Formatters/XmlDeviceIdFormatterTests.cs +++ b/test/DeviceId.Tests/Formatters/XmlDeviceIdFormatterTests.cs @@ -51,5 +51,18 @@ public void GetDeviceId_ComponentsAreValid_ReturnsDeviceId() deviceId.Should().Be(""); } + + [Fact] + public void GetDeviceId_ComponentReturnsNull_ReturnsDeviceId() + { + var formatter = new XmlDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder())); + + var deviceId = formatter.GetDeviceId(new IDeviceIdComponent[] + { + new DeviceIdComponent("Test1", default(string)), + }); + + deviceId.Should().Be(""); + } } } From 47c51af3e753bac93740d315b3f036cb097441c5 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Mon, 20 Apr 2020 19:20:50 +0800 Subject: [PATCH 08/26] Increment version number. --- src/DeviceId/DeviceId.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index fb87b6e..a869e07 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -11,7 +11,7 @@ https://github.com/MatthewKing/DeviceId git deviceid;unique;device;identifier - 4.5.0 + 5.0.0 From 6062a178ff7c5bedf51f3cb37e347053507832af Mon Sep 17 00:00:00 2001 From: "Kevin M. Kelly" Date: Wed, 22 Apr 2020 23:50:02 -0700 Subject: [PATCH 09/26] Add support for system drive serial number on Linux. --- readme.md | 2 +- ...xRootDriveSerialNumberDeviceIdComponent.cs | 130 ++++++++++++++++++ src/DeviceId/DeviceId.csproj | 4 + src/DeviceId/DeviceIdBuilderExtensions.cs | 3 + 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs diff --git a/readme.md b/readme.md index d16535c..a912c06 100644 --- a/readme.md +++ b/readme.md @@ -80,7 +80,7 @@ The following cross-platform support is available: | Processor ID | **Yes** | **Yes** | | MAC address | **Yes** | **Yes** | | Motherboard serial number | **Yes** | **Yes** | -| System drive serial number | **Yes** | No | +| System drive serial number | **Yes** | **Yes** | | System UUID | **Yes** | **Yes** | | OS installation ID | **Yes** | **Yes** | | Registry value | **Yes** | No | diff --git a/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs b/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs new file mode 100644 index 0000000..a1a89ea --- /dev/null +++ b/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +using Newtonsoft.Json; + +namespace DeviceId.Components +{ + /// + /// An implementation of that uses the root drive's serial number. + /// + public class LinuxRootDriveSerialNumberDeviceIdComponent : IDeviceIdComponent + { + /// + /// Gets the name of the component. + /// + public string Name { get; } = "SystemDriveSerialNumber"; + + /// + /// Initializes a new instance of the class. + /// + public LinuxRootDriveSerialNumberDeviceIdComponent() { } + + /// + /// Gets the component value. + /// + /// The component value. + public string GetValue() + { + string json = "lsblk -f -J".Bash(); + lsblkOutput output = JsonConvert.DeserializeObject(json); + + lsblkDevice device = findRootParent(output); + if (device == null) + { + return null; + } + + string udevInfo = string.Format("udevadm info --query=all --name=/dev/{0} | grep ID_SERIAL=", device.name).Bash(); + string[] components = udevInfo.Split('='); + if (components.Length == 2) + { + return components[1]; + } + + return null; + } + + private lsblkDevice findRootParent(lsblkOutput devices) + { + foreach (lsblkDevice device in devices.blockdevices) + { + if (deviceContainsRoot(device)) + { + return device; + } + } + + return null; + } + + private bool deviceContainsRoot(lsblkDevice device) + { + if (device.mountpoint == "/") + { + return true; + } else if (device.children != null && device.children.Count > 0) + { + foreach (lsblkDevice child in device.children) + { + if(deviceContainsRoot(child)) + { + return true; + } + } + } + + return false; + } + + private class lsblkOutput + { + public List blockdevices { get; set; } = new List(); + } + + private class lsblkDevice + { + public string name { get; set; } = string.Empty; + public string fstype { get; set; } = string.Empty; + public string label { get; set; } = string.Empty; + public string uuid { get; set; } = string.Empty; + public string mountpoint { get; set; } = string.Empty; + public List children { get; set; } = new List(); + } + } + + /// + /// Extension method for running Bash scripts courtesy https://loune.net/2017/06/running-shell-bash-commands-in-net-core/ + /// + public static class ShellHelper + { + /// + /// Execute a string as a Bash command + /// + /// The Bash command to run + /// The result of the Bash command + public static string Bash(this string cmd) + { + var escapedArgs = cmd.Replace("\"", "\\\""); + + var process = new Process() + { + StartInfo = new ProcessStartInfo + { + FileName = "/bin/bash", + Arguments = $"-c \"{escapedArgs}\"", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + } + }; + process.Start(); + string result = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + return result; + } + } +} diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index a869e07..19532f1 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -34,6 +34,10 @@ + + + + false diff --git a/src/DeviceId/DeviceIdBuilderExtensions.cs b/src/DeviceId/DeviceIdBuilderExtensions.cs index d40377f..c877ee8 100644 --- a/src/DeviceId/DeviceIdBuilderExtensions.cs +++ b/src/DeviceId/DeviceIdBuilderExtensions.cs @@ -126,6 +126,9 @@ public static DeviceIdBuilder AddSystemDriveSerialNumber(this DeviceIdBuilder bu if (OS.IsWindows) { return builder.AddComponent(new SystemDriveSerialNumberDeviceIdComponent()); + } else if (OS.IsLinux) + { + return builder.AddComponent(new LinuxRootDriveSerialNumberDeviceIdComponent()); } else { From 7daa831a6095fd081e2f4affbd730e68584e5785 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sun, 26 Apr 2020 14:28:35 +0800 Subject: [PATCH 10/26] Add some tests for the Linux system drive serial number implementation. --- ...xRootDriveSerialNumberDeviceIdComponent.cs | 106 +++++------- src/DeviceId/DeviceIdBuilderExtensions.cs | 3 +- src/DeviceId/Internal/CommandExecutor.cs | 27 +++ .../Internal/CommandExecutorExtensions.cs | 12 ++ src/DeviceId/Internal/ICommandExecutor.cs | 7 + src/DeviceId/InternalsVisibleTo.cs | 1 + ...DriveSerialNumberDeviceIdComponentTests.cs | 157 ++++++++++++++++++ test/DeviceId.Tests/DeviceId.Tests.csproj | 1 + 8 files changed, 250 insertions(+), 64 deletions(-) create mode 100644 src/DeviceId/Internal/CommandExecutor.cs create mode 100644 src/DeviceId/Internal/CommandExecutorExtensions.cs create mode 100644 src/DeviceId/Internal/ICommandExecutor.cs create mode 100644 test/DeviceId.Tests/Components/LinuxRootDriveSerialNumberDeviceIdComponentTests.cs diff --git a/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs b/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs index a1a89ea..00ffde2 100644 --- a/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs +++ b/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; - +using System.Collections.Generic; +using DeviceId.Internal; using Newtonsoft.Json; namespace DeviceId.Components @@ -18,10 +14,25 @@ public class LinuxRootDriveSerialNumberDeviceIdComponent : IDeviceIdComponent /// public string Name { get; } = "SystemDriveSerialNumber"; + /// + /// Command executor. + /// + private readonly ICommandExecutor _commandExecutor; + /// /// Initializes a new instance of the class. /// - public LinuxRootDriveSerialNumberDeviceIdComponent() { } + public LinuxRootDriveSerialNumberDeviceIdComponent() + : this(CommandExecutor.Default) { } + + /// + /// Initializes a new instance of the class. + /// + /// Command executor. + internal LinuxRootDriveSerialNumberDeviceIdComponent(ICommandExecutor commandExecutor) + { + _commandExecutor = commandExecutor; + } /// /// Gets the component value. @@ -29,30 +40,33 @@ public LinuxRootDriveSerialNumberDeviceIdComponent() { } /// The component value. public string GetValue() { - string json = "lsblk -f -J".Bash(); - lsblkOutput output = JsonConvert.DeserializeObject(json); + var outputJson = _commandExecutor.Bash("lsblk -f -J"); + var output = JsonConvert.DeserializeObject(outputJson); - lsblkDevice device = findRootParent(output); + var device = FindRootParent(output); if (device == null) { return null; } - string udevInfo = string.Format("udevadm info --query=all --name=/dev/{0} | grep ID_SERIAL=", device.name).Bash(); - string[] components = udevInfo.Split('='); - if (components.Length == 2) + var udevInfo = _commandExecutor.Bash($"udevadm info --query=all --name=/dev/{device.Name} | grep ID_SERIAL="); + if (udevInfo != null) { - return components[1]; + var components = udevInfo.Split('='); + if (components.Length == 2) + { + return components[1]; + } } return null; } - private lsblkDevice findRootParent(lsblkOutput devices) + private LsblkDevice FindRootParent(LsblkOutput devices) { - foreach (lsblkDevice device in devices.blockdevices) + foreach (var device in devices.BlockDevices) { - if (deviceContainsRoot(device)) + if (DeviceContainsRoot(device)) { return device; } @@ -61,16 +75,17 @@ private lsblkDevice findRootParent(lsblkOutput devices) return null; } - private bool deviceContainsRoot(lsblkDevice device) + private bool DeviceContainsRoot(LsblkDevice device) { - if (device.mountpoint == "/") + if (device.MountPoint == "/") { return true; - } else if (device.children != null && device.children.Count > 0) + } + else if (device.Children != null && device.Children.Count > 0) { - foreach (lsblkDevice child in device.children) + foreach (var child in device.Children) { - if(deviceContainsRoot(child)) + if (DeviceContainsRoot(child)) { return true; } @@ -80,51 +95,16 @@ private bool deviceContainsRoot(lsblkDevice device) return false; } - private class lsblkOutput + private sealed class LsblkOutput { - public List blockdevices { get; set; } = new List(); + public List BlockDevices { get; set; } = new List(); } - private class lsblkDevice + private sealed class LsblkDevice { - public string name { get; set; } = string.Empty; - public string fstype { get; set; } = string.Empty; - public string label { get; set; } = string.Empty; - public string uuid { get; set; } = string.Empty; - public string mountpoint { get; set; } = string.Empty; - public List children { get; set; } = new List(); - } - } - - /// - /// Extension method for running Bash scripts courtesy https://loune.net/2017/06/running-shell-bash-commands-in-net-core/ - /// - public static class ShellHelper - { - /// - /// Execute a string as a Bash command - /// - /// The Bash command to run - /// The result of the Bash command - public static string Bash(this string cmd) - { - var escapedArgs = cmd.Replace("\"", "\\\""); - - var process = new Process() - { - StartInfo = new ProcessStartInfo - { - FileName = "/bin/bash", - Arguments = $"-c \"{escapedArgs}\"", - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true, - } - }; - process.Start(); - string result = process.StandardOutput.ReadToEnd(); - process.WaitForExit(); - return result; + public string Name { get; set; } = string.Empty; + public string MountPoint { get; set; } = string.Empty; + public List Children { get; set; } = new List(); } } } diff --git a/src/DeviceId/DeviceIdBuilderExtensions.cs b/src/DeviceId/DeviceIdBuilderExtensions.cs index c877ee8..4d0030f 100644 --- a/src/DeviceId/DeviceIdBuilderExtensions.cs +++ b/src/DeviceId/DeviceIdBuilderExtensions.cs @@ -126,7 +126,8 @@ public static DeviceIdBuilder AddSystemDriveSerialNumber(this DeviceIdBuilder bu if (OS.IsWindows) { return builder.AddComponent(new SystemDriveSerialNumberDeviceIdComponent()); - } else if (OS.IsLinux) + } + else if (OS.IsLinux) { return builder.AddComponent(new LinuxRootDriveSerialNumberDeviceIdComponent()); } diff --git a/src/DeviceId/Internal/CommandExecutor.cs b/src/DeviceId/Internal/CommandExecutor.cs new file mode 100644 index 0000000..c42cfef --- /dev/null +++ b/src/DeviceId/Internal/CommandExecutor.cs @@ -0,0 +1,27 @@ +using System.Diagnostics; + +namespace DeviceId.Internal +{ + internal class CommandExecutor : ICommandExecutor + { + public static CommandExecutor Default { get; } = new CommandExecutor(); + + public string Execute(string command, string arguments) + { + var psi = new ProcessStartInfo(); + psi.FileName = command; + psi.Arguments = arguments; + psi.RedirectStandardOutput = true; + psi.UseShellExecute = false; + psi.CreateNoWindow = true; + + using var process = Process.Start(psi); + + var output = process.StandardOutput.ReadToEnd(); + + process.WaitForExit(); + + return output; + } + } +} diff --git a/src/DeviceId/Internal/CommandExecutorExtensions.cs b/src/DeviceId/Internal/CommandExecutorExtensions.cs new file mode 100644 index 0000000..0f61d31 --- /dev/null +++ b/src/DeviceId/Internal/CommandExecutorExtensions.cs @@ -0,0 +1,12 @@ +namespace DeviceId.Internal +{ + internal static class CommandExecutorExtensions + { + public static string Bash(this ICommandExecutor commandExecutor, string arguments) + { + var escapedArguments = arguments.Replace("\"", "\\\""); + + return commandExecutor.Execute("/bin/bash", $"-c \"{escapedArguments}\""); + } + } +} diff --git a/src/DeviceId/Internal/ICommandExecutor.cs b/src/DeviceId/Internal/ICommandExecutor.cs new file mode 100644 index 0000000..167f369 --- /dev/null +++ b/src/DeviceId/Internal/ICommandExecutor.cs @@ -0,0 +1,7 @@ +namespace DeviceId.Internal +{ + internal interface ICommandExecutor + { + string Execute(string command, string arguments); + } +} diff --git a/src/DeviceId/InternalsVisibleTo.cs b/src/DeviceId/InternalsVisibleTo.cs index faea2da..abce50f 100644 --- a/src/DeviceId/InternalsVisibleTo.cs +++ b/src/DeviceId/InternalsVisibleTo.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("DeviceId.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001008906d2e5a92d72693cfdd24b29f9c3ea5ca51972be746724afef8a65000a1ebbc88aee54e4c9c3bef49c0e837702170e99919a8b8075cfd6ed8494c5f9cd1a640a57cc907a84861bfe7ecb877d475a94ec333c6c0a586b6f37a15e67431381cac046217c0fa570c3e8e140e733254686213b77ae53fccdc1f5b3ab806ac692c1")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/test/DeviceId.Tests/Components/LinuxRootDriveSerialNumberDeviceIdComponentTests.cs b/test/DeviceId.Tests/Components/LinuxRootDriveSerialNumberDeviceIdComponentTests.cs new file mode 100644 index 0000000..f54f170 --- /dev/null +++ b/test/DeviceId.Tests/Components/LinuxRootDriveSerialNumberDeviceIdComponentTests.cs @@ -0,0 +1,157 @@ +using DeviceId.Components; +using DeviceId.Internal; +using FluentAssertions; +using Moq; +using Xunit; + +namespace DeviceId.Tests.Components +{ + public class LinuxRootDriveSerialNumberDeviceIdComponentTests + { + [Fact] + public void GoogleCloudUbuntu1804VM() + { + const string deviceName = "sda"; + + const string lsblkOutput = @" + { + ""blockdevices"": [ + {""name"": ""loop0"", ""fstype"": ""squashfs"", ""label"": null, ""uuid"": null, ""mountpoint"": ""/snap/core18/1705""}, + {""name"": ""loop1"", ""fstype"": ""squashfs"", ""label"": null, ""uuid"": null, ""mountpoint"": ""/snap/google-cloud-sdk/127""}, + {""name"": ""loop2"", ""fstype"": ""squashfs"", ""label"": null, ""uuid"": null, ""mountpoint"": ""/snap/core/8935""}, + {""name"": ""loop3"", ""fstype"": ""squashfs"", ""label"": null, ""uuid"": null, ""mountpoint"": ""/snap/core/9066""}, + {""name"": ""loop4"", ""fstype"": ""squashfs"", ""label"": null, ""uuid"": null, ""mountpoint"": ""/snap/google-cloud-sdk/128""}, + {""name"": ""sda"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null, + ""children"": [ + {""name"": ""sda1"", ""fstype"": ""ext4"", ""label"": ""cloudimg-rootfs"", ""uuid"": ""e0c095ca-21b2-4a02-bbea-0dc95c07cfc8"", ""mountpoint"": ""/""}, + {""name"": ""sda14"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null}, + {""name"": ""sda15"", ""fstype"": ""vfat"", ""label"": ""UEFI"", ""uuid"": ""84B5-FED0"", ""mountpoint"": ""/boot/efi""} + ] + } + ] + }"; + + const string udevadmOutput = "E: ID_SERIAL=0Google_PersistentDisk_webserver"; + + var componentValue = GetComponentValue(deviceName, lsblkOutput, udevadmOutput); + + componentValue.Should().Be("0Google_PersistentDisk_webserver"); + } + + [Fact] + public void VirtualBoxVM() + { + const string deviceName = "sda"; + + const string lsblkOutput = @" + { + ""blockdevices"": [ + {""name"": ""loop0"", ""fstype"": ""squashfs"", ""label"": null, ""uuid"": null, ""mountpoint"": ""/snap/gnome-logs/45""}, + {""name"": ""loop1"", ""fstype"": ""squashfs"", ""label"": null, ""uuid"": null, ""mountpoint"": ""/snap/gnome-characters/139""}, + {""name"": ""loop2"", ""fstype"": ""squashfs"", ""label"": null, ""uuid"": null, ""mountpoint"": ""/snap/gnome-calculator/260""}, + {""name"": ""loop3"", ""fstype"": ""squashfs"", ""label"": null, ""uuid"": null, ""mountpoint"": ""/snap/gnome-3-26-1604/74""}, + {""name"": ""loop4"", ""fstype"": ""squashfs"", ""label"": null, ""uuid"": null, ""mountpoint"": ""/snap/core/6350""}, + {""name"": ""loop5"", ""fstype"": ""squashfs"", ""label"": null, ""uuid"": null, ""mountpoint"": ""/snap/gtk-common-themes/818""}, + {""name"": ""loop6"", ""fstype"": ""squashfs"", ""label"": null, ""uuid"": null, ""mountpoint"": ""/snap/gnome-system-monitor/57""}, + {""name"": ""sda"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null, + ""children"": [ + {""name"": ""sda1"", ""fstype"": ""LVM2_member"", ""label"": null, ""uuid"": ""Eqsoey-BADi-y8Sf-JFVM-UPyk-gXQE-uu9XCv"", ""mountpoint"": null, + ""children"": [ + {""name"": ""ubuntu--vg-root"", ""fstype"": ""ext4"", ""label"": null, ""uuid"": ""c72aecd8-a254-4b1c-9a17-4257e89796d2"", ""mountpoint"": ""/""}, + {""name"": ""ubuntu--vg-swap_1"", ""fstype"": ""swap"", ""label"": null, ""uuid"": ""93ff3926-f0df-470d-996b-af57b0991b06"", ""mountpoint"": ""[SWAP]""} + ] + } + ] + }, + {""name"": ""sr0"", ""fstype"": ""iso9660"", ""label"": ""VBox_GAs_6.0.14"", ""uuid"": ""2019-10-10-18-52-14-12"", ""mountpoint"": ""/media/kevin/VBox_GAs_6.0.14""} + ] + }"; + + const string udevadmOutput = "E: ID_SERIAL=VBOX_HARDDISK_VB5e245c00-220de489"; + + var componentValue = GetComponentValue(deviceName, lsblkOutput, udevadmOutput); + + componentValue.Should().Be("VBOX_HARDDISK_VB5e245c00-220de489"); + } + + [Fact] + public void BareMetalServer() + { + const string deviceName = "sda"; + + const string lsblkOutput = @" + { + ""blockdevices"": [ + {""name"": ""sda"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null, + ""children"": [ + {""name"": ""sda1"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null}, + {""name"": ""sda2"", ""fstype"": ""linux_raid_member"", ""label"": ""s590:0"", ""uuid"": ""22aca146-b40c-6b41-265b-383c15d49106"", ""mountpoint"": null, + ""children"": [ + {""name"": ""md0"", ""fstype"": ""xfs"", ""label"": null, ""uuid"": ""3d22ab9f-9ddb-43d5-82c7-a02725c1f4cb"", ""mountpoint"": ""/""} + ] + }, + {""name"": ""sda3"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null}, + {""name"": ""sda4"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null} + ] + }, + {""name"": ""sdb"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null, + ""children"": [ + {""name"": ""sdb1"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null}, + {""name"": ""sdb2"", ""fstype"": ""linux_raid_member"", ""label"": ""s590:0"", ""uuid"": ""22aca146-b40c-6b41-265b-383c15d49106"", ""mountpoint"": null, + ""children"": [ + {""name"": ""md0"", ""fstype"": ""xfs"", ""label"": null, ""uuid"": ""3d22ab9f-9ddb-43d5-82c7-a02725c1f4cb"", ""mountpoint"": ""/""} + ] + }, + {""name"": ""sdb3"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null}, + {""name"": ""sdb4"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null} + ] + } + ] + }"; + + const string udevadmOutput = "E: ID_SERIAL=WDC_WD4000FYYZ-01UL1B1_WD-WCC131942520"; + + var componentValue = GetComponentValue(deviceName, lsblkOutput, udevadmOutput); + + componentValue.Should().Be("WDC_WD4000FYYZ-01UL1B1_WD-WCC131942520"); + } + + [Fact] + public void DigitalOceanVMWithoutSerialID() + { + const string deviceName = "sda"; + + const string lsblkOutput = @" + { + ""blockdevices"": [ + {""name"": ""vda"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null, + ""children"": [ + {""name"": ""vda1"", ""fstype"": ""ext4"", ""label"": ""cloudimg-rootfs"", ""uuid"": ""bd5e9837-a464-48d5-a8f9-323fc9074bf3"", ""mountpoint"": ""/""}, + {""name"": ""vda14"", ""fstype"": null, ""label"": null, ""uuid"": null, ""mountpoint"": null}, + {""name"": ""vda15"", ""fstype"": ""vfat"", ""label"": ""UEFI"", ""uuid"": ""B99C-461D"", ""mountpoint"": ""/boot/efi""} + ] + } + ] + }"; + + const string udevadmOutput = ""; + + var componentValue = GetComponentValue(deviceName, lsblkOutput, udevadmOutput); + + componentValue.Should().BeNull(); + } + + private string GetComponentValue(string rootParentDeviceName, string lsblkOutput, string udevadmOutput) + { + var commandExecutorMock = new Mock(); + commandExecutorMock.Setup(x => x.Execute("/bin/bash", "-c \"lsblk -f -J\"")).Returns(lsblkOutput); + commandExecutorMock.Setup(x => x.Execute("/bin/bash", $"-c \"udevadm info --query=all --name=/dev/{rootParentDeviceName} | grep ID_SERIAL=\"")).Returns(udevadmOutput); + + var component = new LinuxRootDriveSerialNumberDeviceIdComponent(commandExecutorMock.Object); + + var value = component.GetValue(); + + return value; + } + } +} diff --git a/test/DeviceId.Tests/DeviceId.Tests.csproj b/test/DeviceId.Tests/DeviceId.Tests.csproj index 7f001a1..5dbfe45 100644 --- a/test/DeviceId.Tests/DeviceId.Tests.csproj +++ b/test/DeviceId.Tests/DeviceId.Tests.csproj @@ -10,6 +10,7 @@ + From efff9ca12eee0053d708325f86b492ce9af0d95e Mon Sep 17 00:00:00 2001 From: Matthew King Date: Thu, 30 Apr 2020 08:31:54 +0800 Subject: [PATCH 11/26] Tweak JSON deserialization. * Use System.Text.Json instead of Newtonsoft. * Don't include the dependency in the .NET 4 build, as it's not needed. --- ...xRootDriveSerialNumberDeviceIdComponent.cs | 3 +-- src/DeviceId/DeviceId.csproj | 5 +--- src/DeviceId/Internal/Json.cs | 25 +++++++++++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 src/DeviceId/Internal/Json.cs diff --git a/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs b/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs index 00ffde2..c30c4cf 100644 --- a/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs +++ b/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using DeviceId.Internal; -using Newtonsoft.Json; namespace DeviceId.Components { @@ -41,7 +40,7 @@ internal LinuxRootDriveSerialNumberDeviceIdComponent(ICommandExecutor commandExe public string GetValue() { var outputJson = _commandExecutor.Bash("lsblk -f -J"); - var output = JsonConvert.DeserializeObject(outputJson); + var output = Json.Deserialize(outputJson); var device = FindRootParent(output); if (device == null) diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index 19532f1..257b4fd 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -32,10 +32,7 @@ - - - - + diff --git a/src/DeviceId/Internal/Json.cs b/src/DeviceId/Internal/Json.cs new file mode 100644 index 0000000..90dcafd --- /dev/null +++ b/src/DeviceId/Internal/Json.cs @@ -0,0 +1,25 @@ +using System; + +namespace DeviceId.Internal +{ + internal static class Json + { +#if NETSTANDARD + private static System.Text.Json.JsonSerializerOptions JsonSerializerOptions { get; } = new System.Text.Json.JsonSerializerOptions() + { + IgnoreNullValues = true, + PropertyNameCaseInsensitive = true, + }; +#endif + + public static T Deserialize(string value) + { +#if NETSTANDARD + //return Newtonsoft.Json.JsonConvert.DeserializeObject(value); + return System.Text.Json.JsonSerializer.Deserialize(value, JsonSerializerOptions); +#else + throw new NotImplementedException(); +#endif + } + } +} From b36ca646bd3ed200b2d05582bf02813b83c59e1b Mon Sep 17 00:00:00 2001 From: Matthew King Date: Thu, 30 Apr 2020 08:39:46 +0800 Subject: [PATCH 12/26] Increment version number. --- src/DeviceId/DeviceId.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index 257b4fd..ece51b5 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -11,7 +11,7 @@ https://github.com/MatthewKing/DeviceId git deviceid;unique;device;identifier - 5.0.0 + 5.1.0 From ac3bcbda2ac9795c7eac71dfbe105d023f671bee Mon Sep 17 00:00:00 2001 From: Kamil Monicz Date: Wed, 17 Jun 2020 03:53:52 +0200 Subject: [PATCH 13/26] Improve WmiDeviceIdComponent. Previously WmiDeviceIdComponent queried all properties from the class. Now it only queries one property specified by _wmiProperty which makes the whole process near-instant. --- src/DeviceId/Components/WmiDeviceIdComponent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DeviceId/Components/WmiDeviceIdComponent.cs b/src/DeviceId/Components/WmiDeviceIdComponent.cs index f6c4cbc..44cb569 100644 --- a/src/DeviceId/Components/WmiDeviceIdComponent.cs +++ b/src/DeviceId/Components/WmiDeviceIdComponent.cs @@ -44,9 +44,9 @@ public string GetValue() { var values = new List(); - using var mc = new ManagementClass(_wmiClass); + using var mc = new ManagementObjectSearcher($"SELECT {_wmiProperty} FROM {_wmiClass}"); - foreach (var mo in mc.GetInstances()) + foreach (var mo in mc.Get()) { try { From 78213648cb92653774b1ad2e4376a5bc4cf4d452 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Wed, 17 Jun 2020 10:19:35 +0800 Subject: [PATCH 14/26] Increment version number. --- src/DeviceId/DeviceId.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index ece51b5..ddeef0c 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -11,7 +11,7 @@ https://github.com/MatthewKing/DeviceId git deviceid;unique;device;identifier - 5.1.0 + 5.1.1 From f2d090d39a7096cc9f44eef10704c75542df53da Mon Sep 17 00:00:00 2001 From: Matthew King Date: Wed, 24 Jun 2020 12:10:23 +0800 Subject: [PATCH 15/26] Clean up WMI usage and add some extra error handling. And by "error handling" I just mean silently swallowing exceptions. --- .../NetworkAdapterDeviceIdComponent.cs | 60 +++++++++---------- .../Components/WmiDeviceIdComponent.cs | 29 +++++---- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs b/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs index 70bee40..8ca7038 100644 --- a/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs +++ b/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs @@ -19,11 +19,6 @@ public class NetworkAdapterDeviceIdComponent : IDeviceIdComponent /// public string Name { get; } = "MACAddress"; - /// - /// Value to use when a result is not obtainable - /// - private const string NoValue = "NoValue"; - /// /// A value indicating whether non-physical adapters should be excluded. /// @@ -114,32 +109,36 @@ internal List GetMacAddressesUsingWmi() { var values = new List(); - using var mc = new ManagementClass("Win32_NetworkAdapter"); - - foreach (var adapter in mc.GetInstances()) + try { - try + using var managementObjectSearcher = new ManagementObjectSearcher("select MACAddress, PhysicalAdapter from Win32_NetworkAdapter"); + using var managementObjectCollection = managementObjectSearcher.Get(); + foreach (var managementObject in managementObjectCollection) { - var isPhysical = (bool)adapter["PhysicalAdapter"]; - var adapterType = adapter["AdapterType"] as string; - - // Skip non physcial adapters if instructed to do so. - if (_excludeNonPhysical && !isPhysical) + try { - continue; + // Skip non physcial adapters if instructed to do so. + var isPhysical = (bool)managementObject["PhysicalAdapter"]; + if (_excludeNonPhysical && !isPhysical) + { + continue; + } + + var macAddress = (string)managementObject["MACAddress"]; + if (!string.IsNullOrEmpty(macAddress)) + { + values.Add(macAddress); + } } - - // Add the MAC address to the list of values. - var value = adapter["MACAddress"] as string; - if (value != null) + finally { - values.Add(value); + managementObject.Dispose(); } } - finally - { - adapter.Dispose(); - } + } + catch + { + } return values; @@ -153,29 +152,28 @@ internal List GetMacAddressesUsingCimV2() { var values = new List(); - using var mc = new ManagementClass("root/StandardCimv2", "MSFT_NetAdapter", new ObjectGetOptions { }); + using var managementClass = new ManagementClass("root/StandardCimv2", "MSFT_NetAdapter", new ObjectGetOptions { }); - foreach (var adapter in mc.GetInstances()) + foreach (var managementInstance in managementClass.GetInstances()) { try { - var isPhysical = (bool)adapter["ConnectorPresent"]; - var ndisMedium = (uint)adapter["NdisPhysicalMedium"]; - // Skip non physcial adapters if instructed to do so. + var isPhysical = (bool)managementInstance["ConnectorPresent"]; if (_excludeNonPhysical && !isPhysical) { continue; } // Skip wireless adapters if instructed to do so. + var ndisMedium = (uint)managementInstance["NdisPhysicalMedium"]; if (_excludeWireless && ndisMedium == 9) // Native802_11 { continue; } // Add the MAC address to the list of values. - var value = adapter["PermanentAddress"] as string; + var value = managementInstance["PermanentAddress"] as string; if (value != null) { // Ensure the hardware addresses are formatted as MAC addresses if possible. @@ -186,7 +184,7 @@ internal List GetMacAddressesUsingCimV2() } finally { - adapter.Dispose(); + managementInstance.Dispose(); } } diff --git a/src/DeviceId/Components/WmiDeviceIdComponent.cs b/src/DeviceId/Components/WmiDeviceIdComponent.cs index 44cb569..bf049a8 100644 --- a/src/DeviceId/Components/WmiDeviceIdComponent.cs +++ b/src/DeviceId/Components/WmiDeviceIdComponent.cs @@ -44,22 +44,29 @@ public string GetValue() { var values = new List(); - using var mc = new ManagementObjectSearcher($"SELECT {_wmiProperty} FROM {_wmiClass}"); - - foreach (var mo in mc.Get()) + try { - try + using var managementObjectSearcher = new ManagementObjectSearcher($"SELECT {_wmiProperty} FROM {_wmiClass}"); + using var managementObjectCollection = managementObjectSearcher.Get(); + foreach (var managementObject in managementObjectCollection) { - var value = mo[_wmiProperty] as string; - if (value != null) + try { - values.Add(value); + var value = managementObject[_wmiProperty] as string; + if (value != null) + { + values.Add(value); + } + } + finally + { + managementObject.Dispose(); } } - finally - { - mo.Dispose(); - } + } + catch + { + } values.Sort(); From d38a9a446453187781fb15d17830fcf02ca292da Mon Sep 17 00:00:00 2001 From: Tim Tavli Date: Mon, 6 Jul 2020 10:46:31 +0300 Subject: [PATCH 16/26] Add OSX support. The following components are added: * ProcessorId (as Apple Serial Number) * SystemDriveSerialNumber --- readme.md | 28 +++++----- .../Components/BashExecutorComponent.cs | 52 +++++++++++++++++++ src/DeviceId/DeviceIdBuilderExtensions.cs | 15 +++++- src/DeviceId/OS.cs | 7 +++ 4 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 src/DeviceId/Components/BashExecutorComponent.cs diff --git a/readme.md b/readme.md index a912c06..39ad8d5 100644 --- a/readme.md +++ b/readme.md @@ -72,19 +72,21 @@ There are a number of encoders that can be used customize the formatter. These i The following cross-platform support is available: -| Component | Windows | Linux | -| -------------------------- | ------- | ------- | -| User name | **Yes** | **Yes** | -| Machine name | **Yes** | **Yes** | -| OS version | **Yes** | **Yes** | -| Processor ID | **Yes** | **Yes** | -| MAC address | **Yes** | **Yes** | -| Motherboard serial number | **Yes** | **Yes** | -| System drive serial number | **Yes** | **Yes** | -| System UUID | **Yes** | **Yes** | -| OS installation ID | **Yes** | **Yes** | -| Registry value | **Yes** | No | -| File token | **Yes** | **Yes** | +| Component | Windows | Linux | OSX | +| -------------------------- | ------- | ------- | ------- | +| User name | **Yes** | **Yes** | No | +| Machine name | **Yes** | **Yes** | No | +| OS version | **Yes** | **Yes** | No | +| Processor ID | **Yes** | **Yes** | **Yes***| +| MAC address | **Yes** | **Yes** | No | +| Motherboard serial number | **Yes** | **Yes** | No | +| System drive serial number | **Yes** | **Yes** | **Yes** | +| System UUID | **Yes** | **Yes** | No | +| OS installation ID | **Yes** | **Yes** | No | +| Registry value | **Yes** | No | No | +| File token | **Yes** | **Yes** | No | + +\* OSX Processor ID is Apple Serial Number ## Installation diff --git a/src/DeviceId/Components/BashExecutorComponent.cs b/src/DeviceId/Components/BashExecutorComponent.cs new file mode 100644 index 0000000..b22c504 --- /dev/null +++ b/src/DeviceId/Components/BashExecutorComponent.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using DeviceId.Internal; + +namespace DeviceId.Components +{ + /// + /// An implementation of to execute bash commands + /// + public class BashExecutorComponent : IDeviceIdComponent + { + /// + /// Gets the name of the component. + /// + public string Name { get; } = "BashExecutorComponent"; + public string Command { get; } = "echo 'set command'"; + + /// + /// Command executor. + /// + private readonly ICommandExecutor _commandExecutor; + + /// + /// Initializes a new instance of the class. + /// + public BashExecutorComponent(string name, string command) + : this(CommandExecutor.Default) + { + Name = name; + Command = command; + } + + /// + /// Initializes a new instance of the class. + /// + /// Command executor. + internal BashExecutorComponent(ICommandExecutor commandExecutor) + { + _commandExecutor = commandExecutor; + } + + /// + /// Gets the component value by executing given bash command. + /// + /// The component value. + public string GetValue() + { + var result = _commandExecutor.Bash(Command); + /// remove newline chars \n, whitespaces at start/end if exists + return result.Trim('\n').TrimStart().TrimEnd(); + } + } +} diff --git a/src/DeviceId/DeviceIdBuilderExtensions.cs b/src/DeviceId/DeviceIdBuilderExtensions.cs index 4d0030f..d2bca30 100644 --- a/src/DeviceId/DeviceIdBuilderExtensions.cs +++ b/src/DeviceId/DeviceIdBuilderExtensions.cs @@ -33,7 +33,7 @@ public static DeviceIdBuilder AddComponent(this DeviceIdBuilder builder, IDevice } /// - /// Adds the current user name to the device identifier. + /// Adds the current user name to the device identifier. /// /// The to add the component to. /// The instance. @@ -89,6 +89,13 @@ public static DeviceIdBuilder AddProcessorId(this DeviceIdBuilder builder) { return builder.AddComponent(new FileDeviceIdComponent("ProcessorId", "/proc/cpuinfo", true)); } + else if (OS.IsOsx) + { + /// OSX doesn't provide CPU ID but gives Serial Number unique per Apple device. + return builder.AddComponent( + new BashExecutorComponent("ProcessorId", + "ioreg -l | grep IOPlatformSerialNumber | sed 's/.*= //' | sed 's/\"//g'")); + } else { return builder.AddComponent(new UnsupportedDeviceIdComponent("ProcessorId")); @@ -131,6 +138,12 @@ public static DeviceIdBuilder AddSystemDriveSerialNumber(this DeviceIdBuilder bu { return builder.AddComponent(new LinuxRootDriveSerialNumberDeviceIdComponent()); } + else if (OS.IsOsx) + { + return builder.AddComponent( + new BashExecutorComponent("SystemDriveSerialNumber", + "system_profiler SPSerialATADataType | sed -En 's/.*Serial Number: ([\\d\\w]*)//p'")); + } else { return builder.AddComponent(new UnsupportedDeviceIdComponent("SystemDriveSerialNumber")); diff --git a/src/DeviceId/OS.cs b/src/DeviceId/OS.cs index 592139d..ba47c02 100644 --- a/src/DeviceId/OS.cs +++ b/src/DeviceId/OS.cs @@ -31,6 +31,13 @@ internal static class OS = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); #endif + public static bool IsOsx { get; } +#if NETFRAMEWORK + = false; +#elif NETSTANDARD + = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); +#endif + public static string Version { get; } #if NETFRAMEWORK = Environment.OSVersion.ToString(); From 8122ec01ea6138e07f40348b5a4fc1c44dfbb658 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Thu, 9 Jul 2020 10:04:34 +0800 Subject: [PATCH 17/26] Refactor. --- .../CommandExecutors/BashCommandExecutor.cs | 18 +++++++ .../CommandExecutors/CommandExecutor.cs | 13 +++++ .../CommandExecutors/CommandExecutorBase.cs | 41 +++++++++++++++ .../CommandExecutors/ICommandExecutor.cs | 15 ++++++ .../Components/BashExecutorComponent.cs | 52 ------------------- src/DeviceId/Components/CommandComponent.cs | 47 +++++++++++++++++ ...xRootDriveSerialNumberDeviceIdComponent.cs | 9 ++-- src/DeviceId/DeviceIdBuilderExtensions.cs | 17 +++--- src/DeviceId/Internal/CommandExecutor.cs | 27 ---------- .../Internal/CommandExecutorExtensions.cs | 12 ----- src/DeviceId/Internal/ICommandExecutor.cs | 7 --- src/DeviceId/Internal/Json.cs | 7 +-- ...DriveSerialNumberDeviceIdComponentTests.cs | 8 +-- 13 files changed, 155 insertions(+), 118 deletions(-) create mode 100644 src/DeviceId/CommandExecutors/BashCommandExecutor.cs create mode 100644 src/DeviceId/CommandExecutors/CommandExecutor.cs create mode 100644 src/DeviceId/CommandExecutors/CommandExecutorBase.cs create mode 100644 src/DeviceId/CommandExecutors/ICommandExecutor.cs delete mode 100644 src/DeviceId/Components/BashExecutorComponent.cs create mode 100644 src/DeviceId/Components/CommandComponent.cs delete mode 100644 src/DeviceId/Internal/CommandExecutor.cs delete mode 100644 src/DeviceId/Internal/CommandExecutorExtensions.cs delete mode 100644 src/DeviceId/Internal/ICommandExecutor.cs diff --git a/src/DeviceId/CommandExecutors/BashCommandExecutor.cs b/src/DeviceId/CommandExecutors/BashCommandExecutor.cs new file mode 100644 index 0000000..5865d88 --- /dev/null +++ b/src/DeviceId/CommandExecutors/BashCommandExecutor.cs @@ -0,0 +1,18 @@ +namespace DeviceId.CommandExecutors +{ + /// + /// An implementation of that uses /bin/bash to execute commands. + /// + public class BashCommandExecutor : CommandExecutorBase + { + /// + /// Executes the specified command. + /// + /// The command to execute. + /// The command output. + public override string Execute(string command) + { + return RunWithShell("/bin/bash", $"-c \"{command.Replace("\"", "\\\"")}\"").Trim('\r').Trim('\n').TrimEnd().TrimStart(); + } + } +} diff --git a/src/DeviceId/CommandExecutors/CommandExecutor.cs b/src/DeviceId/CommandExecutors/CommandExecutor.cs new file mode 100644 index 0000000..0b3422b --- /dev/null +++ b/src/DeviceId/CommandExecutors/CommandExecutor.cs @@ -0,0 +1,13 @@ +namespace DeviceId.CommandExecutors +{ + /// + /// Enumerate the various command executors that are available. + /// + public static class CommandExecutor + { + /// + /// Gets a command executor that uses /bin/bash to execute commands. + /// + public static ICommandExecutor Bash { get; } = new BashCommandExecutor(); + } +} diff --git a/src/DeviceId/CommandExecutors/CommandExecutorBase.cs b/src/DeviceId/CommandExecutors/CommandExecutorBase.cs new file mode 100644 index 0000000..b5d9589 --- /dev/null +++ b/src/DeviceId/CommandExecutors/CommandExecutorBase.cs @@ -0,0 +1,41 @@ +using System.Diagnostics; + +namespace DeviceId.CommandExecutors +{ + /// + /// A base implementation of . + /// + public abstract class CommandExecutorBase : ICommandExecutor + { + /// + /// Executes the specified command. + /// + /// The command to execute. + /// The command output. + public abstract string Execute(string command); + + /// + /// Runs the specified command with the specified shell. + /// + /// The shell to use. + /// The command to run. + /// The output. + protected string RunWithShell(string shell, string command) + { + var psi = new ProcessStartInfo(); + psi.FileName = shell; + psi.Arguments = command; + psi.RedirectStandardOutput = true; + psi.UseShellExecute = false; + psi.CreateNoWindow = true; + + using var process = Process.Start(psi); + + process.WaitForExit(); + + var output = process.StandardOutput.ReadToEnd(); + + return output; + } + } +} diff --git a/src/DeviceId/CommandExecutors/ICommandExecutor.cs b/src/DeviceId/CommandExecutors/ICommandExecutor.cs new file mode 100644 index 0000000..cb111fa --- /dev/null +++ b/src/DeviceId/CommandExecutors/ICommandExecutor.cs @@ -0,0 +1,15 @@ +namespace DeviceId.CommandExecutors +{ + /// + /// Provides functionality to execute a command. + /// + public interface ICommandExecutor + { + /// + /// Executes the specified command. + /// + /// The command to execute. + /// The command output. + string Execute(string command); + } +} diff --git a/src/DeviceId/Components/BashExecutorComponent.cs b/src/DeviceId/Components/BashExecutorComponent.cs deleted file mode 100644 index b22c504..0000000 --- a/src/DeviceId/Components/BashExecutorComponent.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections.Generic; -using DeviceId.Internal; - -namespace DeviceId.Components -{ - /// - /// An implementation of to execute bash commands - /// - public class BashExecutorComponent : IDeviceIdComponent - { - /// - /// Gets the name of the component. - /// - public string Name { get; } = "BashExecutorComponent"; - public string Command { get; } = "echo 'set command'"; - - /// - /// Command executor. - /// - private readonly ICommandExecutor _commandExecutor; - - /// - /// Initializes a new instance of the class. - /// - public BashExecutorComponent(string name, string command) - : this(CommandExecutor.Default) - { - Name = name; - Command = command; - } - - /// - /// Initializes a new instance of the class. - /// - /// Command executor. - internal BashExecutorComponent(ICommandExecutor commandExecutor) - { - _commandExecutor = commandExecutor; - } - - /// - /// Gets the component value by executing given bash command. - /// - /// The component value. - public string GetValue() - { - var result = _commandExecutor.Bash(Command); - /// remove newline chars \n, whitespaces at start/end if exists - return result.Trim('\n').TrimStart().TrimEnd(); - } - } -} diff --git a/src/DeviceId/Components/CommandComponent.cs b/src/DeviceId/Components/CommandComponent.cs new file mode 100644 index 0000000..3609deb --- /dev/null +++ b/src/DeviceId/Components/CommandComponent.cs @@ -0,0 +1,47 @@ +using DeviceId.CommandExecutors; + +namespace DeviceId.Components +{ + /// + /// An implementation of that executes a command. + /// + public class CommandComponent : IDeviceIdComponent + { + /// + /// Gets the name of the component. + /// + public string Name { get; } + + /// + /// The command executed by the component. + /// + private readonly string _command; + + /// + /// The command executor to use. + /// + private readonly ICommandExecutor _commandExecutor; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the component. + /// The command executed by the component. + /// The command executor. + internal CommandComponent(string name, string command, ICommandExecutor commandExecutor) + { + Name = name; + _command = command; + _commandExecutor = commandExecutor; + } + + /// + /// Gets the component value. + /// + /// The component value. + public string GetValue() + { + return _commandExecutor.Execute(_command); + } + } +} diff --git a/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs b/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs index c30c4cf..f26b689 100644 --- a/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs +++ b/src/DeviceId/Components/LinuxRootDriveSerialNumberDeviceIdComponent.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using DeviceId.CommandExecutors; using DeviceId.Internal; namespace DeviceId.Components @@ -22,12 +23,12 @@ public class LinuxRootDriveSerialNumberDeviceIdComponent : IDeviceIdComponent /// Initializes a new instance of the class. /// public LinuxRootDriveSerialNumberDeviceIdComponent() - : this(CommandExecutor.Default) { } + : this(CommandExecutor.Bash) { } /// /// Initializes a new instance of the class. /// - /// Command executor. + /// The command executor to use. internal LinuxRootDriveSerialNumberDeviceIdComponent(ICommandExecutor commandExecutor) { _commandExecutor = commandExecutor; @@ -39,7 +40,7 @@ internal LinuxRootDriveSerialNumberDeviceIdComponent(ICommandExecutor commandExe /// The component value. public string GetValue() { - var outputJson = _commandExecutor.Bash("lsblk -f -J"); + var outputJson = _commandExecutor.Execute("lsblk -f -J"); var output = Json.Deserialize(outputJson); var device = FindRootParent(output); @@ -48,7 +49,7 @@ public string GetValue() return null; } - var udevInfo = _commandExecutor.Bash($"udevadm info --query=all --name=/dev/{device.Name} | grep ID_SERIAL="); + var udevInfo = _commandExecutor.Execute($"udevadm info --query=all --name=/dev/{device.Name} | grep ID_SERIAL="); if (udevInfo != null) { var components = udevInfo.Split('='); diff --git a/src/DeviceId/DeviceIdBuilderExtensions.cs b/src/DeviceId/DeviceIdBuilderExtensions.cs index d2bca30..58144c9 100644 --- a/src/DeviceId/DeviceIdBuilderExtensions.cs +++ b/src/DeviceId/DeviceIdBuilderExtensions.cs @@ -1,4 +1,5 @@ using System; +using DeviceId.CommandExecutors; using DeviceId.Components; namespace DeviceId @@ -91,10 +92,11 @@ public static DeviceIdBuilder AddProcessorId(this DeviceIdBuilder builder) } else if (OS.IsOsx) { - /// OSX doesn't provide CPU ID but gives Serial Number unique per Apple device. - return builder.AddComponent( - new BashExecutorComponent("ProcessorId", - "ioreg -l | grep IOPlatformSerialNumber | sed 's/.*= //' | sed 's/\"//g'")); + // OSX doesn't provide CPU ID but gives Serial Number unique per Apple device. + return builder.AddComponent(new CommandComponent( + name: "ProcessorId", + command: "ioreg -l | grep IOPlatformSerialNumber | sed 's/.*= //' | sed 's/\"//g'", + commandExecutor: CommandExecutor.Bash)); } else { @@ -140,9 +142,10 @@ public static DeviceIdBuilder AddSystemDriveSerialNumber(this DeviceIdBuilder bu } else if (OS.IsOsx) { - return builder.AddComponent( - new BashExecutorComponent("SystemDriveSerialNumber", - "system_profiler SPSerialATADataType | sed -En 's/.*Serial Number: ([\\d\\w]*)//p'")); + return builder.AddComponent(new CommandComponent( + name: "SystemDriveSerialNumber", + command: "system_profiler SPSerialATADataType | sed -En 's/.*Serial Number: ([\\d\\w]*)//p'", + commandExecutor: CommandExecutor.Bash)); } else { diff --git a/src/DeviceId/Internal/CommandExecutor.cs b/src/DeviceId/Internal/CommandExecutor.cs deleted file mode 100644 index c42cfef..0000000 --- a/src/DeviceId/Internal/CommandExecutor.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Diagnostics; - -namespace DeviceId.Internal -{ - internal class CommandExecutor : ICommandExecutor - { - public static CommandExecutor Default { get; } = new CommandExecutor(); - - public string Execute(string command, string arguments) - { - var psi = new ProcessStartInfo(); - psi.FileName = command; - psi.Arguments = arguments; - psi.RedirectStandardOutput = true; - psi.UseShellExecute = false; - psi.CreateNoWindow = true; - - using var process = Process.Start(psi); - - var output = process.StandardOutput.ReadToEnd(); - - process.WaitForExit(); - - return output; - } - } -} diff --git a/src/DeviceId/Internal/CommandExecutorExtensions.cs b/src/DeviceId/Internal/CommandExecutorExtensions.cs deleted file mode 100644 index 0f61d31..0000000 --- a/src/DeviceId/Internal/CommandExecutorExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace DeviceId.Internal -{ - internal static class CommandExecutorExtensions - { - public static string Bash(this ICommandExecutor commandExecutor, string arguments) - { - var escapedArguments = arguments.Replace("\"", "\\\""); - - return commandExecutor.Execute("/bin/bash", $"-c \"{escapedArguments}\""); - } - } -} diff --git a/src/DeviceId/Internal/ICommandExecutor.cs b/src/DeviceId/Internal/ICommandExecutor.cs deleted file mode 100644 index 167f369..0000000 --- a/src/DeviceId/Internal/ICommandExecutor.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace DeviceId.Internal -{ - internal interface ICommandExecutor - { - string Execute(string command, string arguments); - } -} diff --git a/src/DeviceId/Internal/Json.cs b/src/DeviceId/Internal/Json.cs index 90dcafd..f788da1 100644 --- a/src/DeviceId/Internal/Json.cs +++ b/src/DeviceId/Internal/Json.cs @@ -1,6 +1,4 @@ -using System; - -namespace DeviceId.Internal +namespace DeviceId.Internal { internal static class Json { @@ -15,10 +13,9 @@ internal static class Json public static T Deserialize(string value) { #if NETSTANDARD - //return Newtonsoft.Json.JsonConvert.DeserializeObject(value); return System.Text.Json.JsonSerializer.Deserialize(value, JsonSerializerOptions); #else - throw new NotImplementedException(); + throw new System.NotImplementedException(); #endif } } diff --git a/test/DeviceId.Tests/Components/LinuxRootDriveSerialNumberDeviceIdComponentTests.cs b/test/DeviceId.Tests/Components/LinuxRootDriveSerialNumberDeviceIdComponentTests.cs index f54f170..f0c877b 100644 --- a/test/DeviceId.Tests/Components/LinuxRootDriveSerialNumberDeviceIdComponentTests.cs +++ b/test/DeviceId.Tests/Components/LinuxRootDriveSerialNumberDeviceIdComponentTests.cs @@ -1,5 +1,5 @@ -using DeviceId.Components; -using DeviceId.Internal; +using DeviceId.CommandExecutors; +using DeviceId.Components; using FluentAssertions; using Moq; using Xunit; @@ -144,8 +144,8 @@ public void DigitalOceanVMWithoutSerialID() private string GetComponentValue(string rootParentDeviceName, string lsblkOutput, string udevadmOutput) { var commandExecutorMock = new Mock(); - commandExecutorMock.Setup(x => x.Execute("/bin/bash", "-c \"lsblk -f -J\"")).Returns(lsblkOutput); - commandExecutorMock.Setup(x => x.Execute("/bin/bash", $"-c \"udevadm info --query=all --name=/dev/{rootParentDeviceName} | grep ID_SERIAL=\"")).Returns(udevadmOutput); + commandExecutorMock.Setup(x => x.Execute("lsblk -f -J")).Returns(lsblkOutput); + commandExecutorMock.Setup(x => x.Execute($"udevadm info --query=all --name=/dev/{rootParentDeviceName} | grep ID_SERIAL=")).Returns(udevadmOutput); var component = new LinuxRootDriveSerialNumberDeviceIdComponent(commandExecutorMock.Object); From bc5fc111eb9db2ea7393986f7806e75191dffcf2 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Thu, 9 Jul 2020 10:07:44 +0800 Subject: [PATCH 18/26] Update cross-platform support matrix for OSX. --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 39ad8d5..35be877 100644 --- a/readme.md +++ b/readme.md @@ -74,17 +74,17 @@ The following cross-platform support is available: | Component | Windows | Linux | OSX | | -------------------------- | ------- | ------- | ------- | -| User name | **Yes** | **Yes** | No | -| Machine name | **Yes** | **Yes** | No | -| OS version | **Yes** | **Yes** | No | +| User name | **Yes** | **Yes** | **Yes** | +| Machine name | **Yes** | **Yes** | **Yes** | +| OS version | **Yes** | **Yes** | **Yes** | | Processor ID | **Yes** | **Yes** | **Yes***| -| MAC address | **Yes** | **Yes** | No | +| MAC address | **Yes** | **Yes** | **Yes** | | Motherboard serial number | **Yes** | **Yes** | No | | System drive serial number | **Yes** | **Yes** | **Yes** | | System UUID | **Yes** | **Yes** | No | | OS installation ID | **Yes** | **Yes** | No | | Registry value | **Yes** | No | No | -| File token | **Yes** | **Yes** | No | +| File token | **Yes** | **Yes** | **Yes** | \* OSX Processor ID is Apple Serial Number From 7e0be70f1a8ec2a652eaaa20f89103087e5c247b Mon Sep 17 00:00:00 2001 From: Ben Chester Date: Fri, 2 Oct 2020 15:51:23 +1000 Subject: [PATCH 19/26] Add missing brackets in readme. --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 35be877..806058d 100644 --- a/readme.md +++ b/readme.md @@ -29,7 +29,7 @@ The following extension methods are available out of the box to suit some common * `AddMotherboardSerialNumber()` adds the motherboard serial number to the device ID. * `AddSystemDriveSerialNumber()` adds the system drive's serial number to the device ID. * `AddSystemUUID()` adds the system UUID to the device ID. -* `AddOSInstallationID` adds the OS installation ID. +* `AddOSInstallationID()` adds the OS installation ID. * `AddFileToken(path)` adds a token stored at the specified path to the device ID. * `AddRegistryValue()` adds a value from the registry. * `AddComponent(component)` adds a custom component (see below) to the device ID. From 17a9376f7dd15645952d453fcc7b1162215e0934 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sun, 25 Oct 2020 09:05:48 +0800 Subject: [PATCH 20/26] Add support for .NET 3.5. --- .../Components/NetworkAdapterDeviceIdComponent.cs | 4 ++-- src/DeviceId/Components/WmiDeviceIdComponent.cs | 2 +- src/DeviceId/DeviceId.csproj | 8 ++++++-- src/DeviceId/DeviceIdBuilder.cs | 4 ++++ src/DeviceId/Formatters/HashDeviceIdFormatter.cs | 2 +- src/DeviceId/Formatters/StringDeviceIdFormatter.cs | 2 +- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs b/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs index 8ca7038..4616fdb 100644 --- a/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs +++ b/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs @@ -97,7 +97,7 @@ public string GetValue() } return (values != null && values.Count > 0) - ? string.Join(",", values) + ? string.Join(",", values.ToArray()) : null; } @@ -209,7 +209,7 @@ internal static string FormatMacAddress(string input) var parts = Enumerable.Range(0, input.Length / partSize).Select(x => input.Substring(x * partSize, partSize)); // Put the parts in the AA:BB:CC format. - var result = string.Join(":", parts); + var result = string.Join(":", parts.ToArray()); return result; } diff --git a/src/DeviceId/Components/WmiDeviceIdComponent.cs b/src/DeviceId/Components/WmiDeviceIdComponent.cs index bf049a8..f07b73a 100644 --- a/src/DeviceId/Components/WmiDeviceIdComponent.cs +++ b/src/DeviceId/Components/WmiDeviceIdComponent.cs @@ -72,7 +72,7 @@ public string GetValue() values.Sort(); return (values != null && values.Count > 0) - ? string.Join(",", values) + ? string.Join(",", values.ToArray()) : null; } } diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index ddeef0c..788a407 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -15,7 +15,7 @@ - net40;netstandard2.0 + net35;net40;netstandard2.0 latest true true @@ -23,7 +23,11 @@ DeviceId.snk - + + $(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client + + + diff --git a/src/DeviceId/DeviceIdBuilder.cs b/src/DeviceId/DeviceIdBuilder.cs index 7dea80c..113ee7b 100644 --- a/src/DeviceId/DeviceIdBuilder.cs +++ b/src/DeviceId/DeviceIdBuilder.cs @@ -20,7 +20,11 @@ public class DeviceIdBuilder /// /// A set containing the components that will make up the device identifier. /// +#if NET35 + public HashSet Components { get; } +#else public ISet Components { get; } +#endif /// /// Initializes a new instance of the class. diff --git a/src/DeviceId/Formatters/HashDeviceIdFormatter.cs b/src/DeviceId/Formatters/HashDeviceIdFormatter.cs index 71520bb..9e60cdd 100644 --- a/src/DeviceId/Formatters/HashDeviceIdFormatter.cs +++ b/src/DeviceId/Formatters/HashDeviceIdFormatter.cs @@ -44,7 +44,7 @@ public string GetDeviceId(IEnumerable components) throw new ArgumentNullException(nameof(components)); } - var value = string.Join(",", components.OrderBy(x => x.Name).Select(x => x.GetValue())); + var value = string.Join(",", components.OrderBy(x => x.Name).Select(x => x.GetValue()).ToArray()); var bytes = Encoding.UTF8.GetBytes(value); using var algorithm = _hashAlgorithm.Invoke(); var hash = algorithm.ComputeHash(bytes); diff --git a/src/DeviceId/Formatters/StringDeviceIdFormatter.cs b/src/DeviceId/Formatters/StringDeviceIdFormatter.cs index e60ed90..fcb7bc2 100644 --- a/src/DeviceId/Formatters/StringDeviceIdFormatter.cs +++ b/src/DeviceId/Formatters/StringDeviceIdFormatter.cs @@ -35,7 +35,7 @@ public string GetDeviceId(IEnumerable components) throw new ArgumentNullException(nameof(components)); } - return string.Join(".", components.OrderBy(x => x.Name).Select(x => _encoder.Encode(x))); + return string.Join(".", components.OrderBy(x => x.Name).Select(x => _encoder.Encode(x)).ToArray()); } } } From c80b252139b323f4925e5b8e92b1c2f7d7140f68 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sun, 25 Oct 2020 09:39:23 +0800 Subject: [PATCH 21/26] Move the IOPlatformSerialNumber logic to the OSInstallationID component. It didn't really fit under ProcessorId. It doesn't really fit in OSInstallationID either but it's a slightly better option. --- src/DeviceId/DeviceIdBuilderExtensions.cs | 17 ++++++++--------- src/DeviceId/OS.cs | 8 +++++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/DeviceId/DeviceIdBuilderExtensions.cs b/src/DeviceId/DeviceIdBuilderExtensions.cs index 58144c9..4ca2c98 100644 --- a/src/DeviceId/DeviceIdBuilderExtensions.cs +++ b/src/DeviceId/DeviceIdBuilderExtensions.cs @@ -90,14 +90,6 @@ public static DeviceIdBuilder AddProcessorId(this DeviceIdBuilder builder) { return builder.AddComponent(new FileDeviceIdComponent("ProcessorId", "/proc/cpuinfo", true)); } - else if (OS.IsOsx) - { - // OSX doesn't provide CPU ID but gives Serial Number unique per Apple device. - return builder.AddComponent(new CommandComponent( - name: "ProcessorId", - command: "ioreg -l | grep IOPlatformSerialNumber | sed 's/.*= //' | sed 's/\"//g'", - commandExecutor: CommandExecutor.Bash)); - } else { return builder.AddComponent(new UnsupportedDeviceIdComponent("ProcessorId")); @@ -140,7 +132,7 @@ public static DeviceIdBuilder AddSystemDriveSerialNumber(this DeviceIdBuilder bu { return builder.AddComponent(new LinuxRootDriveSerialNumberDeviceIdComponent()); } - else if (OS.IsOsx) + else if (OS.IsOSX) { return builder.AddComponent(new CommandComponent( name: "SystemDriveSerialNumber", @@ -189,6 +181,13 @@ public static DeviceIdBuilder AddOSInstallationID(this DeviceIdBuilder builder) { return builder.AddComponent(new FileDeviceIdComponent("OSInstallationID", new string[] { "/var/lib/dbus/machine-id", "/etc/machine-id" })); } + else if (OS.IsOSX) + { + return builder.AddComponent(new CommandComponent( + name: "OSInstallationID", + command: "ioreg -l | grep IOPlatformSerialNumber | sed 's/.*= //' | sed 's/\"//g'", + commandExecutor: CommandExecutor.Bash)); + } else { return builder.AddComponent(new UnsupportedDeviceIdComponent("OSInstallationID")); diff --git a/src/DeviceId/OS.cs b/src/DeviceId/OS.cs index ba47c02..7b2dd59 100644 --- a/src/DeviceId/OS.cs +++ b/src/DeviceId/OS.cs @@ -31,13 +31,19 @@ internal static class OS = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); #endif - public static bool IsOsx { get; } + /// + /// Gets a value indicating whether this is OS X. + /// + public static bool IsOSX { get; } #if NETFRAMEWORK = false; #elif NETSTANDARD = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); #endif + /// + /// Gets the current OS version. + /// public static string Version { get; } #if NETFRAMEWORK = Environment.OSVersion.ToString(); From d66152b79ae3ae40bb386fc9c952dffef9131f22 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sun, 25 Oct 2020 09:40:21 +0800 Subject: [PATCH 22/26] Update readme. --- readme.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 806058d..2c8441f 100644 --- a/readme.md +++ b/readme.md @@ -77,17 +77,15 @@ The following cross-platform support is available: | User name | **Yes** | **Yes** | **Yes** | | Machine name | **Yes** | **Yes** | **Yes** | | OS version | **Yes** | **Yes** | **Yes** | -| Processor ID | **Yes** | **Yes** | **Yes***| +| Processor ID | **Yes** | **Yes** | No | | MAC address | **Yes** | **Yes** | **Yes** | | Motherboard serial number | **Yes** | **Yes** | No | | System drive serial number | **Yes** | **Yes** | **Yes** | | System UUID | **Yes** | **Yes** | No | -| OS installation ID | **Yes** | **Yes** | No | +| OS installation ID | **Yes** | **Yes** | **Yes** | | Registry value | **Yes** | No | No | | File token | **Yes** | **Yes** | **Yes** | -\* OSX Processor ID is Apple Serial Number - ## Installation Just grab it from [NuGet](https://www.nuget.org/packages/DeviceId/) From 225ee5147e2c0ac7a829a5ac41d00f85c54fbdc1 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Sun, 25 Oct 2020 09:40:48 +0800 Subject: [PATCH 23/26] Increment version number. --- src/DeviceId/DeviceId.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index 788a407..fc36b6a 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -11,7 +11,7 @@ https://github.com/MatthewKing/DeviceId git deviceid;unique;device;identifier - 5.1.1 + 5.2.0 From dd027689b0a2a79b605048ee6416e6d81c5a1884 Mon Sep 17 00:00:00 2001 From: Matthew King Date: Wed, 4 Nov 2020 14:59:02 +0800 Subject: [PATCH 24/26] Allow the delimiter to be specified. --- .../Formatters/StringDeviceIdFormatter.cs | 16 ++++++++++- .../StringDeviceIdFormatterTests.cs | 28 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/DeviceId/Formatters/StringDeviceIdFormatter.cs b/src/DeviceId/Formatters/StringDeviceIdFormatter.cs index fcb7bc2..4de42c8 100644 --- a/src/DeviceId/Formatters/StringDeviceIdFormatter.cs +++ b/src/DeviceId/Formatters/StringDeviceIdFormatter.cs @@ -14,13 +14,27 @@ public class StringDeviceIdFormatter : IDeviceIdFormatter /// private readonly IDeviceIdComponentEncoder _encoder; + /// + /// The delimiter to use when concatenating the encoded component values. + /// + private readonly string _delimiter; + /// /// Initializes a new instance of the class. /// /// The instance to use to encode individual components. public StringDeviceIdFormatter(IDeviceIdComponentEncoder encoder) + : this(encoder, ".") { } + + /// + /// Initializes a new instance of the class. + /// + /// The instance to use to encode individual components. + /// The delimiter to use when concatenating the encoded component values. + public StringDeviceIdFormatter(IDeviceIdComponentEncoder encoder, string delimiter) { _encoder = encoder ?? throw new ArgumentNullException(nameof(encoder)); + _delimiter = delimiter; } /// @@ -35,7 +49,7 @@ public string GetDeviceId(IEnumerable components) throw new ArgumentNullException(nameof(components)); } - return string.Join(".", components.OrderBy(x => x.Name).Select(x => _encoder.Encode(x)).ToArray()); + return string.Join(_delimiter, components.OrderBy(x => x.Name).Select(x => _encoder.Encode(x)).ToArray()); } } } diff --git a/test/DeviceId.Tests/Formatters/StringDeviceIdFormatterTests.cs b/test/DeviceId.Tests/Formatters/StringDeviceIdFormatterTests.cs index 4914d31..596cf48 100644 --- a/test/DeviceId.Tests/Formatters/StringDeviceIdFormatterTests.cs +++ b/test/DeviceId.Tests/Formatters/StringDeviceIdFormatterTests.cs @@ -64,5 +64,33 @@ public void GetDeviceId_ComponentReturnsNull_ReturnsDeviceId() deviceId.Should().Be("d41d8cd98f00b204e9800998ecf8427e"); } + + [Fact] + public void NoDelimiter() + { + var formatter = new StringDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder()), null); + + var deviceId = formatter.GetDeviceId(new IDeviceIdComponent[] + { + new DeviceIdComponent("Test1", "Test1"), + new DeviceIdComponent("Test2", "Test2"), + }); + + deviceId.Should().Be("e1b849f9631ffc1829b2e31402373e3cc454552d52d55d3ef56408742887362b"); + } + + [Fact] + public void CustomDelimiter() + { + var formatter = new StringDeviceIdFormatter(new HashDeviceIdComponentEncoder(() => MD5.Create(), new HexByteArrayEncoder()), "+"); + + var deviceId = formatter.GetDeviceId(new IDeviceIdComponent[] + { + new DeviceIdComponent("Test1", "Test1"), + new DeviceIdComponent("Test2", "Test2"), + }); + + deviceId.Should().Be("e1b849f9631ffc1829b2e31402373e3c+c454552d52d55d3ef56408742887362b"); + } } } From abc48916c1fe21591498cc7c3c8d64cbcf3014bd Mon Sep 17 00:00:00 2001 From: Shahar Zini Date: Sun, 7 Feb 2021 23:20:50 +1100 Subject: [PATCH 25/26] Add support for MMI (System.Management.Infrastructure) in dotnet core instead of deprecated System.Management Add DeviceIdComponentFailedToObtainValueException to indicate when a component can't be obtained (as opposed to having an empty or null value) --- .../Components/FileDeviceIdComponent.cs | 5 + .../Components/FileTokenDeviceIdComponent.cs | 5 +- .../NetworkAdapterDeviceIdComponent.cs | 77 ++++++------ .../RegistryValueDeviceIdComponent.cs | 7 +- ...ystemDriveSerialNumberDeviceIdComponent.cs | 27 ++--- .../Components/WmiDeviceIdComponent.cs | 26 +--- src/DeviceId/DeviceId.csproj | 4 +- ...IdComponentFailedToObtainValueException.cs | 46 ++++++++ .../Formatters/HashDeviceIdFormatter.cs | 14 ++- .../Formatters/StringDeviceIdFormatter.cs | 14 ++- .../Formatters/XmlDeviceIdFormatter.cs | 14 ++- src/DeviceId/WmiHelper.cs | 111 ++++++++++++++++++ 12 files changed, 262 insertions(+), 88 deletions(-) create mode 100644 src/DeviceId/DeviceIdComponentFailedToObtainValueException.cs create mode 100644 src/DeviceId/WmiHelper.cs diff --git a/src/DeviceId/Components/FileDeviceIdComponent.cs b/src/DeviceId/Components/FileDeviceIdComponent.cs index 8d1e3da..aab700c 100644 --- a/src/DeviceId/Components/FileDeviceIdComponent.cs +++ b/src/DeviceId/Components/FileDeviceIdComponent.cs @@ -85,6 +85,11 @@ public string GetValue() catch (UnauthorizedAccessException) { // Can fail if we have no permissions to access the file. + throw new DeviceIdComponentFailedToObtainValueException("Can't access file"); + } + catch(Exception e) + { + throw new DeviceIdComponentFailedToObtainValueException("Failed reading file", e); } } diff --git a/src/DeviceId/Components/FileTokenDeviceIdComponent.cs b/src/DeviceId/Components/FileTokenDeviceIdComponent.cs index d6fe671..e16fdc6 100644 --- a/src/DeviceId/Components/FileTokenDeviceIdComponent.cs +++ b/src/DeviceId/Components/FileTokenDeviceIdComponent.cs @@ -49,7 +49,10 @@ public string GetValue() var value = Encoding.ASCII.GetString(bytes); return value; } - catch { } + catch(Exception e) + { + throw new DeviceIdComponentFailedToObtainValueException("Failed to read file contents", e); + } } else { diff --git a/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs b/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs index 4616fdb..75ab26c 100644 --- a/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs +++ b/src/DeviceId/Components/NetworkAdapterDeviceIdComponent.cs @@ -1,6 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; -using System.Management; using System.Net.NetworkInformation; namespace DeviceId.Components @@ -61,15 +61,18 @@ public string GetValue() // First attempt to retrieve the addresses using the CIMv2 interface. values = GetMacAddressesUsingCimV2(); } - catch (ManagementException ex) + catch { - // In case we are notified of an invalid namespace, attempt to lookup the adapters using WMI. + // In case we are notified of an exception (usually, invalid namespace), attempt to lookup the adapters using WMI. // Could avoid this catch by manually checking for the CIMv2 namespace. - - if (ex.ErrorCode == ManagementStatus.InvalidNamespace) + try { values = GetMacAddressesUsingWmi(); } + catch + { + // Can also fail (for example, some Windows 7 machines) + } } } @@ -81,20 +84,18 @@ public string GetValue() values = NetworkInterface.GetAllNetworkInterfaces() .Where(x => !_excludeWireless || x.NetworkInterfaceType != NetworkInterfaceType.Wireless80211) .Select(x => x.GetPhysicalAddress().ToString()) - .Where(x => x != "000000000000") + .Where(x => x != "" && x != "000000000000") .Select(x => FormatMacAddress(x)) .ToList(); } catch { - + throw new DeviceIdComponentFailedToObtainValueException("Failed collecting network adapters"); } } if (values != null) - { values.Sort(); - } return (values != null && values.Count > 0) ? string.Join(",", values.ToArray()) @@ -109,36 +110,29 @@ internal List GetMacAddressesUsingWmi() { var values = new List(); - try + var adapters = WmiHelper.GetWMIInstances(@"root\cimv2", "Win32_NetworkAdapter", new string[] { "MACAddress", "PhysicalAdapter" }); + + foreach (var adapter in adapters) { - using var managementObjectSearcher = new ManagementObjectSearcher("select MACAddress, PhysicalAdapter from Win32_NetworkAdapter"); - using var managementObjectCollection = managementObjectSearcher.Get(); - foreach (var managementObject in managementObjectCollection) - { - try + try + { + var isPhysical = Boolean.Parse(adapter["PhysicalAdapter"] as string); + + if (_excludeNonPhysical && !isPhysical) { - // Skip non physcial adapters if instructed to do so. - var isPhysical = (bool)managementObject["PhysicalAdapter"]; - if (_excludeNonPhysical && !isPhysical) - { - continue; - } - - var macAddress = (string)managementObject["MACAddress"]; - if (!string.IsNullOrEmpty(macAddress)) - { - values.Add(macAddress); - } + continue; } - finally + + var macAddress = adapter["MACAddress"] as string; + if (!string.IsNullOrEmpty(macAddress)) { - managementObject.Dispose(); + values.Add(macAddress); } } - } - catch - { - + catch + { + + } } return values; @@ -152,28 +146,29 @@ internal List GetMacAddressesUsingCimV2() { var values = new List(); - using var managementClass = new ManagementClass("root/StandardCimv2", "MSFT_NetAdapter", new ObjectGetOptions { }); + var adapters = WmiHelper.GetWMIInstances(@"root\StandardCimv2", "MSFT_NetAdapter", new string[] { "ConnectorPresent", "NdisPhysicalMedium", "PermanentAddress" }); - foreach (var managementInstance in managementClass.GetInstances()) + foreach (var adapter in adapters) { try { + var isPhysical = Boolean.Parse(adapter["ConnectorPresent"] as string); + var ndisMedium = UInt32.Parse(adapter["NdisPhysicalMedium"] as string); + // Skip non physcial adapters if instructed to do so. - var isPhysical = (bool)managementInstance["ConnectorPresent"]; if (_excludeNonPhysical && !isPhysical) { continue; } // Skip wireless adapters if instructed to do so. - var ndisMedium = (uint)managementInstance["NdisPhysicalMedium"]; if (_excludeWireless && ndisMedium == 9) // Native802_11 { continue; } // Add the MAC address to the list of values. - var value = managementInstance["PermanentAddress"] as string; + var value = adapter["PermanentAddress"] as string; if (value != null) { // Ensure the hardware addresses are formatted as MAC addresses if possible. @@ -182,9 +177,9 @@ internal List GetMacAddressesUsingCimV2() values.Add(value); } } - finally + catch { - managementInstance.Dispose(); + } } diff --git a/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs b/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs index bd6ab27..570da70 100644 --- a/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs +++ b/src/DeviceId/Components/RegistryValueDeviceIdComponent.cs @@ -1,4 +1,5 @@ -using Microsoft.Win32; +using System; +using Microsoft.Win32; namespace DeviceId.Components { @@ -46,9 +47,9 @@ public string GetValue() var value = Registry.GetValue(_key, _valueName, null); return value?.ToString(); } - catch + catch(Exception e) { - return null; + throw new DeviceIdComponentFailedToObtainValueException(String.Format("Failed to read registry value ({0}\\{1})", _key, _valueName), e); } } } diff --git a/src/DeviceId/Components/SystemDriveSerialNumberDeviceIdComponent.cs b/src/DeviceId/Components/SystemDriveSerialNumberDeviceIdComponent.cs index f48b09c..7e5cd31 100644 --- a/src/DeviceId/Components/SystemDriveSerialNumberDeviceIdComponent.cs +++ b/src/DeviceId/Components/SystemDriveSerialNumberDeviceIdComponent.cs @@ -1,5 +1,5 @@ using System; -using System.Management; +using System.Collections.Generic; namespace DeviceId.Components { @@ -24,24 +24,19 @@ public SystemDriveSerialNumberDeviceIdComponent() { } /// The component value. public string GetValue() { - var systemLogicalDiskDeviceId = Environment.GetFolderPath(Environment.SpecialFolder.System).Substring(0, 2); - - var queryString = $"SELECT * FROM Win32_LogicalDisk where DeviceId = '{systemLogicalDiskDeviceId}'"; - using var searcher = new ManagementObjectSearcher(queryString); + try + { + var systemLogicalDiskDeviceId = Environment.GetFolderPath(Environment.SpecialFolder.System).Substring(0, 2); - foreach (ManagementObject disk in searcher.Get()) + var logicalDiskToPartition = Array.Find(WmiHelper.GetWMIInstances(@"root\cimv2", "Win32_LogicalDiskToPartition"), logicalDriveToPartition => (logicalDriveToPartition["Dependent"] as IDictionary)["DeviceID"] as string == systemLogicalDiskDeviceId); + var partition = Array.Find(WmiHelper.GetWMIInstances(@"root\cimv2", "Win32_DiskDriveToDiskPartition"), partition => (partition["Dependent"] as IDictionary)["DeviceID"] as string == (logicalDiskToPartition["Antecedent"] as IDictionary)["DeviceID"] as string); + var diskdrive = Array.Find(WmiHelper.GetWMIInstances(@"root\cimv2", "Win32_DiskDrive "), diskDriveToPartition => diskDriveToPartition["DeviceID"] as string == (partition["Antecedent"] as IDictionary)["DeviceID"] as string); + return diskdrive["SerialNumber"] as string; + } + catch(Exception e) { - foreach (ManagementObject partition in disk.GetRelated("Win32_DiskPartition")) - { - foreach (ManagementObject drive in partition.GetRelated("Win32_DiskDrive")) - { - var serialNumber = drive["SerialNumber"] as string; - return serialNumber; - } - } + throw new DeviceIdComponentFailedToObtainValueException("Failed to GetValue() in SystemDriveSerialNumberDeviceIdComponent", e); } - - return null; } } } diff --git a/src/DeviceId/Components/WmiDeviceIdComponent.cs b/src/DeviceId/Components/WmiDeviceIdComponent.cs index f07b73a..13bec9f 100644 --- a/src/DeviceId/Components/WmiDeviceIdComponent.cs +++ b/src/DeviceId/Components/WmiDeviceIdComponent.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Management; namespace DeviceId.Components { @@ -44,29 +43,16 @@ public string GetValue() { var values = new List(); - try + foreach (var obj in WmiHelper.GetWMIInstances(@"root\cimv2", _wmiClass)) { - using var managementObjectSearcher = new ManagementObjectSearcher($"SELECT {_wmiProperty} FROM {_wmiClass}"); - using var managementObjectCollection = managementObjectSearcher.Get(); - foreach (var managementObject in managementObjectCollection) + try { - try - { - var value = managementObject[_wmiProperty] as string; - if (value != null) - { - values.Add(value); - } - } - finally - { - managementObject.Dispose(); - } + values.Add(((IDictionary)obj)[_wmiProperty].ToString()); } - } - catch - { + catch + { + } } values.Sort(); diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index fc36b6a..3d48cdf 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -34,7 +34,7 @@ - + @@ -42,7 +42,7 @@ false - + diff --git a/src/DeviceId/DeviceIdComponentFailedToObtainValueException.cs b/src/DeviceId/DeviceIdComponentFailedToObtainValueException.cs new file mode 100644 index 0000000..5e15f9f --- /dev/null +++ b/src/DeviceId/DeviceIdComponentFailedToObtainValueException.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace DeviceId +{ + class DeviceIdComponentFailedToObtainValueException : Exception + { + // + // Summary: + // Initializes a new instance of the DeviceIDComponentException class. + public DeviceIdComponentFailedToObtainValueException() + { + + } + + // + // Summary: + // Initializes a new instance of the DeviceIDComponentException class with a specified error + // message. + // + // Parameters: + // message: + // The message that describes the error. + public DeviceIdComponentFailedToObtainValueException(string message) : base(message) + { + } + + // + // Summary: + // Initializes a new instance of the DeviceIDComponentException class with a specified error + // message and a reference to the inner exception that is the cause of this exception. + // + // Parameters: + // message: + // The error message that explains the reason for the exception. + // + // innerException: + // The exception that is the cause of the current exception, or a null reference + // (Nothing in Visual Basic) if no inner exception is specified. + public DeviceIdComponentFailedToObtainValueException(string message, Exception innerException): base(message, innerException) + { + } + } +} diff --git a/src/DeviceId/Formatters/HashDeviceIdFormatter.cs b/src/DeviceId/Formatters/HashDeviceIdFormatter.cs index 9e60cdd..b201718 100644 --- a/src/DeviceId/Formatters/HashDeviceIdFormatter.cs +++ b/src/DeviceId/Formatters/HashDeviceIdFormatter.cs @@ -44,11 +44,23 @@ public string GetDeviceId(IEnumerable components) throw new ArgumentNullException(nameof(components)); } - var value = string.Join(",", components.OrderBy(x => x.Name).Select(x => x.GetValue()).ToArray()); + var value = string.Join(",", components.OrderBy(x => x.Name).Select(x => GetValue(x)).ToArray()); var bytes = Encoding.UTF8.GetBytes(value); using var algorithm = _hashAlgorithm.Invoke(); var hash = algorithm.ComputeHash(bytes); return _byteArrayEncoder.Encode(hash); } + + private string GetValue(IDeviceIdComponent component) + { + try + { + return component.GetValue(); + } + catch(DeviceIdComponentFailedToObtainValueException) + { + return ""; + } + } } } diff --git a/src/DeviceId/Formatters/StringDeviceIdFormatter.cs b/src/DeviceId/Formatters/StringDeviceIdFormatter.cs index 4de42c8..68952d2 100644 --- a/src/DeviceId/Formatters/StringDeviceIdFormatter.cs +++ b/src/DeviceId/Formatters/StringDeviceIdFormatter.cs @@ -49,7 +49,19 @@ public string GetDeviceId(IEnumerable components) throw new ArgumentNullException(nameof(components)); } - return string.Join(_delimiter, components.OrderBy(x => x.Name).Select(x => _encoder.Encode(x)).ToArray()); + return string.Join(_delimiter, components.OrderBy(x => x.Name).Select(x => GetValue(x)).ToArray()); + } + + private string GetValue(IDeviceIdComponent component) + { + try + { + return _encoder.Encode(component); + } + catch (DeviceIdComponentFailedToObtainValueException) + { + return ""; + } } } } diff --git a/src/DeviceId/Formatters/XmlDeviceIdFormatter.cs b/src/DeviceId/Formatters/XmlDeviceIdFormatter.cs index d9088bc..e674321 100644 --- a/src/DeviceId/Formatters/XmlDeviceIdFormatter.cs +++ b/src/DeviceId/Formatters/XmlDeviceIdFormatter.cs @@ -61,9 +61,17 @@ private XElement GetElement(IEnumerable components) /// An representing the specified instance. private XElement GetElement(IDeviceIdComponent component) { - return new XElement("Component", - new XAttribute("Name", component.Name), - new XAttribute("Value", _encoder.Encode(component))); + try + { + return new XElement("Component", + new XAttribute("Name", component.Name), + new XAttribute("Value", _encoder.Encode(component))); + } + catch + { + return new XElement("Component", + new XAttribute("Name", component.Name)); + } } } } diff --git a/src/DeviceId/WmiHelper.cs b/src/DeviceId/WmiHelper.cs new file mode 100644 index 0000000..89c1e39 --- /dev/null +++ b/src/DeviceId/WmiHelper.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +#if NETSTANDARD +using System.Dynamic; +using Microsoft.Management.Infrastructure; +#endif +#if NETFRAMEWORK +using System.Management; +#endif +namespace DeviceId +{ + class WmiHelper + { + /// + /// Get an IDictionary representation of WMI objects of a specified class + /// + /// The scope in which to look for objects. + /// The WMI class name. + public static IDictionary[] GetWMIInstances(string wmiNamespace, string objectClass) + { + return GetWMIInstances(wmiNamespace, objectClass, new string[0]); + } + +#if NETSTANDARD + /// + /// Get an IDictionary representation of WMI objects of a specified class + /// + /// The scope in which to look for objects. + /// The WMI class name. + /// The list of attributes to obtain (or null for all attributes). + public static IDictionary[] GetWMIInstances(string wmiNamespace, string objectClass, string[] attributes) + { + try + { + string attributesSelector = (attributes != null && attributes.Length > 0) ? String.Join(",", attributes) : "*"; + + return CimSession.Create(null) // null instead of localhost which would otherwise require certain MMI services running + .QueryInstances(wmiNamespace, "WQL", $"SELECT {attributesSelector} FROM {objectClass}").Select((obj) => CimInstanceToExpandoObject(obj)).ToArray(); + } + catch(CimException e) + { + throw new DeviceIdComponentFailedToObtainValueException(String.Format("Failed in GetWMIInstances({0},{1})", wmiNamespace, objectClass), e); + } + } + + /// + /// Get an ExpandoObject representation of a CimInstance object + /// + /// The object to convert. + private static ExpandoObject CimInstanceToExpandoObject(CimInstance obj) + { + try + { + ExpandoObject result = new ExpandoObject(); + var resultDictionary = (IDictionary)result; + + foreach (var property in obj.CimInstanceProperties.Where(property => property.Value != null)) + resultDictionary[property.Name] = property.Value is CimInstance ? CimInstanceToExpandoObject(property.Value as CimInstance) : property.Value.ToString(); + + return result; + } + finally + { + obj.Dispose(); + } + } +#endif +#if NETFRAMEWORK + /// + /// Get an IDictionary representation of WMI objects of a specified class + /// + /// The scope in which to look for objects. + /// The WMI class name. + /// The list of attributes to obtain (or null for all attributes). + public static IDictionary[] GetWMIInstances(string wmiNamespace, string objectClass, string[] attributes) + { + try + { + string attributesSelector = attributes.Length > 0 ? String.Join(",", attributes) : "*"; + using var managementObjectSearcher = new ManagementObjectSearcher(wmiNamespace, $"select {attributesSelector} from {objectClass}"); + using var managementObjectCollection = managementObjectSearcher.Get(); + + return managementObjectCollection.Cast().Select(obj => ManagementObjectToDictionary(obj)).ToArray(); + } + catch(ManagementException e) + { + throw new DeviceIdComponentFailedToObtainValueException(String.Format("Failed in GetWMIInstances({0},{1})", wmiNamespace, objectClass), e); + } + } + + /// + /// Get a Dictionary representation of a ManagementBaseObject object + /// + /// The object to convert. + private static Dictionary ManagementObjectToDictionary(ManagementBaseObject obj) + { + try + { + return obj.Properties.Cast().ToDictionary(property => property.Name, property => + property.Value is ManagementBaseObject ? ManagementObjectToDictionary(property.Value as ManagementBaseObject) as object : property.Value.ToString()); + } + finally + { + obj.Dispose(); + } + } +#endif + } +} From 228a8326b05ba4220c18136ffcf19091109869f1 Mon Sep 17 00:00:00 2001 From: Shahar Zini Date: Sun, 7 Feb 2021 23:37:39 +1100 Subject: [PATCH 26/26] Add MMI --- src/DeviceId/DeviceId.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/DeviceId/DeviceId.csproj b/src/DeviceId/DeviceId.csproj index fc36b6a..1d19974 100644 --- a/src/DeviceId/DeviceId.csproj +++ b/src/DeviceId/DeviceId.csproj @@ -34,7 +34,7 @@ - + @@ -44,5 +44,4 @@ false -