Shrinking VMDK files with PowerCLI

 

Update 2014/09/15: Several People have had issues with this working recently, and I can no longer seem to get it to work as well either. I think there may have been a change to how VMware addresses the vmdk file, but I have been unable to spend the proper time to resolve it. FYI

I think we can all agree that no matter how you look at it shrinking vmdk files is pretty much a pain in the ass. But to start let me first explain what I mean when I say shrinking a VMDK.

If you are looking at the picture above, our goal would be to just click the Down Arrow for this particular harddisk. As most of us know though…that button does not operate as some would rightfully assume it should. Anyway, so when I am talking about shrinking a VMDK, I mean, shrinking the size that is allocated for that particular harddisk.

Alright so lets get started. First I would make sure the amount of space you want to remove is already un-partitioned in the GuestOS. So if I want to take 10GB away from this Server 2008 Guest

Essentially we don’t want to take space the guest thinks is part of its partition. Depending on the OS there are several ways to do this, but I will leave that part between you and google :).

Next is the script, a couple things to note first though…

  1. You most certainly can corrupt your OS if you cut the VMDK too short
  2. You can corrupt your vmdk potentially by doing this
  3. I am not responsible if you f stuff up!!!!
That being said, I have so far had really good luck with this. If you are hesitant though you can always toss a clone out there and try it against a clone. Also to alleviate some potential fear, the script does create a “Backup” copy of the vmdk before it messes with it. I have also successfully recovered from this before as well. The backup is based off the VM name and the exact time the script was run, so it should be always unique.
This script works by connecting to the datastore, grabbing and copying the vmdk header file locally, changing the vmdk header file, and copying it back up. This is to get around having to use the vMA or ssh into hosts to shrink the VMDK and all that other jazz.
Just one last thing to note is after this script is complete a cold storage migration is needed for this to take affect. And since a cold storage migration seem to be required, I usually ran this script with the VM off, but I don’t believe it is a requirement. You can try a Storage vMotion, but that wasn’t working for me.
Function Resize-VMDK{
<#
.Synopsis
This function adjusts the reduces the size of a vmdk file

.Description
In the vCenter GUI there is no way to reduce the size of a vmdk. You can increase one by just clicking an up button in the VM setting. Decreasing doesn't work that way though.

This script/function, copies the vmdk header file locally, changes a parameter within vmdk header, and copies the vmdk header file back up to the Datastore.

Further, before the vmdk header file is altered, a copy/backup of it is made. I have successfully used this backup to fix a vm after screwing up the first file. Mileage may vary.

After the command runs a cold storage migration of the VM will still be needed before the affect takes place.

.Parameter VM
This is the VM whose vmdk file will be shrunk

.Parameter NewSizeGB
The new vmdk file size in GB

.Parameter Local
The directory locally where the vmdk header file will be copied

.Example

resize-vmdk -vm testvm01 -local C:\Powershell -newsizGB 45

This will take testvm01 and resize the selected vmdk to be 45GB and will work locally in directory C:\Powershell

.Link
www.vnoob.com

./Notes

Please make sure you have already unpartitioned the space in the guest OS before trying to remove it in vmdk

====================================================================
Author:
Conrad Ramos <conrad@vnoob.com> http://www.vnoob.com/

Date: 2012-10-23
Revision: 1.0

Disclaimer:
With this function you can easily corrupt and VM partition by resizing the VMDK for a greater amount than you should. Please be careful, you have been warned, and I am not responsible if you corrupt any/all of your VMs.

====================================================================
#>

param([Parameter(Mandatory=$true)]$vm, [Parameter(Mandatory=$true)]$local,[Parameter(Mandatory=$true)][int]$newsizeGB)

$disks=get-vm $vm |get-harddisk|select -expand filename
$count=$disks.count

#Convert the New size of the vmdk to the size listed in the vmdk
$size=(($newsizeGB)*([math]::pow(1024,3)) )/512

#Setting up a Multiple Choice decision for the user
#to select the vmdk they want to resize
$i=0
$choicearray=@()

$caption = "Choose Action"
$message = "Which HardDisk are you down-sizing"
IF ($count -gt 1)
{
do {
$i
$($disks[$($i)])
$choice=$null
$test=$null
[string]$test=$($disks[$($i)])
$choice=new-Object System.Management.Automation.Host.ChoiceDescription "&$($i) $test","Choose which disk"
$choicearray+=$choice
$i++

}
while ($i -lt $count)
$choices = [System.Management.Automation.Host.ChoiceDescription[]]($choicearray)
$answer = $host.ui.PromptForChoice($caption,$message,$choices,0)
$disk=$disks[$answer]
}

#If there is only one disk attached to the VM it
#skips the multiple choice
Else {$disk=$disks}

#From the VMDK name in VMware, regex out the
#relevant information like datastore location etc..
$disk
$ds=($disk -split "\s+")[0]

$ds=$ds.trimend("]").trimstart("[")
$file=($disk -split " ")[1]
$vmdk=$file.split("/")[1]
$RW="RW"

#Create a PSDrive to grab the vmdk from
Remove-PSDrive -Name vDS -erroraction silentlycontinue
new-psdrive -name vDS -location (get-datastore $ds) -psprovider vimdatastore -root '/' |out-null
#Copy VMDK Locally

copy-datastoreitem -item vDS:$file -destination $local

#Create Backup of VMDK
$date=get-date -format yyyyMMddhhmmss
Copy-Item $local/$vmdk $local/"Backup"$date$vmdk -Confirm

#Find the Current size setting and replace it
[string]$string=(get-content $local\$vmdk)
$array=$string -split "\s+"
$num=[array]::indexof($array, $RW)
$string.replace("$($array[$num+1])", "$size")|set-content $local\$vmdk

#Copy the vmdk back up to its rightfull place
Copy-DatastoreItem -item $local\$vmdk -destination vDS:$file -confirm
}
Awesome, so let’s look at it in action….
After running the command, it goes out and finds all the VMDKs associated with the guest, and asks you to choose which one of them you are resizing.  This makes it easier, since the user, doesn’t also have to know the exact file name of the vmdk or the Datastore it resides on.
Once it copies the file locally, it confirms creating a backup, makes its changes, and confirms the copy back up to the datastore.
Finally you will want to do a cold storage migration. Then everyone should be happy!
A Before and After
Download from TechNet or from right below
Resize-VMDK
Resize-VMDK
resizevmdk.ps1
Version: 1
3.8 KiB
1244 Downloads
Details...
Some References:

http://www.windowsitpro.com/blog/powershell-with-a-purpose-blog-36/windows-powershell/script-users-choice-139528

http://professionalvmware.com/2009/09/how-to-shrink-a-vmdk-file-in-esx/

22 Responses to Shrinking VMDK files with PowerCLI

  1. Would like to know more on how to use this. Such as where it needs to run from, and prereqs to make it work, basically a step by quick step. Any guidance would be appreciated.

    • Basically you want to start up powercli.
      Connect to your vcenter server with the connect-viserver cmdlet
      Dot-Source the script “. ./nameoffile.ps1”
      And run the commands. If you look at the script or in the help there are a couple of examples for running the cmdlet.
      The only prereqs I can really think of is you have permissions to connect to vcenter and affect datastores
      I hope this helps

  2. Thanks for the info! Seems I have been doing it right but it errors out mentioning that the Resize-VMDK is not a known command or function and returns back to the powercli prompt

    • I am getting the error below when running the script:

      Method invocation failed because [System.Object[]] doesn’t contain a method named ‘replace’.
      At C:\resize-vmdkvm.ps1:128 char:16
      + $string.replace <<<< ("$($array[$num+1])", "$size")|set-content $local\$vmdk
      + CategoryInfo : InvalidOperation: (replace:String) [], RuntimeException
      + FullyQualifiedErrorId : MethodNotFound

      • John,

        I have updated the Script. It seems I just needed [string] to be the start of line 110.

        The downloadable script should be updated as well.

        Thanks!

  3. This script is exactly what I was looking for!

    I’m getting a bunch of errors.
    It’s not copying the vmdk locally.

    Any help would be appreciated.

    PS D:\scripts\fontillas> Resize-VMDK -vm usplsvulm040shrink -local f:\fontillas -newsizeGB 5
    [TSANPL1322(VDISK851 pleva8400c)] usplsvulm040shrink/usplsvulm040shrink_1.vmdk
    Get-Datastore : 4/25/2013 10:54:27 AM Get-Datastore Datastore with name ‘TSANPL1322(VDISK851’ was not found using
    the specified filter(s).
    At D:\Scripts\fontillas\resizevmdk.ps1:113 char:47
    + new-psdrive -name vDS -location (get-datastore <<<< $ds) -psprovider vimdatastore -root '/' |out-null
    + CategoryInfo : ObjectNotFound: (:) [Get-Datastore], VimException
    + FullyQualifiedErrorId : Core_OutputHelper_WriteNotFoundError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetDatastor
    e

    New-PSDrive : Cannot bind parameter 'Datastore' to the target. Exception setting "Datastore": "Invalid location type. Locati
    on accepts only VIDatastore objects."
    At D:\Scripts\fontillas\resizevmdk.ps1:113 char:32
    + new-psdrive -name vDS -location <<<< (get-datastore $ds) -psprovider vimdatastore -root '/' |out-null
    + CategoryInfo : WriteError: (:) [New-PSDrive], ParameterBindingException
    + FullyQualifiedErrorId : ParameterBindingFailed,Microsoft.PowerShell.Commands.NewPSDriveCommand

    Copy-DatastoreItem : 4/25/2013 10:54:27 AM Copy-DatastoreItem Cannot find drive. A drive with the name 'vDS' does
    not exist.
    At D:\Scripts\fontillas\resizevmdk.ps1:116 char:19
    + copy-datastoreitem <<<< -item vDS:$file -destination $local
    + CategoryInfo : InvalidArgument: (System.Object[]:Object[]) [Copy-DatastoreItem], VimException
    + FullyQualifiedErrorId : Core_CopyDatastoreItem_TryValidateSourceItem_InvalidSourceItem,VMware.VimAutomation.ViCore.Cm
    dlets.Commands.CopyDatastoreItem

    Get-Content : Could not find a part of the path 'F:\fontillas\'.
    At D:\Scripts\fontillas\resizevmdk.ps1:123 char:21
    + $string=(get-content <<<< $local\$vmdk)
    + CategoryInfo : ObjectNotFound: (F:\fontillas\:String) [Get-Content], DirectoryNotFoundException
    + FullyQualifiedErrorId : GetContentReaderDirectoryNotFoundError,Microsoft.PowerShell.Commands.GetContentCommand

    You cannot call a method on a null-valued expression.
    At D:\Scripts\fontillas\resizevmdk.ps1:126 char:16
    + $string.replace <<<< ("$($array[$num+1])", "$size")|set-content $local\$vmdk
    + CategoryInfo : InvalidOperation: (replace:String) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    Copy-DatastoreItem : Cannot find drive. A drive with the name 'vDS' does not exist.
    At D:\Scripts\fontillas\resizevmdk.ps1:129 char:19
    + Copy-DatastoreItem <<<< -item $local\$vmdk -destination vDS:$file -confirm
    + CategoryInfo : ObjectNotFound: (vDS:String) [Copy-DatastoreItem], DriveNotFoundException
    + FullyQualifiedErrorId : DriveNotFound,VMware.VimAutomation.ViCore.Cmdlets.Commands.CopyDatastoreItem

    • Alright, essentially, all the errors you are seeing are because of the first error, which I believe is being caused because of how your datastores might be named.
      For this line
      new-psdrive -name vDS -location (get-datastore $ds) -psprovider vimdatastore -root ‘/’ |out-null

      Just replace $ds in this line with the name of the datastore you are copying the vmdk from

      Hopefully this helps.

  4. Hi C-Rad, love this script idea, however I’m see the same results as John posted. Problem with the ‘replace’ in the string command, this line:

    $string.replace(“$($array[$num+1])”, “$size”) | set-content $local\$vmdk

    Must not like something from the vmdk. Have you looked into this?
    Thank you

    • Cy,

      I have updated the Script. It seems I just needed [string] to be the start of line 110.

      The downloadable script should be updated as well.

      Thanks!

  5. Love the script, thank you very much! One question. When I go to SVMotion the VM, it comes up with “the virtual disk is either corrupt or in an unsupported format”.

    If I go into the file and build it out correctly (with carriage returns after each line), it seems to work ok.

    Any help would be most appreciated.
    Thank you,
    Shawn

  6. I just downloaded the ps1 file, i didn’t try to copy and paste the code out. The file that is being created isn’t separating out the lines. Please see the following:

    # Disk DescriptorFile version=1 encoding=”UTF-8″ CID=b3772342 parentCID=ffffffff isNativeSnapshot=”no” createType=”vmfs” # Extent description RW 16777216 VMFS “SEW02058_3-flat.vmdk” # The Disk Data Base #DDB ddb.deletable = “true” ddb.virtualHWVersion = “9” ddb.longContentID = “aa66b689f3624178393b5dd0b3772342” ddb.uuid = “60 00 C2 98 31 cb 81 20-84 93 04 d5 72 22 b8 15” ddb.geometry.cylinders = “2610” ddb.geometry.heads = “255” ddb.geometry.sectors = “63” ddb.thinProvisioned = “1” ddb.adapterType = “lsilogic”

    Any help would be most appreciated.

    • Hmm, that is really weird, I downloaded the file and it looks fine. Maybe it is the program you are trying to open it with?

      Both Notepad and Notepad++ work for me, and of course the Powershell ISE should work as well.

  7. The script works fine. It gives me an error when I go to SVMotion the VM from one datastore to another. I have tried to copy out the script and re-run, the same issue seems to occur. The issue i think is with the descriptor file header itself on vSphere 5.1. Do you know what version of vSphere you’ve tried this on? Is there anything specific that needs to be done to the VM before SVMotion?

    • And after running the script you are doing a cold(turned off) storage migration right? That is where it is failing?

  8. That is correct, it is failing upon storage migration once the script is finished. The VM is turned off during the entire process.

    • Hey Shawn, Sorry i haven’t forgotten about this. I just haven’t had time to setup a test on my end. I will let you know as soon as I know anything.

  9. I am trying two things that doesn’t work right.
    1st Code:
    Param (
    [String]$List = “ReplacementList.csv”,
    [String]$Files = “.\try\*.*”
    )
    $ReplacementList = Import-Csv $List;
    Get-ChildItem $Files |

    ForEach-Object {
    $Content = Get-Content -Path $_.FullName;
    foreach ($ReplacementItem in $ReplacementList)
    {
    $old = $ReplacementItem.OldValue
    $new = $ReplacementItem.NewValue
    $Content = $Content -Replace “$old”, “$new”
    }
    Set-Content -Path $_.FullName -Value $Content
    }

    The output I am getting is “ABC_10_9_8_7_6_5_4_3_2_1″ in all files.. =(

    How can I make the text output like this? Each File should be changed with different text from the .csv list
    “ABC_1″ in the 1st file (FILE1)
    “ABC_2″ in the 2nd file (FILE2)
    “ABC_3″ in the 3rd file (FILE3)

    I tried the 2nd Code:
    Param (
    [String]$List = “ReplacementList.csv”,
    [String]$Files = “.\try\*.*”
    )
    $ReplacementList = Import-Csv $List;
    Get-ChildItem $Files |

    ForEach-Object {
    $Content = Get-Content -Path $_.FullName;
    foreach ($ReplacementItem in $ReplacementList)
    {
    $Content = $Content.replace(“$ReplacementItem.OldValue”, “$ReplacementItem.NewValue”)
    }
    Set-Content -Path $_.FullName -Value $Content
    }

    The output is:
    Method invocation failed because [System.Object[]] doesn’t contain a method named ‘replace’.
    At C:\try\Files.pst:11 chat:34
    $Content = $Content.replace <<<< ("$ReplacementItem.OldValue", "$ReplacementItem.NewValue")
    Category Info: InvalidOperation: (replace:String)[], RuntimeException
    MethodNotFound

    Any suggestions?

Leave a Reply