如何列出物理磁盘?

92
如何在Windows中列出物理磁盘? 为了获取可用的"\\\\.\PhysicalDrive0"列表。
16个回答

86

WMIC

wmic 是一个非常全面的工具

wmic diskdrive list

提供一个(太多)详细列表,例如
对于较少信息
wmic diskdrive list brief 

#C Sebastian Godelet在评论中提到:

用C语言编写:

system("wmic diskdrive list");

正如评论所述,您也可以调用WinAPI,但是...就像在“如何使用C应用程序从WMI获取数据?”中所示的那样,这相当复杂(通常使用C++而不是C来完成)。

PowerShell

或者使用PowerShell:

Get-WmiObject Win32_DiskDrive

2022年2月更新,微软在“我们不再开发的Windows 10功能”中宣布:

WMIC工具在Windows 10版本21H1和Windows Server的21H1 General Availability Channel发布中已被弃用。

此工具已被Windows PowerShell for WMI取代。

注意:此弃用仅适用于命令行管理工具。WMI本身不受影响。


16
未回答问题,该问题要求介绍在C语言中如何实现。 - unixman83
13
+1并没有回答这个问题,但这是非常有用的一条信息 :-) - Grodriguez
8
你可以在C中执行system("wmic diskdrive list"); - Sebastian
你也可以通过WinApi使用WMI,而不仅仅是调用wmic应用程序。 - Alex P.
当启用软件 RAID 或 StorageSpaces 时,Win32_DiskDrive 不会列出物理磁盘。原始物理磁盘被过滤掉了。可以轻松使用 PowerShell 的 Get-PhysicalDisk 进行比较。 - Dmitry Gusarov
最近,我开始尝试让C++调用MI(类似API的WMI后继者,现已弃用),并且针对我的目的(处理VHD、卷和文件系统设备),我决定通过在运行时生成Powershell一行命令,并通过system调用它们,这样做要少得多的工作量。 - Keith Russell

52

一种方法:

  1. 使用GetLogicalDrives枚举逻辑驱动器。

  2. 对于每个逻辑驱动器,打开名为 "\\.\X:"(不加引号)的文件,其中 X 是逻辑驱动器字母。

  3. 调用DeviceIoControl,将上一步中打开的文件的句柄和 dwIoControlCode 参数设置为IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS

    HANDLE hHandle;
    VOLUME_DISK_EXTENTS diskExtents;
    DWORD dwSize;
    [...]
    
    iRes = DeviceIoControl(
        hHandle,
        IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
        NULL,
        0,
        (LPVOID) &diskExtents,
        (DWORD) sizeof(diskExtents),
        (LPDWORD) &dwSize,
        NULL);
    
    这将返回逻辑卷物理位置信息,以VOLUME_DISK_EXTENTS结构体的形式。
    在卷驱动器位于单个物理驱动器上的简单情况下,物理驱动器号码可以在diskExtents.Extents[0].DiskNumber中获得。

14
如果有一张空的磁盘没有任何(已挂载的)卷怎么办? - j_kubik
2
请注意,如果一个卷跨越多个磁盘,则他建议的 DeviceIoControl(IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS) 调用实现将失败。换句话说,您首先需要询问 DeviceIoControl 获取 VOLUME_DISK_EXTENTS 结构体的大小,然后分配相应的内存,然后再使用已分配的缓冲区再次调用它。它之所以按照上面显示的方式工作,是因为大多数卷都驻留在一个磁盘上。 - ahmd0
抱歉,我无法成功使用CreateFile((_T("\.\C:"), GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,///FILE_FLAG_WRITE_THROUGH |/FILE_FLAG_NO_BUFFERING, NULL)打开"\.\C:"。你能帮我找出问题吗? - bowman han
@ahmd0 VOLUME_DISK_EXTENTS 只保存一个 extent 的内存,所以你可以像 Grodriguez 建议的那样调用它,然后检查 success || ERROR_MORE_DATA == GetLastError(),因为我们只关心第一个 extent。 - Millie Smith
@bowmanhan 这就是我正在使用的(仅限于“FILE_ATTRIBUTE_NORMAL”)。尝试使用管理员权限。 - Millie Smith
1
使用0代替GENERIC_READ,这将允许打开磁盘而无需管理员权限,但仍然可以读取元信息,如磁盘范围。 - ivan.ukr

40

也许已经晚了5年 :). 不过因为我看到还没有答案,所以补充一下。

我们可以使用Setup APIs来获取磁盘列表,即系统中实现GUID_DEVINTERFACE_DISK接口的设备。

一旦我们拥有它们的设备路径,就可以发出IOCTL_STORAGE_GET_DEVICE_NUMBER命令,用STORAGE_DEVICE_NUMBER.DeviceNumber构建"\\.\PHYSICALDRIVE%d"

另请参见SetupDiGetClassDevs函数

#include <Windows.h>
#include <Setupapi.h>
#include <Ntddstor.h>

#pragma comment( lib, "setupapi.lib" )

#include <iostream>
#include <string>
using namespace std;

#define START_ERROR_CHK()           \
    DWORD error = ERROR_SUCCESS;    \
    DWORD failedLine;               \
    string failedApi;

#define CHK( expr, api )            \
    if ( !( expr ) ) {              \
        error = GetLastError( );    \
        failedLine = __LINE__;      \
        failedApi = ( api );        \
        goto Error_Exit;            \
    }

#define END_ERROR_CHK()             \
    error = ERROR_SUCCESS;          \
    Error_Exit:                     \
    if ( ERROR_SUCCESS != error ) { \
        cout << failedApi << " failed at " << failedLine << " : Error Code - " << error << endl;    \
    }

int main( int argc, char **argv ) {

    HDEVINFO diskClassDevices;
    GUID diskClassDeviceInterfaceGuid = GUID_DEVINTERFACE_DISK;
    SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
    PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData;
    DWORD requiredSize;
    DWORD deviceIndex;

    HANDLE disk = INVALID_HANDLE_VALUE;
    STORAGE_DEVICE_NUMBER diskNumber;
    DWORD bytesReturned;

    START_ERROR_CHK();

    //
    // Get the handle to the device information set for installed
    // disk class devices. Returns only devices that are currently
    // present in the system and have an enabled disk device
    // interface.
    //
    diskClassDevices = SetupDiGetClassDevs( &diskClassDeviceInterfaceGuid,
                                            NULL,
                                            NULL,
                                            DIGCF_PRESENT |
                                            DIGCF_DEVICEINTERFACE );
    CHK( INVALID_HANDLE_VALUE != diskClassDevices,
         "SetupDiGetClassDevs" );

    ZeroMemory( &deviceInterfaceData, sizeof( SP_DEVICE_INTERFACE_DATA ) );
    deviceInterfaceData.cbSize = sizeof( SP_DEVICE_INTERFACE_DATA );
    deviceIndex = 0;

    while ( SetupDiEnumDeviceInterfaces( diskClassDevices,
                                         NULL,
                                         &diskClassDeviceInterfaceGuid,
                                         deviceIndex,
                                         &deviceInterfaceData ) ) {

        ++deviceIndex;

        SetupDiGetDeviceInterfaceDetail( diskClassDevices,
                                         &deviceInterfaceData,
                                         NULL,
                                         0,
                                         &requiredSize,
                                         NULL );
        CHK( ERROR_INSUFFICIENT_BUFFER == GetLastError( ),
             "SetupDiGetDeviceInterfaceDetail - 1" );

        deviceInterfaceDetailData = ( PSP_DEVICE_INTERFACE_DETAIL_DATA ) malloc( requiredSize );
        CHK( NULL != deviceInterfaceDetailData,
             "malloc" );

        ZeroMemory( deviceInterfaceDetailData, requiredSize );
        deviceInterfaceDetailData->cbSize = sizeof( SP_DEVICE_INTERFACE_DETAIL_DATA );

        CHK( SetupDiGetDeviceInterfaceDetail( diskClassDevices,
                                              &deviceInterfaceData,
                                              deviceInterfaceDetailData,
                                              requiredSize,
                                              NULL,
                                              NULL ),
             "SetupDiGetDeviceInterfaceDetail - 2" );

        disk = CreateFile( deviceInterfaceDetailData->DevicePath,
                           GENERIC_READ,
                           FILE_SHARE_READ | FILE_SHARE_WRITE,
                           NULL,
                           OPEN_EXISTING,
                           FILE_ATTRIBUTE_NORMAL,
                           NULL );
        CHK( INVALID_HANDLE_VALUE != disk,
             "CreateFile" );

        CHK( DeviceIoControl( disk,
                              IOCTL_STORAGE_GET_DEVICE_NUMBER,
                              NULL,
                              0,
                              &diskNumber,
                              sizeof( STORAGE_DEVICE_NUMBER ),
                              &bytesReturned,
                              NULL ),
             "IOCTL_STORAGE_GET_DEVICE_NUMBER" );

        CloseHandle( disk );
        disk = INVALID_HANDLE_VALUE;

        cout << deviceInterfaceDetailData->DevicePath << endl;
        cout << "\\\\?\\PhysicalDrive" << diskNumber.DeviceNumber << endl;
        cout << endl;
    }
    CHK( ERROR_NO_MORE_ITEMS == GetLastError( ),
         "SetupDiEnumDeviceInterfaces" );

    END_ERROR_CHK();

Exit:

    if ( INVALID_HANDLE_VALUE != diskClassDevices ) {
        SetupDiDestroyDeviceInfoList( diskClassDevices );
    }

    if ( INVALID_HANDLE_VALUE != disk ) {
        CloseHandle( disk );
    }

    return error;
}

添加另一个链接(我没有足够的声望在答案中发布)设置API函数 - arun
1
听起来很有趣。比我之前的回答更完整。+1 - VonC
请注意,这些SetupAPI函数不仅会列出所有物理驱动器,还会列出虚拟驱动器 - 实际上,每个已注册的磁盘驱动器接口都将被列出。我认为这可能是问题的解决方案,但它也会产生大量的“噪音数据”。使用SetupAPI比本答案中建议的方法要复杂得多。 - specializt
我编写了一个小型库,名为libwindevblk,基于上面列出的答案,该库可以列出驱动器,在可能的情况下检索卷名称,并提供API,允许简单地读/写分区。 - Vincent Ricosti
似乎 IOCTL_STORAGE_GET_DEVICE_NUMBER 阶段需要管理员权限。有人知道如何在没有管理员权限的情况下获取该信息吗? - Harry Mallon

26

答案比上述所有答案都要简单得多。物理驱动器列表实际上存储在一个注册表键中,该键还提供了设备映射。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\disk\Enum

Count 是 PhysicalDrive#的数量,每个编号的注册表值是相应的物理驱动器。

例如,注册表值“0”是PhysicalDrive0。该值是实际设备PhysicalDrive0映射到的值。此处包含的值可以传递到pDeviceID参数中的CM_Locate_DevNode 中,以使用即插即用服务。这将允许您收集有关设备的大量信息。例如来自设备管理器的属性,如“友好显示名称”,如果需要驱动器名称、序列号等。

无需WMI服务,可能不在系统上运行,也无需其他黑客技巧。自至少2000年起,这个功能一直存在于Windows中,并且在Windows 10中仍然如此。


有趣的替代方案,可能比我七年多前的答案更相关。+1 - VonC
在我看来,这是最好的选择,因为它简单可靠,而且使用注册表可能是Windows开发人员设计Windows时想要的。 - felknight
比我的答案好得多,+1。最后一个问题是为什么它必须包含实际信息。它有文档吗?Windows 何时在那里写入数据?管理控制台是否使用它? - polkovnikov.ph
2
这是一个很好的方法,但有一个小缺陷:它无法列出每个物理驱动器的大小,因为这些信息没有存储在注册表中(WMI服务可以提供这些信息)。对于获取每个驱动器的制造商和型号等信息,它仍然更好且资源占用更少,所以我给它点赞。我需要获取每个驱动器的大小,而且我没有使用C语言,所以我必须使用WMI方式。这与物理内存的情况几乎相同,其详细数据也未存储在注册表中... - Yin Cognyto
1
这种方法的另一个问题是 - 磁盘编号可能不正确。如果您有一个USB存储设备,您可以插入USB1使其显示为磁盘1,USB2作为磁盘2。然后拔掉USB1。USB2将保持为磁盘2,但是注册表错误地报告它为1。要验证,请使用diskpart。 - ymerej
显示剩余2条评论

14

我修改了一个名为"dskwipe"的开源程序,以便从中提取此磁盘信息。Dskwipe是用C语言编写的,您可以从中提取此函数。二进制文件和源代码可在此处获取:dskwipe 0.3已发布

返回的信息将类似于以下内容:

Device Name                         Size Type      Partition Type
------------------------------ --------- --------- --------------------
\\.\PhysicalDrive0               40.0 GB Fixed
\\.\PhysicalDrive1               80.0 GB Fixed
\Device\Harddisk0\Partition0     40.0 GB Fixed
\Device\Harddisk0\Partition1     40.0 GB Fixed     NTFS
\Device\Harddisk1\Partition0     80.0 GB Fixed
\Device\Harddisk1\Partition1     80.0 GB Fixed     NTFS
\\.\C:                           80.0 GB Fixed     NTFS
\\.\D:                            2.1 GB Fixed     FAT32
\\.\E:                           40.0 GB Fixed     NTFS

1
我以为它是这样的,但它强制暴力搜索驱动器...难道没有一个API可以直接报告设备吗? - CiNN
2
是的。在Win32中,SetupApi是一个函数库,函数名以SetupDi开头。 - Warren P
每个 \.\PhysicalDriveX 是否都有一个或多个与之匹配的 \Device\HarddiskX 是一种被保证的已知事实吗?也就是说,是否会有一个与物理驱动器编号相匹配的硬盘设备,并且这两个编号是相同的? - stu

11

唯一正确的答案是由@Grodriguez提供的答案,以下是他懒得写的代码:

#include <windows.h>
#include <iostream>
#include <bitset>
#include <vector>
using namespace std;

typedef struct _DISK_EXTENT {
    DWORD         DiskNumber;
    LARGE_INTEGER StartingOffset;
    LARGE_INTEGER ExtentLength;
} DISK_EXTENT, *PDISK_EXTENT;

typedef struct _VOLUME_DISK_EXTENTS {
    DWORD       NumberOfDiskExtents;
    DISK_EXTENT Extents[ANYSIZE_ARRAY];
} VOLUME_DISK_EXTENTS, *PVOLUME_DISK_EXTENTS;

#define CTL_CODE(DeviceType, Function, Method, Access) \
    (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
#define IOCTL_VOLUME_BASE ((DWORD)'V')
#define METHOD_BUFFERED 0
#define FILE_ANY_ACCESS 0x00000000
#define IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS CTL_CODE(IOCTL_VOLUME_BASE, 0, METHOD_BUFFERED, FILE_ANY_ACCESS)

int main() {
    bitset<32> drives(GetLogicalDrives());
    vector<char> goodDrives;
    for (char c = 'A'; c <= 'Z'; ++c) {
        if (drives[c - 'A']) {
            if (GetDriveType((c + string(":\\")).c_str()) == DRIVE_FIXED) {
                goodDrives.push_back(c);
            }
        }
    }
    for (auto & drive : goodDrives) {
        string s = string("\\\\.\\") + drive + ":";
        HANDLE h = CreateFileA(
            s.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
            OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_RANDOM_ACCESS, NULL
        );
        if (h == INVALID_HANDLE_VALUE) {
            cerr << "Drive " << drive << ":\\ cannot be opened";
            continue;
        }
        DWORD bytesReturned;
        VOLUME_DISK_EXTENTS vde;
        if (!DeviceIoControl(
            h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
            NULL, 0, &vde, sizeof(vde), &bytesReturned, NULL
        )) {
            cerr << "Drive " << drive << ":\\ cannot be mapped into physical drive";
            continue;
        }
        cout << "Drive " << drive << ":\\ is on the following physical drives: ";
        for (int i = 0; i < vde.NumberOfDiskExtents; ++i) {
            cout << vde.Extents[i].DiskNumber << ' ';
        }
        cout << endl;
    }
}

我认为安装Windows驱动程序开发工具是一个相当繁琐的过程,因此我已经包含了在此任务中需要使用DeviceIoControl的声明。


2
重新定义Windows宏可能是最糟糕的想法 - 这样的应用程序很快就会崩溃并停止工作。 - specializt
2
就像我在对这个答案的评论中所示,您正在错误地调用 DeviceIoControl。您不能假设只有一个区间。您需要向 DeviceIoControl 询问所需的 VOLUME_DISK_EXTENTS 缓冲区的大小。 - ahmd0
@ahmd0 我很乐意修复它。你能指出描述这种行为的 MSDN 页面吗?(不过,创建一个位于两个范围上的磁盘的方法也可以,因为我找不到测试它的方法。) - polkovnikov.ph
5
并非每个物理驱动器都会与逻辑驱动器相关联,即使是逻辑驱动器也不一定会被分配一个驱动器字母。 - anni
2
就像@anni所说,文件系统设备可以在没有驱动器字母的情况下附加。我正在以编程方式挂载VHDs,为了打开很多VHDs,如果我没有传递no-drive-letter参数,我会用完所有的字母。 - Keith Russell
显示剩余6条评论

11

唯一确定的方法是对所有的\\.\Physicaldiskx(其中 x 从 0 到 15,16 是允许的最大磁盘数)调用 CreateFile()。检查返回的句柄值。如果无效,则检查 GetLastError() 返回的ERROR_FILE_NOT_FOUND。如果返回其他内容,则表示该磁盘已存在但出于某种原因无法访问。


7
你从哪里得到这个数字? - weaknespase
1
为什么限制在“15”?继续枚举直到失败。我不确定操作系统是否会跳过某些设备号。 - Ajay
3
@Ajay,我的最佳猜测是,在你插入设备A、插入设备B,然后拔掉设备A。 - morganbaz
diskpart跳到了Disk 3(重新插入USB闪存驱动器后),所以我取消了对这个答案的赞,但我无法取消。 \\.\Physicaldisk2存在,但它不是驱动器(我不知道它是什么)。在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\disk\Enum中,2以“USBSTOR\”开头,而不是“SCSI\”。设置API找到0,1,3,所以这个方法可行(@arun的答案)。 - Mr. Doge
USBSTOR\ is actually the correct drive(because CreateFile below, then will list the 4 partitions), it's swapped with a SCSI that I don't know of : registry: 2 is USBSTOR, 3 is SCSI, vs Setup API (and diskpart too): 3 is USBSTOR, 2 is SCSI, this specific SCSI isn't a disk SCSI\Disk&Ven_MSFT&Prod_XVDD - Mr. Doge

10

GetLogicalDrives() 枚举所有已挂载的磁盘分区,而非物理驱动器。

您可以使用 GetLogicalDrives 枚举驱动器字母(带或不带),然后调用 QueryDosDevice() 查找该字母映射到哪个物理驱动器。

另外,您还可以解码 HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices 中注册表的信息。但是,那里的二进制数据编码并不明显。如果您有 Russinovich 和 Solomon 的书 Microsoft Windows Internals 的副本,则可以在第10章中讨论此注册表。


3
QueryDosDevice 返回分区,而不是磁盘本身。 单个磁盘被分割为 C: 和 D:,Win7 x64。所以: c => "\Device\HarddiskVolume2"; d => "\Device\HarddiskVolume3'" - Arioch 'The

7
这个WMIC命令组合效果良好:
wmic volume list brief

1
卷 ≠ 物理磁盘。此命令不会列出包含零卷的物理磁盘,例如未初始化的磁盘。(此外,尽管这不像前一个问题那样是致命问题,但此命令的输出需要进一步处理以去重包含多个卷的物理磁盘的 ID。) - Keith Russell

3
如果你只需要查看现有磁盘,那么这个就足够了:
powershell "get-physicaldisk"

始终要详细编写您的答案,例如它是如何工作的,如果对于 OP 不起作用,则给出原因,有关更多详细信息,请查看此链接。 https://stackoverflow.com/help/how-to-answer - DaniyalAhmadSE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接