3
votes

I need to get the following information for all of the physical disks in the system when Windows 10 storage spaces is enabled.

  • Model
  • Serial Number
  • Firmware Version
  • Capacity
  • Index of the Disk
  • Pnp Id of the disk (to get the SCSI controller name using CM_Get_Parent)
  • Location information (Bus number, Target Id and LUN)

What I have tried so far:

  1. Used WMI class MSFT_PhysicalDisk Although this class gives me the adapter number (so i can do without the disk PNP), the location information it gives is not complete when a disk is connected to a different PCI storage controller (such as the Marvell 92xx SATA 6g controller).

  2. Used SetupDiGetClassDevs with GUID_DEVINTERFACE_DISK, passed the handle to SetupDiGetDeviceInterface and used SetupDiGetDeviceInterfaceDetail for location information (Bus/Target Id/LUN), PNP Id, and Device Path. I can pass the device path to CreateFile and get the rest of the information (similar to this approach). The problem with this is that it does not give me all the physical disks. The disks under the storage spaces pool are omitted.

  3. Use an approach similar to the second one but instead of SetupDiGetDeviceInterface and SetupDiGetDeviceInterfaceDetail, use SetupDiEnumDeviceInfo and CM_Get_DevNode_Registry_Property (using the Disk Drives Guid from here). Although this gives me the location and PNP id for all of the physical disks, I can't use anything here (that I know of) to call CreateFile and get the rest of the details.

How can I get the above details for each of the physical disks when storage spaces is enabled?

As a side note, if there is a way to get the disk PNP id from the disk index using the CreateFile and DeviceIoControl, that can also be very helpful for me.

1
really better use CM_Get_Device_Interface_List with GUID_DEVINTERFACE_DISK and other CM_ api instead SetupDi api. CM_Get_Device_Interface_PropertyW with DEVPKEY_Device_InstanceId and than CM_Locate_DevNodeW. location - DEVPKEY_Device_LocationInfo with CM_Get_DevNode_PropertyW , Capacity, Serial Number - open device and send IOCTLs. really too many questions or you want complete program hereRbMm
@RbMm A sample would be much appreciatedAli Zahid
@RbMm Unfortunately using CM_Get_Device_Interface_List also skips over disks in the storage spaces poolAli Zahid
no, the CM_Get_Device_Interface_List is perfect working and nothis skip. error in your codeRbMm

1 Answers

2
votes

at first we need enumerate all disks in system by call CM_Get_Device_Interface_ListW and CM_Get_Device_Interface_List_SizeW with GUID_DEVINTERFACE_DISK

#include <Shlwapi.h>
#include <cfgmgr32.h>
#undef _NTDDSTOR_H_
#include <ntddstor.h>
#include <ntdddisk.h>

static volatile UCHAR guz;

CONFIGRET EnumDisks(PCSTR prefix, PGUID InterfaceClassGuid)
{
    CONFIGRET err;

    PVOID stack = alloca(guz);
    ULONG BufferLen = 0, NeedLen = 256;

    union {
        PVOID buf;
        PWSTR pszDeviceInterface;
    };

    for(;;) 
    {
        if (BufferLen < NeedLen)
        {
            BufferLen = RtlPointerToOffset(buf = alloca((NeedLen - BufferLen) * sizeof(WCHAR)), stack) / sizeof(WCHAR);
        }

        switch (err = CM_Get_Device_Interface_ListW(InterfaceClassGuid, 
            0, pszDeviceInterface, BufferLen, CM_GET_DEVICE_INTERFACE_LIST_PRESENT))
        {
        case CR_BUFFER_SMALL:
            if (err = CM_Get_Device_Interface_List_SizeW(&NeedLen, InterfaceClassGuid, 
                0, CM_GET_DEVICE_INTERFACE_LIST_PRESENT))
            {
        default:
            return err;
            }
            continue;

        case CR_SUCCESS:

            while (*pszDeviceInterface)
            {
                DbgPrint("Interface=[%S]\n", pszDeviceInterface);

                HANDLE hFile = CreateFileW(pszDeviceInterface, FILE_GENERIC_READ, 
                    FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);

                if (hFile != INVALID_HANDLE_VALUE)
                {
                    GetDiskPropertyByHandle(hFile);
                    CloseHandle(hFile);
                }

                GetPropertyByInterface(prefix, pszDeviceInterface);

                pszDeviceInterface += 1 + wcslen(pszDeviceInterface);
            }

            return CR_SUCCESS;
        }
    }
}

CONFIGRET EnumDisks()
{
    char prefix[256];
    memset(prefix, '\t', sizeof(prefix));
    prefix[sizeof(prefix) - 1] = 0;
    prefix[0] = 0;
    return EnumDisks(prefix + sizeof(prefix) - 1, const_cast<PGUID>(&GUID_DEVINTERFACE_DISK));
}

CM_Get_Device_Interface_ListW return multiple, NULL-terminated Unicode strings, each representing the symbolic link name of an interface instance.

from one side this symbolic link name can be passed to CreateFileW for open disk device. after this we can set some ioctl to disk - for get

  • Index of the Disk
  • Capacity
  • Serial Number
  • Partition Information

example:

void GetDiskPropertyByHandle(HANDLE hDisk)
{
    HANDLE hPartition;
    IO_STATUS_BLOCK iosb;
    STORAGE_DEVICE_NUMBER sdn;
    GET_LENGTH_INFORMATION li;

    NTSTATUS status = NtDeviceIoControlFile(hDisk, 0, 0, 0, &iosb,
        IOCTL_STORAGE_GET_DEVICE_NUMBER, 0, 0, &sdn, sizeof(sdn));

    if (0 <= status && sdn.DeviceType == FILE_DEVICE_DISK && !sdn.PartitionNumber)
    {
        DbgPrint("\\Device\\Harddisk%d\n", sdn.DeviceNumber);

        WCHAR sz[64], *c = sz + swprintf(sz, L"\\Device\\Harddisk%d\\Partition", sdn.DeviceNumber);

        WCHAR szSize[32];

        if (0 <= (status = NtDeviceIoControlFile(hDisk, 0, 0, 0, &iosb,
            IOCTL_DISK_GET_LENGTH_INFO, 0, 0, &li, sizeof(li))))
        {
            DbgPrint("Length = %S (%I64x)\n", 
                StrFormatByteSizeW(li.Length.QuadPart, szSize, RTL_NUMBER_OF(szSize)), 
                li.Length.QuadPart);
        }

        UNICODE_STRING ObjectName;
        OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName, OBJ_CASE_INSENSITIVE };

        PVOID stack = alloca(guz);

        union {
            PVOID buf;
            PDRIVE_LAYOUT_INFORMATION_EX pdli;
            PSTORAGE_DEVICE_DESCRIPTOR psdd;
            PCSTR psz;
        };

        STORAGE_PROPERTY_QUERY spq = { StorageDeviceProperty, PropertyStandardQuery }; 

        ULONG cb = 0, rcb = sizeof(STORAGE_DEVICE_DESCRIPTOR) + 0x40, PartitionCount = 4;

        do 
        {
            if (cb < rcb)
            {
                cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
            }

            switch (status = (NtDeviceIoControlFile(hDisk, 0, 0, 0, &iosb, 
                IOCTL_STORAGE_QUERY_PROPERTY, &spq, sizeof(spq), buf, cb)))
            {
            case STATUS_SUCCESS:
            case STATUS_BUFFER_OVERFLOW:
                if (psdd->Version == sizeof(STORAGE_DEVICE_DESCRIPTOR))
                {
                    if (psdd->Size > cb)
                    {
                        rcb = psdd->Size;
                        status = STATUS_BUFFER_OVERFLOW;
                    }
                    else
                    {
                        if (psdd->SerialNumberOffset)
                        {
                            DbgPrint("SerialNumber = %s\n", psz + psdd->SerialNumberOffset);
                        }
                    }
                }
                else
                {
                    status = STATUS_INVALID_PARAMETER;
                }
                break;
            }
        } while (status == STATUS_BUFFER_OVERFLOW);

        for (;;)
        {
            if (cb < (rcb = FIELD_OFFSET(DRIVE_LAYOUT_INFORMATION_EX, PartitionEntry[PartitionCount])))
            {
                cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
            }

            if (0 <= (status = NtDeviceIoControlFile(hDisk, 0, 0, 0, &iosb,
                IOCTL_DISK_GET_DRIVE_LAYOUT_EX, 0, 0, buf, cb)))
            {
                if (PartitionCount = pdli->PartitionCount)
                {
                    PPARTITION_INFORMATION_EX PartitionEntry = pdli->PartitionEntry;

                    do 
                    {
                        if (!PartitionEntry->PartitionNumber)
                        {
                            continue;
                        }

                        _itow(PartitionEntry->PartitionNumber, c, 10);

                        RtlInitUnicodeString(&ObjectName, sz);

                        DbgPrint("%wZ\nOffset=%S ", &ObjectName, 
                            StrFormatByteSizeW(PartitionEntry->StartingOffset.QuadPart, szSize, RTL_NUMBER_OF(szSize)));

                        DbgPrint("Length=%S\n", 
                            StrFormatByteSizeW(PartitionEntry->PartitionLength.QuadPart, szSize, RTL_NUMBER_OF(szSize)));

                        char PartitionName[256], *szPartitionName;

                        switch (PartitionEntry->PartitionStyle)
                        {
                        case PARTITION_STYLE_MBR:
                            DbgPrint("MBR: type=%x boot=%x", PartitionEntry->Mbr.PartitionType, PartitionEntry->Mbr.BootIndicator);
                            break;
                        case PARTITION_STYLE_GPT:

                            if (IsEqualGUID(PartitionEntry->Gpt.PartitionType, PARTITION_ENTRY_UNUSED_GUID))
                            {
                                szPartitionName = "UNUSED";
                            }
                            else if (IsEqualGUID(PartitionEntry->Gpt.PartitionType, PARTITION_SYSTEM_GUID))
                            {
                                szPartitionName = "SYSTEM";
                            }
                            else if (IsEqualGUID(PartitionEntry->Gpt.PartitionType, PARTITION_MSFT_RESERVED_GUID))
                            {
                                szPartitionName = "RESERVED";
                            }
                            else if (IsEqualGUID(PartitionEntry->Gpt.PartitionType, PARTITION_BASIC_DATA_GUID))
                            {
                                szPartitionName = "DATA";
                            }
                            else if (IsEqualGUID(PartitionEntry->Gpt.PartitionType, PARTITION_MSFT_RECOVERY_GUID))
                            {
                                szPartitionName = "RECOVERY";
                            }
                            else if (IsEqualGUID(PartitionEntry->Gpt.PartitionType, PARTITION_MSFT_SNAPSHOT_GUID))
                            {
                                szPartitionName = "SNAPSHOT";
                            }
                            else
                            {
                                sprintf(szPartitionName = PartitionName, "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", 
                                    PartitionEntry->Gpt.PartitionType.Data1,
                                    PartitionEntry->Gpt.PartitionType.Data2,
                                    PartitionEntry->Gpt.PartitionType.Data3,
                                    PartitionEntry->Gpt.PartitionType.Data4[0],
                                    PartitionEntry->Gpt.PartitionType.Data4[1],
                                    PartitionEntry->Gpt.PartitionType.Data4[2],
                                    PartitionEntry->Gpt.PartitionType.Data4[3],
                                    PartitionEntry->Gpt.PartitionType.Data4[4],
                                    PartitionEntry->Gpt.PartitionType.Data4[5],
                                    PartitionEntry->Gpt.PartitionType.Data4[6],
                                    PartitionEntry->Gpt.PartitionType.Data4[7]);
                            }
                            DbgPrint("[%s] %I64x \"%S\"", 
                                szPartitionName,
                                PartitionEntry->Gpt.Attributes,
                                PartitionEntry->Gpt.Name);
                            break;
                        }

                        if (0 <= NtOpenFile(&hPartition, FILE_GENERIC_READ, &oa, &iosb,
                            FILE_SHARE_VALID_FLAGS, FILE_SYNCHRONOUS_IO_NONALERT))
                        {
                            union {
                                BYTE bb[sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + 32*sizeof(WCHAR) ];
                                FILE_FS_ATTRIBUTE_INFORMATION ffai;
                            };

                            switch (NtQueryVolumeInformationFile(hPartition, &iosb, &ffai, sizeof(bb), FileFsAttributeInformation))
                            {
                            case STATUS_SUCCESS:
                            case STATUS_BUFFER_OVERFLOW:
                                DbgPrint(" \"%.*S\"\n", ffai.FileSystemNameLength >> 1 , ffai.FileSystemName);
                                break;
                            }

                            NtClose(hPartition);
                        }

                    } while (PartitionEntry++, --PartitionCount);
                }
                return ;
            }

            switch (status)
            {
            case STATUS_BUFFER_OVERFLOW:
                PartitionCount = pdli->PartitionCount;
                continue;
            case STATUS_INFO_LENGTH_MISMATCH:
            case STATUS_BUFFER_TOO_SMALL:
                PartitionCount <<= 1;
                continue;
            default:
                return ;
            }
        }
    }
}

from another size we can get Device Instance ID from interface string by call CM_Get_Device_Interface_PropertyW with DEVPKEY_Device_InstanceId. after this we call CM_Locate_DevNodeW for get device instance handle.

CONFIGRET GetPropertyByInterface(PCSTR prefix, PCWSTR pszDeviceInterface)
{
    ULONG cb = 0, rcb = 256;

    PVOID stack = alloca(guz);
    DEVPROPTYPE PropertyType;

    CONFIGRET status;

    union {
        PVOID pv;
        PWSTR DeviceID;
        PBYTE pb;
    };

    do 
    {
        if (cb < rcb)
        {
            rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
        }

        status = CM_Get_Device_Interface_PropertyW(pszDeviceInterface, &DEVPKEY_Device_InstanceId, &PropertyType, pb, &rcb, 0);

        if (status == CR_SUCCESS)
        {
            if (PropertyType == DEVPROP_TYPE_STRING)
            {
                DbgPrint("%sDeviceID = %S\n", prefix, DeviceID);

                DEVINST dnDevInst; 

                if (CR_SUCCESS == (status = CM_Locate_DevNodeW(&dnDevInst, DeviceID, CM_LOCATE_DEVNODE_NORMAL)))
                {
                    GetPropertyByDeviceID(prefix, dnDevInst);
                }
            }
            else
            {
                status = CR_WRONG_TYPE;
            }

            break;
        }

    } while (status == CR_BUFFER_SMALL);

    return status;
}

with device instance handle we can query many device properties via CM_Get_DevNode_PropertyW like: DEVPKEY_Device_LocationInfo, DEVPKEY_NAME, DEVPKEY_Device_PDOName, DEVPKEY_Device_FirmwareVersion, DEVPKEY_Device_Model, DEVPKEY_Device_DriverVersion and many others - look full list in devpkey.h

finally we can call CM_Get_Parent and recursive query all this properties for parent device(s) until we not rich top of stack:

#define OPEN_PDO

void GetPropertyByDeviceID(PCSTR prefix, DEVINST dnDevInst)
{
#ifdef OPEN_PDO
    HANDLE hFile;
    IO_STATUS_BLOCK iosb;
    UNICODE_STRING ObjectName;
    OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName, OBJ_CASE_INSENSITIVE };
#endif

    CONFIGRET status;

    ULONG cb = 0, rcb = 0x80;

    PVOID stack = alloca(guz);

    DEVPROPTYPE PropertyType;

    union {
        PVOID pv;
        PWSTR sz;
        PBYTE pb;
    };

    static struct  
    {
        CONST DEVPROPKEY *PropertyKey;
        PCWSTR PropertyName;
    } PropertyKeys[] = {
        { &DEVPKEY_Device_PDOName, L"PDOName"},
        { &DEVPKEY_Device_Parent, L"Parent"},
        { &DEVPKEY_Device_DriverVersion, L"DriverVersion"},
        { &DEVPKEY_Device_LocationInfo, L"LocationInfo"},
        { &DEVPKEY_Device_FirmwareVersion, L"FirmwareVersion"},
        { &DEVPKEY_Device_Model, L"Model"},
        { &DEVPKEY_NAME, L"NAME"},
        { &DEVPKEY_Device_InstanceId, L"DeviceID"}
    };

    do 
    {
        int n = RTL_NUMBER_OF(PropertyKeys);

        do 
        {
            CONST DEVPROPKEY *PropertyKey = PropertyKeys[--n].PropertyKey;

            do 
            {
                if (cb < rcb)
                {
                    rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
                }

                status = CM_Get_DevNode_PropertyW(dnDevInst, PropertyKey, &PropertyType, pb, &rcb, 0);

                if (status == CR_SUCCESS)
                {
                    if (PropertyType == DEVPROP_TYPE_STRING)
                    {
                        DbgPrint("%s%S=[%S]\n", prefix, PropertyKeys[n].PropertyName, sz);

#ifdef OPEN_PDO

                        if (!n)
                        {
                            // DEVPKEY_Device_PDOName can use in NtOpenFile

                            RtlInitUnicodeString(&ObjectName, sz);

                            if (0 <= NtOpenFile(&hFile, FILE_READ_ATTRIBUTES|SYNCHRONIZE, &oa,
                                &iosb, FILE_SHARE_VALID_FLAGS, FILE_SYNCHRONOUS_IO_NONALERT))
                            {
                                NtClose(hFile);
                            }
                        }
#endif
                    }
                }

            } while (status == CR_BUFFER_SMALL);

        } while (n);

        if (!*--prefix) break;

    } while (CM_Get_Parent(&dnDevInst, dnDevInst, 0) == CR_SUCCESS);
}

also string returned by DEVPKEY_Device_PDOName we can use in NtOpenFile call for open PDO device.