【问题标题】:How to list physical disks?如何列出物理磁盘?
【发布时间】:2010-09-24 13:42:50
【问题描述】:

如何在 Windows 中列出物理磁盘? 为了获得"\\\\.\PhysicalDrive0"的列表可用。

【问题讨论】:

    标签: c windows winapi hard-drive


    【解决方案1】:

    #WMIC wmic是一个非常完善的工具

    wmic diskdrive list
    

    提供一个(太多的)详细列表,例如

    了解较少

    wmic diskdrive list brief 
    

    #C Sebastian Godelet 提到in the comments

    在 C 中:

    system("wmic diskdrive list");
    

    正如评论,您也可以调用 WinAPI,但是...如“How to obtain data from WMI using a C Application?”所示,这是相当复杂的(通常使用 C++,而不是 C)。


    #PowerShell 或者使用 PowerShell:

    Get-WmiObject Win32_DiskDrive
    

    2022 年 2 月更新,微软在“Windows 10 features we're no longer developing”中宣布

    WMIC 工具在 Windows 10 版本 21H1 和 Windows Server 的 21H1 通用可用性频道版本中已弃用。

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

    注意:此弃用仅适用于 command-line management tool。 WMI 本身不受影响。

    【讨论】:

    • -1 不回答问题,即询问如何在 C 中进行。
    • +1 不回答问题,但这是一条非常有用的信息:-)
    • 你可以在 C 中做一个system("wmic diskdrive list");
    • 你也可以通过WinApi使用WMI,而不仅仅是调用wmic app。
    • Win32_DiskDrive 在启用软件 raid 或 StorageSpaces 时不会列出物理磁盘。原始物理磁盘已被过滤掉。易于与 PowerShell Get-PhysicalDisk 进行比较
    【解决方案2】:

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

    您可以使用(或不使用)GetLogicalDrives 枚举驱动器盘符,然后调用 QueryDosDevice() 来找出该盘符映射到哪个物理驱动器。

    或者,您可以解码注册表中 HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices 中的信息。然而,那里的二进制数据编码并不明显。如果你有一本 Russinovich 和 Solomon 的著作《Microsoft Windows Internals》,第 10 章会讨论这个注册表配置单元。

    【讨论】:

    • QueryDosDevice 返回分区,而不是磁盘本身。单盘拆分为C:和D:,Win7 x64。所以: c => "\Device\HarddiskVolume2"; d => "\Device\HarddiskVolume3'"
    【解决方案3】:

    我修改了一个名为“dskwipe”的开源程序,以便从中提取磁盘信息。 Dskwipe 是用 C 编写的,您可以从中提取此功能。二进制文件和源代码在这里:dskwipe 0.3 has been released

    返回的信息如下所示:

    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
    

    【讨论】:

    • 我以为是它,但它会强制搜索驱动器..不是有一个 API 可以只报告设备吗?
    • 是的。 Win32中的SetupApi,函数名以SetupDi开头
    【解决方案4】:

    我今天刚刚在我的 RSS 阅读器中遇到了这个问题。我有一个更清洁的解决方案给你。这个例子是在 Delphi 中的,但可以很容易地转换为 C/C++(都是 Win32)。

    从以下注册表位置查询所有值名称: HKLM\SYSTEM\MountedDevices

    将它们一一传递给下面的函数,您将返回设备名称。相当干净和简单! I found this code on a blog here.

    function VolumeNameToDeviceName(const VolName: String): String;
    var
      s: String;
      TargetPath: Array[0..MAX_PATH] of WideChar;
      bSucceeded: Boolean;
    begin
      Result := ”;
      // VolumeName has a format like this: \\?\Volume{c4ee0265-bada-11dd-9cd5-806e6f6e6963}\
      // We need to strip this to Volume{c4ee0265-bada-11dd-9cd5-806e6f6e6963}
      s :=  Copy(VolName, 5, Length(VolName) - 5);
    
      bSucceeded := QueryDosDeviceW(PWideChar(WideString(s)), TargetPath, MAX_PATH) <> 0;
      if bSucceeded then
      begin
        Result := TargetPath;
      end
      else begin
        // raise exception
      end;
    
    end;
    

    【讨论】:

    • 我想要物理名称,以便我可以使用未分配的空间,所以我猜这个未分配的空间不会有安装的卷 guid...
    • '恐怕这不是我们要找的,与@Alnitak 的回答类似。
    • 你应该在windows xp及更高版本中使用SetupApi,不再使用注册表,这在Win98中是这样做的,但现在不行了。
    【解决方案5】:

    列出美国英语字母表中的所有字母,跳过 a 和 b。 “CDEFGHIJKLMNOPQRSTUVWXYZ”。使用CreateFile 打开每个驱动器,例如CreateFile("\\.\C:")。如果它没有返回INVALID_HANDLE_VALUE,那么你有一个“好”的驱动器。接下来使用该句柄并通过DeviceIoControl 运行它以获取磁盘#。 See my related answer for more details

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 你从哪里得到这个号码的?
      • 为什么限制为15?继续枚举,直到你失败。我不确定操作系统是否会跳过某些设备编号。
      • @Ajay 我最好的猜测是,如果您插入设备 A,插入设备 B,然后拔下设备 A
      【解决方案7】:

      一种方法:

      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 中获得

      【讨论】:

      • 如果有一个没有任何(挂载)卷的空磁盘怎么办?
      • 请注意,如果一个卷跨越多个磁盘,他建议的DeviceIoControl(IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS) 调用实现将失败。换句话说,您首先需要向DeviceIoControl 询问VOLUME_DISK_EXTENTS 结构的大小,然后分配那么多内存,然后才使用分配的缓冲区再次调用它。它的工作方式如上所示,因为大多数卷仅驻留在一个磁盘上。
      • 抱歉,我无法使用 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); 你能找出我的问题吗?
      • @ahmd0 VOLUME_DISK_EXTENTS 为一个区段拥有足够的内存,因此您可以像 Grodriguez 建议的那样调用它,然后检查 success || ERROR_MORE_DATA == GetLastError(),因为无论如何我们只关心第一个区段。
      • 使用 0 代替 GENERIC_READ,这将允许打开磁盘,即使没有管理员权限,但您仍然可以读取磁盘范围等元信息。
      【解决方案8】:

      Thic WMIC 命令组合工作正常:

      wmic volume list brief
      

      【讨论】:

      • 卷 != 物理磁盘。此命令不会列出包含零卷的物理磁盘,例如未初始化的磁盘。 (此外,虽然它不像上一个那样是致命问题,但该命令的输出将需要进一步处理以对包含多个卷的物理磁盘的 id 进行重复数据删除。)
      【解决方案9】:

      这可能晚了 5 年 :)。但由于我还没有看到这个问题的答案,所以添加这个。

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

      一旦我们有了他们的设备路径,我们就可以发出IOCTL_STORAGE_GET_DEVICE_NUMBER 来构造"\\.\PHYSICALDRIVE%d"STORAGE_DEVICE_NUMBER.DeviceNumber

      另见SetupDiGetClassDevs function

      #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;
      }
      

      【讨论】:

      • 添加另一个链接(我没有足够的代表在答案中发布)Setup API Functions
      • 听起来很有趣。比我上面的答案更完整。 +1
      • 请注意,这些 SetupAPI 函数将 仅列出所有 物理 驱动器,还会列出虚拟驱动器 - 事实上每个将列出已注册的磁盘驱动器接口,我认为这可能是问题的解决方案,但它也会产生很多“噪音数据”,使用SetupAPI比使用SetupAPI复杂得多这个答案的建议是什么
      • 我根据上面列出驱动器的答案编写了一个名为libwindevblk 的小型库,尽可能检索卷名并提供一个允许在分区上简单读/写的api
      【解决方案10】:

      可能想要包括旧的 A: 和 B: 驱动器,因为您永远不知道谁可能在使用它们! 我厌倦了 USB 驱动器与我的两个仅用于 Readyboost 的 SDHC 驱动器碰撞。 我一直将它们分配给高字母 Z: Y: 使用一个实用程序,它可以根据需要为设备分配驱动器号。我想知道.... 我可以制作一个 Readyboost 驱动器号 A: 吗?是的! 我可以将我的第二个 SDHC 驱动器号设置为 B: 吗?是的!

      我以前用过软盘驱动器,从没想过 A: 或 B: 会派上用场 准备好提振。

      我的意思是,不要假设 A: & B: 不会被任何人用于任何事情 您甚至可能会发现正在使用旧的 SUBST 命令!

      【讨论】:

        【解决方案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 来完成此任务的声明。

        【讨论】:

        • 另外:重新定义 Windows 宏可能是有史以来最糟糕的想法 - 这样的应用程序很快就会崩溃并停止工作。
        • 就像我在对this answer 的评论中显示的那样,您错误地调用了DeviceIoControl。你不能假设只有一个范围。您需要向DeviceIoControl 询问所需VOLUME_DISK_EXTENTS 缓冲区的大小。
        • @ahmd0 我很乐意修复它。你能指出描述这种行为的msdn页面吗? (虽然创建一个位于两个区段上的磁盘的方法也可以,因为我刚刚发现没有办法对其进行测试。)
        • 不是每个物理驱动器都会关联到一个逻辑驱动器,即使这样也不是每个逻辑驱动器都会分配一个驱动器号。
        • 就像@anni 所说,文件系统设备可以在没有驱动器号的情况下附加。我正在为一个同时打开很多 VHD 的产品以编程方式安装 VHD,如果我没有传递 no-drive-letter 参数,我会用完字母。
        【解决方案12】:

        如果您想要“物理”访问,我们正在开发此 API,最终将允许您与存储设备进行通信。它是开源的,您可以查看当前代码以获取一些信息。查看更多功能: https://github.com/virtium/vtStor

        【讨论】:

          【解决方案13】:

          答案远比上述所有答案简单。物理驱动器列表实际上存储在注册表项中,该注册表项还提供了设备映射。

          HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\disk\Enum

          Count是PhysicalDrive#的编号,每个编号的Registry Value是对应的物理驱动器。

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

          不需要可能不在系统或其他黑客上运行的 WMI 服务,并且此功能至少自 2000 年以来就已存在于 Windows 中,并且在 Windows 10 中仍然如此。

          【讨论】:

          • 有趣的选择,可能比我 7 岁以上的答案更相关。 +1
          • 我认为最好的选择,因为它简单、可靠,而且注册表的使用可能是 windows 开发人员在设计 windows 时想要的。
          • 比我的答案更好的选择,+1。最后一个问题是为什么它必须包含实际信息。是否记录在案?什么时候 Windows 在那里写入数据?管理控制台是否使用它?
          • 很好的方法,但它有一个小缺点:它不能列出每个物理驱动器的大小,因为它们没有存储在注册表中(WMI 服务确实提供了它们)。对于诸如获取每个驱动器的制造商和型号之类的事情,它仍然要好得多且资源消耗更少,所以我+1。我需要获取每个驱动器的大小,并且我没有在 C 中使用它,所以我必须采用 WMI 方式。这与物理内存的情况大致相同,其详细数据也不存储在注册表中......
          • “至少从 2000 年开始出现在 Windows 中”:无法确认。 Windows XP 和 Windows 7 中缺少注册表项。
          【解决方案14】:

          Here 是一种解决方案,通过 WMI 调用来实现。
          那么你需要做的就是打电话:

          queryAndPrintResult(L"SELECT * FROM Win32_DiskDrive", L"Name");
          

          【讨论】:

            【解决方案15】:

            如果你只需要查看现有磁盘,这个就足够了:

            powershell "get-physicaldisk"
            

            【讨论】:

            • 请务必详细说明您的答案,例如它是如何工作的,如果它不适用于 OP,请说明原因,查看此链接了解更多详细信息。 stackoverflow.com/help/how-to-answer
            猜你喜欢
            • 2013-10-05
            • 2011-11-01
            • 2012-12-28
            • 1970-01-01
            • 1970-01-01
            • 2019-03-11
            • 2019-05-29
            • 1970-01-01
            • 2015-12-03
            相关资源
            最近更新 更多