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

How to Retrieve SMART Information from NVMe SSDs in Delphi|Health Log & IOCTL Usage Example

Japanese

How to Retrieve SMART Information from NVMe SSDs in Delphi|Health Log & IOCTL Usage Example

This page explains how to retrieve SMART information (Health Information Log) from NVMe‑connected SSDs using Delphi.
It provides an implementation example that uses Windows IOCTL_STORAGE_QUERY_PROPERTY to check the health status of NVMe storage devices.
This is a technical note for developers who want to perform diagnostics or retrieve logs from NVMe SSDs in Delphi.

If you want to obtain S.M.A.R.T. information (disk health data) from SATA‑connected SSDs/HDDs or SATA‑type M.2 SSDs, please refer to the following page:
Retrieve SMART Information from SATA SSDs and Hard Drives (https://mam-mam.net/en/delphi/smart.html)

Launch Delphi with Administrator Privileges

Start Delphi with administrator privileges.

Creating a New Project and Designing the Form

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.

Configure the Application to Run with Administrator Privileges

Click [Project] → [Options].
In the left pane, select [Application] → [Manifest].
Set the execution level to “Require administrator” and click the “Save” button.

Writing the Source Code

Double‑click “Button1” 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, Vcl.Grids;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  NVMeStorageQueryProperty =packed record
    PropertyId:UInt32;    //STORAGE_PROPERTY_ID
    QueryType:UInt32;     //STORAGE_QUERY_TYPE
    ProtocolType:UInt32;  //STORAGE_PROTOCOL_TYPE
    DataType:UInt32;      //STORAGE_PROTOCOL_NVME_DATA_TYPE
    ProtocolDataRequestValue:UInt32;
    ProtocolDataRequestSubValue:UInt32;
    ProtocolDataOffset:UInt32;
    ProtocolDataLength:UInt32;
    FixedProtocolReturnData:UInt32;
    ProtocolDataRequestSubValue2:UInt32;
    ProtocolDataRequestSubValue3:UInt32;
    ProtocolDataRequestSubValue4:UInt32;
    SMARTData:Array[0..511] of Byte;
  end;
  TNVMeStorageQueryProperty=NVMeStorageQueryProperty;

  STORAGE_PROPERTY_QUERY = packed record
    PropertyId: UInt32; //STORAGE_PROPERTY_ID
    QueryType: UInt32;  //STORAGE_QUERY_TYPE
    AdditionalParameters: array [0..0] of Byte;
  end;
  TStoragePropertyQuery=STORAGE_PROPERTY_QUERY;

  STORAGE_PROTOCOL_SPECIFIC_DATA=packed record
    ProtocolType:UInt32;//STORAGE_PROTOCOL_TYPE
    DataType:UInt32;    //STORAGE_PROTOCOL_NVME_DATA_TYPE
    ProtocolDataRequestValue:UInt32;
    ProtocolDataRequestSubValue:UInt32;
    ProtocolDataOffset:UInt32;
    ProtocolDataLength:UInt32;
    FixedProtocolReturnData:UInt32;
    ProtocolDataRequestSubValue2:UInt32;
    ProtocolDataRequestSubValue3:UInt32;
    ProtocolDataRequestSubValue4:UInt32;
  end;
  TStorageProtocolSpecificData=STORAGE_PROTOCOL_SPECIFIC_DATA;
  PStorageProtocolSpecificData=^TStorageProtocolSpecificData;

  NVME_HEALTH_INFO_LOG=packed record
    DUMMYSTRUCTNAME:Byte;
    Temperature:Array[0..1] of Byte;
    AvailableSpare:Byte;
    AvailableSpareThreshold:Byte;
    PercentageUsed:Byte;
    Reserved0:array[0..25] of Byte;
    DataUnitRead:array[0..15] of Byte;
    DataUnitWritten:array[0..15] of Byte;
    HostReadCommands:array[0..15] of Byte;
    HostWrittenCommands:array[0..15] of Byte;
    ControllerBusyTime:array[0..15] of Byte;
    PowerCycle:array[0..15] of Byte;
    PowerOnHours:array[0..15] of Byte;
    UnsafeShutdowns:array[0..15] of Byte;
    MediaErrors:array[0..15] of Byte;
    ErrorInfoLogEntryCount:array[0..15] of Byte;
    WarningCompositeTemperatureTime:Cardinal;
    CriticalCompositeTemperatureTime:Cardinal;
    TemperatureSensor1:Word;
    TemperatureSensor2:Word;
    TemperatureSensor3:Word;
    TemperatureSensor4:Word;
    TemperatureSensor5:Word;
    TemperatureSensor6:Word;
    TemperatureSensor7:Word;
    TemperatureSensor8:Word;
    Reserved1:array[0..295] of Byte;
  end;
  TNVMeHealthInfoLog=NVME_HEALTH_INFO_LOG;
  PNVMeHealthInfoLog=^TNVMeHealthInfoLog;

  STORAGE_PROPERTY_ID = (
    StorageDeviceProperty = 0,
    StorageAdapterProperty,
    StorageDeviceIdProperty,
    StorageDeviceUniqueIdProperty,
    StorageDeviceWriteCacheProperty,
    StorageMiniportProperty,
    StorageAccessAlignmentProperty,
    StorageDeviceSeekPenaltyProperty,
    StorageDeviceTrimProperty,
    StorageDeviceWriteAggregationProperty,
    StorageDeviceDeviceTelemetryProperty,
    StorageDeviceLBProvisioningProperty,
    StorageDevicePowerProperty,
    StorageDeviceCopyOffloadProperty,
    StorageDeviceResiliencyProperty,
    StorageDeviceMediumProductType,
    StorageAdapterRpmbProperty,
    StorageAdapterCryptoProperty,
    StorageDeviceIoCapabilityProperty = 48,
    StorageAdapterProtocolSpecificProperty,
    StorageDeviceProtocolSpecificProperty,
    StorageAdapterTemperatureProperty,
    StorageDeviceTemperatureProperty,
    StorageAdapterPhysicalTopologyProperty,
    StorageDevicePhysicalTopologyProperty,
    StorageDeviceAttributesProperty,
    StorageDeviceManagementStatus,
    StorageAdapterSerialNumberProperty,
    StorageDeviceLocationProperty,
    StorageDeviceNumaProperty,
    StorageDeviceZonedDeviceProperty,
    StorageDeviceUnsafeShutdownCount,
    StorageDeviceEnduranceProperty,
    StorageDeviceLedStateProperty,
    StorageDeviceSelfEncryptionProperty = 64,
    StorageFruIdProperty
  );

  STORAGE_QUERY_TYPE = (
    PropertyStandardQuery = 0,
    PropertyExistsQuery,
    PropertyMaskQuery,
    PropertyQueryMaxDefined
  );

  STORAGE_PROTOCOL_TYPE=(
    ProtocolTypeUnknown = $00,
    ProtocolTypeScsi,
    ProtocolTypeAta,
    ProtocolTypeNvme,
    ProtocolTypeSd,
    ProtocolTypeUfs,
    ProtocolTypeProprietary = $7E,
    ProtocolTypeMaxReserved = $7F
  );

  STORAGE_PROTOCOL_NVME_DATA_TYPE=(
    NVMeDataTypeUnknown,
    NVMeDataTypeIdentify,
    NVMeDataTypeLogPage,
    NVMeDataTypeFeature
  );

var
  Form1: TForm1;

const
  NVME_NAMESPACE_ALL:UINT32              =$FFFFFFFF;
  NVME_LOG_PAGE_ERROR_INFO               = $01;
  NVME_LOG_PAGE_HEALTH_INFO              = $02;
  NVME_LOG_PAGE_FIRMWARE_SLOT_INFO       = $03;
  NVME_LOG_PAGE_CHANGED_NAMESPACE_LIST   = $04;
  NVME_LOG_PAGE_COMMAND_EFFECTS          = $05;
  NVME_LOG_PAGE_DEVICE_SELF_TEST         = $06;
  NVME_LOG_PAGE_TELEMETRY_HOST_INITIATED = $07;
  NVME_LOG_PAGE_TELEMETRY_CTLR_INITIATED = $08;
  NVME_LOG_PAGE_RESERVATION_NOTIFICATION = $80;
  NVME_LOG_PAGE_SANITIZE_STATUS          = $81;
  NVME_LOG_PAGE_VU_START                 = $C0;
  NVME_LOG_PAGE_WCS_CLOUD_SSD            = $C0;
  NVME_LOG_PAGE_VU_END                   = $FF;


implementation

{$R *.dfm}

function GetNVMeHealthInfoLog(DriveNum:Integer):TNVMeHealthInfoLog;
var hd:THandle;
    st:string;
    Buffer: array [0..4095] of Byte;
    // Create a variable that shares the same memory address as Buffer
    SPQuery:TStoragePropertyQuery absolute Buffer;
    pSPSpecificData:PStorageProtocolSpecificData;
    BytesReturned:UInt32;
    pHealthInfoLog:PNVMeHealthInfoLog;
begin
  ZeroMemory(@result,SizeOf(TNVMeHealthInfoLog));
  st:='\\.\PhysicalDrive'+Trim(InttoStr(DriveNum));
  // Obtain the device handle
  hd:=CreateFile(PChar(st),GENERIC_READ or GENERIC_WRITE,
    FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0
  );
  if hd<>INVALID_HANDLE_VALUE then
  begin
    ZeroMemory(@Buffer[0],Length(Buffer));
    SPQuery.PropertyId := Ord(STORAGE_PROPERTY_ID.StorageAdapterProtocolSpecificProperty);
    SPQuery.QueryType := Ord(STORAGE_QUERY_TYPE.PropertyStandardQuery);

    pSPSpecificData:=@(SPQuery.AdditionalParameters[0]);
    pSPSpecificData.ProtocolType:=ord(STORAGE_PROTOCOL_TYPE.ProtocolTypeNvme);
    pSPSpecificData.DataType:=ord(STORAGE_PROTOCOL_NVME_DATA_TYPE.NVMeDataTypeLogPage);
    pSPSpecificData.ProtocolDataRequestValue:=NVME_LOG_PAGE_HEALTH_INFO;
    pSPSpecificData.ProtocolDataRequestSubValue:=0;
    pSPSpecificData.ProtocolDataRequestSubValue2:=0;
    pSPSpecificData.ProtocolDataRequestSubValue3:=0;
    pSPSpecificData.ProtocolDataRequestSubValue4:=0;
    pSPSpecificData.ProtocolDataOffset:=SizeOf(STORAGE_PROTOCOL_SPECIFIC_DATA);//40
    pSPSpecificData.ProtocolDataLength:=sizeof(NVME_HEALTH_INFO_LOG);//512
    pHealthInfoLog:=
      @buffer[SizeOf(TStoragePropertyQuery)+
      Sizeof(TStorageProtocolSpecificData)-1];

    if DeviceIoControl(hd,IOCTL_STORAGE_QUERY_PROPERTY,
      @buffer[0],Length(buffer),
      @buffer[0],Length(Buffer),BytesReturned,nil) then
    begin
      CopyMemory(@result,pHealthInfoLog,SizeOf(TNVMeHealthInfoLog));
    end;
    CloseHandle(hd);
  end;
end;

function toRawValue(b:array of Byte):string;
var i:Integer;
begin
  result:='';
  for i := Low(b) to High(b) do
  begin
    result:=result+Format('%2.2x',[b[i]]);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var reg:TRegistry;
    i,ct:integer;
    InfoLog:TNVMeHealthInfoLog;
begin
  Memo1.Clear;
  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;

  for i := 0 to ct-1 do //Loop through all physical drives
  begin
    InfoLog:=GetNVMeHealthInfoLog(i);
    if (InfoLog.Temperature[0]<>0) or (InfoLog.Temperature[1]<>0) then
    begin
      Memo1.Lines.Add(
        Format('Temperature:%d℃',
          [InfoLog.Temperature[0]+(InfoLog.Temperature[1] shl 8)-273]
        )
        +#13#10+
        Format('AvailableSpare:%d%%',[InfoLog.AvailableSpare])
        +#13#10+
        Format('AvailableSpareThreshold:%d%%',[InfoLog.AvailableSpareThreshold])
        +#13#10+
        Format('PercentageUsed:%d%%',[InfoLog.PercentageUsed])
        +#13#10+
        Format('DataUnitRead:%s',[toRawValue(InfoLog.DataUnitRead)])
        +#13#10+
        Format('DataUnitWritten:%s',[toRawValue(InfoLog.DataUnitWritten)])
        +#13#10+
        Format('HostReadCommands:%s',[toRawValue(InfoLog.HostReadCommands)])
        +#13#10+
        Format('HostWrittenCommands:%s',[toRawValue(InfoLog.HostWrittenCommands)])
        +#13#10+
        Format('ControllerBusyTime:%s',[toRawValue(InfoLog.ControllerBusyTime)])
        +#13#10+
        Format('PowerCycle:%s',[toRawValue(InfoLog.PowerCycle)])
        +#13#10+
        Format('PowerOnHours:%s',[toRawValue(InfoLog.PowerOnHours)])
        +#13#10+
        Format('UnsafeShutdowns:%s',[toRawValue(InfoLog.UnsafeShutdowns)])
        +#13#10+
        Format('MediaErrors:%s',[toRawValue(InfoLog.MediaErrors)])
        +#13#10+
        Format('ErrorInfoLogEntryCount:%s',
          [toRawValue(InfoLog.ErrorInfoLogEntryCount)]
        )
        +#13#10+
        Format('WarningCompositeTemperatureTime:%d',
          [InfoLog.WarningCompositeTemperatureTime]
        )
        +#13#10+
        Format('CriticalCompositeTemperatureTime:%d',
          [InfoLog.CriticalCompositeTemperatureTime]
        )
      );

      Memo1.Lines.Add(
        Format('TemperatureSensor1:%d', [InfoLog.TemperatureSensor1])
        +#13#10+
        Format('TemperatureSensor2:%d', [InfoLog.TemperatureSensor2])
        +#13#10+
        Format('TemperatureSensor3:%d', [InfoLog.TemperatureSensor3])
        +#13#10+
        Format('TemperatureSensor4:%d', [InfoLog.TemperatureSensor4])
        +#13#10+
        Format('TemperatureSensor5:%d', [InfoLog.TemperatureSensor5])
        +#13#10+
        Format('TemperatureSensor6:%d', [InfoLog.TemperatureSensor6])
        +#13#10+
        Format('TemperatureSensor7:%d', [InfoLog.TemperatureSensor7])
        +#13#10+
        Format('TemperatureSensor8:%d', [InfoLog.TemperatureSensor8])
      );
    end;
  end;
end;

end.

Run the Application

Click [Run] → [Run], or press the “Run” button to execute the application.
(The project will be compiled and then executed.)
If your system has an NVMe‑connected M.2 SSD, clicking “Button1” will display its health information.