找回密码
 立即注册
首页 业界区 安全 .NET 磁盘管理-技术方案选型

.NET 磁盘管理-技术方案选型

舒娅友 昨天 21:00
在家庭以及企业场景下的网络磁盘产品,使用Iscsi均需要对磁盘进行管理。不同Windows版本、安装第三方软件,导致每个C端用户的运行环境不同,对磁盘的管理带来一定的使用干扰
本地介绍下磁盘管理的几种方案以及存在的一些问题
对磁盘管理主要有以下操作入口/方式:

  • Powershell
  • Diskpart
  • WMI
  • WIN32(IOCTL)
下面介绍下四者之间的关系以及所依赖的windows系统服务
Windows磁盘管理服务依赖层级

从操作系统角度看,这几种方式编程/操作入口是围绕同一套内核与服务堆栈的不同“壳”,完成套娃封装
从高到低,依次列下windows主要的磁盘相关入口和服务
1. GUI/工具层
MMC - Windows系统磁盘管理工具,如果需要快速查看和操作磁盘分区的话,可以用这个
1.png

以及Storage Spaces GUI - Windows系统设置存储管理
2.png
3.png

这俩个工具主要是使用WMI相关操作来实现
2. 脚本/命令层
Powershell磁盘管理命令
diskpart磁盘管理命令
CIM磁盘管理命令
3. API/管理接口层
WMI服务:Winmgmt(Windows Management Instrumentation),使用Win32_DiskDrive 等
4.png

磁盘管理服务:Virtual Disk,VDS进程名称vds.exe
5.png

磁盘存储服务:Microsoft Storage Spaces SMP
6.png

4. 内核/驱动/IOCTL层
Storage Management Provider:系统组件,不是单独服务可见
IOCTL: Win32API、DevicerIoControl
磁盘类驱动(disk.sys)、卷管理器(volmgr/vdsci)、文件系统驱动(NTFS/ReFS)
而上面说的四种方案,依赖的底层服务:
PowerShell 基于 WMI / Storage Management API封装,依赖的组件最多:Winmgmt、Microsoft Storage Spaces SMP、Storage Service、VDS等
WMI/CIM 有部分是走 VDS / Storage API,有部分直接调用底层驱动,依赖:VDS服务、Winmgmt服务
diskpart 内部是调用 VDS / Storage API / IOCTL,依赖相对较少:VDS服务等
Win32 IOCTL 是最底层(用户态可达)的接口,不依赖上层框架
比如下方的WMI服务不存在,会导致powershell磁盘查询不到,WMI磁盘查询不到,但diskpart访问正常:
  1. 1 PS C:\Users\yudong> Get-Disk
  2. 2 PS C:\Users\yudong> Get-CimInstance -Namespace root/Microsoft/Windows/Storage -ClassName MSFT_Disk
  3. 3 PS C:\Users\yudong> diskpart
  4. 4
  5. 5 Microsoft DiskPart 版本 10.0.26100.1150
  6. 6
  7. 7 Copyright (C) Microsoft Corporation.
  8. 8 在计算机上: GIH-D-24762
  9. 9
  10. 10 DISKPART> list disk
  11. 11
  12. 12   磁盘 ###  状态           大小     可用     Dyn  Gpt
  13. 13   --------  -------------  -------  -------  ---  ---
  14. 14   磁盘 0    联机             3726 GB  1024 KB        *
  15. 15   磁盘 1    联机             3726 GB  1024 KB        *
  16. 16   磁盘 2    联机             2794 GB      0 B        *
  17. 17   磁盘 3    联机              931 GB      0 B        *
  18. 18   磁盘 4    联机              465 GB  1024 KB        *
  19. 19   磁盘 5    联机             1863 GB      0 B
  20. 20   磁盘 6    联机             7452 GB      0 B        *
复制代码
还有Microsoft Storage Spaces SMP服务被第三方软件禁用,导致Powershell Get-Disk获取结果为空:
7.png

下面对各个模块展开介绍下
Powershell磁盘管理

上面说了,PowerShell使用 Storage Management API + 新的 WMI/CIM 类,磁盘命令本质是对这些 WMI 类的包装。层级如下:
PowerShell cmdlet
-> MSFT_* WMI 类 (CIM)、WMI服务Winmgmt
-> Storage Management Provider
-> 内核驱动 (disk.sys, partmgr.sys, volmgr.sys)
-> 设备硬件
powershell有以下查找主要命令,
Get-Disk - 查找磁盘
Get-Partition - 查找分区
Get-Volume - 查找卷
Get-Disk | Where-Object -FilterScript {  $_.BusType -Eq "iSCSI" -and $_.SerialNumber -Eq "8fa461f8-9436-4260-8191-789b23859757"} - 查找指定Iscsi协议磁盘
8.png

操作磁盘命令,比如初始化GPT磁盘:Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -UseMaximumSize | Format-Volume -FileSystem:NTFS -NewFileSystemLabel:测试盘 -Confirmfalse -Force
Powershell命令因易用性,非常适合脚本自动化、用户级的使用。但非常与用户环境有关,换个用户或换台机器就经常表现不同,比如:卡很久、超时、直接报错、磁盘盘就是查询不到
几个原因:
WMI / CIM 调用超时

  • WMI 服务卡住、存储驱动响应慢
  • 网络/防火墙导致远程调用超时
硬件IO超时

  • 坏盘 / 坏 U 盘 / USB 扩展坞质量问题
  • 大量重新尝试 I/O 导致操作整体拖得很长
具体场景,发现公司内部某个部门发生powershell命令超时概率很多,因为这些设备都在跑软件压力测试。。。导致磁盘获取命令,很容易超时
还有些特殊情况,服务异常出现的情况比较多。如WMI服务,以下是修复成功案例:
  1. 1 PS C:\Users\yudong> Get-WmiObject Win32_OperatingSystem
  2. 2 Get-WmiObject : 无效类 “Win32_OperatingSystem”
  3. 3 所在位置 行:1 字符: 1
  4. 4 + Get-WmiObject Win32_OperatingSystem
  5. 5 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  6. 6     + CategoryInfo          : InvalidType: (:) [Get-WmiObject], ManagementException
  7. 7     + FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
  8. 8
  9. 9 PS C:\Users\yudong> net stop winmgmt /y
  10. 10 Windows Management Instrumentation 服务正在停止.
  11. 11 Windows Management Instrumentation 服务已成功停止。
  12. 12
  13. 13 PS C:\Users\yudong> winmgmt /resetrepository
  14. 14 WMI 存储库已重置
  15. 15
  16. 16 PS C:\Users\yudong> Get-WmiObject Win32_OperatingSystem
  17. 17
  18. 18
  19. 19 SystemDirectory : C:\WINDOWS\system32
  20. 20 Organization    : Online Game Dept
  21. 21 BuildNumber     : 26100
  22. 22 RegisteredUser  : Windows 用户
  23. 23 SerialNumber    : 00329-00000-00003-AA238
  24. 24 Version         : 10.0.26100
复制代码
还有Microsoft Storage Spaces SMP服务,如果Get-Disk拿不到磁盘,定位客户问题发现很大可能是这个服务异常了。重启一下即可
WMI/CIM磁盘管理

WMI相关命令,需要拆分为俩部分:WIN32_*经典类,以及MSFT_*新的StorageWMI类
经典类:

  • Win32_DiskDrive
  • Win32_DiskPartition
  • Win32_LogicalDisk
  • Win32_Volume
早期设计,很多是通过内核 API + IOCTL 和 VDS 实现。主要用于查询,修改操作有限
依赖服务:Winmgmt、RPCSS(RPC服务)、以及少量依赖VDS
StorageWmi类

  • MSFT_Disk
  • MSFT_Partition
  • MSFT_Volume
  • MSFT_StoragePool
  • MSFT_VirtualDisk
这是Windows8之后的新存储管理WMI接口,详见官网文档:Storage Management API Classes - Windows drivers | Microsoft Learn, 依赖层级:
WMI (MSFT_* 类)
  -> Storage Management Provider
     -> IOCTL -> disk.sys / partmgr.sys / ...
具体依赖的服务:Winmgmt(WMI 服务)
WMI 是“管理数据模型 + 接口”,本身不是一个磁盘管理“方案”,而是很多方案的基础接口。相对Powershell Storage管理,算是比较稳定和依赖较少的了
直接使用.NET通过WMI获取详细的磁盘列表数据,代码如下:
  1. 1     public OperateResult<List<LocalDisk>> GetDisks()
  2. 2     {
  3. 3         var disks = new List<LocalDisk>();
  4. 4         try
  5. 5         {
  6. 6             // Win32_DiskDrive: 物理磁盘
  7. 7             using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive"))
  8. 8             using (var driveCollection = searcher.Get())
  9. 9             {
  10. 10                 foreach (ManagementObject drive in driveCollection)
  11. 11                 {
  12. 12                     var diskInfo = new LocalDisk();
  13. 13
  14. 14                     // 1. 磁盘编号 PhysicalDriveN
  15. 15                     // Win32_DiskDrive.DeviceID 一般为 "\\.\PHYSICALDRIVE0"
  16. 16                     var deviceId = (drive["DeviceID"] as string) ?? string.Empty;
  17. 17                     var diskNumber = ParsePhysicalDriveNumber(deviceId);
  18. 18                     diskInfo.Number = diskNumber;
  19. 19
  20. 20                     // 2. 序列号 (不同厂商格式不统一;有时需要 Win32_PhysicalMedia)
  21. 21                     diskInfo.SerialNumber = (drive["SerialNumber"] as string)?.Trim() ?? string.Empty;
  22. 22
  23. 23                     // 3. DeviceName
  24. 24                     diskInfo.DeviceName = (drive["Model"] as string)?.Trim() ?? string.Empty;
  25. 25
  26. 26                     // 4. 只读/在线状态(WMI 并没有非常标准的字段,这里用粗略映射)
  27. 27                     //   Win32_DiskDrive.Status: "OK" / "Error" / "Degraded" ...
  28. 28                     diskInfo.IsOffline = GetOffline(diskNumber);
  29. 29
  30. 30                     // 没有直接 readonly 标记,先默认为 false,
  31. 31                     // 如需更精确可以通过 Win32_Volume 或 DeviceIoControl 获取。
  32. 32                     diskInfo.IsReadOnly = GetReadonly(diskNumber);
  33. 33
  34. 34                     // 5. 总线类型(没有 STORAGE_BUS_TYPE 枚举,使用 InterfaceType 粗略映射)
  35. 35                     var interfaceType = (drive["InterfaceType"] as string)?.Trim();
  36. 36                     diskInfo.BusType = MapBusType(interfaceType, diskInfo.DeviceName);
  37. 37
  38. 38                     // 6. 磁盘容量 (字节 -> GB)
  39. 39                     // Win32_DiskDrive.Size 为字节数(string)
  40. 40                     if (drive["Size"] != null && long.TryParse(drive["Size"].ToString(), out long sizeBytes))
  41. 41                     {
  42. 42                         diskInfo.DiskSize = sizeBytes;
  43. 43                     }
  44. 44
  45. 45                     // 7. 获取挂载点及已用容量,通过 3 张 WMI 关联表:
  46. 46                     // Win32_DiskDrive -> Win32_DiskDriveToDiskPartition -> Win32_DiskPartition ->
  47. 47                     // Win32_LogicalDiskToPartition -> Win32_LogicalDisk
  48. 48                     FillMountPathsAndUsedSize(diskInfo, drive);
  49. 49                     disks.Add(diskInfo);
  50. 50
  51. 51                     diskInfo.Tag = GetVolumeLabel(diskInfo.MountPaths.FirstOrDefault());
  52. 52                 }
  53. 53             }
  54. 54
  55. 55             return OperateResult<List<LocalDisk>>.ToSuccess(disks.OrderBy(i => i.Number).ToList());
  56. 56         }
  57. 57         catch (Exception ex)
  58. 58         {
  59. 59             return OperateResult<List<LocalDisk>>.ToError(ex.Message);
  60. 60         }
  61. 61     }
复制代码
附带的一些属性获取函数:
9.gif
10.gif
  1.   1     private int ParsePhysicalDriveNumber(string deviceId)
  2.   2     {
  3.   3         // "\\.\PHYSICALDRIVE0" -> 0
  4.   4         if (string.IsNullOrWhiteSpace(deviceId))
  5.   5             return -1;
  6.   6
  7.   7         var upper = deviceId.ToUpperInvariant();
  8.   8         var idx = upper.LastIndexOf("PHYSICALDRIVE", StringComparison.Ordinal);
  9.   9         if (idx < 0) return -1;
  10. 10
  11. 11         var numPart = upper.Substring(idx + "PHYSICALDRIVE".Length);
  12. 12         if (int.TryParse(numPart, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num))
  13. 13             return num;
  14. 14
  15. 15         return -1;
  16. 16     }
  17. 17
  18. 18     private StorageBusType MapBusType(string interfaceType, string deviceName)
  19. 19     {
  20. 20         if (string.IsNullOrEmpty(interfaceType))
  21. 21             return StorageBusType.Unknown;
  22. 22
  23. 23         switch (interfaceType.ToUpperInvariant())
  24. 24         {
  25. 25             case "SCSI":
  26. 26                 if (deviceName.Contains("SCSI"))
  27. 27                 {
  28. 28                     return StorageBusType.Iscsi;
  29. 29                 }
  30. 30                 return StorageBusType.Scsi;
  31. 31             case "IDE":
  32. 32             case "ATA":
  33. 33                 return StorageBusType.Ata;
  34. 34             case "USB":
  35. 35                 return StorageBusType.Usb;
  36. 36             // 可根据需要扩展映射
  37. 37             default:
  38. 38                 return StorageBusType.Unknown;
  39. 39         }
  40. 40     }
  41. 41
  42. 42     /// <summary>
  43. 43     /// 填充 MountPaths(盘符)和 DiskUsedSize(GB)
  44. 44     /// </summary>
  45. 45     private OperateResult FillMountPathsAndUsedSize(LocalDisk diskInfo, ManagementObject diskDrive)
  46. 46     {
  47. 47         long totalUsedBytes = 0;
  48. 48
  49. 49         // 通过 Win32_DiskDriveToDiskPartition 关联到分区
  50. 50         using (var partitionRel = new ManagementObjectSearcher(
  51. 51                    "ASSOCIATORS OF {Win32_DiskDrive.DeviceID='" + diskDrive["DeviceID"] +
  52. 52                    "'} WHERE AssocClass = Win32_DiskDriveToDiskPartition"))
  53. 53         using (var partitions = partitionRel.Get())
  54. 54         {
  55. 55             foreach (ManagementObject partition in partitions)
  56. 56             {
  57. 57                 // 通过 Win32_LogicalDiskToPartition 关联到逻辑磁盘(盘符)
  58. 58                 using (var logicalRel = new ManagementObjectSearcher(
  59. 59                            "ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" + partition["DeviceID"] +
  60. 60                            "'} WHERE AssocClass = Win32_LogicalDiskToPartition"))
  61. 61                 using (var logicalDisks = logicalRel.Get())
  62. 62                 {
  63. 63                     foreach (ManagementObject logicalDisk in logicalDisks)
  64. 64                     {
  65. 65                         // 计算已用空间
  66. 66                         if (logicalDisk["Size"] != null &&
  67. 67                             logicalDisk["FreeSpace"] != null &&
  68. 68                             long.TryParse(logicalDisk["Size"].ToString(), out long volSize) &&
  69. 69                             long.TryParse(logicalDisk["FreeSpace"].ToString(), out long free))
  70. 70                         {
  71. 71                             totalUsedBytes += (volSize - free);
  72. 72                         }
  73. 73                     }
  74. 74                 }
  75. 75             }
  76. 76         }
  77. 77
  78. 78         diskInfo.DiskUsedSize =totalUsedBytes;
  79. 79
  80. 80         try
  81. 81         {
  82. 82             var paths = GetAccessPaths(diskInfo.Number);
  83. 83             var filtedPaths = paths.Where(i => !i.StartsWith(@"\\?\Volume")).ToList();
  84. 84             diskInfo.MountPaths = filtedPaths;
  85. 85             return OperateResult.ToSuccess();
  86. 86         }
  87. 87         catch (Exception e)
  88. 88         {
  89. 89            return OperateResult.ToError(e);
  90. 90         }
  91. 91     }
  92. 92     /// <summary>
  93. 93     /// 获取磁盘的所有访问路径
  94. 94     /// </summary>
  95. 95     private List<string> GetAccessPaths(int diskNumber)
  96. 96     {
  97. 97         ManagementScope scope = new ManagementScope(@"\\.\root\Microsoft\Windows\Storage");
  98. 98         scope.Connect();
  99. 99         string query = $"SELECT * FROM MSFT_Partition WHERE DiskNumber = {diskNumber}";
  100. 100         using ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, new ObjectQuery(query));
  101. 101         var pathList = new List<string>();
  102. 102         foreach (var partition in searcher.Get().Cast<ManagementObject>())
  103. 103         {
  104. 104             // 获取 AccessPaths 属性(数组)
  105. 105             var accessPaths = partition["AccessPaths"] as string[];
  106. 106             if (accessPaths == null)
  107. 107             {
  108. 108                 continue;
  109. 109             }
  110. 110             pathList.AddRange(accessPaths);
  111. 111         }
  112. 112         return pathList;
  113. 113     }
复制代码
View Code遍历磁盘列表,4块盘耗时接近1s:
11.png

DiskPart磁盘管理

diskpart 是 原生 Win32 命令行工具,内部大致通过:

  • VDS / Storage Management API(老系统)
  • 新系统上,一部分功能由新的 Storage API 接管
  • 再往下还是 IOCTL 调用内核驱动
调用层级如下,diskpart.exe
  -> VDS / Storage Management API
     -> 内核驱动 (disk.sys, partmgr.sys, volmgr.sys)
        -> 设备硬件
diskpart常用命令列表:

  • list disk
  • select disk 1
  • detail disk
  • list partition
  • list volume
12.png

DiskPart对WMI并不强依赖,基本上依赖服务就一个Virtual Disk了,操作也比较简单。但缺点也比较明显,访问性能比较差、磁盘操作使用Powersehell调用diskpart命令基本也在s级以上
Win32 IOCTL磁盘管理

IOCTL是指通过直接调用 Windows API DeviceIoControl 对磁盘、卷、文件句柄发送控制码:

  • IOCTL_DISK_*
  • IOCTL_STORAGE_*
  • FSCTL_*(针对文件系统)
IOCTL文档:deviceIoControl 函数 (ioapiset.h) - Win32 apps | Microsoft Learn、Winioctl.h 标头 - Win32 apps | Microsoft Learn
磁盘管理详细文档:磁盘管理 - Win32 apps | Microsoft Learn
WIN32方案,不依赖 VDS / WMI 等上层框架
仅依赖:

  • Win32 子系统 + 内核 I/O 栈
  • 对应的设备驱动(disk.sys, storport.sys, nvme.sys 等)
需要基于WIN32API一层层处理细节,比如获取磁盘列表:
  1. 1     /// <summary>
  2. 2     /// 通过磁盘编号获取序列号SerialNumber
  3. 3     /// </summary>
  4. 4     /// <param name="diskNumber">磁盘编号</param>
  5. 5     /// <param name="volumeMaps"></param>
  6. 6     /// <returns></returns>
  7. 7     private OperateResult<LocalDisk> GetDiskInfoByDiskNumber(int diskNumber, Dictionary<int, List<string>> volumeMaps)
  8. 8     {
  9. 9         //逐个尝试 PhysicalDrive0..N
  10. 10         string physicalDrive = @"\\.\PhysicalDrive" + diskNumber;
  11. 11         IntPtr hDisk = CreateFile(
  12. 12             physicalDrive,
  13. 13             GENERIC_READ,
  14. 14             FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  15. 15             IntPtr.Zero,
  16. 16             OPEN_EXISTING,
  17. 17             0,
  18. 18             IntPtr.Zero);
  19. 19         try
  20. 20         {
  21. 21             // 不存在这个物理盘(或者无权限),忽略此异常
  22. 22             if (hDisk == INVALID_HANDLE_VALUE)
  23. 23             {
  24. 24                 return OperateResult<LocalDisk>.ToSuccess();
  25. 25             }
  26. 26             var diskInfo = new LocalDisk();
  27. 27             diskInfo.Number = diskNumber;
  28. 28
  29. 29             //获取磁盘基础信息
  30. 30             var getDiskPropertiesResult = GetDiskProperties(hDisk);
  31. 31             if (!getDiskPropertiesResult.Success)
  32. 32             {
  33. 33                 return OperateResult<LocalDisk>.ToError($"Get disk {physicalDrive} properties failed, {getDiskPropertiesResult.Message}", getDiskPropertiesResult.Exception, getDiskPropertiesResult.Code);
  34. 34             }
  35. 35             var diskProperties = getDiskPropertiesResult.Data;
  36. 36             diskInfo.SerialNumber = diskProperties.SerialNumber;
  37. 37             diskInfo.DeviceName = diskProperties.DeviceName;
  38. 38             diskInfo.BusType = diskProperties.BusType;
  39. 39
  40. 40             //是否只读/联机
  41. 41             var diskAttributesResult = GetDiskAttributes(hDisk);
  42. 42             if (!diskAttributesResult.Success)
  43. 43             {
  44. 44                 return OperateResult<LocalDisk>.ToError($"Get disk {diskProperties.DeviceName} attributes failed, {diskAttributesResult.Message}", diskAttributesResult.Exception, diskAttributesResult.Code);
  45. 45             }
  46. 46             var diskStorageAttributes = diskAttributesResult.Data;
  47. 47             diskInfo.IsReadOnly = diskStorageAttributes.IsReadOnly;
  48. 48             diskInfo.IsOffline = diskStorageAttributes.IsOffline;
  49. 49
  50. 50             //磁盘容量
  51. 51             var getDiskSizeResult = GetDiskSize(hDisk);
  52. 52             diskInfo.DiskSize = getDiskSizeResult.Data;
  53. 53
  54. 54             //获取分区信息
  55. 55             var partitionInfoResult = GetPartitionInfo(hDisk);
  56. 56             if (!partitionInfoResult.Success)
  57. 57             {
  58. 58                 return OperateResult<LocalDisk>.ToError($"Get disk {diskProperties.DeviceName} partition failed, {partitionInfoResult.Message}", partitionInfoResult.Exception, partitionInfoResult.Code);
  59. 59             }
  60. 60             var diskPartitionInfo = partitionInfoResult.Data;
  61. 61             diskInfo.PartitionStyle = (DiskPartitionStyle)diskPartitionInfo.PartitionStyle;
  62. 62             diskInfo.PartitionCount = diskPartitionInfo.PartitionCount;
  63. 63             //基础数据区分大小
  64. 64             diskInfo.DiskAllocateSize = diskPartitionInfo.Partitions.FirstOrDefault(i => i.PartitionType.ToUpper() == "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7")?.PartitionLength ?? 0;
  65. 65
  66. 66             //挂载路径
  67. 67             if (volumeMaps.TryGetValue(diskNumber, out var mounts) && mounts != null)
  68. 68             {
  69. 69                 diskInfo.MountPaths = mounts;
  70. 70             }
  71. 71             //获取卷标名称
  72. 72             if (diskInfo.MountPaths.Any())
  73. 73             {
  74. 74                 //通过任意一个mountPath获取
  75. 75                 var mountPath = diskInfo.MountPaths.First();
  76. 76                 var getVolumeInfoResult = GetVolumeInfo(mountPath);
  77. 77                 diskInfo.Tag = getVolumeInfoResult.Data?.VolumeLabel ?? string.Empty;
  78. 78                 diskInfo.FileSystemType = getVolumeInfoResult.Data?.FileSystemType ?? string.Empty;
  79. 79             }
  80. 80             //磁盘已使用大小
  81. 81             if (diskInfo.MountPaths.Any())
  82. 82             {
  83. 83                 long diskUsedSize = 0L;
  84. 84                 //通过所有mountPath相加,获取磁盘已使用大小
  85. 85                 foreach (var mountPath in diskInfo.MountPaths)
  86. 86                 {
  87. 87                     var usageByMountPathResult = GetDiskSizeUsageByMountPath(mountPath);
  88. 88                     diskUsedSize += usageByMountPathResult.Data?.UsedBytes ?? 0;
  89. 89                 }
  90. 90                 diskInfo.DiskUsedSize = diskUsedSize;
  91. 91             }
  92. 92             return OperateResult<LocalDisk>.ToSuccess(diskInfo);
  93. 93         }
  94. 94         finally
  95. 95         {
  96. 96             CloseHandle(hDisk);
  97. 97         }
  98. 98     }
复制代码
其中磁盘属性获取细节,就不展示了:
13.gif
14.gif
  1.   1         /// <summary>
  2.   2         /// 获取所有磁盘
  3.   3         /// </summary>
  4.   4         /// <returns></returns>
  5.   5         public OperateResult<List<LocalDisk>> GetDisks()
  6.   6         {
  7.   7             // 1. 先拿卷 -> 卷所属的物理磁盘号 + 盘符/挂载点
  8.   8             var getVolumesResult = GetAllVolumeMountPaths();
  9.   9             if (!getVolumesResult.Success)
  10. 10             {
  11. 11                 return OperateResult<List<LocalDisk>>.ToError(getVolumesResult.Message, getVolumesResult.Exception, getVolumesResult.Code);
  12. 12             }
  13. 13             var volumeMaps = getVolumesResult.Data;
  14. 14
  15. 15             // 2. 获取磁盘列表
  16. 16             var diskList = new List<LocalDisk>();
  17. 17             // 根据卷信息推一个最大磁盘号,同时至少查询16 个
  18. 18             int maxDiskNumberCount = Math.Max(volumeMaps.Max(i => i.Key), 16);
  19. 19             for (int diskNumber = 0; diskNumber <= maxDiskNumberCount; diskNumber++)
  20. 20             {
  21. 21                 var getDiskResult = GetDiskInfoByDiskNumber(diskNumber, volumeMaps);
  22. 22                 if (!getDiskResult.Success)
  23. 23                 {
  24. 24                     //结束查询
  25. 25                     if (diskNumber == maxDiskNumberCount - 1)
  26. 26                     {
  27. 27                         return getDiskResult.ToResult<List<LocalDisk>>();
  28. 28                     }
  29. 29                     //继续查询其它
  30. 30                     continue;
  31. 31                 }
  32. 32                 //可能为空
  33. 33                 if (getDiskResult.Data == null)
  34. 34                 {
  35. 35                     continue;
  36. 36                 }
  37. 37                 diskList.Add(getDiskResult.Data);
  38. 38             }
  39. 39
  40. 40             return OperateResult<List<LocalDisk>>.ToSuccess(diskList);
  41. 41         }
  42. 42
  43. 43         /// <summary>
  44. 44         /// 获取所有磁盘卷的挂载路径信息
  45. 45         /// <remarks>通过枚举卷,并使用 IOCTL_STORAGE_GET_DEVICE_NUMBER 映射到设备号。</remarks>
  46. 46         /// </summary>
  47. 47         /// <returns>PhysicalDiskNumber -> 对应的所有挂载路径(盘符、挂载点)</returns>
  48. 48         private OperateResult<Dictionary<int, List<string>>> GetAllVolumeMountPaths()
  49. 49         {
  50. 50             var diskDict = new Dictionary<int, List<string>>();
  51. 51
  52. 52             int maxPath = 1024;
  53. 53             var volNameSb = new StringBuilder(maxPath);
  54. 54             IntPtr findVolumeHandle = FindFirstVolumeW(volNameSb, (uint)volNameSb.Capacity);
  55. 55             try
  56. 56             {
  57. 57                 if (findVolumeHandle == (IntPtr)(-1))
  58. 58                 {
  59. 59                     return OperateResult<Dictionary<int, List<string>>>.ToSuccess(diskDict);
  60. 60                 }
  61. 61                 while (true)
  62. 62                 {
  63. 63                     string volumeName = volNameSb.ToString();
  64. 64                     // volumeName: \\?\Volume{GUID}\
  65. 65
  66. 66                     // 打开卷设备
  67. 67                     string volumePathForDevice = volumeName.TrimEnd('\\'); // \\?\Volume{GUID}
  68. 68                     IntPtr hVolume = CreateFile(
  69. 69                         volumePathForDevice,
  70. 70                         0, // 只需要 IOCTL,不读写
  71. 71                         FILE_SHARE_READ | FILE_SHARE_WRITE,
  72. 72                         IntPtr.Zero,
  73. 73                         OPEN_EXISTING,
  74. 74                         0,
  75. 75                         IntPtr.Zero);
  76. 76
  77. 77                     uint? diskNumber = null;
  78. 78
  79. 79                     if (hVolume != (IntPtr)(-1))
  80. 80                     {
  81. 81                         // 取 STORAGE_DEVICE_NUMBER
  82. 82                         uint size = (uint)Marshal.SizeOf<STORAGE_DEVICE_NUMBER>();
  83. 83                         IntPtr outBuf = Marshal.AllocHGlobal((int)size);
  84. 84                         try
  85. 85                         {
  86. 86                             if (DeviceIoControl(
  87. 87                                     hVolume,
  88. 88                                     IOCTL_STORAGE_GET_DEVICE_NUMBER,
  89. 89                                     IntPtr.Zero,
  90. 90                                     0,
  91. 91                                     outBuf,
  92. 92                                     size,
  93. 93                                     out uint bytesReturned,
  94. 94                                     IntPtr.Zero))
  95. 95                             {
  96. 96                                 STORAGE_DEVICE_NUMBER devNum = Marshal.PtrToStructure<STORAGE_DEVICE_NUMBER>(outBuf);
  97. 97                                 // DeviceType 为 FILE_DEVICE_DISK(0x07) 一般表示物理磁盘
  98. 98                                 diskNumber = devNum.DeviceNumber;
  99. 99                             }
  100. 100                         }
  101. 101                         finally
  102. 102                         {
  103. 103                             Marshal.FreeHGlobal(outBuf);
  104. 104                             CloseHandle(hVolume);
  105. 105                         }
  106. 106                     }
  107. 107
  108. 108                     if (diskNumber.HasValue)
  109. 109                     {
  110. 110                         if (!diskDict.TryGetValue((int)diskNumber.Value, out var list))
  111. 111                         {
  112. 112                             list = new List<string>();
  113. 113                             diskDict[(int)diskNumber.Value] = list;
  114. 114                         }
  115. 115                         // 获取卷的挂载路径列表(可能有多个)
  116. 116                         var getMountPathsResult = GetMountPathsForVolume(volumeName);
  117. 117                         if (!getMountPathsResult.Success)
  118. 118                         {
  119. 119                             return OperateResult<Dictionary<int, List<string>>>.ToError($"磁盘{diskNumber}卷挂载路径获取失败, {getMountPathsResult.Message}", getMountPathsResult.Exception, getMountPathsResult.Code);
  120. 120                         }
  121. 121                         foreach (var mp in getMountPathsResult.Data)
  122. 122                         {
  123. 123                             if (!list.Contains(mp))
  124. 124                                 list.Add(mp);
  125. 125                         }
  126. 126                     }
  127. 127
  128. 128                     // 下一卷
  129. 129                     volNameSb.Clear();
  130. 130                     volNameSb.EnsureCapacity(maxPath);
  131. 131
  132. 132                     if (!FindNextVolumeW(findVolumeHandle, volNameSb, (uint)volNameSb.Capacity))
  133. 133                     {
  134. 134                         int err = Marshal.GetLastWin32Error();
  135. 135                         // ERROR_NO_MORE_FILES
  136. 136                         if (err == 18)
  137. 137                             break;
  138. 138
  139. 139                         return OperateResult<Dictionary<int, List<string>>>.ToWin32Error("query disk volumes failed", err);
  140. 140                     }
  141. 141                 }
  142. 142             }
  143. 143             catch (Exception ex)
  144. 144             {
  145. 145                 return OperateResult<Dictionary<int, List<string>>>.ToError("query disk volumes error", ex);
  146. 146             }
  147. 147             finally
  148. 148             {
  149. 149                 FindVolumeClose(findVolumeHandle);
  150. 150             }
  151. 151             return OperateResult<Dictionary<int, List<string>>>.ToSuccess(diskDict);
  152. 152         }
  153. 153
  154. 154         /// <summary>
  155. 155         /// 获取分区信息
  156. 156         /// </summary>
  157. 157         /// <param name="hDisk"></param>
  158. 158         /// <returns></returns>
  159. 159         private OperateResult<DiskPartitionInfo> GetPartitionInfo(IntPtr hDisk)
  160. 160         {
  161. 161             int outSize = Marshal.SizeOf<DRIVE_LAYOUT_INFORMATION_EX>() + 128 * 64; // 给多一点空间
  162. 162             IntPtr outBuffer = Marshal.AllocHGlobal(outSize);
  163. 163
  164. 164             try
  165. 165             {
  166. 166                 if (!DeviceIoControl(
  167. 167                         hDisk,
  168. 168                         IOCTL_DISK_GET_DRIVE_LAYOUT_EX,
  169. 169                         IntPtr.Zero,
  170. 170                         0,
  171. 171                         outBuffer,
  172. 172                         (uint)outSize,
  173. 173                         out _,
  174. 174                         IntPtr.Zero))
  175. 175                 {
  176. 176                     return OperateResult<DiskPartitionInfo>.ToWin32Error("DeviceIoControl.IOCTL_DISK_GET_DRIVE_LAYOUT_EX failed", Marshal.GetLastWin32Error());
  177. 177                 }
  178. 178
  179. 179                 // 只取结构开头
  180. 180                 var layout = Marshal.PtrToStructure<DRIVE_LAYOUT_INFORMATION_EX_HEADER>(outBuffer);
  181. 181                 var partitionInfo = new DiskPartitionInfo()
  182. 182                 {
  183. 183                     PartitionCount = (int)layout.PartitionCount,
  184. 184                     PartitionStyle = layout.PartitionStyle,
  185. 185                     DiskId = layout.Gpt.DiskId,
  186. 186                     StartingUsableOffset = layout.Gpt.StartingUsableOffset,
  187. 187                     UsableLength = layout.Gpt.UsableLength,
  188. 188                     MaxPartitionCount = layout.Gpt.MaxPartitionCount
  189. 189                 };
  190. 190                 // 指向第一个 PARTITION_INFORMATION_EX 的指针:
  191. 191
  192. 192                 IntPtr pCurrent = IntPtr.Add(outBuffer, Marshal.SizeOf<DRIVE_LAYOUT_INFORMATION_EX>());
  193. 193                 int partSize = Marshal.SizeOf<PARTITION_INFORMATION_EX>();
  194. 194                 for (int i = 0; i < layout.PartitionCount; i++)
  195. 195                 {
  196. 196                     var part = Marshal.PtrToStructure<PARTITION_INFORMATION_EX>(pCurrent);
  197. 197                     var item = new PartitionEntryInfo
  198. 198                     {
  199. 199                         PartitionNumber = (int)part.PartitionNumber,
  200. 200                         StartingOffset = part.StartingOffset,
  201. 201                         PartitionLength = part.PartitionLength,
  202. 202                         PartitionType = part.Gpt.PartitionType.ToString(),
  203. 203                         PartitionName = part.Gpt.Name
  204. 204                     };
  205. 205
  206. 206                     partitionInfo.Partitions.Add(item);
  207. 207                     pCurrent = IntPtr.Add(pCurrent, partSize);
  208. 208                 }
  209. 209
  210. 210                 return OperateResult<DiskPartitionInfo>.ToSuccess(partitionInfo);
  211. 211             }
  212. 212             finally
  213. 213             {
  214. 214                 Marshal.FreeHGlobal(outBuffer);
  215. 215             }
  216. 216         }
  217. 217
  218. 218         /// <summary>
  219. 219         /// 获取磁盘静态属性
  220. 220         /// </summary>
  221. 221         /// <param name="hDisk"></param>
  222. 222         /// <returns></returns>
  223. 223         private OperateResult<DiskStorageProperty> GetDiskProperties(IntPtr hDisk)
  224. 224         {
  225. 225             var storageProperties = new DiskStorageProperty();
  226. 226             var query = new STORAGE_PROPERTY_QUERY
  227. 227             {
  228. 228                 PropertyId = STORAGE_PROPERTY_ID.StorageDeviceProperty,
  229. 229                 QueryType = STORAGE_QUERY_TYPE.PropertyStandardQuery,
  230. 230                 AdditionalParameters = new byte[1]
  231. 231             };
  232. 232             uint allocSize = 1024;
  233. 233             IntPtr buffer = Marshal.AllocHGlobal((int)allocSize);
  234. 234             try
  235. 235             {
  236. 236                 if (!DeviceIoControl(
  237. 237                         hDisk,
  238. 238                         IOCTL_STORAGE_QUERY_PROPERTY,
  239. 239                         ref query,
  240. 240                         (uint)Marshal.SizeOf<STORAGE_PROPERTY_QUERY>(),
  241. 241                         buffer,
  242. 242                         allocSize,
  243. 243                         out var bytesReturned,
  244. 244                         IntPtr.Zero))
  245. 245                 {
  246. 246                     //读取失败
  247. 247                     int err = Marshal.GetLastWin32Error();
  248. 248                     if (err == ERROR_INSUFFICIENT_BUFFER && bytesReturned > allocSize)
  249. 249                     {
  250. 250                         // 重新分配更大缓冲区
  251. 251                         Marshal.FreeHGlobal(buffer);
  252. 252                         allocSize = bytesReturned;
  253. 253                         buffer = Marshal.AllocHGlobal((int)allocSize);
  254. 254                         if (!DeviceIoControl(
  255. 255                                 hDisk,
  256. 256                                 IOCTL_STORAGE_QUERY_PROPERTY,
  257. 257                                 ref query,
  258. 258                                 (uint)Marshal.SizeOf<STORAGE_PROPERTY_QUERY>(),
  259. 259                                 buffer,
  260. 260                                 allocSize,
  261. 261                                 out bytesReturned,
  262. 262                                 IntPtr.Zero))
  263. 263                         {
  264. 264                             //重新分配缓冲区,读取失败
  265. 265                             return OperateResult<DiskStorageProperty>.ToWin32Error("DeviceIoControl.IOCTL_STORAGE_QUERY_PROPERTY execute failed after adjust buffer size", Marshal.GetLastWin32Error());
  266. 266                         }
  267. 267                     }
  268. 268                     else
  269. 269                     {
  270. 270                         return OperateResult<DiskStorageProperty>.ToWin32Error("DeviceIoControl.IOCTL_STORAGE_QUERY_PROPERTY execute failed", err);
  271. 271                     }
  272. 272                 }
  273. 273
  274. 274                 // 至少要包含 Version/Size/几个 offset
  275. 275                 if (bytesReturned < 24)
  276. 276                     return OperateResult<DiskStorageProperty>.ToError($"DeviceIoControl.IOCTL_STORAGE_QUERY_PROPERTY execute success but bytesReturned {bytesReturned} is lower than 24");
  277. 277
  278. 278                 // --- 读取头部固定字段(按官方 C 结构手工偏移)---
  279. 279                 // Size    (ULONG) at offset 0x04
  280. 280                 uint size = (uint)Marshal.ReadInt32(buffer, 4);
  281. 281                 if (size > bytesReturned) size = bytesReturned;
  282. 282
  283. 283                 // 磁盘序列号,同 Get-Disk 的 SerialNumber
  284. 284                 uint serialOffset = (uint)Marshal.ReadInt32(buffer, 0x18);
  285. 285                 string serialRaw = ReadAnsiStringSafe(buffer, size, serialOffset);
  286. 286                 string serialClean = CleanSerialString(serialRaw);
  287. 287                 storageProperties.SerialNumber = serialClean;
  288. 288
  289. 289                 // 磁盘厂商/名称相关
  290. 290                 uint vendorOffset = (uint)Marshal.ReadInt32(buffer, 0x0C);
  291. 291                 uint productOffset = (uint)Marshal.ReadInt32(buffer, 0x10);
  292. 292                 uint revisionOffset = (uint)Marshal.ReadInt32(buffer, 0x14);
  293. 293                 storageProperties.Vendor = ReadAnsiStringSafe(buffer, size, vendorOffset);
  294. 294                 storageProperties.Product = ReadAnsiStringSafe(buffer, size, productOffset);
  295. 295                 storageProperties.Version = ReadAnsiStringSafe(buffer, size, revisionOffset);
  296. 296                 storageProperties.DeviceName = $"{storageProperties.Vendor} {storageProperties.Product}";
  297. 297                 // BusType
  298. 298                 uint busTypeOffset = (uint)Marshal.ReadInt32(buffer, 0x1C);
  299. 299                 storageProperties.BusType = Enum.IsDefined(typeof(StorageBusType), (int)busTypeOffset)
  300. 300                     ? (StorageBusType)busTypeOffset
  301. 301                     : StorageBusType.Unknown;
  302. 302                 return OperateResult<DiskStorageProperty>.ToSuccess(storageProperties);
  303. 303             }
  304. 304             catch (Exception ex)
  305. 305             {
  306. 306                 return OperateResult<DiskStorageProperty>.ToError(ex);
  307. 307             }
  308. 308             finally
  309. 309             {
  310. 310                 Marshal.FreeHGlobal(buffer);
  311. 311             }
  312. 312         }
  313. 313
  314. 314         /// <summary>
  315. 315         /// 获取磁盘大小(Bytes)
  316. 316         /// </summary>
  317. 317         /// <param name="hDisk"></param>
  318. 318         /// <returns></returns>
  319. 319         public OperateResult<long> GetDiskSize(IntPtr hDisk)
  320. 320         {
  321. 321             // 用一个足够大的缓冲区,一般 1024 字节足够
  322. 322             const int bufferSize = 1024;
  323. 323             IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
  324. 324             try
  325. 325             {
  326. 326                 bool ok = DeviceIoControl(
  327. 327                     hDisk,
  328. 328                     IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
  329. 329                     IntPtr.Zero,
  330. 330                     0,
  331. 331                     buffer,
  332. 332                     (uint)bufferSize,
  333. 333                     out var bytesReturned,
  334. 334                     IntPtr.Zero);
  335. 335                 if (!ok)
  336. 336                     return OperateResult<long>.ToError("DeviceIoControl.IOCTL_DISK_GET_DRIVE_GEOMETRY_EX failed", Marshal.GetLastWin32Error());
  337. 337                 if (bytesReturned < Marshal.SizeOf<DISK_GEOMETRY_EX>())
  338. 338                     return OperateResult<long>.ToSuccess(0);
  339. 339
  340. 340                 var geomEx = Marshal.PtrToStructure<DISK_GEOMETRY_EX>(buffer);
  341. 341                 return OperateResult<long>.ToSuccess(geomEx.DiskSize);
  342. 342             }
  343. 343             catch (Exception e)
  344. 344             {
  345. 345                 return OperateResult<long>.ToError(e);
  346. 346             }
  347. 347             finally
  348. 348             {
  349. 349                 Marshal.FreeHGlobal(buffer);
  350. 350             }
  351. 351         }
  352. 352
  353. 353         /// <summary>
  354. 354         /// 获取磁盘扩展属性
  355. 355         /// </summary>
  356. 356         /// <param name="hDisk"></param>
  357. 357         /// <returns></returns>
  358. 358         private OperateResult<DiskStorageAttribues> GetDiskAttributes(IntPtr hDisk)
  359. 359         {
  360. 360             try
  361. 361             {
  362. 362                 int getSize = Marshal.SizeOf<GET_DISK_ATTRIBUTES>();
  363. 363                 var getAttr = new GET_DISK_ATTRIBUTES
  364. 364                 {
  365. 365                     Version = (uint)getSize, // 关键:Version = sizeof(GET_DISK_ATTRIBUTES)
  366. 366                     Reserved1 = 0,
  367. 367                     Attributes = 0
  368. 368                 };
  369. 369
  370. 370                 if (!DeviceIoControl_DiskAttributes(
  371. 371                         hDisk,
  372. 372                         IOCTL_DISK_GET_DISK_ATTRIBUTES,
  373. 373                         ref getAttr,
  374. 374                         (uint)getSize,
  375. 375                         ref getAttr,
  376. 376                         (uint)getSize,
  377. 377                         out _,
  378. 378                         IntPtr.Zero))
  379. 379                 {
  380. 380                     return OperateResult<DiskStorageAttribues>.ToWin32Error("IOCTL_DISK_GET_DISK_ATTRIBUTES 失败", Marshal.GetLastWin32Error());
  381. 381                 }
  382. 382                 //磁盘扩展属性
  383. 383                 var diskStorageAttributes = new DiskStorageAttribues();
  384. 384                 diskStorageAttributes.IsOffline = (getAttr.Attributes & DISK_ATTRIBUTE_OFFLINE) != 0;
  385. 385                 diskStorageAttributes.IsReadOnly = (getAttr.Attributes & DISK_ATTRIBUTE_READ_ONLY) != 0;
  386. 386                 return OperateResult<DiskStorageAttribues>.ToSuccess(diskStorageAttributes);
  387. 387             }
  388. 388             catch (Exception ex)
  389. 389             {
  390. 390                 return OperateResult<DiskStorageAttribues>.ToError(ex);
  391. 391             }
  392. 392         }
  393. 393
  394. 394         /// <summary>
  395. 395         /// 通过任意挂载路径(盘符、目录挂载点、Volume GUID)获取卷大小与使用量
  396. 396         /// </summary>
  397. 397         private OperateResult<DiskSizeUsage> GetDiskSizeUsageByMountPath(string mountPath)
  398. 398         {
  399. 399             if (string.IsNullOrWhiteSpace(mountPath))
  400. 400             {
  401. 401                 return OperateResult<DiskSizeUsage>.ToError($"parameter {nameof(mountPath)} is empty");
  402. 402             }
  403. 403
  404. 404             // 确保路径末尾有反斜杠对某些场景更稳妥
  405. 405             if (!mountPath.EndsWith("\"))
  406. 406                 mountPath += "\";
  407. 407
  408. 408             if (!GetDiskFreeSpaceExW(mountPath,
  409. 409                     out var freeAvailable,
  410. 410                     out var totalBytes,
  411. 411                     out var totalFreeBytes))
  412. 412             {
  413. 413                 return OperateResult<DiskSizeUsage>.ToError("GetDiskFreeSpaceExW failed", Marshal.GetLastWin32Error());
  414. 414             }
  415. 415
  416. 416             return OperateResult<DiskSizeUsage>.ToSuccess(new DiskSizeUsage((long)totalBytes, (long)totalFreeBytes));
  417. 417         }
  418. 418
  419. 419         /// <summary>
  420. 420         /// 通过挂载路径获取卷信息
  421. 421         /// </summary>
  422. 422         /// <param name="mountPath">盘符, e.g. "E:\"</param>
  423. 423         /// <returns></returns>
  424. 424         private OperateResult<VolumeInfo> GetVolumeInfo(string mountPath)
  425. 425         {
  426. 426             var volumeName = new StringBuilder(256);
  427. 427             var fileSystemType = new StringBuilder(256);
  428. 428
  429. 429             if (!mountPath.EndsWith("\"))
  430. 430                 mountPath += "\";
  431. 431             var success = GetVolumeInformationW(
  432. 432                 mountPath,
  433. 433                 volumeName, volumeName.Capacity,
  434. 434                 out _, out _, out _,
  435. 435                 fileSystemType, fileSystemType.Capacity);
  436. 436             if (!success)
  437. 437             {
  438. 438                 int err = Marshal.GetLastWin32Error();
  439. 439                 return OperateResult<VolumeInfo>.ToWin32Error($"GetVolumeInformationW get {mountPath} volume info failed", err);
  440. 440             }
  441. 441
  442. 442             var volumeInfo = new VolumeInfo()
  443. 443             {
  444. 444                 VolumeLabel = volumeName.ToString(),
  445. 445                 FileSystemType = fileSystemType.ToString()
  446. 446             };
  447. 447             return OperateResult<VolumeInfo>.ToSuccess(volumeInfo);
  448. 448         }
  449. 449
  450. 450         /// <summary>
  451. 451         /// 通过挂载路径获取磁盘信息
  452. 452         /// <para>先获取磁盘列表,再筛选</para>
  453. 453         /// </summary>
  454. 454         /// <param name="mountPath"></param>
  455. 455         /// <returns></returns>
  456. 456         public OperateResult<LocalDisk> GetDiskByMountPath(string mountPath)
  457. 457         {
  458. 458             var getDisksResult = GetDisks();
  459. 459             if (!getDisksResult.Success)
  460. 460             {
  461. 461                 return getDisksResult.ToResult<LocalDisk>();
  462. 462             }
  463. 463
  464. 464             var iscsiDisks = getDisksResult.Data.FirstOrDefault(i => i.MountPaths.Contains(mountPath));
  465. 465             return OperateResult<LocalDisk>.ToSuccess(iscsiDisks);
  466. 466         }
复制代码
View Code同样的遍历磁盘列表(4块),首次耗时20ms,二次查询仅7ms:
15.png

封装WIN32,异常码只有基础的Win32Exception异常码,不像Powershell Storage有相对上层更多的业务异常码和异常描述那么好理解。
比如句柄CreateFile失败,GetLastError异常码是 0x00000002,转换Win32Exception描述:“系统找不到指定的文件”。鬼知道是啥问题。。。结合上下文,才知道原来磁盘IsOffline状态是无法查找卷、也无法创建分区访问句柄
 
回到.NET方案选型,
没有复杂的C端环境的话,磁盘管理操作可以使用Powersshell
对磁盘操作要求稳定、但又想快速实现功能,推荐WMI
对磁盘操作要求稳定、性能要求高,推荐WIN32
 
磁盘相关的其它文章:
Windows 本地虚拟磁盘 - 唐宋元明清2188 - 博客园
Windows 网络存储ISCSI介绍 - 唐宋元明清2188 - 博客园
网络虚拟存储 Iscsi实现方案 - 唐宋元明清2188 - 博客园
出处:http://www.cnblogs.com/kybs0/让学习成为习惯,假设明天就有重大机遇等着你,你准备好了么本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册