确定所有可引导分区






2.81/5 (6投票s)
使用 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 的函数,例如CreateFile
、DeviceIoControl
等。
首先;我们需要获取系统中所有已挂载硬盘的句柄。因此,我们枚举这些驱动器。然后,我们可以为所有物理驱动器获取分区列表。然后,我们可以枚举所有分区。
这是完整的代码主体
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 编程感兴趣的开发人员准备的。我认为底层编程非常困难且有趣。因此,我在开发程序时喜欢学习新知识。我认为如果您对学习基础知识感兴趣,那么这个主题将帮助您以编程方式识别引导分区。希望您喜欢阅读这篇文章。