Peace For All

September 9, 2011

How To Properly Get The Drive Letter of a Mounted VHD in PowerShell

Filed under: PowerShell — Tags: , , , , , — Devlin Bentley @ 1:06 pm

There are multiple ways to get the drive letter of a mounted VHD in PowerShell. The most common way is to enumerate all drive letters, mount a VHD, enumerate all drive letters again, and find out which new drive letters have appeared. This strategy is NOT safe and will break down if you have more than one program or instance of your script trying to use this method at the same time.

The reason why it isn’t safe is quite obvious. Assume you have a machine with just one active drive at start, “C”.

  1. Script 1 enumerates all drives, gets back a list {“C”}
  2. Script 1 mounts VHD1, VHD1 is assigned drive letter “D”.
  3. Script 2 enumerates all drives, gets back a list {“C”, “D”}
  4. Script 2 mounts VHD2, VHD2 is assigned drive letter “E”.
  5. Script 1 enumerates all drives, gets back a list {“C”, “D”, “E”}

At this point Script 1 is not sure which drive belongs to the VHD it mounted. If you have a UNIQUE volume name, great! You can do select based on volume name and you are in luck.

If you don’t though, you have ran into the limitations of this technique.

But there is a better way!

Credit goes out to the PowerShell Management Library for HyperV. They do it properly!

First thing to know is that VHDs are mounted as virtual SCSI Disks. A virtual SCSI Disk can be uniquely identified by a combination of LUN, SCSI Target ID and SCSI Port. Our basic strategy is going to be mapping from Mounted VHD path to a Virtual SCSI Disk and then digging into that Disk object to find out what drive letter it has.

So lets break out some WMI shall we?


# Given the full path to an already mounted VHD and the name of a volume on it,
# returns the drive letter that VHD was mounted to
function GetDriveLetterOfMountedVHD($FullPathToVHD, $VolumeName)
{
   $MountedDiskImage = Get-WmiObject -Namespace root\virtualization -query "SELECT * FROM MSVM_MountedStorageImage WHERE Name ='$($VHDPath.Replace("\", "\\"))'"
   $Disk = Get-WmiObject -Query ("SELECT * FROM Win32_DiskDrive " +
        "WHERE Model='Msft Virtual Disk SCSI Disk Device' AND ScsiTargetID=$($MountedDiskImage.TargetId) " +
        "AND   ScsiLogicalUnit=$($MountedDiskImage.Lun)   AND ScsiPort=$($MountedDiskImage.PortNumber)" )
    $Partitions = $Disk.getRelated("Win32_DiskPartition")
    $LogicalDisks = $Partitions | foreach-object{$_.getRelated("win32_logicalDisk")}
    $DriveLetter = ($LogicalDisks | where {$_.VolumeName -eq $VolumeName}).DeviceID
    return $DriveLetter
}

The key thing to notice here is that you are asking WMI for a MountedStorageImage based on the full path of the VHD you mounted. This guarantees that you are not conflicting with any other script’s VHD activities. All the info returned to you is only about the VHD you mounted.

The rest of the function is pretty straight forward. It can actually all be done in one line but I expanded it out here for clarity.

  1. Using the knowledge you have about the MountedDiskImage’s assigned Virtual SCSI info, get a Win32 Disk Drive by searching on the matching LUN, TargetID and SCSI Port
  2. Get a list of partitions on that disk.
  3. Get a list of logical disks (the things you see in My Computer) associated with each partition
  4. Return the drive letter of the volume that you want.

Now if your logical disks happen to have identical volume labels you can index into $LogicalDisks and pick out which one you want that way, and so long as you don’t go rearranging partititions in your VHD that may work just fine. In addition, you can replace the last where {$_.VolumeName -eq …} bit with something unique to your situation (Size, FileSystem, etc).

One final note, due to the use of the virtualization WMI namespace, this code will only work on Windows Servers that have the Hyper-V Role installed. With the announcement that Wndows 8 is getting Hyper-V I am hopeful that the WMI virtualization namespace will become available to client OSs as well!

Advertisements

4 Comments »

  1. Hi! I would really like to use this script, however I am getting these errors:

    You cannot call a method on a null-valued expression.
    At V:\Templates\letter.ps1:8 char:131
    + … WHERE Name =’$($VHDPath.Replace(“\”, “\\”))'”
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    Get-WmiObject : Invalid query “SELECT * FROM Win32_DiskDrive WHERE Model=’Msft Virtual Disk SCSI Disk Device’ AND
    ScsiTargetID= AND ScsiLogicalUnit= AND ScsiPort=”
    At V:\Templates\letter.ps1:9 char:12
    + $Disk = Get-WmiObject -Query (“SELECT * FROM Win32_DiskDrive ” +
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidArgument: (:) [Get-WmiObject], ManagementException
    + FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand

    You cannot call a method on a null-valued expression.
    At V:\Templates\letter.ps1:12 char:5
    + $Partitions = $Disk.getRelated(“Win32_DiskPartition”)
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    You cannot call a method on a null-valued expression.
    At V:\Templates\letter.ps1:13 char:50
    + $LogicalDisks = $Partitions | foreach-object{$_.getRelated(“win32_logicalDis …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    I am using (GetDriveLetterOfMountedVHD “V:\Templates\Win2012 – Fake 1.vhdx” “TestVHD”) to call the function.

    Get-WmiObject -Namespace root\virtualization -query “SELECT * FROM MSVM_MountedStorageImage”

    returns nothing, while:

    Get-WmiObject -Query (“SELECT * FROM Win32_DiskDrive ” + “WHERE Model=’Microsoft Virtual Disk'”)
    gives me this:

    Partitions : 2
    DeviceID : \\.\PHYSICALDRIVE3
    Model : Microsoft Virtual Disk
    Size : 53686402560
    Caption : Microsoft Virtual Disk

    I am having a hard time finding/making a script that will be able to identify the drive letter based on the vhd file location. I would really like to be able to mount a VHD to a folder/path rather than a drive letter as it would resolve the issue with possible script mixups when the driveletter was not read correctly etc.

    Comment by kiwizz — December 20, 2012 @ 11:25 pm

    • Sorry, I do not have access to a PowerShell environment with VHDs anymore.

      All my scripts were on Server 2010, there may have been changes to Server 2012 that makes them not work anymore.

      Comment by Devlin Bentley — January 26, 2013 @ 7:05 pm

      • Would you be able to help out if I gave you a 2012 VPS to try it out?

        Comment by kiwizz — February 15, 2013 @ 3:35 pm

      • Sorry I am currently doing embedded C++ work, my PowerShell skills are fairly rusty!

        Comment by Devlin Bentley — March 13, 2013 @ 12:48 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: