65.9K
CodeProject 正在变化。 阅读更多。
Home

确定所有可引导分区

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.81/5 (6投票s)

2018年6月23日

CPOL

2分钟阅读

viewsIcon

21659

downloadIcon

358

使用 PInvoke 确定所有可引导分区

引言

在本文中,我将展示如何使用 C# 和 Win API(PInvoke)确定 Windows 操作系统的引导分区。

背景

Windows 操作系统在磁盘上有不同类型的分区。通常会有一个系统分区(C:\),除此之外,还可能有用户分区,例如D:\E:\。还可能存在另一种类型的分区,例如引导分区,这正是我们文章的主题。有时,开发人员希望识别引导分区,以避免损坏系统,因为此分区对于 Windows 操作系统的引导操作非常重要。

感谢 Richard,我意识到引导指示器属性在 GPT 磁盘上无效。因此,我更新了源代码和文章,使其与 GPT 磁盘兼容。

Using the Code

我使用了 C# (PInvoke)。我需要定义经典的 MS 定义,例如结构体、常量等。因此,我不会在下面提及这些定义。

Native 类包含 Win API 的结构体和常量定义。

The NativeApi 类包含 Win API 的函数,例如CreateFileDeviceIoControl 等。

首先;我们需要获取系统中所有已挂载硬盘的句柄。因此,我们枚举这些驱动器。然后,我们可以为所有物理驱动器获取分区列表。然后,我们可以枚举所有分区。

这是完整的代码主体

  public const uint MAX_NUMBER_OF_DRIVES = 64;

  for (uint i = 0; i < MAX_NUMBER_OF_DRIVES; i++)
  {
     // try to open the current physical drive
     string volume = string.Format("\\\\.\\PhysicalDrive{0}", i); 
     SafeFileHandle hndl = CreateFile(volume, Native.GENERIC_READ | Native.GENERIC_WRITE,
                                                Native.FILE_SHARE_READ | Native.FILE_SHARE_WRITE,
                                                IntPtr.Zero,
                                                Native.OPEN_EXISTING,
                                                Native.FILE_ATTRIBUTE_READONLY,
                                                IntPtr.Zero);
     //We have got handle now.

     //Some necessary variables definitions
     IntPtr driveLayoutPtr = IntPtr.Zero;
     int DRIVE_LAYOUT_BUFFER_SIZE = 1024;
     int error;
     uint dummy = 0;
                
     do
     {
       error = 0;
       driveLayoutPtr = Marshal.AllocHGlobal(DRIVE_LAYOUT_BUFFER_SIZE);
       if (DeviceIoControl
          (hndl, Native.IOCTL_DISK_GET_DRIVE_LAYOUT_EX, IntPtr.Zero, 0, driveLayoutPtr, 
          (uint)DRIVE_LAYOUT_BUFFER_SIZE, ref dummy, IntPtr.Zero))
          {
            // I/O-control has been invoked successfully, 
            //convert to DRIVE_LAYOUT_INFORMATION_EX
             DRIVE_LAYOUT_INFORMATION_EX driveLayout = (DRIVE_LAYOUT_INFORMATION_EX)
             Marshal.PtrToStructure(driveLayoutPtr, typeof(DRIVE_LAYOUT_INFORMATION_EX));
             //Now i have got partition
             for (uint p = 0; p < driveLayout.PartitionCount; p++)
             {
                //Enumerate partition by using pointer arithmetic
                IntPtr ptr = new IntPtr(driveLayoutPtr.ToInt64() + 
                Marshal.OffsetOf(typeof(DRIVE_LAYOUT_INFORMATION_EX), "PartitionEntry").ToInt64()
                + (p * Marshal.SizeOf(typeof(PARTITION_INFORMATION_EX))));

                PARTITION_INFORMATION_EX partInfo = (PARTITION_INFORMATION_EX)
                Marshal.PtrToStructure(ptr, typeof(PARTITION_INFORMATION_EX));
                //Check partition is recognized or not
               if (partInfo.PartitionStyle != PARTITION_STYLE.PARTITION_STYLE_GPT)
               {
                 if ((partInfo.PartitionStyle != PARTITION_STYLE.PARTITION_STYLE_MBR) || 
                     (partInfo.Mbr.RecognizedPartition))
                 {
                   if (partInfo.Mbr.BootIndicator == true)
                   {
                    Console.WriteLine("Drive No: " + i + " Partition Number :" + 
                                       partInfo.PartitionNumber + " is boot partition");

                   }

                 }
               }
               else if (partInfo.PartitionStyle == PARTITION_STYLE.PARTITION_STYLE_GPT) {
                 //e3c9e316-0b5c-4db8-817d-f92df00215ae guid is defined from MS here:
                 //https://msdn.microsoft.com/en-us/library/windows/desktop/aa365449(v=vs.85).aspx
                 if (partInfo.Gpt.PartitionType== new Guid("e3c9e316-0b5c-4db8-817d-f92df00215ae"))
                 {
                 Console.WriteLine("Drive No: " + i + " Partition Number :" 
                                  + partInfo.PartitionNumber + " is boot partition");


                 }
              }
             }
            }
       else
       {
          error = Marshal.GetLastWin32Error();
          DRIVE_LAYOUT_BUFFER_SIZE *= 2;                    
        }
       Marshal.FreeHGlobal(driveLayoutPtr);
       driveLayoutPtr = IntPtr.Zero;
  } while (error == Native.ERROR_INSUFFICIENT_BUFFER);
}

如上所示,我们获取所有分区并检查每个分区是否为 BootIndicator

重要的是首先使用 DeviceIoControl 函数填充 DRIVE_LAYOUT_INFORMATION_EX 结构。然后,需要进行指针运算来提取缓冲区。最后,我们需要将字节缓冲区转换为 PARTITION_INFORMATION_EX 结构。就这些了!

请记住,并非所有已安装 Windows 的计算机都必须将引导分区分离。请考虑引导操作系统文件可能位于已安装操作系统的分区上,通常是“本地磁盘 (C:)”。

最后,如果您想测试示例应用程序,该应用程序需要管理员凭据才能正确运行。

 

关注点

我写这篇文章是为对 Win API 编程感兴趣的开发人员准备的。我认为底层编程非常困难且有趣。因此,我在开发程序时喜欢学习新知识。我认为如果您对学习基础知识感兴趣,那么这个主题将帮助您以编程方式识别引导分区。希望您喜欢阅读这篇文章。

© . All rights reserved.