Powershell and wmi, how to map logical disk/volume

2019-06-24 07:31发布

问题:

Get-WmiObject -ComputerName $ip -Credential $credential -Class Win32_logicaldisk

This gets me disks as I see them in "My computer", eg. C:, D:, E: Now how I get corresponding underlying physical disks ?

If I run following command

Get-WmiObject -ComputerName $ip -Credential $credential -Class win32_diskdrive

I get disk 0, disk 1, disk 2

So how to find out which logical disk is on which physical disk ?

Another question is how to find out volume number ? If I run diskpart and executes "list volume" I get the following output

  Volume ###  Ltr  Label        Fs     Type        Size     Status     Info
  ----------  ---  -----------  -----  ----------  -------  ---------  --------
  Volume 2     C                NTFS   Partition     59 GB  Healthy    Boot
  ...

How can I find out that logical disk C: is Volume 2 ?

best regards, Primoz.

回答1:

Try this

Get-WMIObject Win32_LogicalDisk | Foreach-Object {
    Get-WmiObject -Query "Associators of {Win32_LogicalDisk.DeviceID='$($_.DeviceID)'} WHERE ResultRole=Antecedent"
} | Format-Table

This gives you the related instances of WIn32_logicalDisk where Win32_LogicalDisk is the dependent entity in the relationship. So, you get the Win32_DiskDrive instances.



回答2:

Here's a complete script I created to list logical disk, partition and the partition offset to check for disk alignment issues. (source: my blog http://sev17.com/2009/02/disk-alignment-partitioning-the-good-the-bad-the-ok-and-the-not-so-ugly/)

param ($computer)

$partitions = Get-WmiObject -computerName $computer Win32_DiskPartition

$partitions | foreach { Get-WmiObject -computerName $computer -query “ASSOCIATORS OF {Win32_DiskPartition.DeviceID=’$($_.DeviceID)’} WHERE AssocClass = Win32_LogicalDiskToPartition” |
add-member -membertype noteproperty PartitionName $_.Name -passthru |
add-member -membertype noteproperty Block $_.BlockSize -passthru |
add-member -membertype noteproperty StartingOffset $_.StartingOffset -passthru |
add-member -membertype noteproperty StartSector $($_.StartingOffset/$_.BlockSize) -passthru } |
Select SystemName, Name, PartitionName, Block, StartingOffset, StartSector


回答3:

The other answers with WMI relation are good if you don't care about mountpoint volume because there is no possible way with WMI directly to associate mountpoint volume with a partition or disk.

But there is a registry key that can help you figure out some information that will help you build the connection as windows does it :

function getVolumeInformation{
    param($hostName, $credential)
    $volumeInformation = getVolumeInformation -computerName $hostName -credential $credential                   


    $WMIRegistryMountedDeviceInfo = WMIRegistryMountedDeviceInfo -computerName $hostName -Credential $credential
    foreach($volumeSerial in $volumeInformation.keys){                        
        if($WMIRegistryMountedDeviceInfo.containsKey($volumeSerial)){                        
            $volumeInformation[$volumeSerial]["diskPartitionStartingOffset"]=$WMIRegistryMountedDeviceInfo[$volumeSerial].diskPartitionStartingOffset
            $volumeInformation[$volumeSerial]["diskDriveSignature"]=$WMIRegistryMountedDeviceInfo[$volumeSerial].diskDriveSignature
            $volumeInformation[$volumeSerial]["wmiValueRegistry"]=$WMIRegistryMountedDeviceInfo[$volumeSerial].wmiValue
        }   
    }
    return $volumeInformation
}



function getWMIVolumeInformation{
    param($computerName, $credential)
    $diskToVolumeAssociation = @{}
    $regexGetVolumeSerial = ".*Volume{(.*)}.*"
    $wmiVolumes = $null
    $wmiVolumes = Get-WmiObject Win32_Volume -Credential $credential -ComputerName $computerName -filter "DriveType != 5" -ErrorVariable errorWMIVolume -ErrorAction SilentlyContinue

    $volumeInfo = @{}

    $wmiVolumes |   Foreach-Object {
        $wmiVolume = $_
        if($wmiVolume.DeviceID -match  $regexGetVolumeSerial){
            $wmiVolumeSerial = $Matches[1]
            $volumeInfo[$wmiVolumeSerial] = @{}
            $volumeInfo[$wmiVolumeSerial]["wmiInfo"] = $wmiVolume
            $volumeInfo[$wmiVolumeSerial]["volumeDirectoryName"] = $wmiVolume.Name
            $volumeInfo[$wmiVolumeSerial]["label"] = $wmiVolume.label
            $volumeInfo[$wmiVolumeSerial]["serial"] = $wmiVolumeSerial
            $volumeInfo[$wmiVolumeSerial]["capacity"] = $wmiVolume.Capacity
            $volumeInfo[$wmiVolumeSerial]["freeSpace"] = $wmiVolume.FreeSpace
        }
    }
    return $volumeInfo  


}
function WMIRegistryMountedDeviceInfo{
    param($computerName, $mycreds)
    $MountPointRegistryInformation = @{}

    $hklm = 2147483650
    $registryKeyMountedDevices = "SYSTEM\MountedDevices"
    $regexMountPoint = ".*{(.*)}.*"

    $wmi = get-wmiobject -list "StdRegProv" -namespace root\default -computername $computerName -credential $mycreds
    $wmiMountedDeviceKey = ($wmi.EnumValues($hklm,$registryKeyMountedDevices))

    foreach($mountedDeviceRegistryName in $wmiMountedDeviceKey.sNames){

        if($mountedDeviceRegistryName -match $regexMountPoint){
            $wmiValue = ($wmi.GetBinaryValue($hklm,$registryKeyMountedDevices,$mountedDeviceRegistryName))
            if($wmiValue.uValue.Count -eq 12){
                $diskDriveSignature = [bitconverter]::ToUInt32($wmiValue.uValue[0..3],0)
                $diskPartitionStartingOffset = [bitconverter]::ToUInt64($wmiValue.uValue[4..11],0)
                $MountPointRegistryInformation[$Matches[1]]=@{"serial"=$Matches[1];
                    "mountedDeviceRegistryName"=$mountedDeviceRegistryName;
                    "diskDriveSignature"=$diskDriveSignature;
                    "diskPartitionStartingOffset"=$diskPartitionStartingOffset;
                    "wmiValue"=$wmiValue.uValue}

            }
        }      
    }

   return $MountPointRegistryInformation
}

This code should give return you every signature a hash table with the disk drive signature of the disk that are associated with the mountedDevice. It also returns DiskPartitionStartingOffset that will be the same as the partition.StartingOffset associated with the mountedDevice.

If you want to write the code yourself, the first 4 bytes of this registry SYSTEM\MountedDevices value are the disk drive signature but be careful of the little-endian. The next 8 bytes are the PartitionStartingOffset.

With this information you can use WMI to get disk drive information and plug everything together to realy get all the volume related to all the physical drive you have regardless of how they are mounted.

This doesnt work for spanned volume.

Be very careful when playing in those kind of registry.



回答4:

Check Win32_LogicalDisktoPartition That give you a map of the logical disk to the physical disk and partition on that disk in the Antecedent. To just get the label for each disk drive

 gwmi win32_volume | select name,label


回答5:

here is another alternative using the kernel. Microsoft advises againts it but it has shown to be very efficient trapping spanned volume, mountpoint and other volume type information to help associate disk and volume.

 $scriptBlockInvokeCommandKernel32 = {
    param([array]$arrayOfVolumeSerial)

    $cSharpCodeKernel32GetDisk = @"
        using System;
        using Microsoft.Win32.SafeHandles;
        using System.IO;
        using System.Runtime.InteropServices;


        public class GetDisk
        {
            private const uint IoctlVolumeGetVolumeDiskExtents = 0x560000;
            private const uint HASMOREDATA = 234;
            // 4 rounded to 8 + 8 + 8= 24
            [StructLayout(LayoutKind.Sequential)]
            public struct DiskExtent
            {
                public int DiskNumber;
                public Int64 StartingOffset;
                public Int64 ExtentLength;
            }

            //size 4 rounded to 8 + number of extends * 24
            [StructLayout(LayoutKind.Sequential)]
            public struct DiskExtents
            {
                public int numberOfExtents;
                public DiskExtent extents;
            }
            //4
            [StructLayout(LayoutKind.Sequential)]
            public struct DiskExtentsBeforeArray
            {
                public int numberOfExtents;
            }

            [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            private static extern SafeFileHandle CreateFile(
                string lpFileName,
                [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
                [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
                IntPtr lpSecurityAttributes,
                [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
                [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
                IntPtr hTemplateFile
            );

            [DllImport("Kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)]
            private static extern bool DeviceIoControl(
                SafeFileHandle hDevice,
                uint IoControlCode,
                [MarshalAs(UnmanagedType.AsAny)] [In] object InBuffer,
                uint nInBufferSize,
                ref DiskExtents OutBuffer,
                int nOutBufferSize,
                ref uint pBytesReturned,
                IntPtr Overlapped
            );

            [DllImport("Kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)]
            private static extern bool DeviceIoControl(
                SafeFileHandle hDevice,
                uint IoControlCode,
                [MarshalAs(UnmanagedType.AsAny)] [In] object InBuffer,
                uint nInBufferSize,
                IntPtr outBuffer,
                int nOutBufferSize,
                ref uint pBytesReturned,
                IntPtr Overlapped
            );


            [DllImport("Kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)]
            private static extern int GetLastError();

            public static DiskExtent[] GetPhysicalDriveString(string path)
            {
                DiskExtent[] diskExtent = null;
                path = path.TrimEnd('\\');
                if (!path.StartsWith(@"\\.\"))
                    path = @"\\.\" + path;

                SafeFileHandle safeFileHandle = CreateFile(path, FileAccess.Read, FileShare.Read | FileShare.Write, IntPtr.Zero, FileMode.Open, 0,
                IntPtr.Zero);
                if (safeFileHandle.IsInvalid)
                {
                    Exception e = Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());
                }

                uint bytesReturned = new uint();
                DiskExtents VOLUME_DISK_EXTENTS = new DiskExtents();
                bool result = DeviceIoControl(
                    safeFileHandle,
                    IoctlVolumeGetVolumeDiskExtents,
                    IntPtr.Zero,
                    0,
                    ref VOLUME_DISK_EXTENTS,
                    Marshal.SizeOf(VOLUME_DISK_EXTENTS),
                    ref bytesReturned,
                    IntPtr.Zero);

                if (result)
                {
                    diskExtent = new DiskExtent[1];
                    diskExtent[0] = VOLUME_DISK_EXTENTS.extents; 
                }
                else
                {
                    int numberOfExtents = VOLUME_DISK_EXTENTS.numberOfExtents;
                    int lastError = GetLastError();

                    if (lastError == HASMOREDATA)
                    {
                        int size = 8 + 24 * numberOfExtents;
                        uint bytesReturned2 = new uint();

                        IntPtr blob = default(IntPtr);
                        blob = Marshal.AllocHGlobal(size);
                        bool resultMutipleDiskExtent = DeviceIoControl(
                            safeFileHandle,
                            IoctlVolumeGetVolumeDiskExtents,
                            IntPtr.Zero,
                            0,
                            blob,
                            (int)size,
                            ref bytesReturned2,
                            IntPtr.Zero
                        );

                        if (resultMutipleDiskExtent)
                        {
                            DiskExtentsBeforeArray lie = default(DiskExtentsBeforeArray);
                            lie = (DiskExtentsBeforeArray)Marshal.PtrToStructure(blob, typeof(DiskExtentsBeforeArray));
                            diskExtent = new DiskExtent[lie.numberOfExtents];
                            for (int i = 0; i <= lie.numberOfExtents - 1; i++)
                            {
                                IntPtr offset = new IntPtr(blob.ToInt64() + 8 + 24 * i);
                                diskExtent[i] = (DiskExtent)Marshal.PtrToStructure(offset, typeof(DiskExtent));
                            }

                        }
                    }
                    else{
                        throw new System.ComponentModel.Win32Exception();
                    }   
                }
                safeFileHandle.Close();
                return diskExtent;
            }
        } 
"@
    $resultOfOperation =@{}
    $volumeKernelInfo =@{} 
    $type = Add-Type -TypeDefinition $cSharpCodeKernel32GetDisk -ErrorAction Continue -ErrorVariable errorAddType -PassThru
    if($errorAddType){
        $resultOfOperation["error"]= ("error at CsharpBuild" + $errorAddType)
    }
    foreach($volumeSerial in $arrayOfVolumeSerial){
        $volumeString = "Volume{" + $volumeSerial + "}"
        $volumeKernelInfo[$volumeSerial] = [GetDisk]::GetPhysicalDriveString($volumeString)
    }
    $resultOfOperation["volumeKernelInfo"]=$volumeKernelInfo

    return $resultOfOperation
}

you can call it with :

$resultVolumeMappingFromKernel = Invoke-Command -ScriptBlock $scriptBlockInvokeCommandKernel32 -ComputerName $hostName -Credential $credential -ArgumentList (,$arrayOfVolumeSerial)

It needs to be run on the remote machine you want the information with an admin account.



回答6:

Here is Chad Miller's answer modified to work with PowerShell Core and with proper quotes and apostrophes so that it actually works on Linux/Windows and regardless of term settings (i.e., UTF-8 or not):

param ($ComputerName)

$partitions = Get-CimInstance -ComputerName $ComputerName Win32_DiskPartition

$partitions |
foreach `
{
  Get-CimInstance -ComputerName $ComputerName `
                  -Query "ASSOCIATORS OF `
                          {Win32_DiskPartition.DeviceID='$($_.DeviceID)'} `
                          WHERE AssocClass=Win32_LogicalDiskToPartition" |
  Add-Member -MemberType NoteProperty PartitionName $_.Name -PassThru |
  Add-Member -MemberType NoteProperty Block $_.BlockSize -PassThru |
  Add-Member -MemberType NoteProperty StartingOffset $_.StartingOffset -PassThru |
  Add-Member -MemberType NoteProperty StartSector ($_.StartingOffset/$_.BlockSize) `
             -PassThru
} |
Select SystemName, Name, PartitionName, Block, StartingOffset, StartSector |
Sort-Object -Property Name # Sort by Drive letter to improve quality of life
# Select code above starting to the left of the hash in this comment