Recently I have needed to get SMART data from a disk drive. I maintain a server at home and from experience that disks appear to last around four or five years if they are run all the time and SMART information is an excellent way to spot when a disk is about to die. Again from experience any pending bad sectors is a very bed sign.

I was writing a disk monitor program in C# and I found some great code from Llewellyn Kruger that really gave me a head start.

// retrieve list of drives on computer (this will return both HDD's and CDROM's and Virtual CDROM's)                    
var dicDrives = new Dictionary<int, HDD>();
var wdSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive");

// extract model and interface information
int iDriveIndex = 0;
foreach (ManagementObject drive in wdSearcher.Get())
{
	var hdd = new HDD();
	hdd.Model = drive["Model"].ToString().Trim();
	hdd.Type = drive["InterfaceType"].ToString().Trim();
	dicDrives.Add(iDriveIndex, hdd); 
	iDriveIndex++;
}

// get wmi access to hdd 
var searcher = new ManagementObjectSearcher("Select * from Win32_DiskDrive");
searcher.Scope = new ManagementScope(@"\root\wmi");      

// retrieve attribute flags, value worst and vendor data information
searcher.Query = new ObjectQuery("Select * from MSStorageDriver_FailurePredictData");
iDriveIndex = 0;  
foreach (ManagementObject data in searcher.Get())
{              
	Byte[] bytes = (Byte[])data.Properties["VendorSpecific"].Value;
	for (int i = 0; i < 30; ++i)
	{
		int id = bytes[i*12 + 2];

		int flags = bytes[i * 12 + 4]; // least significant status byte, +3 most significant byte, but not used so ignored.
		//bool advisory = (flags & 0x1) == 0x0;
		bool failureImminent = (flags & 0x1) == 0x1;
		//bool onlineDataCollection = (flags & 0x2) == 0x2;

		int value = bytes[i*12 + 5];
		int worst = bytes[i*12 + 6];
		int vendordata = BitConverter.ToInt32(bytes, i*12 + 7);
		if (id == 0) continue;

		var attr = dicDrives[iDriveIndex].Attributes[id];
		attr.Current = value;
		attr.Worst = worst;
		attr.Data = vendordata;
		attr.IsOK = failureImminent == false;
	}
	iDriveIndex++;
}

However if you look at the original code you can see that it is assumed that if you have multiple hard disks the drives returned from SELECT * FROM Win32_DiskDrive and also from Select * from MSStorageDriver_FailurePredictData are returned in the same order.

It turns out that on my computer with multiple hard disks that Win32_DiskDrive returned the physical disks in the order 1 and then 0. MSStorageDriver_FailurePredictData always returns data in physical drive order.

To correct this problem I needed to be able to work out the physical disk number of the disks returned from Win32_DiskDrive. Initially I had a mad cap scheme to use the device name \\.\PHYSICALDRIVE0, however when I looked further I found that there is a property Index that gives the physical disk number, then I changed the code to be like this

public Dictionary<int, HDD> GetAllDisks()
{
	// retrieve list of drives on computer (this will return both HDD's and CDROM's and Virtual CDROM's)                    
	var dicDrives = new Dictionary<int, HDD>();
	var wdSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive");

	// extract model and interface information
	int iDriveIndex = 0;
	foreach (ManagementObject drive in wdSearcher.Get())
	{
		var hdd = new HDD();
		hdd.Id = drive["DeviceId"].ToString().Trim();
		hdd.Index = Convert.ToInt32(drive["Index"].ToString().Trim());
		hdd.Model = drive["Model"].ToString().Trim();
		hdd.Type = drive["InterfaceType"].ToString().Trim();
		_logger.Write($"disk drive {hdd.Index} {hdd.Id} {hdd.Model}");
		dicDrives.Add(iDriveIndex, hdd);
		iDriveIndex++;
	}
	return dicDrives;
}

public void GetSMARTInformation(HDD drive)
{
	// this is the actual physical drive number
	int index = drive.Index;
	_logger.Write($"Drive Number {index} Drive {drive.Id}, {drive.Model}");

	// get wmi access to hdd 
	var searcher = new ManagementObjectSearcher("Select * from Win32_DiskDrive");
	searcher.Scope = new ManagementScope(@"\root\wmi");

	// retrieve attribute flags, value worst and vendor data information
	searcher.Query = new ObjectQuery("Select * from MSStorageDriver_FailurePredictData");
	iDriveIndex = 0;
	foreach (ManagementObject data in searcher.Get())
	{
		if (index == iDriveIndex)
		{
			Byte[] bytes = (Byte[])data.Properties["VendorSpecific"].Value;
			for (int i = 0; i < 30; ++i)
			{
				int id = 0;
				try
				{
					id = bytes[i * 12 + 2];

					int flags = bytes[i * 12 + 4]; // least significant status byte, +3 most significant byte, but not used so ignored.
													//bool advisory = (flags & 0x1) == 0x0;
					bool failureImminent = (flags & 0x1) == 0x1;
					//bool onlineDataCollection = (flags & 0x2) == 0x2;

					int value = bytes[i * 12 + 5];
					int worst = bytes[i * 12 + 6];
					int vendordata = BitConverter.ToInt32(bytes, i * 12 + 7);
					if (id == 0) continue;

					var attr = drive.Attributes[id];
					attr.Current = value;
					attr.Worst = worst;
					attr.Data = vendordata;
					attr.IsOK = failureImminent == false;
				}
				catch
				{
					// given key does not exist in attribute collection (attribute not in the dictionary of attributes)
					_logger.Write($"SMART Key Not found {id}");
				}
			}
		}
		iDriveIndex++;
	}