/// <summary> /// Detects all normal (not primary, not boot) disks, and gathers attributes about each. /// </summary> /// <param name="hadError"></param> /// <param name="errorMessage"></param> /// <returns></returns> public List<DetectedDisk> Detect(bool doGatherSmartData, out Boolean hadError, out String errorMessage) { // Inititialize out parameters. hadError = false; errorMessage = null; // Initialize return object. List<DetectedDisk> detectedDisks = new List<DetectedDisk>(); // Assume primary and boot partitions are both Physical Disk 0. UInt32 primaryPartitionDiskNumber = 0; UInt32 bootPartitionDiskNumber = 0; // Keep track of errors encountered while iterating disks. List<string> diskIterationErrors = new List<string>(); // Get the list of Win32_DiskDrive items. List<Win32_DiskDrive> w32diskDrives; try { w32diskDrives = GetDiskDrives(); } catch (Exception ex) { hadError = true; errorMessage = "Error while retrieving disk drives: " + ex.Message; w32diskDrives = new List<Win32_DiskDrive>(); } foreach (Win32_DiskDrive w32diskDrive in w32diskDrives) { try { if (w32diskDrive.Index.HasValue) { // Skip this if it is the hosting the boot partition or primary partition. if ((w32diskDrive.Index.Value != primaryPartitionDiskNumber) && (w32diskDrive.Index.Value != bootPartitionDiskNumber)) { // Make sure it has the other attributes we need. if ((w32diskDrive.Size.HasValue) && (w32diskDrive.BytesPerSector.HasValue) && (w32diskDrive.SectorsPerTrack.HasValue) && (w32diskDrive.TotalTracks.HasValue)) { // This is one we want, as a detected disk. DetectedDisk disk = new DetectedDisk(); // Parse the Vendor and ProductId out of the PNPDeviceId. string vendor = "UNK"; string productId = "UNK"; string pnpDeviceId = w32diskDrive.PNPDeviceID; if (false == String.IsNullOrEmpty(pnpDeviceId)) { // EXAMPLE: SCSI\DISK&VEN_WDC&PROD_WD3200BEKX-75B7W\4&2C8732C3&0&000000 string[] pnpThreePartsArray = pnpDeviceId.Split('\\'); if (pnpThreePartsArray.Length >= 2) { string[] typeVenProdRevArray = pnpThreePartsArray[1].Split('&'); foreach (string part in typeVenProdRevArray) { if (part.StartsWith("VEN_")) { vendor = part.Substring(("VEN_").Length); } if (part.StartsWith("PROD_")) { productId = part.Substring(("PROD_").Length); } } } } disk.DiskNumber = w32diskDrive.Index.Value; disk.Vendor = vendor; disk.ProductId = productId; //Data From Win32_DiskDrive Below disk.Availability = w32diskDrive.Availability; disk.BytesPerSector = w32diskDrive.BytesPerSector; disk.Capabilities = w32diskDrive.Capabilities; disk.CapabilityDescriptions = w32diskDrive.CapabilityDescriptions; disk.Caption = w32diskDrive.Caption; disk.CompressionMethod = w32diskDrive.CompressionMethod; disk.ConfigManagerErrorCode = w32diskDrive.ConfigManagerErrorCode; disk.ConfigManagerUserConfig = w32diskDrive.ConfigManagerUserConfig; disk.CreationClassName = w32diskDrive.CreationClassName; disk.DefaultBlockSize = w32diskDrive.DefaultBlockSize; disk.Description = w32diskDrive.Description; disk.DeviceID = w32diskDrive.DeviceID; disk.ErrorCleared = w32diskDrive.ErrorCleared; disk.ErrorDescription = w32diskDrive.ErrorDescription; disk.ErrorMethodology = w32diskDrive.ErrorMethodology; disk.FirmwareRevision = w32diskDrive.FirmwareRevision; disk.Index = w32diskDrive.Index; disk.InstallDate = w32diskDrive.InstallDate; disk.InterfaceType = w32diskDrive.InterfaceType; disk.Manufacturer = w32diskDrive.Manufacturer; disk.MaxBlockSize = w32diskDrive.MaxBlockSize; disk.MaxMediaSize = w32diskDrive.MaxMediaSize; disk.MediaLoaded = w32diskDrive.MediaLoaded; disk.MediaType = w32diskDrive.MediaType; disk.MinBlockSize = w32diskDrive.MinBlockSize; disk.Model = w32diskDrive.Model; disk.Name = w32diskDrive.Name; disk.NeedsCleaning = w32diskDrive.NeedsCleaning; disk.NumberOfMediaSupported = w32diskDrive.NumberOfMediaSupported; disk.Partitions = w32diskDrive.Partitions; disk.PNPDeviceID = w32diskDrive.PNPDeviceID; disk.PowerManagementCapabilities = w32diskDrive.PowerManagementCapabilities; disk.PowerManagementSupported = w32diskDrive.PowerManagementSupported; disk.SCSIBus = w32diskDrive.SCSIBus; disk.SCSILogicalUnit = w32diskDrive.SCSILogicalUnit; disk.SCSIPort = w32diskDrive.SCSIPort; disk.SCSITargetId = w32diskDrive.SCSITargetId; disk.SectorsPerTrack = w32diskDrive.SectorsPerTrack; disk.SerialNumber = String.IsNullOrEmpty(w32diskDrive.SerialNumber) ? "" : w32diskDrive.SerialNumber.Trim(); disk.Signature = w32diskDrive.Signature; disk.Size = w32diskDrive.Size; disk.Status = w32diskDrive.Status; disk.StatusInfo = w32diskDrive.StatusInfo; disk.SystemCreationClassName = w32diskDrive.SystemCreationClassName; disk.SystemName = w32diskDrive.SystemName; disk.TotalCylinders = w32diskDrive.TotalCylinders; disk.TotalHeads = w32diskDrive.TotalHeads; disk.TotalSectors = w32diskDrive.TotalSectors; disk.TotalTracks = w32diskDrive.TotalTracks; disk.TracksPerCylinder = w32diskDrive.TracksPerCylinder; detectedDisks.Add(disk); } else { diskIterationErrors.Add("Physical Disk " + w32diskDrive.Index.Value.ToString() + " is missing fundamental attributes."); continue; // Jump out without adding it to the collection to return. } } } } catch (Exception ex) { if ((null != w32diskDrive) && (w32diskDrive.Index.HasValue)) { diskIterationErrors.Add("Unexpected error while processing Physical Disk " + w32diskDrive.Index.Value.ToString() + ": " + ex.Message); continue; // Jump out without adding it to the collection to return. } else { diskIterationErrors.Add("Unexpected error while processing Physical Disk: " + ex.Message); continue; // Jump out without adding it to the collection to return. } } } // See if we had any errors while iterating the disks. if (false == hadError) { if (diskIterationErrors.Count > 0) { hadError = true; errorMessage = "Encountered errors while iterating disks: " + String.Join("; ", diskIterationErrors); } } if (false == hadError) { // Since gathering SMART data can take a while, we don't do it unless caller wants us to. if (doGatherSmartData) { if (false == File.Exists(SMARTCTL_FULL_FILE_PATH)) { hadError = true; errorMessage = "Unable to find third-party SMARTCTL software at: " + SMARTCTL_FULL_FILE_PATH; } else { try { // Grab the SMART-related data for the detected disks. populateSmartAttributes_againstSmartctl(SMARTCTL_FULL_FILE_PATH, detectedDisks); // Consider the SMART-related data, and apply the SMART grades. foreach (DetectedDisk disk in detectedDisks) { // Interpret the SMART output only if it is available and enabled. if (disk.SmartSupport == DiskSmartSupport.AvailableAndEnabled) { var smartAttributeFailures = disk.SmartAttributes.Values.Where(sav => false == sav.IsOK); if (true == smartAttributeFailures.Any()) { // Okay, we know there is at least one failure reported by the SMART attributes. // We will want to list out the problems. // So see if there was also an overall failure. string smartGrade = "FAILED"; if (disk.OverallFailurePredicted.HasValue && disk.OverallFailurePredicted.Value) { // Also an overall failure. smartGrade += " [overall failure predicted; also failures with SMART attributes: "; } else { // No overall failure predicted, but at least one SMART attribute failure reported. smartGrade += " [failures with SMART attributes: "; } bool isFirstFailure = true; foreach (var failure in smartAttributeFailures) { if (false == isFirstFailure) { smartGrade += ", "; } smartGrade += failure.AttributeDescription; } smartGrade += "]"; disk.SmartGrade = smartGrade; disk.SmartPassed = false; } else { if (disk.OverallFailurePredicted.HasValue) { if (disk.OverallFailurePredicted.Value) { disk.SmartGrade = "FAILED [overall failure predicted]"; disk.SmartPassed = false; } else { // We want to let the user know if we are passing it based on only the crude check (without detailed SMART data). if (disk.SmartAttributes.Values.Any()) { // We had some detailed SMART data, yet nothing to cause failure. disk.SmartGrade = "PASSED [found SMART attributes]"; disk.SmartPassed = true; } else { // We are passing it on the crude check, without any detailed SMART data. disk.SmartGrade = "PASSED [no SMART attributes found]"; disk.SmartPassed = true; } } } else { // We really don't know. disk.SmartGrade = "UNK [unable to find overall failure indicator]"; disk.SmartPassed = null; // To indicate unknown. } } } else { // SMART is not available and enabled on the disk. if (disk.SmartSupport == DiskSmartSupport.Unavailable) { disk.SmartGrade = "UNK [SMART unavailable]"; disk.SmartPassed = null; // To indicate unknown. } else if (disk.SmartSupport == DiskSmartSupport.AvailableButDisabled) { disk.SmartGrade = "UNK [SMART disabled]"; disk.SmartPassed = null; // To indicate unknown. } else { disk.SmartGrade = "UNK [unable to determine SMART support]"; disk.SmartPassed = null; // To indicate unknown. } } } } catch (Exception ex) { hadError = true; errorMessage = "Encountered unexpected error while gathering SMART data: " + ex.Message; } } } } return detectedDisks; }
private void populateSmartAttributesForDisk_againstSmartctl(string smartCtlExeFullPath, DetectedDisk disk, string forceDeviceType = null) { // Build up the command that will gather all SMART information for a given physical disk. string command = null; if (String.IsNullOrEmpty(forceDeviceType)) { // Call the normal routine to gather all SMART data, letting it determine the device type by itself. command = @"""" + smartCtlExeFullPath + @"""" + " -a /dev/pd" + disk.DiskNumber.ToString(); } else { // Rather than rely on SMARTCTL to determine the device type, force it to treat it as a certain device type, because we have found // that it doesn't always choose the correct device type, by itself. And when it has the wrong device type, it will incorrectly // report that SMART support is unavailable. command = @"""" + smartCtlExeFullPath + @"""" + " -a -d " + forceDeviceType + " /dev/pd" + disk.DiskNumber.ToString(); } System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command); procStartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; procStartInfo.RedirectStandardOutput = true; procStartInfo.RedirectStandardError = false; procStartInfo.UseShellExecute = false; procStartInfo.CreateNoWindow = true; System.Diagnostics.Process proc = new System.Diagnostics.Process(); proc.StartInfo = procStartInfo; proc.Start(); string standardOutput = proc.StandardOutput.ReadToEnd(); // Seems odd to read to end before waiting for exit, but that's what ol' MSDN tells us to do. proc.WaitForExit(); if (false == String.IsNullOrEmpty(standardOutput)) { // Grab a copy of the big old output. disk.SmartctlOutput = standardOutput; string[] outputLines = standardOutput.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); // Read SMART support and rotation rate, which is in the information section. // Seek past the header line that denotes the information section, and parse out the lines that speak to SMART support and rotation rate. bool isPastInfoSectionHeader = false; string supportLabel = "SMART support is: "; string rotationRateLabel = "Rotation Rate: "; bool foundSupportLabels = false; bool supportIsAvailable = false; bool supportIsEnabled = false; for (int lineIdx = 0; lineIdx < outputLines.Length; lineIdx++) { string line = outputLines[lineIdx]; if ((false == isPastInfoSectionHeader) && (line.StartsWith("=== START OF INFORMATION SECTION ==="))) { isPastInfoSectionHeader = true; } else if (isPastInfoSectionHeader) { if (line.StartsWith("=== START")) { // Start of the next section, so we are past the area that reveals SMART support and rotation rate. break; } else { // This is in the first chunk of lines after the information section header, where we find the SMART support indicators and rotation rate. if (line.StartsWith(supportLabel)) { foundSupportLabels = true; string supportValue = line.Substring(supportLabel.Length).Trim(); if (supportValue.StartsWith("Available")) { supportIsAvailable = true; } else if (supportValue.StartsWith("Enabled")) { supportIsEnabled = true; } } else if (line.StartsWith(rotationRateLabel)) { // Found the rotation rate. disk.SmartRotationRate = line.Substring(rotationRateLabel.Length).Trim(); } } } } if (foundSupportLabels) { if (supportIsAvailable) { if (supportIsEnabled) { disk.SmartSupport = DiskSmartSupport.AvailableAndEnabled; } else { disk.SmartSupport = DiskSmartSupport.AvailableButDisabled; } } else { disk.SmartSupport = DiskSmartSupport.Unavailable; } } // Look for additional SMART information only if SMART support is available and enabled. if (disk.SmartSupport == DiskSmartSupport.AvailableAndEnabled) { // Read SMART overall health test result (or health status, as not every drive supports self test logging). // Seek past the header line that denotes the data section, and parse out the line that speaks to the SMART overall health test result (or health status). bool isPastDataSectionHeader = false; string overallTestResultLabel = "SMART overall-health self-assessment test result: "; string healthStatusLabel = "SMART Health Status: "; for (int lineIdx = 0; lineIdx < outputLines.Length; lineIdx++) { string line = outputLines[lineIdx]; if ((false == isPastDataSectionHeader) && (line.StartsWith("=== START OF READ SMART DATA SECTION ==="))) { isPastDataSectionHeader = true; } else if (isPastDataSectionHeader) { if (line.StartsWith("=== START")) { // Start of the next section, so we are past the area that reveals SMART overall health test result (or health status). break; } else { // This is in the first chunk of lines after the data section header, where we find the SMART overall health test result (or health status). if (line.StartsWith(overallTestResultLabel)) { string overallTestResultValue = line.Substring(overallTestResultLabel.Length).Trim(); if (overallTestResultValue.StartsWith("PASSED")) { disk.OverallFailurePredicted = false; } else { disk.OverallFailurePredicted = true; } } else if (line.StartsWith(healthStatusLabel)) { // Remember, no every drive supports self test logging, so on these disks we read the health status instead. string healthStatusResultValue = line.Substring(healthStatusLabel.Length).Trim(); if (healthStatusResultValue == "OK") { disk.OverallFailurePredicted = false; } else { disk.OverallFailurePredicted = true; } } } } } // Read SMART attributes. // Seek past the header line that appears above the attribute lines, then parse the attribute lines. bool isPastAttrHeader = false; bool hadErrorParsingAttributes = false; for (int lineIdx = 0; lineIdx < outputLines.Length; lineIdx++) { string line = outputLines[lineIdx]; if ((false == isPastAttrHeader) && (line.StartsWith("ID#"))) { isPastAttrHeader = true; } else if (isPastAttrHeader) { if (line.Trim().Length == 0) { // First blank line after listing the attributes, which means there are no more attributes listed. break; } else { // This must be an attribute line. bool hadParseError = false; int smartAttributeId = -1; string attributeName = null; int value = 0; int worst = 0; int thresh = 0; string attrType = null; string rawValue = null; parseSmartctlAttributeOutputLine(line, out hadParseError, out smartAttributeId, out attributeName, out value, out worst, out thresh, out attrType, out rawValue); if (hadParseError) { // Keep track of the error. hadErrorParsingAttributes = true; } else if (false == disk.SmartAttributes.ContainsKey(smartAttributeId)) { DetectedDisk.SmartAttribute attr = new DetectedDisk.SmartAttribute(attributeName); attr.AttributeType = attrType; attr.Current = value; attr.Worst = worst; attr.RawValue = rawValue; attr.Threshold = thresh; if (attrType.ToUpper() == "PRE-FAIL") { // This type of attribute has a meaningful threshold. if (value <= thresh) { // Failure is imminent. attr.IsOK = false; } else { attr.IsOK = true; } } else { attr.IsOK = true; // Must be an OLD-AGE attribute, so no real threshold ... let's just say its okay. } disk.SmartAttributes.Add(smartAttributeId, attr); } } } } if (hadErrorParsingAttributes) { // There was at least one attribute line that we had trouble parsing. // So, if the disk hasn't had any sort of SMART failure indicated up to this point, we are going to flip the SMART support indicator to "Unknown", since // we don't really want to give it a passing grade if we had problems parsing attributes. if ((disk.OverallFailurePredicted.HasValue) && (disk.OverallFailurePredicted.Value)) { // Already going to be reporting a failure, so the parse error doesn't really affect anything. } else { var smartAttributeFailures = disk.SmartAttributes.Values.Where(sav => false == sav.IsOK); if (true == smartAttributeFailures.Any()) { // Already going to be reporting a failure, so the parse error doesn't really affect anything. } else { // No SMART failure of any kind to report -- so we need to flip the SMART support indicator to "Unknown", so that this disk doesn't slip through with a passing grade. disk.SmartSupport = DiskSmartSupport.Unknown; } } } } } }