Last active
December 6, 2024 12:54
-
-
Save mjs3339/bdc492245ea09b18c1fec30f1c31b286 to your computer and use it in GitHub Desktop.
C# Get Disk/Drive Physical/Logical and Smart Information
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class SmartI | |
{ | |
private readonly Dictionary<int, Smart> _smartInfo = new Dictionary<int, Smart>(); | |
public readonly HashSet<int> FutureReserchUnknownAttributes = new HashSet<int>(); | |
private static bool Is64Bit => IntPtr.Size == 8; | |
private static uint OffsetSize => Is64Bit ? 8u : 6u; | |
public Dictionary<int, Smart> SmartInfo | |
{ | |
get | |
{ | |
if(_smartInfo.Count == 0) | |
GetSmartInfo(); | |
return _smartInfo; | |
} | |
} | |
public IEnumerable<string> GetDriveReadyList => from driveInfo | |
in DriveInfo.GetDrives() | |
where driveInfo.IsReady | |
select driveInfo.Name; | |
private Dictionary<string, string> LogicalDrives | |
{ | |
get | |
{ | |
var logicalDrives = new Dictionary<string, string>(); | |
foreach(var drive in GetDriveReadyList) | |
{ | |
var sb = new StringBuilder(1024); | |
if(NativeWin32.GetVolumeNameForVolumeMountPoint(drive, sb, sb.Capacity)) | |
logicalDrives.Add(drive.Replace("\\", ""), sb.ToString()); | |
} | |
return logicalDrives; | |
} | |
} | |
private Dictionary<string, List<NativeWin32.DISK_EXTENT>> DiskNumbers | |
{ | |
get | |
{ | |
var diskNumbers = new Dictionary<string, List<NativeWin32.DISK_EXTENT>>(); | |
foreach(var ld in LogicalDrives) | |
{ | |
var dkexts = new List<NativeWin32.DISK_EXTENT>(); | |
var hFile = NativeWin32.CreateFile(@"\\.\" + ld.Key, NativeWin32.GENERIC_READ, | |
NativeWin32.FILE_SHARE_READ | NativeWin32.FILE_SHARE_WRITE, IntPtr.Zero, | |
NativeWin32.OPEN_EXISTING, 0, IntPtr.Zero); | |
if(hFile == (IntPtr) NativeWin32.INVALID_HANDLE_VALUE) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
var size = 1024; | |
var buffer = Marshal.AllocHGlobal(size); | |
var alloced = buffer; | |
var bytesReturned = 0; | |
try | |
{ | |
if(!NativeWin32.DeviceIoControl(hFile, | |
NativeWin32.IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, IntPtr.Zero, 0, buffer, size, | |
out bytesReturned, IntPtr.Zero)) | |
{ | |
} | |
} | |
catch(Exception e) | |
{ | |
var m = e.Message; | |
} | |
finally | |
{ | |
NativeWin32.CloseHandle(hFile); | |
} | |
if(bytesReturned > 0) | |
{ | |
var vde = new NativeWin32.VOLUME_DISK_EXTENTS(); | |
var dextent = new NativeWin32.DISK_EXTENT(); | |
Marshal.PtrToStructure(buffer, vde); | |
buffer = new IntPtr(buffer.ToInt64() + Marshal.SizeOf(vde)); | |
for(var i = 0; i < vde.NumberOfDiskExtents; i++) | |
{ | |
dextent = | |
(NativeWin32.DISK_EXTENT) Marshal.PtrToStructure(buffer, | |
typeof(NativeWin32.DISK_EXTENT)); | |
dkexts.Add(dextent); | |
buffer = new IntPtr(buffer.ToInt64() + Marshal.SizeOf(dextent)); | |
} | |
} | |
Marshal.FreeHGlobal(alloced); | |
diskNumbers.Add(ld.Key, dkexts); | |
} | |
return diskNumbers; | |
} | |
} | |
private(string Vendor, string Product) GetVendorProduct(string data) | |
{ | |
var ven = data.Substring(data.IndexOf("VEN_", StringComparison.CurrentCultureIgnoreCase) + 4); | |
var end = ven.SuperIndexOf("&", 1); | |
if(end == -1) | |
end = ven.SuperIndexOf("\\", 1); | |
if(end == -1) | |
return(null, null); | |
var VendorStr = ven.Substring(0, end).ToUpper(); | |
var pro = data.Substring(data.IndexOf("PROD_", StringComparison.CurrentCultureIgnoreCase) + 5); | |
var end1 = pro.SuperIndexOf("&", 1); | |
if(end1 == -1) | |
end1 = pro.SuperIndexOf("\\", 1); | |
if(end1 == -1) | |
return(VendorStr, null); | |
var ProductStr = pro.Substring(0, end1).ToUpper(); | |
end1 = ProductStr.SuperIndexOf("\\", 1); | |
if(end1 == -1) | |
return(VendorStr, ProductStr); | |
ProductStr = pro.Substring(0, end1).ToUpper(); | |
return(VendorStr, ProductStr); | |
} | |
private int GetIndex(string ven, string pro) | |
{ | |
for(var i = 0; i < _smartInfo.Count; ++i) | |
{ | |
var v = _smartInfo[i]; | |
if(ven == v.Vendor && pro == v.Product) | |
return i; | |
} | |
return-1; | |
} | |
/// <summary> | |
/// Item Data | |
/// 0 and 1 Unknown usually zero | |
/// 2 Attribute | |
/// 3 Status | |
/// 4 Unknown usually zero | |
/// 5 Value | |
/// 6 Worst | |
/// 7,8 Raw Value | |
/// 9,10,11 Unknown usually zero | |
/// </summary> | |
private void GetSmartInfo() | |
{ | |
try | |
{ | |
var pdski = new PDiskInformation(); | |
_smartInfo.Clear(); | |
var driveletterlist = new List<string>(); | |
var ldSearcher = new ManagementObjectSearcher("select * from Win32_LogicalDisk"); | |
foreach(ManagementObject drive in ldSearcher.Get()) | |
{ | |
var drvn = drive["Name"].ToString().Trim(); | |
driveletterlist.Add(drvn); | |
} | |
var wdSearcher = new ManagementObjectSearcher("select * from Win32_DiskDrive"); | |
var Index = 0; | |
foreach(ManagementObject drive in wdSearcher.Get()) | |
{ | |
var smart = new Smart(); | |
smart.Model = drive["Model"].ToString().Trim(); | |
var PNPDeviceID = drive["PNPDeviceID"].ToString().Trim(); | |
var (vendor, product) = GetVendorProduct(PNPDeviceID); | |
smart.Vendor = vendor; | |
smart.Product = product; | |
smart.Type = drive["InterfaceType"].ToString().Trim(); | |
smart.Disk = drive["Index"].ToString().Trim(); | |
smart.Size = drive["Size"].ToString().Trim(); | |
smart.Status = drive["Status"].ToString().Trim(); | |
_smartInfo.Add(Index, smart); | |
Index++; | |
} | |
var pmsearcher = new ManagementObjectSearcher("select * from Win32_PhysicalMedia"); | |
foreach(ManagementObject drive in pmsearcher.Get()) | |
{ | |
var dsk = drive["Tag"].ToString().Trim().Last().ToString().ToInt32(); | |
_smartInfo[dsk].Serial = drive["SerialNumber"] == null ? "None" : drive["SerialNumber"].ToString().Trim(); | |
} | |
var searcher = new ManagementObjectSearcher("root\\WMI", "SELECT * FROM MSStorageDriver_ATAPISmartData"); | |
foreach(ManagementObject data in searcher.Get()) | |
{ | |
var pt = data.Properties; | |
var VendorSpecific = (byte[]) data.Properties["VendorSpecific"].Value; | |
var vsData = data["InstanceName"].ToString().Trim(); | |
var (vendor, product) = GetVendorProduct(vsData); | |
var idx = GetIndex(vendor, product); | |
if(idx != -1) | |
for(var offset = 2; offset < VendorSpecific.Length; offset += 12) | |
{ | |
var a = FromBytes<SmartAttribute>(VendorSpecific, offset); | |
int id = VendorSpecific[offset]; | |
try | |
{ | |
int value; | |
if(id == (int) SmartAttributeTypes.Temperature) | |
value = Convert.ToInt32(a.VendorData[0]); | |
else | |
value = BitConverter.ToInt32(a.VendorData, 0); | |
if(id == 0) continue; | |
if(_smartInfo[idx].AttributesTemplate.ContainsKey(id)) | |
{ | |
_smartInfo[idx].Attributes.Add(id, _smartInfo[idx].AttributesTemplate[id]); | |
var attr = _smartInfo[idx].Attributes[id]; | |
attr.Current = value; | |
attr.FailureImminent = a.FailureImminent; | |
attr.Advisory = a.Advisory; | |
attr.OnlineDataCollection = a.OnlineDataCollection; | |
attr.Status = a.FailureImminent == false ? "Ok" : "Fail"; | |
} | |
} | |
catch | |
{ | |
if(!FutureReserchUnknownAttributes.Contains(id)) | |
FutureReserchUnknownAttributes.Add(id); | |
} | |
} | |
} | |
var vp = GetVolumePaths(); | |
for(var idx = 0; idx < _smartInfo.Count; idx++) | |
{ | |
var driveletter = ""; | |
foreach(var drive in driveletterlist) | |
if(_smartInfo[idx].Disk == vp[drive].DiskNumbers[0].ToString()) | |
{ | |
driveletter = drive; | |
break; | |
} | |
if(driveletter != "") | |
{ | |
_smartInfo[idx].Drive = driveletter; | |
_smartInfo[idx].MediaType = new PDMediaTypesStr().types[pdski.PDiskInfo[_smartInfo[idx].Disk].MediaType]; | |
} | |
} | |
} | |
catch(ManagementException e) | |
{ | |
throw new Exception($"WMI data error: {e.Message}"); | |
} | |
} | |
private T FromBytes<T>(byte[] bytearray, int offset) | |
{ | |
var ptr = IntPtr.Zero; | |
try | |
{ | |
ptr = Marshal.AllocHGlobal(12); | |
Marshal.Copy(bytearray, offset, ptr, 12); | |
return(T) Marshal.PtrToStructure(ptr, typeof(T)); | |
} | |
finally | |
{ | |
if(ptr != IntPtr.Zero) | |
Marshal.FreeHGlobal(ptr); | |
} | |
} | |
private Dictionary<string, SetupDi.VOLUME_INFO> GetVolumePaths() | |
{ | |
var _volumepaths = new Dictionary<string, SetupDi.VOLUME_INFO>(); | |
var classGuid = NativeWin32.GUID_DEVINTERFACE.GUID_DEVINTERFACE_VOLUME; | |
var hDevInfo = NativeWin32.SetupDiGetClassDevs(ref classGuid, null, IntPtr.Zero, | |
NativeWin32.DICFG.DEVICEINTERFACE | NativeWin32.DICFG.PRESENT); | |
if(hDevInfo == (IntPtr) NativeWin32.INVALID_HANDLE_VALUE) | |
throw new Exception("Read hardware information error."); | |
var devIndex = 0; | |
do | |
{ | |
var dia = new NativeWin32.SP_DEVICE_INTERFACE_DATA(); | |
dia.cbSize = (uint) Marshal.SizeOf(typeof(NativeWin32.SP_DEVICE_INTERFACE_DATA)); | |
if(NativeWin32.SetupDiEnumDeviceInterfaces(hDevInfo, IntPtr.Zero, classGuid, (uint) devIndex, ref dia)) | |
{ | |
var didd = new NativeWin32.SP_DEVICE_INTERFACE_DETAIL_DATA(); | |
didd.cbSize = OffsetSize; | |
var devInfoData = new NativeWin32.SP_DEVINFO_DATA(); | |
devInfoData.cbSize = (uint) Marshal.SizeOf(typeof(NativeWin32.SP_DEVINFO_DATA)); | |
uint nRequiredSize = 0; | |
uint nBytes = 1024; | |
if(NativeWin32.SetupDiGetDeviceInterfaceDetail(hDevInfo, ref dia, ref didd, nBytes, ref nRequiredSize, ref devInfoData)) | |
{ | |
var sb = new StringBuilder(1024); | |
if(NativeWin32.GetVolumeNameForVolumeMountPoint(didd.devicePath + @"\", sb, sb.Capacity)) | |
{ | |
var cv = sb.ToString(); | |
var di = new SetupDi.VOLUME_INFO(); | |
foreach(var V in LogicalDrives) | |
if(V.Value.IndexOf(cv, StringComparison.OrdinalIgnoreCase) != -1) | |
{ | |
di.Drive = V.Key; | |
di.VolumeMountPoint = cv; | |
di.DevicePath = didd.devicePath; | |
foreach(var de in DiskNumbers[V.Key]) | |
{ | |
di.DiskNumbers.Add(de.DiskNumber); | |
di.ExtentLengths.Add(de.ExtentLength); | |
di.StartingOffsets.Add(de.StartingOffset); | |
} | |
_volumepaths.Add(di.Drive.Trim(), di); | |
break; | |
} | |
} | |
} | |
else | |
{ | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
} | |
} | |
else | |
{ | |
break; | |
} | |
devIndex++; | |
} while(true); | |
NativeWin32.SetupDiDestroyDeviceInfoList(hDevInfo); | |
return _volumepaths; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
public struct SmartAttribute | |
{ | |
public readonly SmartAttributeTypes AttributeType; | |
public readonly ushort Flags; | |
public readonly byte Value; | |
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] | |
public readonly byte[] VendorData; | |
public bool Advisory => (Flags & 0x1) == 0x0; | |
public bool FailureImminent => (Flags & 0x1) == 0x1; | |
public bool OnlineDataCollection => (Flags & 0x2) == 0x2; | |
} | |
} | |
public class Smart | |
{ | |
public readonly Dictionary<int, SmartData> Attributes = new Dictionary<int, SmartData>(); | |
public readonly Dictionary<int, SmartData> AttributesTemplate = new Dictionary<int, SmartData> | |
{ | |
{0, new SmartData("Invalid")}, | |
{1, new SmartData("read error rate")}, | |
{2, new SmartData("Throughput performance")}, | |
{3, new SmartData("Spin up time")}, | |
{4, new SmartData("Start/Stop count")}, | |
{5, new SmartData("Reallocated sector count")}, | |
{6, new SmartData("Read channel margin")}, | |
{7, new SmartData("Seek error rate")}, | |
{8, new SmartData("Seek timer performance")}, | |
{9, new SmartData("Power-on hours count")}, | |
{10, new SmartData("Spin up retry count")}, | |
{11, new SmartData("Calibration retry count")}, | |
{12, new SmartData("Power cycle count")}, | |
{13, new SmartData("Soft read error rate")}, | |
{22, new SmartData("Current Helium Level")}, | |
{160, new SmartData("Uncorrectable sector count read or write")}, | |
{161, new SmartData("Remaining spare block percentage")}, | |
{164, new SmartData("Total Erase Count")}, | |
{165, new SmartData("Maximum Erase Count")}, | |
{166, new SmartData("Minimum Erase Count")}, | |
{167, new SmartData("Average Erase Count")}, | |
{168, new SmartData("Max NAND Erase Count from specification")}, | |
{169, new SmartData("Remaining life percentage")}, | |
{170, new SmartData("Available Reserved Space")}, | |
{171, new SmartData("SSD Program Fail Count")}, | |
{172, new SmartData("SSD Erase Fail Count")}, | |
{173, new SmartData("SSD Wear Leveling Count")}, | |
{174, new SmartData("Unexpected Power Loss Count")}, | |
{175, new SmartData("Power Loss Protection Failure")}, | |
{176, new SmartData("Erase Fail Count")}, | |
{177, new SmartData("Wear Range Delta")}, | |
{178, new SmartData("Used Reserved Block Count (Chip)")}, | |
{179, new SmartData("Used Reserved Block Count (Total)")}, | |
{180, new SmartData("Unused Reserved Block Count Total")}, | |
{181, new SmartData("Program Fail Count Total or Non 4K Aligned Access Count")}, | |
{182, new SmartData("Erase Fail Count")}, | |
{183, new SmartData("SATA Down shift Error Count")}, | |
{184, new SmartData("End-to-End error")}, | |
{185, new SmartData("Head Stability")}, | |
{186, new SmartData("Induced Op Vibration Detection")}, | |
{187, new SmartData("Reported Uncorrectable Errors")}, | |
{188, new SmartData("Command Timeout")}, | |
{189, new SmartData("High Fly Writes")}, | |
{190, new SmartData("Temperature Difference from 100")}, | |
{191, new SmartData("G-sense error rate")}, | |
{192, new SmartData("Power-off retract count")}, | |
{193, new SmartData("Load/Unload cycle count")}, | |
{194, new SmartData("Temperature")}, | |
{195, new SmartData("Hardware ECC recovered")}, | |
{196, new SmartData("Reallocation count")}, | |
{197, new SmartData("Current pending sector count")}, | |
{198, new SmartData("Off-line scan uncorrectable count")}, | |
{199, new SmartData("UDMA CRC error rate")}, | |
{200, new SmartData("Write error rate")}, | |
{201, new SmartData("Soft read error rate")}, | |
{202, new SmartData("Data Address Mark errors")}, | |
{203, new SmartData("Run out cancel")}, | |
{204, new SmartData("Soft ECC correction")}, | |
{205, new SmartData("Thermal asperity rate (TAR)")}, | |
{206, new SmartData("Flying height")}, | |
{207, new SmartData("Spin high current")}, | |
{208, new SmartData("Spin buzz")}, | |
{209, new SmartData("Off-line seek performance")}, | |
{211, new SmartData("Vibration During Write")}, | |
{212, new SmartData("Shock During Write")}, | |
{220, new SmartData("Disk shift")}, | |
{221, new SmartData("G-sense error rate")}, | |
{222, new SmartData("Loaded hours")}, | |
{223, new SmartData("Load/unload retry count")}, | |
{224, new SmartData("Load friction")}, | |
{225, new SmartData("Load/Unload cycle count")}, | |
{226, new SmartData("Load-in time")}, | |
{227, new SmartData("Torque amplification count")}, | |
{228, new SmartData("Power-off retract count")}, | |
{230, new SmartData("Life Curve Status")}, | |
{231, new SmartData("SSD Life Left")}, | |
{232, new SmartData("Endurance Remaining")}, | |
{233, new SmartData("Media Wear out Indicator")}, | |
{234, new SmartData("Average Erase Count AND Maximum Erase Count")}, | |
{235, new SmartData("Good Block Count AND System Free Block Count")}, | |
{240, new SmartData("Head flying hours")}, | |
{241, new SmartData("Lifetime Writes From Host GiB")}, | |
{242, new SmartData("Lifetime Reads From Host GiB")}, | |
{243, new SmartData("Total LBAs Written Expanded")}, | |
{244, new SmartData("Total LBAs Read Expanded")}, | |
{249, new SmartData("NAND Writes GiB")}, | |
{250, new SmartData("Read error retry rate")}, | |
{251, new SmartData("Minimum Spares Remaining")}, | |
{252, new SmartData("Newly Added Bad Flash Block")}, | |
{254, new SmartData("Free Fall Protection")} | |
}; | |
public string Status {get; set;} | |
public string Model {get; set;} | |
public string Vendor {get; set;} | |
public string Product {get; set;} | |
public string Type {get; set;} | |
public string Serial {get; set;} | |
public string Disk {get; set;} | |
public string Drive {get; set;} | |
public string MediaType{get; set;} | |
public string Size {get; set;} | |
public class SmartData | |
{ | |
public SmartData(string attributeName) | |
{ | |
Attribute = attributeName; | |
} | |
public string Attribute {get; set;} | |
public int Current {get; set;} | |
public string Status {get; set;} | |
public bool Advisory {get; set;} | |
public bool FailureImminent {get; set;} | |
public bool OnlineDataCollection{get; set;} | |
} | |
} | |
public enum SmartAttributeTypes : byte | |
{ | |
Invalid = 0, | |
ReadErrorRate = 1, | |
ThroughputPerformance = 2, | |
SpinUpTime = 3, | |
StartStopCount = 4, | |
ReallocatedSectorsCount = 5, | |
ReadChannelMargin = 6, | |
SeekErrorRate = 7, | |
SeekTimePerformance = 8, | |
PowerOnHoursCount = 9, | |
SpinRetryCount = 10, | |
CalibrationRetryCount = 11, | |
PowerCycleCount = 12, | |
SoftReadErrorRate = 13, | |
CurrentHeliumLevel = 22, | |
UncorrectableSectorCountReadOrWrite = 160, | |
RemainingSpareBlockPercentage = 161, | |
TotalEraseCount = 164, | |
MaximumEraseCount = 165, | |
MinimumEraseCount = 166, | |
AverageEraseCount = 167, | |
MaxNANDEraseCountFromSpecification = 168, | |
RemainingLifePercentage = 169, | |
AvailableReservedSpace = 170, | |
SSDProgramFailCount = 171, | |
SSDEraseFailCount = 172, | |
SSDWearLevelingCount = 173, | |
UnexpectedPowerLossCount = 174, | |
PowerLossProtectionFailure = 175, | |
EraseFailCount = 176, | |
WearRangeDelta = 177, | |
UsedReservedBlockCountChip = 178, | |
UsedReservedBlockCountTotal = 179, | |
UnusedReservedBlockCountTotal = 180, | |
ProgramFailCountTotalorNon4KAlignedAccessCount = 181, | |
EraseFailCountSamsung = 182, | |
SATADownshiftErrorCount = 183, | |
EndtoEnderror = 184, | |
HeadStability = 185, | |
InducedOpVibrationDetection = 186, | |
ReportedUncorrectableErrors = 187, | |
CommandTimeout = 188, | |
HighFlyWrites = 189, | |
TemperatureDifferencefrom100 = 190, | |
Gsenseerrorrate = 191, | |
PoweroffRetractCount = 192, | |
LoadCycleCount = 193, | |
Temperature = 194, | |
HardwareECCRecovered = 195, | |
ReallocationEventCount = 196, | |
CurrentPendingSectorCount = 197, | |
UncorrectableSectorCount = 198, | |
UltraDMACRCErrorCount = 199, | |
MultiZoneErrorRate = 200, | |
OffTrackSoftReadErrorRate = 201, | |
DataAddressMarkerrors = 202, | |
RunOutCancel = 203, | |
SoftECCCorrection = 204, | |
ThermalAsperityRateTAR = 205, | |
FlyingHeight = 206, | |
SpinHighCurrent = 207, | |
SpinBuzz = 208, | |
OfflineSeekPerformance = 209, | |
VibrationDuringWrite = 211, | |
ShockDuringWrite = 212, | |
DiskShift = 220, | |
GSenseErrorRate = 221, | |
LoadedHours = 222, | |
LoadUnloadRetryCount = 223, | |
LoadFriction = 224, | |
LoadUnloadCycleCount = 225, | |
LoadInTime = 226, | |
TorqueAmplificationCount = 227, | |
PowerOffRetractCycle = 228, | |
LifeCurveStatus = 230, | |
SSDLifeLeft = 231, | |
EnduranceRemaining = 232, | |
MediaWearoutIndicator = 233, | |
AverageEraseCountANDMaximumEraseCount = 234, | |
GoodBlockCountANDSystemFreeBlockCount = 235, | |
HeadFlyingHours = 240, | |
LifetimeWritesFromHostGiB = 241, | |
LiftetimeReadsFromHostGiB = 242, | |
TotalLBAsWrittenExpanded = 243, | |
TotalLBAsReadExpanded = 244, | |
NANDWrites1GiB = 249, | |
ReadErrorRetryRate = 250, | |
MinimumSparesRemaining = 251, | |
NewlyAddedBadFlashBlock = 252, | |
FreeFallProtection = 254 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
cool!