如何在PowerShell 7中创建支持设备路径或盘符的指定驱动器信息函数
问题描述
我基于一段能列出所有驱动器信息的PowerShell脚本,现在需要编写一个仅查询指定驱动器信息的函数Get-SpecificDriveInfo,要求支持两种传入参数的方式:
- 盘符格式(比如
C) - 设备路径格式(比如
\Device\HarddiskVolume3)
原全量查询脚本如下:
function Get-AllDriveInfo { $signature = @' [DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetVolumePathNamesForVolumeNameW([MarshalAs(UnmanagedType.LPWStr)] string lpszVolumeName, [MarshalAs(UnmanagedType.LPWStr)] [Out] StringBuilder lpszVolumeNamePaths, uint cchBuferLength, ref UInt32 lpcchReturnLength); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr FindFirstVolume([Out] StringBuilder lpszVolumeName, uint cchBufferLength); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool FindNextVolume(IntPtr hFindVolume, [Out] StringBuilder lpszVolumeName, uint cchBufferLength); [DllImport("kernel32.dll", SetLastError = true)] public static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax); '@; Add-Type -MemberDefinition $signature -Name Win32Utils -Namespace PInvoke -Using PInvoke, System.Text; [UInt32] $lpcchReturnLength = 0; [UInt32] $Max = 65535 $sbVolumeName = New-Object System.Text.StringBuilder($Max, $Max) $sbPathName = New-Object System.Text.StringBuilder($Max, $Max) $sbMountPoint = New-Object System.Text.StringBuilder($Max, $Max) [IntPtr] $volumeHandle = [PInvoke.Win32Utils]::FindFirstVolume($sbVolumeName, $Max) do { $volume = $sbVolumeName.toString() $unused = [PInvoke.Win32Utils]::GetVolumePathNamesForVolumeNameW($volume, $sbMountPoint, $Max, [Ref] $lpcchReturnLength); $ReturnLength = [PInvoke.Win32Utils]::QueryDosDevice($volume.Substring(4, $volume.Length - 1 - 4), $sbPathName, [UInt32] $Max); if ($ReturnLength) { $DriveLetter = ($sbMountPoint.toString() -replace ':\\','') $VolumeData = Get-Volume -UniqueId $volume | Select-Object * $PartitionData = Get-Partition -DriveLetter $DriveLetter | Select-Object * [PSCustomObject]@{ DriveLetter = $DriveLetter DevicePath = $sbPathName.ToString() DiskNumber = $PartitionData.DiskNumber HealthStatus = $VolumeData.HealthStatus DriveType = $VolumeData.DriveType FileSystem = $VolumeData.FileSystemType AllocationUnitSize = $VolumeData.AllocationUnitSize FriendlyLabel = $VolumeData.FileSystemLabel Capacity = Format-Bytes -Bytes $VolumeData.Size RemainingSpace = Format-Bytes -Bytes $VolumeData.SizeRemaining VolumeName = $volume GptType = $PartitionData.GptType GUID = $PartitionData.Guid IsActive = $PartitionData.IsActive IsBoot = $PartitionData.IsBoot IsDAX = $PartitionData.IsDAX IsHidden = $PartitionData.IsHidden IsOffline = $PartitionData.IsOffline IsReadOnly = $PartitionData.IsReadOnly IsShadowCopy = $PartitionData.IsShadowCopy IsSystem = $PartitionData.IsSystem MbrType = $PartitionData.MbrType Offset = $PartitionData.Offset } } else { Write-Output "No mountpoint found for: " + $volume } } while ([PInvoke.Win32Utils]::FindNextVolume([IntPtr] $volumeHandle, $sbVolumeName, $Max)); } Get-AllDriveInfo
期望的目标函数结构:
function Get-SpecificDriveInfo { param ( [Parameter(Mandatory,ParameterSetName="DriveLetter")] $DriveLetter, [Parameter(Mandatory,ParameterSetName="DevicePath")] $DevicePath ) ... 筛选指定驱动器并输出信息的代码 ... } # 使用示例: Get-SpecificDriveInfo -DevicePath '\Device\HarddiskVolume3' Get-SpecificDriveInfo -DriveLetter 'C'
由于对脚本中用到的PInvoke.Win32Utils相关函数理解困难,需要方向指引或完整解决方案。
完整实现方案
下面是满足需求的Get-SpecificDriveInfo函数,复用了原脚本的Win32 API调用逻辑,仅针对指定驱动器做查询:
function Get-SpecificDriveInfo { param ( [Parameter(Mandatory, ParameterSetName="DriveLetter")] [ValidatePattern('^[A-Za-z]$')] $DriveLetter, [Parameter(Mandatory, ParameterSetName="DevicePath")] [ValidatePattern('^\\Device\\HarddiskVolume\d+$')] $DevicePath ) # 定义Win32 API签名(复用原逻辑并扩展) $signature = @' [DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetVolumePathNamesForVolumeNameW([MarshalAs(UnmanagedType.LPWStr)] string lpszVolumeName, [MarshalAs(UnmanagedType.LPWStr)] [Out] StringBuilder lpszVolumeNamePaths, uint cchBuferLength, ref UInt32 lpcchReturnLength); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr FindFirstVolume([Out] StringBuilder lpszVolumeName, uint cchBufferLength); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool FindNextVolume(IntPtr hFindVolume, [Out] StringBuilder lpszVolumeName, uint cchBufferLength); [DllImport("kernel32.dll", SetLastError = true)] public static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax); [DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetVolumeNameForVolumeMountPointW([MarshalAs(UnmanagedType.LPWStr)] string lpszVolumeMountPoint, [MarshalAs(UnmanagedType.LPWStr)] [Out] StringBuilder lpszVolumeName, uint cchBufferLength); '@ # 仅在未加载时添加Win32工具类 if (-not ([System.Management.Automation.PSTypeName]'PInvoke.Win32Utils').Type) { Add-Type -MemberDefinition $signature -Name Win32Utils -Namespace PInvoke -Using PInvoke, System.Text } [UInt32]$Max = 65535 $sbVolumeName = New-Object System.Text.StringBuilder($Max, $Max) $sbPathName = New-Object System.Text.StringBuilder($Max, $Max) $sbMountPoint = New-Object System.Text.StringBuilder($Max, $Max) $targetVolume = $null # 根据参数类型定位目标卷 if ($PSCmdlet.ParameterSetName -eq "DriveLetter") { # 盘符转卷唯一ID(\\?\Volume{GUID}\格式) $mountPoint = "$($DriveLetter.ToUpper()):\" $success = [PInvoke.Win32Utils]::GetVolumeNameForVolumeMountPointW($mountPoint, $sbVolumeName, $Max) if (-not $success) { Write-Error "无法获取驱动器 $DriveLetter 的卷信息" return } $targetVolume = $sbVolumeName.ToString() } else { # 设备路径匹配:遍历所有卷,通过QueryDosDevice返回值匹配目标路径 $volumeHandle = [PInvoke.Win32Utils]::FindFirstVolume($sbVolumeName, $Max) do { $currentVolume = $sbVolumeName.ToString() # 提取卷名中的设备标识部分(去掉前后的\\?\和\) $deviceName = $currentVolume.Substring(4, $currentVolume.Length - 5) $returnLength = [PInvoke.Win32Utils]::QueryDosDevice($deviceName, $sbPathName, $Max) if ($returnLength -and $sbPathName.ToString() -eq $DevicePath) { $targetVolume = $currentVolume break } } while ([PInvoke.Win32Utils]::FindNextVolume($volumeHandle, $sbVolumeName, $Max)) if (-not $targetVolume) { Write-Error "未找到设备路径为 $DevicePath 的驱动器" return } } # 获取目标卷的挂载点和设备路径 $lpcchReturnLength = 0 $unused = [PInvoke.Win32Utils]::GetVolumePathNamesForVolumeNameW($targetVolume, $sbMountPoint, $Max, [ref]$lpcchReturnLength) $deviceName = $targetVolume.Substring(4, $targetVolume.Length - 5) $returnLength = [PInvoke.Win32Utils]::QueryDosDevice($deviceName, $sbPathName, $Max) if (-not $returnLength) { Write-Error "无法获取目标卷的设备路径信息" return } # 获取卷和分区的详细数据 $DriveLetter = ($sbMountPoint.ToString() -replace ':\\','') $VolumeData = Get-Volume -UniqueId $targetVolume | Select-Object * $PartitionData = Get-Partition -DriveLetter $DriveLetter | Select-Object * # 返回格式化后的结果对象(兼容无Format-Bytes函数的环境) [PSCustomObject]@{ DriveLetter = $DriveLetter DevicePath = $sbPathName.ToString() DiskNumber = $PartitionData.DiskNumber HealthStatus = $VolumeData.HealthStatus DriveType = $VolumeData.DriveType FileSystem = $VolumeData.FileSystemType AllocationUnitSize = $VolumeData.AllocationUnitSize FriendlyLabel = $VolumeData.FileSystemLabel Capacity = if (Get-Command Format-Bytes -ErrorAction SilentlyContinue) { Format-Bytes -Bytes $VolumeData.Size } else { "$([math]::Round($VolumeData.Size/1GB,2)) GB" } RemainingSpace = if (Get-Command Format-Bytes -ErrorAction SilentlyContinue) { Format-Bytes -Bytes $VolumeData.SizeRemaining } else { "$([math]::Round($VolumeData.SizeRemaining/1GB,2)) GB" } VolumeName = $targetVolume GptType = $PartitionData.GptType GUID = $PartitionData.Guid IsActive = $PartitionData.IsActive IsBoot = $PartitionData.IsBoot IsDAX = $PartitionData.IsDAX IsHidden = $PartitionData.IsHidden IsOffline = $PartitionData.IsOffline IsReadOnly = $PartitionData.IsReadOnly IsShadowCopy = $PartitionData.IsShadowCopy IsSystem = $PartitionData.IsSystem MbrType = $PartitionData.MbrType Offset = $PartitionData.Offset } } # 使用示例 # Get-SpecificDriveInfo -DriveLetter 'C' # Get-SpecificDriveInfo -DevicePath '\Device\HarddiskVolume3'
关键逻辑说明
- 参数校验:通过
ValidatePattern确保传入的盘符/设备路径格式合法,避免无效输入 - Win32 API扩展:新增
GetVolumeNameForVolumeMountPointW函数,实现盘符到卷唯一ID的直接转换 - 目标卷定位:
- 传入盘符时:直接调用API获取对应卷的唯一ID,无需遍历所有卷
- 传入设备路径时:遍历系统中所有卷,通过
QueryDosDevice返回的设备路径匹配目标
- 兼容处理:如果系统中没有
Format-Bytes函数,自动用GB单位格式化容量信息,避免报错 - 复用原逻辑:保留了原脚本中卷和分区数据的查询、格式化逻辑,确保输出格式一致
内容的提问来源于stack exchange,提问作者fmotion1
相关产品推荐
相关产品推荐

