You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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'

关键逻辑说明
  1. 参数校验:通过ValidatePattern确保传入的盘符/设备路径格式合法,避免无效输入
  2. Win32 API扩展:新增GetVolumeNameForVolumeMountPointW函数,实现盘符到卷唯一ID的直接转换
  3. 目标卷定位:
    • 传入盘符时:直接调用API获取对应卷的唯一ID,无需遍历所有卷
    • 传入设备路径时:遍历系统中所有卷,通过QueryDosDevice返回的设备路径匹配目标
  4. 兼容处理:如果系统中没有Format-Bytes函数,自动用GB单位格式化容量信息,避免报错
  5. 复用原逻辑:保留了原脚本中卷和分区数据的查询、格式化逻辑,确保输出格式一致

内容的提问来源于stack exchange,提问作者fmotion1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 05:19:52