diff --git a/client/Interop.cs b/client/Interop.cs index dc9ebed..052c569 100644 --- a/client/Interop.cs +++ b/client/Interop.cs @@ -117,6 +117,7 @@ internal enum TCP_TABLE_CLASS } public const int ERROR_SUCCESS = 0; + public const uint IOCTL_STORAGE_QUERY_PROPERTY = 0x2D1400u; public enum QUERY_DEVICE_CONFIG_FLAGS : uint { @@ -430,6 +431,13 @@ public struct STORAGE_PROTOCOL_SPECIFIC_DATA public uint ProtocolDataRequestSubValue4; } [StructLayout(LayoutKind.Sequential)] + public unsafe struct STORAGE_ADAPTER_SERIAL_NUMBER + { + public uint Version; + public uint Size; + public fixed ushort SerialNumber[128]; + } + [StructLayout(LayoutKind.Sequential)] public struct STORAGE_PROTOCOL_DATA_DESCRIPTOR { public uint Version; diff --git a/client/data/Methods/Hardware.cs b/client/data/Methods/Hardware.cs index 6bc19e7..c7b38e5 100644 --- a/client/data/Methods/Hardware.cs +++ b/client/data/Methods/Hardware.cs @@ -996,8 +996,10 @@ private static async Task GetDiskDriveData() LogEvent($"{e}", Region.Hardware); } - foreach (var drive in drives.Where(x => x.SmartData == null)) + foreach (var drive in drives.Where(x => x.IsNVMe)) { + if (drive.SmartData == null) + { try { GetNvmeSmart(drive); @@ -1009,6 +1011,18 @@ private static async Task GetDiskDriveData() } } + try + { + GetStorageAdapterSerialNumber(drive); + } + catch (Exception e) + { + LogEvent($"Exception during storage adapter serial number retrieval on drive {drive.DeviceName}", Region.Hardware, EventType.ERROR); + LogEvent($"{e}", Region.Hardware); + } + + } + Disks = drives; } @@ -1060,14 +1074,6 @@ private static List GetDiskFreeSpace(List drives) private static DiskDrive GetNvmeSmart(DiskDrive drive) { - // Stop if drive is not an NVME drive. This happens on all external drives. - if (!drive.InterfaceType.Equals("NVMe", StringComparison.CurrentCultureIgnoreCase) && //VDS check - (drive.InterfaceType != "SCSI" || !drive.MediaType.ToLower().Contains("fixed"))) //Old method check - { - LogEvent($"Could not retrieve NVME Smart Data. Drive {drive.DeviceName} is not an NVME drive. Interface: {drive.InterfaceType}. Media type: {drive.MediaType}", Region.Hardware); - return drive; - } - // We can use DeviceId (Name in Windows) as a valid path to CreateFile (ex. \\?\PhysicalDrive0) instead of drive letter var handle = CreateFile(drive.DeviceId, 0x40000000, 0x1 | 0x2, IntPtr.Zero, 0x3, 0, IntPtr.Zero); @@ -1199,6 +1205,66 @@ private static DiskDrive GetNvmeSmart(DiskDrive drive) return drive; } + public static DiskDrive GetStorageAdapterSerialNumber(DiskDrive drive) + { + var handle = CreateFile(drive.DeviceId, 0x40000000, 0x1 | 0x2, IntPtr.Zero, 0x3, 0, IntPtr.Zero); + + // Verify the handle. + if (handle == new IntPtr(-1)) + { + LogEvent($"Storage adapter serial number could not be retrieved. Invalid Handle. {drive.DeviceId}", Region.Hardware, EventType.ERROR); + LogEvent($"Interop Error: {new Win32Exception(Marshal.GetLastWin32Error()).Message}", Region.Hardware); + return drive; + } + + unsafe + { + STORAGE_PROPERTY_QUERY* query = null; + STORAGE_ADAPTER_SERIAL_NUMBER* adapterSn = null; + + var bufferInLength = (uint)sizeof(STORAGE_PROPERTY_QUERY); + var bufferOutLength = (uint)sizeof(STORAGE_ADAPTER_SERIAL_NUMBER); + var bufferIn = Marshal.AllocHGlobal((int)bufferInLength); + var bufferOut = Marshal.AllocHGlobal((int)bufferOutLength); + try + { + query = (STORAGE_PROPERTY_QUERY*)bufferIn; + adapterSn = (STORAGE_ADAPTER_SERIAL_NUMBER*)bufferOut; + + // Set up the Smart log query + query->PropertyId = STORAGE_PROPERTY_ID.StorageAdapterSerialNumberProperty; + query->QueryType = STORAGE_QUERY_TYPE.PropertyStandardQuery; + + var result = DeviceIoControl(handle, + IOCTL_STORAGE_QUERY_PROPERTY, + bufferIn, + bufferInLength, + bufferOut, + bufferOutLength, + out _, + IntPtr.Zero + ); + if (result) + { + var sn = Marshal.PtrToStringUni((IntPtr)adapterSn->SerialNumber); + drive.SerialNumber = sn.Split(' ').FirstOrDefault() ?? sn; + } + else + { + string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + LogEvent($"Interop failure during storage adapter serial number retrieval. {Marshal.GetLastWin32Error()} - {errorMessage} on drive {drive.DiskNumber}", Region.Hardware, EventType.ERROR); + return drive; + } + } + finally + { + Marshal.FreeHGlobal(bufferIn); + Marshal.FreeHGlobal(bufferOut); + } + } + return drive; + } + private unsafe static SmartAttribute MakeNvmeAttribute(byte* attr, byte id, string name) { unsafe @@ -1411,4 +1477,4 @@ private static async Task GetBatteryData() cmd.Close(); Batteries = BatteryInfo; } -} \ No newline at end of file +} diff --git a/client/data/Structs.cs b/client/data/Structs.cs index 4766fe7..88d894a 100644 --- a/client/data/Structs.cs +++ b/client/data/Structs.cs @@ -81,6 +81,9 @@ public class DiskDrive [NonSerialized()] public string InstanceId; // Only used to link SmartData, do not serialize. Unless you really want to. [NonSerialized] public string DeviceId; // Used to get device PnP Id from VDS method and get nvme SmartData + + [JsonIgnore] public bool IsNVMe => InterfaceType.Equals("NVMe", StringComparison.CurrentCultureIgnoreCase) //VDS check + || (InterfaceType == "SCSI" && MediaType.ToLower().Contains("fixed")); //Old method check } public class Partition