Tuesday, March 26, 2013

Converting VHDX VHD and back without Hyper-V

So.  I am really rehashing another script that I already did a few posts ago.

If you look back here: http://itproctology.blogspot.com/2013/01/powershell-for-reducing-size-of-vhd.html

If you being with a virtual disk that is a VHD.

And you alter this one line:

$newVhdPath = $orgvhd.ImagePath.Split(".")[0] + $partNum +"New." + "vhdx"

You end up with a VHDX instead of a VHD.  Go backward, flip it around. 

Server 2012 / Windows 8 have some built in assumptions based on the extension. You get the VHD format that the extension defines. That is why you just cannot modify the file extension.


What got me started on this?  Well, my original post was because I was annoyed with certain cmdlets being only bound to the existence of the Hyper-V virtual machine management service (having Hyper-V installed).  Since the OS storage layer knows about these virtual disks, why can’t I natively manipulate them?

And then this post came through the Hyper-V forums: http://social.technet.microsoft.com/Forums/en-US/winserverhyperv/thread/51984c52-cc69-459f-8999-6ca186d48931

Again, folks with my same original complaint.  It requires Hyper-V.  Ugh.


So. Let’s make this script a one trick pony.  And ONLY let it convert virtual disk formats.

Now, that said.  This is a hack folks.  I did it because I can, and the tools are built in to the OS.  DISM does not convert disks or volumes, it converts partitions.  So you get one virtual disk per partition. (Frankly, who partitions disks anymore anyway?)


# Server 2012 / Windows 8 VHD / VHDX converter.  Using DISM.
# Prototyped on Server 2012 not running Hyper-V
# This utilizes PowerShell v3 and cmdlets from Server 2012 / Windows 8 - it will not run on any older OS.
# Copy write – Brian Ehlert

# Ask the path of the VHD and test it
Do {
    $imagePath = Read-Host "Please enter the full path to your VHD.  i.e. D:\VMs\MyVhd.vhd "
} until ((Test-Path -Path $imagePath ) -eq $true)  # Mount the VHD that will be getting resized

$orgVhd = Mount-DiskImage -ImagePath $imagePath -PassThru
$orgVhd = Get-DiskImage -ImagePath $orgVhd.ImagePath  # Get the partitions
$orgParts = Get-Partition -DiskNumber $orgVhd.Number  # Use DISM command line to capture the VHD one WIM file. Each partition with a different name.
$wimName = ($orgvhd.ImagePath.Split(".")[0] + ".wim") 
foreach ($part in $orgParts) {
    if ($part.Size -gt 524288000){ # skip the partition if it is less than 500MB, most likely there is no OS or it is the System Reserved partition.
        $capDir = $part.DriveLetter + ":\"
        $partNum = $part.PartitionNumber
        "Be patient, this could take a long time"
        & dism /capture-image /ImageFile:$wimName /CaptureDir:$capDir /Name:$partNum
# dismount the VHD that was just captured
Dismount-DiskImage -ImagePath $orgvhd.ImagePath

# Change the extension
Switch ($orgvhd.ImagePath.Split(".")[1]){
    vhd {$diskFormat = "vhdx"}
    vhdx {$diskFormat = "vhd"}

foreach ($part in $orgParts)
    if ($part.Size -gt 367001600){ # skip the partition if it is less than 350MB.
        $capDir = $part.DriveLetter + ":\"
        $partNum = $part.PartitionNumber
        $newVhdPath = $orgvhd.ImagePath.Split(".")[0] + $partNum + "." + $diskFormat           
        $newSize = (([uint64]$orgVhd.Size) /1024 /1024)
        $diskPart = @"
        create vdisk file="$newVhdPath" type=expandable maximum=$newSize
        select vdisk file="$newVhdPath"
        attach vdisk
        create partition primary
        format fs=ntfs quick
        $diskPart | diskpart
        $newVhd = Get-DiskImage -ImagePath $newvhdPath
        $newVhdDrive = (Get-Partition -DiskNumber $newVhd.Number)
        $newVhdLetter = (Get-Partition -DiskNumber $newVhd.Number).DriveLetter + ":"         
        "Be patient, this could take a long time"
        & dism /apply-image /ImageFile:$wimName /ApplyDir:$newVhdLetter /Name:$partNum
        New-PSDrive -PSProvider FileSystem -Name $newVhdDrive.DriveLetter -root ($newVhdDrive.DriveLetter + ":\") # Make the new volume known to your PowerShell session
        # if \Windows then assume a boot volume and create the BCD
        if ((Test-Path -Path ($newVhdLetter + "\Windows") -PathType Container) -eq $true)
            bcdboot $newVhdLetter\Windows /s $newVhdLetter
         Remove-PSDrive -PSProvider FileSystem -Name $newVhdDrive.DriveLetter
        Start-Sleep 10  # Settling time
        Dismount-DiskImage -ImagePath $newVhdPath