如何使用Delphi 7在Windows中检索所有磁盘的磁盘签名?

3

在 Windows XP 及以上操作系统中,使用 Delphi 7,如何检索计算机上每个磁盘的磁盘签名?最好不使用 WMI 或 Diskpart。

如果可能的话,速度要快...

谢谢。

后续编辑:

Documentation: http://pcsupport.about.com/od/termsd/g/disk-signature.htm
MBR disks: http://diddy.boot-land.net/firadisk/files/signature.htm
GPT disks: http://thestarman.pcministry.com/asm/mbr/GPT.htm

How to get it with DiskPart (method found on Google when searching "disk signature"):
Diskpart >> list disk >> select disk [n] >>
detail disk >> Disk ID: 0E35445B for MBR disks
and GUID: 55FD03F2-6B11-49DF-8167-D30B94A4509D for GPT Disks

3
我不喜欢排除可能解决方案的问题。在这种情况下,您排除了WMI?为什么呢?这个问题正是WMI所要解决的。 - David Heffernan
1
所以你想要快速、不使用WMI、用Delphi编写并且在Linux上也能运行的代码。这完全不现实。不同的操作系统是不同的。你需要为不同的操作系统找到不同的解决方案,而WMI是Windows的一个好解决方案。 - David Heffernan
@DavidHeffernan WMI连接速度慢,而且使用它时要么很复杂(当您像乐高积木一样编码所有这些繁琐的细节时),要么很慢(当使用简单的包装器函数时,它们组合对象的乐高积木,获取结果并将其丢弃而不重用)。 然而,我同意您的看法,这应该是一个通用接口,具有不同的实现类用于Win和Lin。 - Arioch 'The
如果您决定直接读取MBR,请注意GPT磁盘。您可能可以从DISK_PARTITION_INFO反向推导,如果Ian的答案没有提供您要查找的签名。我认为,试图提供帮助的答案不应被视为反对您,但这可能是我的英语表达问题。 - Sertac Akyuz
3个回答

5
你可以使用 DeviceIoControlIOCTL_DISK_GET_DRIVE_LAYOUT_EX 来获取所需的信息。
program DiskSignatureGuid;

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows;

type
  TDriveLayoutInformationMbr = record
    Signature: DWORD;
  end;

  TDriveLayoutInformationGpt = record
    DiskId: TGuid;
    StartingUsableOffset: Int64;
    UsableLength: Int64;
    MaxPartitionCount: DWORD;
  end;

  TPartitionInformationMbr = record
    PartitionType: Byte;
    BootIndicator: Boolean;
    RecognizedPartition: Boolean;
    HiddenSectors: DWORD;
  end;

  TPartitionInformationGpt = record
    PartitionType: TGuid;
    PartitionId: TGuid;
    Attributes: Int64;
    Name: array [0..35] of WideChar;
  end;

  TPartitionInformationEx = record
    PartitionStyle: Integer;
    StartingOffset: Int64;
    PartitionLength: Int64;
    PartitionNumber: DWORD;
    RewritePartition: Boolean;
    case Integer of
      0: (Mbr: TPartitionInformationMbr);
      1: (Gpt: TPartitionInformationGpt);
  end;

  TDriveLayoutInformationEx = record
    PartitionStyle: DWORD;
    PartitionCount: DWORD;
    DriveLayoutInformation: record
      case Integer of
      0: (Mbr: TDriveLayoutInformationMbr);
      1: (Gpt: TDriveLayoutInformationGpt);
    end;
    PartitionEntry: array [0..15] of TPartitionInformationGpt;
    //hard-coded maximum of 16 partitions
  end;

const
  PARTITION_STYLE_MBR = 0;
  PARTITION_STYLE_GPT = 1;
  PARTITION_STYLE_RAW = 2;

const
  IOCTL_DISK_GET_DRIVE_LAYOUT_EX = $00070050;

procedure Main;
const
  // Max number of drives assuming primary/secondary, master/slave topology
  MAX_IDE_DRIVES = 16;
var
  i: Integer;
  Drive: string;
  hDevice: THandle;
  DriveLayoutInfo: TDriveLayoutInformationEx;
  BytesReturned: DWORD;
begin
  for i := 0 to MAX_IDE_DRIVES - 1 do
  begin
    Drive := '\\.\PHYSICALDRIVE' + IntToStr(i);
    hDevice := CreateFile(PChar(Drive), 0, FILE_SHARE_READ or FILE_SHARE_WRITE,
      nil, OPEN_EXISTING, 0, 0);
    if hDevice <> INVALID_HANDLE_VALUE then
    begin
      if DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, nil, 0,
        @DriveLayoutInfo, SizeOf(DriveLayoutInfo), BytesReturned, nil) then
      begin
        case DriveLayoutInfo.PartitionStyle of
        PARTITION_STYLE_MBR:
          Writeln(Drive + ', MBR, ' +
            IntToHex(DriveLayoutInfo.DriveLayoutInformation.Mbr.Signature, 8));
        PARTITION_STYLE_GPT:
          Writeln(Drive + ', GPT, ' +
            GUIDToString(DriveLayoutInfo.DriveLayoutInformation.Gpt.DiskId));
        PARTITION_STYLE_RAW:
          Writeln(Drive + ', RAW');
        end;
      end;
      CloseHandle(hDevice);
    end;
  end;
end;

begin
  Main;
  Readln;
end.

请注意,由于将 0 传递给 CreateFile dwDesiredAccess 参数,因此不需要提升权限。 尽管有些含糊,但在文档中已经解释了这一点:

直接访问磁盘或卷受到限制...下列要求必须满足才能成功调用:

  • 调用者必须具有管理员特权。
  • dwCreationDisposition参数必须带有OPEN_EXISTING标志。
  • 打开卷或软盘时,dwShareMode参数必须带有FILE_SHARE_WRITE标志。

注意dwDesiredAccess参数可以为零,允许应用程序查询设备属性而无需访问设备。 例如,对于应用程序来说,无需在驱动器上插入软盘即可确定软盘驱动器的大小和支持的格式。 它也可以用于读取统计信息,而无需高级数据读/写权限。


到目前为止,我正在互联网上搜索“IOCTL_DISK_GET_DRIVE_LAYOUT_EX”,因为你忘记包括它了... - Jack Compton
好的,我想我不需要包含它,因为我使用的是XE3,而在D7中它未被定义。请稍等一下。好的,找到了! - David Heffernan
如果你有兴趣的话,这个回答中的代码可以在没有提权的情况下运行。它和你的回答在提权方面的关键区别在于,这里的代码将 0 传递给 CreateFile 函数的第二个参数。 - David Heffernan
@David Heffernan - 代码片段很棒。谢谢。不过我注意到一个潜在的改进,因为显示的签名是反向显示的:IntToHex(SwapEndian(DriveLayoutInfo.DriveLayoutInformation.Mbr.Signature), 8)); - Gizmo_the_Great
@David Heffernan - 你怎么引用GUID类型(即指定分区创建者的16字节值,例如MS保留类型、Linux类型等)?该值在TDriveLayoutInformationEx中作为PartitionEntry的数组[0..15] of TPartitionInformationGpt存在,但是如何将这个16字节的字节数组提取为TGUID类型的字符串呢?由于数组类型而不是GUID类型,GUIDType := GUIDToString(DriveLayoutInfo.PartitionEntry)无法通过编译器。 - Gizmo_the_Great

2
取决于"磁盘签名"是什么。我不知道那是什么。但我知道以下代码的返回结果为:
- \\?\ide#disksandisk_sdssdx120gg25___________________r201____#5&20f0fb49&0&0.0.0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
- \\?\ide#diskst1000dm003-9yn162______________________hp13____#5&33aaabee&0&1.0.0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}

那些是“磁盘签名”吗?

它使用了Windows的安装配置API

procedure GetDisks(slDisks: TStrings);
var
    InterfaceDevInfo: HDEVINFO;
    index: DWORD;
    status: BOOL;
    Name: string;
begin
    { Get the interface device information set that contains all devices of event class. }
    InterfaceDevInfo := SetupDiGetClassDevs(
            @GUID_DEVINTERFACE_DISK,
            nil,                            // Enumerator
            0,                              // Parent Window
            (DIGCF_PRESENT or DIGCF_INTERFACEDEVICE)    // Only Devices present & Interface class
        );

    if InterfaceDevInfo = HDEVINFO(INVALID_HANDLE_VALUE) then
    begin
        RaiseEnumerateDisksError('SetupDiGetClassDevs failed', GetLastError);
        Exit;
    end;

    { Enumerate all the disk devices }
    Index := 0;
    while (True) do
    begin
        Status := GetDeviceProperty(InterfaceDevInfo, index, Name);
        if not Status then
            Break;

        slDisks.Add(Name);

        Inc(Index);
    end;
    SetupDiDestroyDeviceInfoList(InterfaceDevInfo);
end;

function GetDeviceProperty(InterfaceDevInfo: HDEVINFO; Index: LongWord;
        out Name: string): Boolean;
var
    interfaceData: TSPDeviceInterfaceData;
    interfaceDetailData: PSPDeviceInterfaceDetailData;
    status: BOOL;
    interfaceDetailDataSize: LongInt;
    reqSize: Cardinal;
    errorCode: LongInt;
begin
    Result := False;

    ZeroMemory(@interfaceData, SizeOf(InterfaceData));
    interfaceData.cbSize := SizeOf(interfaceData);

    //Retreiving context structure for specified device interface
    status := SetupDiEnumDeviceInterfaces(
            InterfaceDevInfo,               // Interface Device Info handle
            nil,                                // Device Info data
            GUID_DEVINTERFACE_DISK,     // Interface registered by driver
            Index,                          // Member
            interfaceData);             // Device Interface Data

    if not status then
    begin
        errorCode := GetLastError;
        if (errorCode = ERROR_NO_MORE_ITEMS ) then
        begin
            //no more interfaces, exit returning default value of False
            Exit;
        end
        else
        begin
            RaiseEnumerateDisksError('SetupDiEnumDeviceInterfaces failed.', errorCode);
        end;
    end;

    // Find out required buffer size, so pass nil
    status := SetupDiGetDeviceInterfaceDetail(
            InterfaceDevInfo,       // Interface Device info handle
            @interfaceData,     // Interface data for the event class
            nil,                        // Checking for buffer size
            0,                          // Checking for buffer size
            reqSize,                    // Buffer size required to get the detail data
            nil);                       // Checking for buffer size

    // This call returns ERROR_INSUFFICIENT_BUFFER with reqSize
    // set to the required buffer size. Ignore the above error and
    // pass a bigger penis to get the detail data
    if not status then
    begin
        errorCode := GetLastError;
        if errorCode <> ERROR_INSUFFICIENT_BUFFER then
        begin
            RaiseEnumerateDisksError('SetupDiGetDeviceInterfaceDetail failed.', errorCode);
            Exit;
        end;
    end;

    // Allocate memory to get the interface detail data
    // This contains the devicepath we need to open the device
    interfaceDetailDataSize := reqSize;
    GetMem(interfaceDetailData, interfaceDetailDataSize);
    ZeroMemory(interfaceDetailData, interfaceDetailDataSize);

    interfaceDetailData.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
//  ineerfaceDetailData.cbSize := 5; //ansi version
//  ineerfaceDetailData.cbSize := 6; //unicode version

    //Getting interface detail data into properly sized buffer...
    status := SetupDiGetDeviceInterfaceDetail(
            InterfaceDevInfo,               // Interface Device info handle
            @interfaceData,             // Interface data for the event class
            interfaceDetailData,            // Interface detail data
            interfaceDetailDataSize,    // Interface detail data size
            reqSize,                            // Buffer size required to get the detail data
            nil);                               // Interface device info

    if not Status then
    begin
        RaiseEnumerateDisksError('Error in SetupDiGetDeviceInterfaceDetail', GetLastError);
        Exit;
    end;

    // Now we have the device path.
    Name := PChar(@interfaceDetailData.DevicePath[0]);

(*
    //DevicePath is suitable for a CreateFile, whish is what i want in the end
    hDevice := CreateFile(
            PChar(interfaceDetailData.DevicePath),    // device interface name
            GENERIC_READ or GENERIC_WRITE,       // dwDesiredAccess
            FILE_SHARE_READ or FILE_SHARE_WRITE, // dwShareMode
            nil,                               // lpSecurityAttributes
            OPEN_EXISTING,                      // dwCreationDistribution
            0,                                  // dwFlagsAndAttributes
            0                                // hTemplateFile
        );
*)
    Result := True;
end;

@DavidHeffernan 我不知道什么是磁盘签名。但现在我在问题中看到了它;我被“不要这样做”这部分搞糊涂了。 - Ian Boyd
好的,现在你知道这个问题是关于什么的了,你是不是要留下一个完全错误的答案? - David Heffernan

1

我在多台电脑上测试过这段代码并且它能正常工作:

MBR磁盘:

磁盘签名/标识符是一个4字节(长整型)的数字,当主引导记录/分区表第一次创建时随机生成,并存储于MBR磁盘扇区(0)的1B8(十六进制)或440(十进制)至1BB(十六进制)或443(十进制)字节偏移处。因此,在任何MBR磁盘上,您都可以直接从该位置读取它:

const
  // Max number of drives assuming primary/secondary, master/slave topology
  MAX_IDE_DRIVES = 16;
var
   i: Integer;
   RawMBR: array[0..511] of Byte;
   btsIO: DWORD;
   hDevice: THandle;
   s: string;
begin
   s := '';
   for i := 0 to MAX_IDE_DRIVES - 1 do
   begin
      hDevice := CreateFile(PChar('\\.\PHYSICALDRIVE' + IntToStr(i)), GENERIC_READ,
         FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
      if hDevice <> INVALID_HANDLE_VALUE then
      begin
         SetFilePointer(hDevice, 0, nil, FILE_BEGIN);  //MBR starts in sector 0
         if not ReadFile(hDevice, RawMBR[0], 512, btsIO, nil) then
         begin
            CloseHandle(hDevice);
            Continue;
         end;
         CloseHandle(hDevice);
         s := s + 'Disk ' + IntToStr(i) + ' = ' + IntToHex(RawMBR[443], 2) + ' ' +
            IntToHex(RawMBR[442], 2) + ' ' + IntToHex(RawMBR[441], 2) +
            ' ' + IntToHex(RawMBR[440], 2) + #13#10;
      end;
   end;
   ShowMessage(s);
end;

GPT磁盘:

磁盘签名/标识符是一个16字节(GUID)的数字,在创建GPT时随机生成,并存储在GPT磁盘扇区的字节偏移量038(十六进制)或56(十进制)到047(十六进制)或71(十进制)之间的位置(1)。因此,在任何GPT磁盘上,您都可以直接从该位置读取它:

const
  // Max number of drives assuming primary/secondary, master/slave topology
  MAX_IDE_DRIVES = 16;
var
   i: Integer;
   RawMBR: array[0..511] of Byte;
   btsIO: DWORD;
   hDevice: THandle;
   s: string;
begin
   s := '';
   for i := 0 to MAX_IDE_DRIVES - 1 do
   begin
      hDevice := CreateFile(PChar('\\.\PHYSICALDRIVE' + IntToStr(i)), GENERIC_READ,
         FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
      if hDevice <> INVALID_HANDLE_VALUE then
      begin
         SetFilePointer(hDevice, 512, nil, FILE_BEGIN); //GPT starts in sector 1
         if not ReadFile(hDevice, RawMBR[0], 512, btsIO, nil) then
         begin
            CloseHandle(hDevice);
            Continue;
         end;
         CloseHandle(hDevice);
         s := s + 'Disk ' + IntToStr(i) + ' = ' + IntToHex(RawMBR[59], 2) +
            ' ' + IntToHex(RawMBR[58], 2) + ' ' + IntToHex(RawMBR[57], 2) +
            ' ' + IntToHex(RawMBR[56], 2) + ' - ' + IntToHex(RawMBR[61], 2) +
            ' ' + IntToHex(RawMBR[60], 2) + ' - ' + IntToHex(RawMBR[63], 2) +
            ' ' + IntToHex(RawMBR[62], 2) + ' - ' + IntToHex(RawMBR[64], 2) +
            ' ' + IntToHex(RawMBR[65], 2) + ' - ' + IntToHex(RawMBR[66], 2) +
            ' ' + IntToHex(RawMBR[67], 2) + ' ' + IntToHex(RawMBR[68], 2) +
            ' ' + IntToHex(RawMBR[69], 2) + ' ' + IntToHex(RawMBR[70], 2) +
            ' ' + IntToHex(RawMBR[71], 2) +
            #13#10;
      end;
   end;
   ShowMessage(s);
end;

Ok, now let's combine them:

procedure TForm1.Button1Click(Sender: TObject);
const
   // Max number of drives assuming primary/secondary, master/slave topology
   MAX_IDE_DRIVES = 16;
var
   i: Integer;
   DiskType: Byte;
   RawMBR: array[0..511] of Byte;
   btsIO: DWORD;
   hDevice: THandle;
   s: string;
begin
   s := '';
   for i := 0 to MAX_IDE_DRIVES - 1 do
   begin
      hDevice := CreateFile(PChar('\\.\PHYSICALDRIVE' + IntToStr(i)), GENERIC_READ,
         FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
      if hDevice <> INVALID_HANDLE_VALUE then
      begin
         SetFilePointer(hDevice, 512, nil, FILE_BEGIN); //sector 1 for GPT
         if not ReadFile(hDevice, RawMBR[0], 512, btsIO, nil) then
         begin
            CloseHandle(hDevice);
            Continue;
         end;
         if (IntToHex(RawMBR[0], 2) + IntToHex(RawMBR[1], 2) +
            IntToHex(RawMBR[2], 2) + IntToHex(RawMBR[3], 2) +
            IntToHex(RawMBR[4], 2) + IntToHex(RawMBR[5], 2) +
            IntToHex(RawMBR[6], 2) + IntToHex(RawMBR[7], 2)) =
            '4546492050415254' then //EFI PART
            DiskType := 1 //GPT
         else
         begin
            DiskType := 0; //MBR
            SetFilePointer(hDevice, 0, nil, FILE_BEGIN); //sector 0 for MBR
            if not ReadFile(hDevice, RawMBR[0], 512, btsIO, nil) then
            begin
               CloseHandle(hDevice);
               Continue;
            end;
         end;
         CloseHandle(hDevice);
         if DiskType = 0 then
            s := s + 'Disk ' + IntToStr(i) + ' = ' + IntToHex(RawMBR[443], 2) + ' ' +
               IntToHex(RawMBR[442], 2) + ' ' + IntToHex(RawMBR[441], 2) +
               ' ' + IntToHex(RawMBR[440], 2) + #13#10
         else
            s := s + 'Disk ' + IntToStr(i) + ' = ' + IntToHex(RawMBR[59], 2) +
               ' ' + IntToHex(RawMBR[58], 2) + ' ' + IntToHex(RawMBR[57], 2) +
               ' ' + IntToHex(RawMBR[56], 2) + ' - ' + IntToHex(RawMBR[61], 2) +
               ' ' + IntToHex(RawMBR[60], 2) + ' - ' + IntToHex(RawMBR[63], 2) +
               ' ' + IntToHex(RawMBR[62], 2) + ' - ' + IntToHex(RawMBR[64], 2) +
               ' ' + IntToHex(RawMBR[65], 2) + ' - ' + IntToHex(RawMBR[66], 2) +
               ' ' + IntToHex(RawMBR[67], 2) + ' ' + IntToHex(RawMBR[68], 2) +
               ' ' + IntToHex(RawMBR[69], 2) + ' ' + IntToHex(RawMBR[70], 2) +
               ' ' + IntToHex(RawMBR[71], 2) +
               #13#10;
      end;
   end;
   ShowMessage(s);
end;

这段代码需要提升权限才能访问磁盘。


你能解释一下这个为什么有效吗?如果有人想要使用它,他们能否确定代码在任何硬件上都是可靠的将会很有帮助。此外,这段代码不需要提升权限才能执行原始磁盘访问吗? - David Heffernan
这只是一个例子,以便更清楚地了解它的工作原理,并与Diskpart显示(也是十六进制)进行比较。在应用程序中的实际实现将更加高效... - Jack Compton
这可以从你上面的代码在某些情况下失败可以看出。例如,我在我的机器上有一个读卡器,与读卡器相关联的驱动器不响应此答案中的任何代码。调用ReadFile失败了。但由于您的代码没有检查错误,它仍然会写出这些驱动器的签名,并且它们恰好与机器上的主硬盘的签名匹配,因为RawMBR未被修改。这是一个很好的例子,说明使用官方支持的API是明智的,因为它们能够正确地完成任务。 - David Heffernan
帮助你似乎不太容易,是吧?显然,CreateFile 路径需要提升权限是一个缺点。如果你的应用程序已经需要提升权限,那就没问题了。但对于其他读者来说,指出其优缺点并让他们决定是值得的。我没有做出任何评判。我不知道为什么你指责我给你投了反对票。我一直在提供大量帮助和答案。你为什么要攻击我?我只是想帮忙。 - David Heffernan
没有人,尤其是我,会诋毁你。我们都在努力帮助你。最后,关于投票,你可以投票。我们喜欢为社区做出贡献的人。提出好问题并在被要求时改进它们,就像你所做的那样。参与接受最佳答案和投票。你在这里的存在非常受欢迎,如果你参与社区,那就更加如此。 - David Heffernan
显示剩余9条评论

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