Search
Join the Technical Preview Program
See how NVMe-oF removes iSCSI
bottlenecks in your HCI
The Best Hyperconverged
Infrastructure
(HCI) for Enterprise
ROBO, SMB & Edge
The Best Virtual SAN
for Enterprise ROBO, SMB & Edge

Shrinking VMware VMDK Files with PowerShell

  • January 30, 2025
  • 55 min read
StarWind Post-Sales Support Engineer. Vitalii specializes in storage, virtualization, and backup solutions. With expertise in infrastructure implementation and system recovery, he provides technical leadership in optimizing virtualized environments. Vitalii delivers expert guidance on data protection and high-availability infrastructure, focusing on seamless post-deployment support and performance tuning.
StarWind Post-Sales Support Engineer. Vitalii specializes in storage, virtualization, and backup solutions. With expertise in infrastructure implementation and system recovery, he provides technical leadership in optimizing virtualized environments. Vitalii delivers expert guidance on data protection and high-availability infrastructure, focusing on seamless post-deployment support and performance tuning.

Virtual disks (VMDK files) in VMware vSphere can be thin-provisioned or thick-provisioned, meaning they either grow on demand or occupy a fixed size on the datastore. One common issue with thin disks is that deleting files inside the guest OS does not automatically reduce the VMDK’s size on the datastore. When you delete data in the guest (e.g. Windows), the filesystem only removes references to the blocks, but those blocks remain marked as “in use” on the virtual disk until they are overwritten (zeroed out). In practice, a thin-provisioned VMDK will never shrink on its own – it will continue using as much space as it once did at its peak usage, even if much of that space is now free inside the VM. Thick-provisioned disks are pre-allocated to full size from the start, so they also won’t give space back without manual intervention.

Because VMware does not provide a built-in “shrink” button in the vSphere Client (you can only increase disk size, not decrease it), we have to use alternative methods. In fact, VMware officially states there is no supported way to shrink a virtual disk after provisioning. The traditional approach is to use VMware vCenter Converter to clone the VM to a smaller disk, or perform a V2V migration. (Converter was a free tool for this, though it was discontinued in 2019 with only experimental releases afterward) Instead of such labor-intensive methods, we can automate the shrink process with PowerShell scripts. VMware PowerCLI (VMware’s PowerShell module) allows us to manipulate VMDK files and VM configuration, and by combining that with in-guest partition resizing, we can safely reduce a VM’s virtual disk size without losing data.

Note: Always ensure you have a full backup of the VM or at least the disk in question before attempting a shrink. Although the method described is proven and has been used reliably, it is not officially supported by VMware, so proceed with caution. Also, the procedure only covers basic disks/volumes. If your Windows VM uses Dynamic Disks or if you have Linux LVM volumes spanning the disk, shrinking is more complicated (these technologies store metadata at the end of the disk which can be corrupted by a shrink). The steps below assume a basic disk partition that can be safely resized.

Overview of the Shrink Process

Shrinking a VMDK safely involves two main phases: first shrink the partition inside the guest OS, and second shrink the VMDK file on the datastore to match. By reducing the partition (and filesystem) size from inside the VM, we isolate the used portion of the disk to one end, leaving free space at the end of the disk. Then we can truncate the VMDK to cut off that free space. We will use two PowerShell scripts to automate this process. Conceptually, the workflow is:

  1. Resize the guest OS partition: Inside the VM (Windows, in this case), shrink the target volume to the desired size (usually just above the actual data used). This creates unallocated free space at the end of the virtual disk.
  2. Shrink the VMDK file: Using PowerCLI on a management machine, modify the VMDK’s descriptor to a smaller size and perform a Storage vMotion (move the disk to another datastore and back). This effectively reclaims the space by truncating the VMDK and updating the VM’s configuration.

By separating the volume from the free space in the first step, the second step can safely “cut off” the unused portion of the virtual disk. Figure 1 below illustrates this concept of allocated vs. unused blocks in a thin disk on a datastore:

A diagram of a server AI-generated content may be incorrect.

 

Figure 1: Conceptual diagram of virtual disk space. The thin-provisioned disks (orange = allocated used blocks, gray = allocated unused, green = truly used) initially consume datastore space equal to their peak size. After zeroing out and shrinking, the VMDK can be reduced to remove the unused blocks.

 

Prerequisites

Before you start, ensure the following prerequisites are met:

  • PowerShell & PowerCLI: A Windows PowerShell environment (version 5.1 or PowerShell 7.x) with the VMware PowerCLI module installed (PowerCLI 13.x as of 2025). You can install it from the PowerShell Gallery using Install-Module VMware.PowerCLI. Verify you can connect to vCenter with Connect-VIServer.
  • vCenter/ESXi Access: You need access to a vCenter Server (or an ESXi host) with privileges to power off VMs, modify disks, and register/unregister VMs. The target VM must be managed by this vCenter/host, and you’ll need the vCenter IP/hostname and an administrator username/password for authentication.
  • Temporary Datastore: A second datastore accessible by the ESXi host that has enough free space to hold a copy of the VMDK you plan to shrink. This will be used as a temporary location during the shrink process. It must reside on the same host as the VM for the Storage vMotion step to work efficiently.
  • No Snapshots: Ensure the VM has no snapshots before proceeding. Delete any snapshots on the VM – you cannot safely shrink a disk with snapshots present.
  • VM Powered Off (for Step 2): You will power off the VM before shrinking the VMDK (Step 2). The first step (partition shrink) can be done while the VM is running, but after that, plan for downtime.
  • Windows VM & WinRM: The procedure uses a PowerShell remote session to shrink the partition, so the VM’s guest OS must be Windows (for the Resize-Partition cmdlet). Enable PowerShell Remoting (WinRM) on the Windows VM and ensure your user account has admin rights on the VM and is allowed to connect via WinRM.
  • Credentials: Have the admin credentials ready for: (a) the Windows VM (local admin user and password) to perform the partition resize, and (b) vCenter/ESXi login to perform the VMDK operations.

Step 1: Resize the Guest Partition

In the first step, we shrink the partition inside the guest OS to the new desired size. We will use the Resize-Partition.ps1 PowerShell script to do this remotely. This script runs on your management machine (your PC or a server with PowerCLI) and uses WinRM to connect to the VM and execute the partition shrink.

Steps to shrink the guest partition:

1. Launch the partition resize script: Open a PowerShell console as Administrator on your management machine and run the Resize-Partition.ps1 script with two parameters: -VMIP (the target VM’s IP address or hostname) and -VMUser (a local administrator username on that VM). For example:
PS C:\> .\Resize-Partition.ps1 -VMIP “172.16.1.26” -VMUser “Administrator”

When you run this, you will be prompted to enter the password for the VM’s Administrator account. The script then attempts to open a WinRM remote session to the VM. If the connection fails (e.g. WinRM not enabled or firewall blocked), the script will warn and exit. Make sure WinRM is enabled on the VM and reachable.

2. List and select the volume to shrink: Once connected, the script runs Get-Partition on the VM to list all disks and partitions. It will display a table of Disk Number, Partition Number, Drive Letter, and Current Size (MB) for each volume. For example, you might see output showing volumes C:, F:, etc., with their sizes. Identify which drive you want to shrink. The script then asks: “Enter the drive letter of the partition to shrink (or type ‘Exit’ to cancel)”. Type the drive letter (e.g. F) and press Enter. (If you change your mind, type “Exit” to abort at this point.)

A screen shot of a computer AI-generated content may be incorrect.

 

Figure 2: The script lists available partitions inside the VM (Disk 2 highlighted with drive F: in this example). You then enter the drive letter of the partition you want to shrink. The table shows the current partition sizes in MB.

 

3. Specify the new partition size: After you select the drive, the script will determine the current size of that partition and query the minimum possible size you can shrink to. It then prompts: “Enter the new partition size in MB”. You should enter a size (in MB) that is slightly larger than the amount of data actually used on the drive. In practice, you might choose a number a bit above the minimum shown to leave some wiggle room. For example, if drive F: is mostly empty and you want it to be ~4 GB, you could enter 4096 (MB). The script will validate that the input is a number; if you enter an invalid value (non-numeric), it will warn and exit.

4. Shrink the partition: The script uses the Windows Resize-Partition cmdlet to shrink the volume to the size you specified (converting MB to bytes internally). This operation is done inside the guest OS. If the shrink operation succeeds, the script will print the updated partition table showing the new size of the volume. It also outputs a note indicating the disk number and new size, and recommends adding ~512 MB (or some buffer) when you shrink the VMDK in the next step. For example, after shrinking drive F: to 4096 MB, it might suggest using 4608 MB for the VMDK size to account for any filesystem overhead.

If the Resize-Partition command fails, the script will throw an error. Common reasons for failure include: the volume cannot be shrunk because of unmovable files at the end (e.g. pagefile, shadow copies), or the specified size was too small. In such cases, you may need to ensure there are no immovable files towards the end of the partition (for example, disable or move the pagefile, or use a defragmentation/compaction tool to consolidate free space). Then try running the script again.

5. Verify the partition inside the VM: Once the script completes, it will automatically restore the VM’s WinRM TrustedHosts setting (it had added the VM IP temporarily) and close the remote session. Now, on the VM itself, you should see that the partition is smaller. For instance, if you RDP or open Disk Management in the Windows VM, that drive (F:) will be reduced in size and there will be unallocated free space on the disk after the partition. Figure 3 shows an example before and after shrinking a 15 GB partition down to 4 GB:

A screenshot of a computer AI-generated content may be incorrect.

 

Figure 3: Windows Disk Management view before and after shrinking the partition. In this example, Disk 2 (highlighted) was 15 GB with a single F: drive. After running the script, F: is 4.00 GB and the remainder (~11 GB) is unallocated free space.

 

6. Write down the Disk Number (e.g. Disk 2) and the new partition size (e.g. 4096 MB) that the script reported. You will need these for the next step. If you plan to add a buffer for safety, calculate the target VMDK size (for example, 4096 + 512 = 4608 MB). In our example, we’ll shrink the VMDK to ~4608 MB in step 2. At this point, all the important data on the disk resides within the shrunken partition. The free space at the end is empty and not needed. We can now proceed to remove that excess space from the VMDK itself.

The Resize-Partition.ps1 script

For reference or customization, here is the full content of the partition resizing script, cleaned up for readability. It performs the tasks described above (adding the VM to WinRM trusted hosts, connecting via New-PSSession, then querying and resizing the partition). We have added comments to explain each section:

<#

.SYNOPSIS

Resize a Windows partition on a remote VM via WinRM.

.PARAMETER VMIP

IP address (or hostname) of the target VM (e.g. "10.0.0.2").

.PARAMETER VMUser

Administrator username on the VM (e.g. "Administrator").

.EXAMPLE

PS> .\Resize-Partition.ps1 -VMIP "10.0.0.2" -VMUser "Administrator"

#>

param (

[Parameter(Mandatory=$true)] [string]$VMIP,

[Parameter(Mandatory=$true)] [string]$VMUser

)

# Clear the console for neat output

Clear-Host

# Record current WinRM TrustedHosts, then add the VM IP to it (allows WinRM to that host)

$OriginalTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value

Set-Item WSMan:\localhost\Client\TrustedHosts "$VMIP,$OriginalTrustedHosts" -Force

# Test WinRM connectivity to the VM

try {

Test-WsMan $VMIP -ErrorAction Stop | Out-Null

} catch {

Write-Warning "Cannot connect to $VMIP via WinRM. Is the VM powered on and is WinRM enabled?"

# Restore the original TrustedHosts list and exit on failure

Set-Item WSMan:\localhost\Client\TrustedHosts "$OriginalTrustedHosts" -Force

return # use return instead of exit to allow calling from other scripts

}

# Create a remote PowerShell session to the VM

try {

$session = New-PSSession -ComputerName $VMIP -Credential $VMUser -ErrorAction Stop

} catch {

Write-Warning "Failed to create PowerShell session on $VMIP (authentication or network issue)."

# Restore TrustedHosts and clean up, then exit

Set-Item WSMan:\localhost\Client\TrustedHosts "$OriginalTrustedHosts" -Force

return

}

# Run partition management commands inside the VM session

Invoke-Command -Session $session -ScriptBlock {

# List all partitions with their current sizes in MB

Get-Partition | Format-Table -AutoSize DiskNumber, PartitionNumber, DriveLetter, @{

Name="CurrentSize(MB)"; Expression = {[int]($_.Size/1MB)}

}

# Prompt the user to select a drive letter to shrink

$drive = Read-Host 'Enter the drive letter of the partition to shrink (or type "Exit" to cancel)'

if ($drive -match '^[Ee]xit$') { exit } # exit script block if user aborts

# Get the disk number and current size of the chosen partition

$part = Get-Partition -DriveLetter $drive

$diskNum = $part.DiskNumber

$curSizeMB = [int]($part.Size / 1MB)

Write-Host "Selected Disk $diskNum (Drive $drive) - Current size: $curSizeMB MB"

# Ask for the new size in MB

$newSizeMB = [int](Read-Host "Enter the new partition size in MB")

if ($newSizeMB -match '\D') { Write-Warning "Invalid size entered."; exit }

$newSizeBytes = $newSizeMB * 1MB

# Perform the partition shrink operation

Resize-Partition -DriveLetter $drive -Size $newSizeBytes -ErrorAction Stop

# Output the result

Get-Partition -DriveLetter $drive | Format-Table DiskNumber, PartitionNumber, DriveLetter, @{

Name="Size(MB)"; Expression = {[int]($_.Size/1MB)}

}

# Print a note for the user with advice for VMDK resizing

$shrunkMB = $newSizeMB

Write-Output "`nNote: Disk $diskNum, partition $drive is now $shrunkMB MB.`n" `

+ "You should allocate roughly $($shrunkMB + 512) MB for the VMDK in the next step (adding a buffer)."

} | Out-Host

# Clean up: restore TrustedHosts and remove the remote session

Set-Item WSMan:\localhost\Client\TrustedHosts "$OriginalTrustedHosts" -Force

Remove-PSSession $session

(The above script will shrink a partition on a remote Windows VM. Review the output and note the suggested VMDK size for the next step. In case of errors, no changes are made and the script will exit.)

Step 2: Shrink the VMDK on the Datastore

With the partition inside the guest now reduced, we can proceed to shrink the actual VMDK file to reclaim the space on the datastore. This step is a bit more involved: we will use a PowerCLI script (Resize-VMDK.ps1) to connect to vCenter, identify the VMDK to shrink, and perform a series of actions including modifying the VMDK descriptor and using storage vMotion to truncate the disk. The VM must be powered off for this step.

Before running the script, double-check the prerequisites: the VM should be powered off and have no snapshots (the script will verify this). You should have the Disk Number (from step 1) and the new partition size written down. Also know the name of the temporary datastore you prepared.

Steps to shrink the VMDK:

1. Run the VMDK resize script: On your management machine, open PowerShell as Administrator and execute Resize-VMDK.ps1 with the required parameters: -vCSAIP (the vCenter Server’s IP or name), -vCSAUser (the vCenter admin username), -VMName (the name of the target VM in vCenter), and -TmpDS (the name of the temporary datastore). For example:

PS C:\> .\Resize-VMDK.ps1 -vCSAIP “172.16.0.99” -vCSAUser “Administrator@vsphere.local” `

-VMName “MyVM” -TmpDS “Datastore_Temp”

When you run this, it will prompt you for the vCenter password (since we don’t want to store passwords in plain text). Enter the password for the -vCSAUser account. The script will then attempt to connect to vCenter using Connect-VIServer. If connection fails (wrong creds or IP), it will warn and exit. On success, you’ll see a confirmation that it connected to vCenter.

2. Pre-checks and disk selection: Once connected, the script locates the VM by name (Get-VM). It then checks that the VM is powered off – if not, it will stop and warn you to shut down the VM. It also checks that there are no snapshots on the VM (Get-Snapshot). Assuming all is well, it proceeds to list the VM’s hard disks. You will see output like:

Found 3 hard disks. Select one to shrink:

[0] – [Datastore1] MyVM/MyVM.vmdk

[1] – [Datastore1] MyVM/MyVM_1.vmdk

[2] – [Datastore1] MyVM/MyVM_2.vmdk

Each entry corresponds to a VMDK attached to the VM (the number in brackets is an index the script assigns). The script then prompts: “Enter the disk index to shrink (or ‘Exit’)”. Enter the number corresponding to the VMDK you want to shrink (in our example from Step 1, if that was Disk 2 in Windows, it might correspond to index 2 in this list – the script’s output helps identify by filename). If you type “Exit”, the script will abort here

After you enter the index, the script will confirm which VMDK file you selected and display its name.

3. Confirm and input new size: The script now asks for confirmation that you want to proceed with shrinking this disk, since this is a potentially destructive action. It prompts: “Proceed with shrinking this disk? (Yes/No)”. Type Yes (or Y) to continue. If you type anything else (No), the script will disconnect and exit without making changes. Assuming you confirmed, the script next asks: “Enter new VMDK size in MB”. Here, input the target size for the virtual disk in MB. This should be slightly more than the partition size inside (to include the buffer if you decided on one). For example, continuing our scenario, you might enter 4608 to shrink the VMDK to ~4.5 GB. The script validates that the input is numeric.

4. Modify the VMDK descriptor: With the desired new size in hand, the script calculates the new number of disk sectors for the VMDK descriptor’s “RW” line. (VMware VMDKs define their size in terms of 512-byte sectors.) It does this by newRW = (NewSizeMB * 1MB) / 512. Then the script performs the following actions to apply this change:

  • It creates a local directory Backup (in the same folder as the script) to store a backup copy of the VMDK descriptor.
  • It uses New-PSDrive -PSProvider VimDatastore to mount the datastore of the original VMDK as a drive (PSDrv:). This allows it to copy files to/from the datastore.
  • It determines the path of the VMDK descriptor file on the datastore. (Typically a VMDK consists of a small descriptor .vmdk file and a large -flat.vmdk data file. We need to edit the small descriptor.) The script strips out the datastore name and retrieves the file path for the descriptor.
  • It then copies the VMDK descriptor from the datastore to the local Backup folder using Copy-DatastoreItem. This is our backup of the original descriptor in case we need to restore it.

Next, it reads the descriptor file into a variable and performs a text replacement: finding the line that starts with “RW …” and replacing the number with the new sector count $newRW. It then saves the modified content back to the file (locally).

Finally, it uploads the modified descriptor back to the datastore, overwriting the original descriptor file with our new smaller size value (Copy-DatastoreItem -Force)

At this point, the datastore still has the original -flat.vmdk (which is full size and contains the data), but we’ve changed the descriptor to declare a smaller disk size. The next step will actually truncate the data file.

5. Storage vMotion (disk move) to reclaim space: VMware doesn’t provide a direct “shrink” command, but by moving a VMDK between datastores we can reclaim space if the descriptor size is smaller. The script uses the PowerCLI Move-HardDisk cmdlet to move the selected hard disk to the temporary datastore and back to the original datastore. Because we updated the descriptor before the move, the system will only copy the portion of the disk up to the new size, effectively discarding the tail end of the VMDK beyond the new size. This is analogous to doing a Storage vMotion and specifying thin provisioning – blocks that are beyond the new end-of-disk are not copied. The script does two moves:

  • First move: from the original datastore to the temporary datastore (-Datastore $TmpDS). It preserves the storage format (thin vs thick) by specifying -StorageFormat $currType (it recorded the original disk type earlier). This step “punches out” the free space because the destination disk will be created at the smaller size defined by the descriptor.
  • Second move: it moves the disk back from the temp datastore to the original datastore (-Datastore $origDS), again keeping the same format. We move it back so that the VM’s disk ends up where it started (this also could have been done with a single move to a new datastore, but the script assumes you want it back on the original datastore).

These Move-HardDisk operations may take some time, depending on the disk size and the environment, and the script runs them synchronously (not in background) so it will wait for completion. You’ll see progress or at least the script will appear to hang until done. In the PowerCLI output (if Verbose is on) you might see it creating a new VMDK and copying data. By the end of these moves, the datastore should now have the VMDK at the new reduced size. In other words, the space has been reclaimed.

6. Update VM configuration (re-register): After moving the hard disk, one final step is needed to cleanly update the VM’s configuration. The script uses an unconventional but effective approach: it removes the VM from inventory and re-registers it. This forces vCenter to refresh the VM’s view of its disks. The commands are:

  • Remove-VM -VM $vm -Confirm:$false which unregisters the VM from vCenter (but does not delete any files).
  • New-VM -Name $VMName -VMHost $vmHost -Datastore $origDS -VMFilePath “[origDS] VMName/VMName.vmx” -Confirm:$false which registers the VM’s .vmx file back into inventory on the same host it was on. Essentially, it’s re-adding the VM using its VMX file on the datastore.

This step happens quickly. The VM will reappear in the inventory exactly as before, but now with the updated disk size in its configuration.

7. Power on and finalize: The script then powers the VM on (Start-VM). Because we removed and re-added the VM, vCenter may think the VM was “moved” and present an interactive question (UUID question: “Did you copy or move?”). The script pre-answers this by grabbing any pending question (Get-VMQuestion) and automatically responding with “I moved it”, so the VM boots without manual intervention. Finally, the script disconnects from vCenter (Disconnect-VIServer) and prints a completion message. At this point, the VM should be running again.

8. Verify inside the VM: After the script finishes and the VM is powered on, log into the VM’s OS and open Disk Management one more time. You should observe that the disk capacity reflects the new smaller size. If you provided a buffer (e.g. you shrank the partition to 4096 MB but set VMDK to 4608 MB), the disk will show a small unallocated portion (about 512 MB in this example) at the end. You can now decide if you want to use that buffer or not. It’s often added just to be safe. If everything looks good, you can go ahead and extend the partition inside the guest to fill that remaining buffer space (e.g. extend F: to use the extra 512 MB) so that there is no unallocated gap. This can be done via Disk Management (right-click the partition, Extend Volume) or by rerunning the Resize-Partition.ps1 script with a larger size to grow it. Alternatively, if you didn’t add a buffer and sized the VMDK exactly to the partition, you should see no unallocated space at all – the partition fills the disk completely.

Throughout Step 2, the script provided a lot of automation to avoid manual errors (such as editing descriptor by hand). It’s still performing advanced operations under the hood, so if any step failed (for example, if Move-HardDisk failed due to insufficient temp datastore space or a misconfiguration), the script would attempt to revert changes. Always double-check the datastore after completion to ensure the old large -flat.vmdk file is gone and only the new smaller one exists (the script’s move should have taken care of that).


The Resize-VMDK.ps1 script

Below is the full PowerCLI script used in this step, with added comments for clarity. This script assumes the partition has already been shrunk (Step 1) and that the VM is powered off. It connects to vCenter, verifies conditions, then carries out the VMDK reduction as described:

<#

.SYNOPSIS

Shrink a VM’s VMDK on vCenter by modifying its descriptor and relocating the disk.

.PARAMETER vCSAIP

IP address of vCenter Server (e.g. "10.0.0.100").

.PARAMETER vCSAUser

vCenter username (e.g. "Administrator@vsphere.local").

.PARAMETER VMName

Name of the VM in vCenter inventory.

.PARAMETER TmpDS

Name of a temporary datastore on the same host (to facilitate block reclamation).

.EXAMPLE

PS> .\Resize-VMDK.ps1 -vCSAIP "10.0.0.100" -vCSAUser "Administrator@vsphere.local" `

-VMName "MyVM" -TmpDS "Datastore_Temp"

#>

param (

[Parameter(Mandatory=$true)] [string]$vCSAIP,

[Parameter(Mandatory=$true)] [string]$vCSAUser,

[Parameter(Mandatory=$true)] [string]$VMName,

[Parameter(Mandatory=$true)] [string]$TmpDS

)

# Import the VMware PowerCLI module (assuming it's installed)

Import-Module VMware.PowerCLI | Out-Null

Clear-Host

# Prompt for vCenter password securely

$securePwd = Read-Host "vCenter password for $vCSAUser" -AsSecureString

$vCSAUserPwd = [Runtime.InteropServices.Marshal]::PtrToStringAuto(

[Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePwd))

# Connect to vCenter Server

try {

Connect-VIServer -Server $vCSAIP -User $vCSAUser -Password $vCSAUserPwd -ErrorAction Stop | Out-Null

Write-Host "Connected to vCenter $vCSAIP"

} catch {

Write-Warning "ERROR: Cannot connect to vCenter at $vCSAIP. Check IP/credentials."

return

}

# Get the VM object

$vm = Get-VM -Name $VMName -ErrorAction SilentlyContinue

if (-not $vm) {

Write-Warning "ERROR: VM '$VMName' not found in vCenter!"

Disconnect-VIServer -Server $vCSAIP -Confirm:$false

return

}

# Ensure VM is powered off

if ($vm.PowerState -ne "PoweredOff") {

Write-Warning "VM '$VMName' is not powered off. Please shut it down before shrinking the disk."

Disconnect-VIServer -Server $vCSAIP -Confirm:$false

return

} else {

Write-Host "VM '$VMName' is powered off."

}

# Ensure no snapshots exist

if (Get-Snapshot -VM $vm -ErrorAction SilentlyContinue) {

Write-Warning "VM '$VMName' has snapshots. Please remove all snapshots before proceeding."

Disconnect-VIServer -Server $vCSAIP -Confirm:$false

return

}

# Identify the host and original datastore of the VM (for re-registration and path resolution)

$vmHost = $vm.VMHost

$origDS = (Get-HardDisk -VM $vm | Select-Object -First 1).Filename.Split(']')[0].TrimStart('[')

# List the VM’s hard disks for user selection

$hardDisks = Get-HardDisk -VM $vm

Write-Host "Found $($hardDisks.Count) hard disks for VM '$VMName'."

for ($i = 0; $i -lt $hardDisks.Count; $i++) {

Write-Host "[$i] - $($hardDisks[$i].Filename)"

}

$choice = Read-Host "Enter the disk index to shrink (or type 'Exit' to cancel)"

if ($choice -match '^[Ee]xit$') {

Disconnect-VIServer -Server $vCSAIP -Confirm:$false

return

}

if ($choice -lt 0 -or $choice -ge $hardDisks.Count) {

Write-Warning "Invalid disk selection."

Disconnect-VIServer -Server $vCSAIP -Confirm:$false

return

}

$hardDisk = $hardDisks[$choice]

Write-Host "Selected disk: $($hardDisk.Filename)"

# Confirm with user before proceeding

$confirm = Read-Host "Proceed with shrinking this disk? Type 'Yes' to continue"

if ($confirm -notmatch '^[Yy](es)?$') {

Write-Host "Operation canceled by user."

Disconnect-VIServer -Server $vCSAIP -Confirm:$false

return

}

# Get new desired size

$newSizeMB = [int](Read-Host "Enter the *new* VMDK size in MB (e.g. 4608)")

if (-not ($newSizeMB -match '^\d+$')) {

Write-Warning "Invalid size input."

Disconnect-VIServer -Server $vCSAIP -Confirm:$false

return

}

Write-Host "Target VMDK size: $newSizeMB MB"

# Calculate new size in 512-byte sectors for the VMDK descriptor

$newRW = ($newSizeMB * 1MB) / 512

Write-Host "Calculated new RW sectors: $newRW"

# Prepare backup directory for descriptor

$scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent

$backupDir = Join-Path $scriptDir "Backup"

New-Item -Type Directory -Path $backupDir -Force | Out-Null

# Mount the original datastore as a PSDrive to access files

$dsDrive = New-PSDrive -Name DSTemp -PSProvider VimDatastore -Root "/" -Datastore $origDS -Server $vCSAIP

# Formulate paths for the descriptor file

$descriptorPath = $hardDisk.Filename -replace '^\[.*?\]\s*', '' # remove "[Datastore] " prefix

$localDescriptor = Join-Path $backupDir (Split-Path -Leaf $descriptorPath)

# Backup the original descriptor locally

Copy-DatastoreItem -Item ("DSTemp:$descriptorPath") -Destination $localDescriptor -ErrorAction Stop

# Read descriptor content and replace the RW line with new size

$descriptorContent = Get-Content -Path $localDescriptor -Raw

$descriptorContent = $descriptorContent -replace '(RW\s+)\d+', "`$1$newRW"

Set-Content -Path $localDescriptor -Value $descriptorContent

# Overwrite the descriptor on the datastore with the modified one

Copy-DatastoreItem -Item $localDescriptor -Destination ("DSTemp:$descriptorPath") -Force -ErrorAction Stop

# Save the disk's current provisioning type (thin/thick) and perform storage vMotion

$diskFormat = $hardDisk.StorageFormat # e.g. "Thin" or "Thick"

Write-Host "Moving disk to temp datastore '$TmpDS' and back to '$origDS' to reclaim space..."

Move-HardDisk -HardDisk $hardDisk -Datastore $TmpDS -StorageFormat $diskFormat -Confirm:$false | Out-Null

Move-HardDisk -HardDisk $hardDisk -Datastore $origDS -StorageFormat $diskFormat -Confirm:$false | Out-Null

# Re-register the VM to update its configuration

Remove-VM -VM $vm -Confirm:$false | Out-Null

New-VM -Name $VMName -VMHost $vmHost -Datastore $origDS -VMFilePath "[${origDS}] $VMName/$VMName.vmx" -Confirm:$false | Out-Null

# Power on the VM and handle any questions (e.g., UUID change)

Start-VM -VM $VMName -Confirm:$false | Out-Null

Get-VMQuestion -VM $VMName | Set-VMQuestion -Option "I moved it" -Confirm:$false | Out-Null

# Disconnect from vCenter

Disconnect-VIServer -Server $vCSAIP -Confirm:$false | Out-Null

Write-Host "VMDK shrink operation complete. VM '$VMName' has been powered on with the resized disk."

(This script performs the VMDK shrink by editing the descriptor and using storage vMotion. It cleans up by re-registering the VM and powering it back on. Ensure you update the parameters and test in a safe environment if you modify the script.)

After the Shrink

If everything went well, your VM should be running with a smaller virtual disk. The datastore space that was previously occupied by the now-deleted portion of the VMDK should be freed. For example, if you had a 100 GB thin disk that was half empty and you shrank it to 50 GB, the datastore will regain ~50 GB of free space. Inside the VM, the partition is still the same size you left it in Step 1 (e.g. 4 GB in our example).

At this point, you can decide whether to extend that partition to fill the disk or leave it as-is with some unallocated buffer. If you included a buffer when shrinking the VMDK, you’ll see a small unallocated space at the end of the disk in Disk Management. It’s often a good idea to go ahead and extend the partition to use that space now, to avoid confusion later (the script’s note about adding ~512 MB was mainly to prevent any chance of the partition being slightly larger than the VMDK). You can extend the volume using Windows Disk Management or via PowerShell (Resize-Partition can grow partitions as well). After extension, the partition will occupy the full disk and no unallocated gap will remain.

It’s recommended to verify the integrity of the data on the shrunk volume. Open files, run disk error checking if needed, and ensure the system recognizes the disk properly. There should be no data loss as long as the steps were followed (the script refused to proceed if the partition wasn’t shrunk, preventing the major cause of data loss which would be cutting off a partition without resizing it first).

Finally, consider cleaning up the backup files the script made. In the script’s directory, there will be a Backup folder containing the original VMDK descriptor (and possibly the modified one). You may archive this for safety or delete it if everything is confirmed working. The scripts also temporarily added the VM’s IP to WinRM TrustedHosts (and removed it), and created the PSDrive mapping – those should all be cleaned up by the script itself.

Conclusion

Using this two-step scripted approach, we effectively reduced the size of a VMware virtual disk without using third-party GUI tools. We leveraged native Windows and VMware PowerCLI commands to ensure the process is as automated and safe as possible. This approach separates the concerns: first the guest OS prepares its filesystem, then the hypervisor level handles the disk file reduction, thereby avoiding data loss. Many admins resort to similar steps manually (shrink in guest, then clone or move disk and edit descriptors); the PowerShell scripts here streamline the procedure and reduce the chance of user error. As of 2025, VMware’s official stance is still that shrinking a VMDK is not supported directly, making solutions like this valuable when you need to reclaim space.

Keep in mind to always backup before such operations and test the script in a non-production environment if possible. Also, ensure all prerequisites (no snapshots, VM powered off, etc.) are met – those are essential for success. The screenshots and process described here are based on Windows 10/11 and vSphere 7.0/8.0 environments, which remain consistent with these instructions (the interfaces and cmdlets have not significantly changed). By following these steps, IT generalists and VMware admins alike can safely shrink a virtual disk and better utilize their datastore capacity. Feel free to customize the scripts for your environment or share improvements. Happy shrinking!

Hey! Found Vitalii’s article helpful? Looking to deploy a new, easy-to-manage, and cost-effective hyperconverged infrastructure?
Alex Bykovskyi
Alex Bykovskyi StarWind Virtual HCI Appliance Product Manager
Well, we can help you with this one! Building a new hyperconverged environment is a breeze with StarWind Virtual HCI Appliance (VHCA). It’s a complete hyperconverged infrastructure solution that combines hypervisor (vSphere, Hyper-V, Proxmox, or our custom version of KVM), software-defined storage (StarWind VSAN), and streamlined management tools. Interested in diving deeper into VHCA’s capabilities and features? Book your StarWind Virtual HCI Appliance demo today!