トップへ(mam-mam.net/)

Retrieving SMART Data in Delphi|How to Check SSD/HDD Health Status

Japanese

Retrieving SMART Data in Delphi|How to Check SSD/HDD Health Status

This page explains how to retrieve SMART information (Self-Monitoring Analysis and Reporting Technology) from SATA-connected SSDs and hard drives using Delphi.

If you want to check SATA SMART data and NVMe Health Information Logs all at once, try the Windows application developed in Delphi:
SMART/Health Information Log Reader 3.0.

If you need to obtain Health Information data from an NVMe M.2 SSD using Delphi, please refer to the following source code:
Sample code for retrieving Health Information from an NVMe M.2 SSD

For SATA-based M.2 SSDs (SATA-type M.2), SMART information can be retrieved using the source code provided on this page.
This page explains how to create a unit that reads disk health status and attribute values, along with sample code demonstrating an implementation using the DeviceIoControl API.
Running the application with administrator privileges is required.

Create a New Project in Delphi

Launch Delphi, then select File → New → Windows VCL Application.
Choose a blank application and click OK to create the project.
Drag and drop a TButton and a TMemo onto the form.
Click “Save All” to create a project folder and save the project and unit files.


Creating the SMART Information Retrieval Unit

Select File → New → Unit – Delphi to create a new unit.
Enter the source code shown below and save the file as “USmart.pas”.

unit USmart;

interface

uses Winapi.Windows, System.SysUtils;

type
  TMamSmart=record
    AttrID:Byte;
    StatusFlags:Word;
    Value:Byte;
    WorstValue:Byte;
    Threshold:Byte;
    RawValue:Array[0..5] of Byte;
  end;
  TMamSmartArray=Array of TMamSmart;

// Given DriveNum (starting from 0) and MamSmartArray, retrieves SMART information and stores it in MamSmartArray
procedure getSmart(DriveNum:Integer;var MamSmartArray:TMamSmartArray);

implementation

const
  NUM_ATTR_STRUCTS = 30;
  SMART_READ_ATTRIBUTE_VALUES = $D0;
  SMART_READ_ATTRIBUTE_THRESHOLDS = $D1;

  SMART_CYL_LOW  = $4F;
  SMART_CYL_HIGH = $C2;
  // SMART_CMD: Performs a SMART command
  IDE_EXECUTE_SMART_FUNCTION = $B0;
  // BUFFER SIZE
  READ_ATTRIBUTE_BUFFER_SIZE = 512;
  // Commands for the DeviceIoControl API
  DFP_GET_VERSION          = $00074080;
  DFP_SEND_DRIVE_COMMAND   = $0007c084;
  DFP_RECEIVE_DRIVE_DATA   = $0007c088;

type
  // Structure for SMART attribute information
  DriveAttribute = packed record
    bAttrID:Byte;
    wStatusFlags:Word;
    bAttrValue:Byte;
    bWorstValue:Byte;
    bRawValue:array[0..5] of Byte;
    bReserved:Byte;
  end;

  // Structures for IN/OUT parameters of the DFP_RECEIVE_DRIVE_DATA command
  _IDEREGS=packed record
    bFeaturesReg:Byte;
    bSectorCountReg:Byte;
    bSectorNumberReg:Byte;
    bCylLowReg:Byte;
    bCylHighReg:Byte;
    bDriveHeadReg:Byte;
    bCommandReg:Byte;
    bReserved:Byte;
  end;
  IDEREGS=_IDEREGS;

  _SENDCMDINPARAMS=packed record
    cBufferSize:DWORD;
    irDriveRegs:IDEREGS;
    bDriveNumber:Byte;
    bReserved:array[0..2] of Byte;
    dwReserved:array[0..3] of DWORD;
    bBuffer:array[0..0] of Byte;
  end;
  SENDCMDINPARAMS=_SENDCMDINPARAMS;

  _DRIVERSTATUS = packed record
    bDriveError:Byte;
    bIDEStatus:Byte;
    bReserved:array[0..1] of Byte;
    dwReserved:array[0..1] of DWORD;
  end;
  DRIVERSTATUS=_DRIVERSTATUS;

  _BSENDCMDOUTPARAMS = packed record
    cBufferSize:DWORD;
    DriverStatus:DRIVERSTATUS;
    bBuffer:array [0..511] of Byte;
  end;
  BSENDCMDOUTPARAMS=_BSENDCMDOUTPARAMS;

  BDriveAttribute = packed record
    AttrStructRevision:WORD;
    DA:array[0..NUM_ATTR_STRUCTS-1] of DriveAttribute;
  end;
  PBDriveAttribute =^BDriveAttribute;

  // Structure for SMART threshold information
  AttrThreshold = packed record
    bAttrID:Byte;
    bWarrantyThreshold:Byte;
    bReserved:array[0..9] of Byte;
  end;
  BAttrThreshold = packed record
    ThreStructRevision:Word;
    AT:array[0..NUM_ATTR_STRUCTS-1] of AttrThreshold
  end;
  PBAttrThreshold =^BAttrThreshold;

procedure getSmart(DriveNum:Integer;var MamSmartArray:TMamSmartArray);
var hd:THandle;
    st:string;
    wst:array[0..127] of char;
    br:Cardinal;
    SCIP:SENDCMDINPARAMS;
    SCOP_ATTR:BSENDCMDOUTPARAMS;
    SCOP_THRE:BSENDCMDOUTPARAMS;
    PBAttr:PBDriveAttribute;
    PBThre:PBAttrThreshold;
    i,ct:Integer;
begin
  st:='\\.\PhysicalDrive'+Trim(InttoStr(DriveNum));
  strpcopy(wst,st);

  // Obtain device handle
  hd:=CreateFile(wst,GENERIC_READ or GENERIC_WRITE,
                       FILE_SHARE_READ or FILE_SHARE_WRITE,
                       nil, OPEN_EXISTING, 0, 0);

  if hd = INVALID_HANDLE_VALUE then exit;

  ZeroMemory(@SCIP,SizeOf(SENDCMDINPARAMS));
  ZeroMemory(@SCOP_ATTR,SizeOf(BSENDCMDOUTPARAMS));

  SCIP.irDriveRegs.bFeaturesReg:=SMART_READ_ATTRIBUTE_VALUES;
  SCIP.irDriveRegs.bSectorCountReg:=1;
  SCIP.irDriveRegs.bSectorNumberReg:=1;
  SCIP.irDriveRegs.bCylLowReg:=SMART_CYL_LOW;
  SCIP.irDriveRegs.bCylHighReg:=SMART_CYL_HIGH;
  SCIP.irDriveRegs.bDriveHeadReg:=$A0 or ((DriveNum and 1) shl 4);
  SCIP.irDriveRegs.bCommandReg:=IDE_EXECUTE_SMART_FUNCTION;
  SCIP.cBufferSize:=READ_ATTRIBUTE_BUFFER_SIZE;
  SCIP.bDriveNumber:=DriveNum;

  DeviceIoControl(hd,DFP_RECEIVE_DRIVE_DATA,
    @SCIP,sizeof(SCIP),@SCOP_ATTR,sizeof(SCOP_ATTR),br,nil);
  PBAttr:=PBDriveAttribute(Pointer(@(SCOP_ATTR.bBuffer)[0]));

  ZeroMemory(@SCIP,SizeOf(SENDCMDINPARAMS));
  ZeroMemory(@SCOP_THRE,SizeOf(BSENDCMDOUTPARAMS));

  SCIP.irDriveRegs.bFeaturesReg:=SMART_READ_ATTRIBUTE_THRESHOLDS;
  SCIP.irDriveRegs.bSectorCountReg:=1;
  SCIP.irDriveRegs.bSectorNumberReg:=1;
  SCIP.irDriveRegs.bCylLowReg:=SMART_CYL_LOW;
  SCIP.irDriveRegs.bCylHighReg:=SMART_CYL_HIGH;
  SCIP.irDriveRegs.bDriveHeadReg:=$A0 or ((DriveNum and 1) shl 4);
  SCIP.irDriveRegs.bCommandReg:=IDE_EXECUTE_SMART_FUNCTION;
  SCIP.cBufferSize:=READ_ATTRIBUTE_BUFFER_SIZE;
  SCIP.bDriveNumber:=DriveNum;

  DeviceIoControl(hd,DFP_RECEIVE_DRIVE_DATA,
    @SCIP,sizeof(SCIP),@SCOP_THRE,sizeof(SCOP_THRE),br,nil);
  PBThre:=PBAttrThreshold(pointer(@SCOP_THRE.bBuffer));
  CloseHandle(hd);

  setlength(MamSmartArray,0);
  ct:=0;
  for i := 0 to NUM_ATTR_STRUCTS-1 do
  begin
    if PBAttr.DA[i].bAttrID>0 then
    begin
      inc(ct);
      setlength(MamSmartArray,ct);
      MamSmartArray[ct-1].AttrID:=PBAttr.DA[i].bAttrID;
      MamSmartArray[ct-1].StatusFlags:=PBAttr.DA[i].wStatusFlags;
      MamSmartArray[ct-1].Value:=PBAttr.DA[i].bAttrValue;
      MamSmartArray[ct-1].WorstValue:=PBAttr.DA[i].bWorstValue;
      MamSmartArray[ct-1].Threshold:=PBThre.AT[i].bWarrantyThreshold;
      MamSmartArray[ct-1].RawValue[0]:=PBAttr.DA[i].bRawValue[0];
      MamSmartArray[ct-1].RawValue[1]:=PBAttr.DA[i].bRawValue[1];
      MamSmartArray[ct-1].RawValue[2]:=PBAttr.DA[i].bRawValue[2];
      MamSmartArray[ct-1].RawValue[3]:=PBAttr.DA[i].bRawValue[3];
      MamSmartArray[ct-1].RawValue[4]:=PBAttr.DA[i].bRawValue[4];
      MamSmartArray[ct-1].RawValue[5]:=PBAttr.DA[i].bRawValue[5];
    end;
  end;
end;

end.

Writing the Source Code for the Button1 Click Event

Double‑click Button1 on the Unit1 form to open the event handler and write the source code.

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls
  ,System.Win.Registry ,USmart;
type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;

implementation
{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var reg:TRegistry;
    i,j,ct:integer;
    smart:TMamSmartArray;
begin
  ct:=0;
  // Retrieve the number of physical drives from the registry
  reg:=TRegistry.Create(Winapi.Windows.KEY_READ);
  reg.RootKey:=HKEY_LOCAL_MACHINE;
  if reg.KeyExists(
    'SYSTEM\CurrentControlSet\Services\disk\Enum') then
  begin
    reg.OpenKey('SYSTEM\CurrentControlSet\Services\disk\Enum',False);
    ct:=reg.ReadInteger('Count');
  end;
  reg.CloseKey;
  reg.Free;

  // Loop through all physical drives
  for i := 0 to ct-1 do
  begin
    // Retrieve SMART information
    getSmart(i,smart);

    // If SMART information was successfully retrieved
    if length(smart)>0 then
    begin
      memo1.Lines.Add('DriveNum:'+IntToStr(i));

      // Loop through all SMART attributes
      for j := 0 to length(smart)-1 do
      begin
        memo1.Lines.Add(
          format('%3d,%4d,%2d,%2d,%2d,%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x',
          [ smart[j].AttrID, smart[j].StatusFlags,
            smart[j].Value,  smart[j].WorstValue, smart[j].Threshold,
            smart[j].RawValue[5], smart[j].RawValue[4],
            smart[j].RawValue[3], smart[j].RawValue[2],
            smart[j].RawValue[1], smart[j].RawValue[0]
          ])
        );
      end;
    end;
  end;
end;

end.

Compile

Select [Project] → [Compile All Projects] to build the application.

Run

This application requires administrator privileges (the DeviceIoControl API cannot be used without them), so clicking the Run button and then pressing Button1 will not work.
Right‑click the compiled executable file, choose “Run as administrator,” then click Button1.