How to Expand a VHD File in Batch Script
When a virtual hard disk runs out of space, a virtual machine's data volume is full, or your VHD-based backup container is at capacity, you need to increase its maximum size. Expanding a VHD/VHDX does not add data; it increases the ceiling of how large the virtual disk is allowed to grow. After expanding the VHDX file, you must still extend the partition inside it to consume the newly available space. This is a two-step process: expand the virtual disk container, then extend the volume within it.
This guide will explain how to enlarge virtual disk images programmatically.
Understanding the Two-Step Process
Step 1: EXPAND the VHD container Step 2: EXTEND the partition inside
┌──────────────────────────────────┐ ┌─────────────────────────────────┐
│ VHDX File (100 GB → 200 GB) │ │ VHDX File (200 GB) │
│ ┌─────────────────┬───────────┐ │ │ ┌─────────────────────────────┐│
│ │ NTFS Partition │Unallocated│ │ │ │ NTFS Partition ││
│ │ (100 GB) │(100 GB) │ │ │ │ (200 GB - extended) ││
│ └─────────────────┴───────────┘ │ │ └─────────────────────────────┘│
└──────────────────────────────────┘ └─────────────────────────────────┘
Partition does NOT auto-grow! After EXTEND, partition uses all space
Expanding the VHD only adds unallocated space to the virtual disk. The partition inside does NOT automatically grow to fill it. Without the extend step, the guest OS still sees the original partition size, even though the VHD file is larger.
Method 1: Complete Expand and Extend Workflow
This method performs both steps in a single operation: expands the VHD container and extends the partition inside it to use all available space.
@echo off
setlocal
set "VHDPath=%~1"
set "NewSizeMB=%~2"
if "%NewSizeMB%"=="" (
echo Usage: %~nx0 ^<path_to_vhd^> ^<new_size_mb^>
echo.
echo Expands a VHD/VHDX file to the specified size and extends
echo the partition inside to use all available space.
echo.
echo Size reference:
echo 50 GB = 51200 MB
echo 100 GB = 102400 MB
echo 200 GB = 204800 MB
echo 500 GB = 512000 MB
echo 1 TB = 1048576 MB
echo.
echo Example: %~nx0 D:\VMs\DataDisk.vhdx 204800
echo (Expands DataDisk.vhdx to 200 GB^)
endlocal
exit /b 1
)
:: Verify admin privileges
net session >nul 2>&1
if errorlevel 1 (
echo [ERROR] DiskPart requires administrator privileges. >&2
endlocal
exit /b 1
)
:: Verify the VHD file exists
if not exist "%VHDPath%" (
echo [ERROR] VHD file not found: %VHDPath% >&2
endlocal
exit /b 1
)
:: Get current VHD info
for /f "tokens=1-2 delims=|" %%a in (
'powershell -NoProfile -Command ^
"try {" ^
" $dp = ''%TEMP%\dp_detail_%RANDOM%.txt'';" ^
" (''select vdisk file=\"%VHDPath%\"'', ''detail vdisk'') | Set-Content $dp;" ^
" $out = diskpart /s $dp 2>&1 | Out-String;" ^
" Remove-Item $dp -ErrorAction SilentlyContinue;" ^
" $maxMatch = [regex]::Match($out, ''Virtual size\s*:\s*(\d+)'');" ^
" $typeMatch = if ($out -match ''Expandable|Dynamic'') { ''Dynamic'' }" ^
" elseif ($out -match ''Fixed'') { ''Fixed'' }" ^
" else { ''Unknown'' };" ^
" $currentMB = if ($maxMatch.Success) { $maxMatch.Groups[1].Value } else { ''0'' };" ^
" Write-Output \"$typeMatch|$currentMB\"" ^
"} catch { Write-Output ''Error|0'' }"'
) do (
set "VHDType=%%a"
set "CurrentSizeMB=%%b"
)
:: Display current info
for /f "delims=" %%g in (
'powershell -NoProfile -Command "[math]::Round(%NewSizeMB% / 1024, 1)"'
) do set "NewSizeGB=%%g"
echo [INFO] VHD: %VHDPath%
echo [INFO] Type: %VHDType%
echo [INFO] New size: %NewSizeMB% MB (%NewSizeGB% GB^)
:: Validate the new size is larger
if "%CurrentSizeMB%" neq "0" (
powershell -NoProfile -Command "if (%NewSizeMB% -le %CurrentSizeMB%) { exit 1 } else { exit 0 }" >nul 2>&1
if errorlevel 1 (
echo [ERROR] New size (%NewSizeMB% MB^) must be larger than current size (%CurrentSizeMB% MB^). >&2
echo DiskPart can only expand, not shrink. >&2
endlocal
exit /b 1
)
)
echo.
echo [ACTION] Step 1/2: Expanding VHD container to %NewSizeMB% MB...
set "DPScript=%TEMP%\dp_expand_%RANDOM%.txt"
(
echo select vdisk file="%VHDPath%"
echo expand vdisk maximum=%NewSizeMB%
) > "%DPScript%"
diskpart /s "%DPScript%" >nul 2>&1
set "DPResult=%errorlevel%"
del "%DPScript%" 2>nul
if %DPResult% neq 0 (
echo [ERROR] VHD expansion failed. >&2
echo Common causes: >&2
echo - VHD is in use by a running VM (shut down first^) >&2
echo - VHD is currently mounted >&2
echo - Insufficient space on the host drive for a fixed VHD >&2
endlocal
exit /b 1
)
echo [OK] VHD container expanded.
echo [ACTION] Step 2/2: Extending partition inside the VHD...
set "DPScript=%TEMP%\dp_extend_%RANDOM%.txt"
(
echo select vdisk file="%VHDPath%"
echo attach vdisk
echo wait
echo select partition 1
echo extend
echo detach vdisk
) > "%DPScript%"
diskpart /s "%DPScript%" >nul 2>&1
set "DPResult=%errorlevel%"
del "%DPScript%" 2>nul
if %DPResult% neq 0 (
echo [WARNING] Partition extension may have failed. >&2
echo The VHD container was expanded successfully, but the partition >&2
echo inside may not have been extended. >&2
echo.
echo To extend manually: >&2
echo 1. Mount the VHD (diskpart: attach vdisk^) >&2
echo 2. Open Disk Management >&2
echo 3. Right-click the partition ^> Extend Volume >&2
echo OR extend from within the guest OS after booting the VM. >&2
endlocal
exit /b 1
)
echo [OK] Partition extended to use all available space.
echo.
echo [DONE] VHD expansion complete:
echo File: %VHDPath%
echo New capacity: %NewSizeGB% GB
:: Log the operation
for /f "delims=" %%t in (
'powershell -NoProfile -Command "Get-Date -Format ''yyyy-MM-dd HH:mm:ss''"'
) do echo [%%t] EXPAND: %VHDPath% to %NewSizeMB% MB by %USERNAME% on %COMPUTERNAME% >> "%~dp0vhd_operations.log"
endlocal
exit /b 0
Why the expand and extend are separate DiskPart scripts:
The expand vdisk command must be run while the VHD is detached. The extend command requires the VHD to be attached and a partition to be selected. These are incompatible requirements, so they must be executed as two separate DiskPart operations.
Size reference:
| Desired Size | maximum= Value (MB) |
|---|---|
| 50 GB | 51200 |
| 100 GB | 102400 |
| 200 GB | 204800 |
| 500 GB | 512000 |
| 1 TB | 1048576 |
| 2 TB | 2097152 |
- Dynamic VHD: The
expandcommand increases the maximum capacity. The physical file on the host does NOT grow immediately, it continues to grow on demand as data is written. - Fixed VHD: The
expandcommand must allocate the additional space on the host disk immediately. Ensure the host drive has enough free space for the size increase.
Method 2: Expand with Host Space Verification
For fixed VHDs, the expansion requires the additional space to be available on the host drive. This method checks before proceeding.
@echo off
setlocal
set "VHDPath=%~1"
set "NewSizeMB=%~2"
if "%NewSizeMB%"=="" (
echo Usage: %~nx0 ^<path_to_vhd^> ^<new_size_mb^>
endlocal
exit /b 1
)
net session >nul 2>&1
if errorlevel 1 (
echo [ERROR] Administrator privileges required. >&2
endlocal
exit /b 1
)
if not exist "%VHDPath%" (
echo [ERROR] VHD not found: %VHDPath% >&2
endlocal
exit /b 1
)
:: Check host drive free space
for %%d in ("%VHDPath%") do set "HostDrive=%%~dd"
for /f "delims=" %%f in (
'powershell -NoProfile -Command ^
"$drv = (Get-PSDrive -Name ''%HostDrive:~0,1%'' -ErrorAction SilentlyContinue).Free;" ^
"if ($drv) { [math]::Round($drv / 1MB) } else { 0 }"'
) do set "HostFreeMB=%%f"
:: Get current VHD file size
for %%f in ("%VHDPath%") do set "CurrentFileSize=%%~zf"
for /f "delims=" %%c in (
'powershell -NoProfile -Command "[math]::Round(%CurrentFileSize% / 1MB)"'
) do set "CurrentFileMB=%%c"
:: Calculate potential additional space needed (worst case for fixed VHDs)
set /a "AdditionalMB=%NewSizeMB% - %CurrentFileMB%"
echo [INFO] VHD: %VHDPath%
echo [INFO] Host drive: %HostDrive% (%HostFreeMB% MB free^)
echo [INFO] Current VHD file: %CurrentFileMB% MB
echo [INFO] Requested size: %NewSizeMB% MB
if %AdditionalMB% leq 0 (
echo [INFO] VHD file is already larger than the requested size.
echo [INFO] The VHD may already be expanded, or it is a fixed VHD.
endlocal
exit /b 0
)
echo [INFO] Additional space needed (worst case^): %AdditionalMB% MB
:: Warn if host drive space is tight
powershell -NoProfile -Command "if (%HostFreeMB% -lt %AdditionalMB% * 1.1) { exit 1 } else { exit 0 }" >nul 2>&1
if errorlevel 1 (
echo [WARNING] Host drive may not have enough free space for a fixed VHD expansion. >&2
echo Needed: ~%AdditionalMB% MB Available: %HostFreeMB% MB >&2
echo For dynamic VHDs, the space is allocated on demand (less immediate impact^). >&2
echo.
set /p "Proceed=Proceed anyway? (YES/no): "
if /i not "!Proceed!"=="YES" (
echo [INFO] Cancelled.
endlocal
exit /b 0
)
)
echo.
echo [ACTION] Expanding VHD to %NewSizeMB% MB...
set "DPScript=%TEMP%\dp_expand_%RANDOM%.txt"
(
echo select vdisk file="%VHDPath%"
echo expand vdisk maximum=%NewSizeMB%
) > "%DPScript%"
diskpart /s "%DPScript%" >nul 2>&1
set "DPResult=%errorlevel%"
del "%DPScript%" 2>nul
if %DPResult% neq 0 (
echo [ERROR] Expansion failed. >&2
endlocal
exit /b 1
)
echo [OK] VHD expanded to %NewSizeMB% MB.
echo.
echo [NEXT STEPS] The partition inside the VHD must be extended:
echo Option A: Run the extend script:
echo %~nx0 extend (not implemented here - use Method 1^)
echo Option B: Boot the VM and extend from within the guest OS:
echo Disk Management ^> Right-click partition ^> Extend Volume
echo Option C: Use DiskPart to mount and extend:
echo select vdisk file="%VHDPath%"
echo attach vdisk
echo select partition 1
echo extend
echo detach vdisk
for /f "delims=" %%t in (
'powershell -NoProfile -Command "Get-Date -Format ''yyyy-MM-dd HH:mm:ss''"'
) do echo [%%t] EXPAND: %VHDPath% to %NewSizeMB% MB by %USERNAME% >> "%~dp0vhd_operations.log"
endlocal
exit /b 0
When to extend from inside the guest vs. from the host:
| Scenario | Extend From | Why |
|---|---|---|
| VHD is used by a VM (Hyper-V) | Inside the guest OS | Guest can extend while running; host cannot attach a VM's disk |
| VHD is a standalone data container | Host (DiskPart) | No guest OS to boot; Method 1 handles this |
| VHD has multiple partitions | Guest OS or Disk Management | Need to see all partitions to choose which to extend |
The expand vdisk command requires the VHD to be completely detached, not mounted by DiskPart, not in use by Hyper-V, and not open in any application. Shut down any VMs using the VHD before expanding. After expansion, you can start the VM and extend the partition from within the guest OS.
Method 3: PowerShell Resize (Hyper-V Environments)
For Hyper-V environments, Resize-VHD provides a simpler syntax.
@echo off
setlocal
set "VHDPath=%~1"
set "NewSizeGB=%~2"
if "%NewSizeGB%"=="" (
echo Usage: %~nx0 ^<path_to_vhd^> ^<new_size_gb^>
echo.
echo Example: %~nx0 D:\VMs\DataDisk.vhdx 200
echo (Resizes to 200 GB^)
endlocal
exit /b 1
)
net session >nul 2>&1
if errorlevel 1 (
echo [ERROR] Administrator privileges required. >&2
endlocal
exit /b 1
)
if not exist "%VHDPath%" (
echo [ERROR] VHD not found: %VHDPath% >&2
endlocal
exit /b 1
)
:: Check if Resize-VHD is available
powershell -NoProfile -Command "Get-Command Resize-VHD -ErrorAction SilentlyContinue | Out-Null; if (-not $?) { exit 1 }" >nul 2>&1
if errorlevel 1 (
echo [ERROR] Resize-VHD not available. Requires Hyper-V PowerShell module. >&2
echo Use Method 1 (DiskPart^) instead. >&2
endlocal
exit /b 1
)
echo [INFO] Current VHD info:
powershell -NoProfile -Command ^
"$vhd = Get-VHD -Path '%VHDPath%' -ErrorAction SilentlyContinue;" ^
"if ($vhd) {" ^
" Write-Host \" Type: $($vhd.VhdType)\";" ^
" Write-Host \" Current: $([math]::Round($vhd.Size / 1GB, 1)) GB\";" ^
" Write-Host \" File: $([math]::Round($vhd.FileSize / 1GB, 1)) GB (on disk)\"" ^
"}"
echo.
echo [ACTION] Resizing VHD to %NewSizeGB% GB...
powershell -NoProfile -Command ^
"try {" ^
" Resize-VHD -Path '%VHDPath%' -SizeBytes (%NewSizeGB%GB) -ErrorAction Stop;" ^
" Write-Host '[OK] VHD resized to %NewSizeGB% GB.';" ^
" exit 0" ^
"} catch {" ^
" Write-Error $_.Exception.Message;" ^
" exit 1" ^
"}"
if errorlevel 1 (
echo [ERROR] Resize failed. >&2
echo The VHD may be in use by a running VM. >&2
endlocal
exit /b 1
)
echo.
echo [NEXT] Extend the partition inside the VHD:
echo - If this is a VM disk: Boot the VM, open Disk Management,
echo right-click the partition, select "Extend Volume."
echo - If this is a data VHD: Mount with DiskPart, select partition, extend.
for /f "delims=" %%t in (
'powershell -NoProfile -Command "Get-Date -Format ''yyyy-MM-dd HH:mm:ss''"'
) do echo [%%t] RESIZE: %VHDPath% to %NewSizeGB% GB by %USERNAME% >> "%~dp0vhd_operations.log"
endlocal
exit /b 0
Resize-VHD advantages:
- Accepts size in human-readable format (
200GB) instead of megabytes. - Shows current VHD info via
Get-VHD(type, current size, on-disk file size). - Handles both dynamic and fixed VHDs transparently.
- Better error messages when the VHD is in use.
Resize-VHD requires the Hyper-V PowerShell module (installable without the full Hyper-V role). DiskPart's expand vdisk works on any Windows system without additional features. Use DiskPart for non-Hyper-V environments and Resize-VHD where Hyper-V management tools are available.
How to Avoid Common Errors
Wrong Way: Forgetting to Extend the Partition
After expand only:
┌─────────────────────────────────┐
│ VHDX (200 GB) │
│ ┌────────────────┬───────────┐ │
│ │ NTFS (100 GB) │ WASTED! │ │ ← Guest OS still sees 100 GB
│ └────────────────┴───────────┘ │
└─────────────────────────────────┘
The VHD is 200 GB but the partition inside is still 100 GB. The extra space is unallocated and unusable until the partition is extended.
Correct Way: Always perform both steps, expand the container AND extend the partition (Method 1 does both automatically).
Wrong Way: Trying to Shrink with expand
:: FAILS: expand only increases, never decreases
expand vdisk maximum=51200
:: (if current size is already 102400 MB, this fails)
DiskPart's expand vdisk can only increase the maximum size. To reduce a VHD's size, you must create a new smaller VHD and copy the data.
Solution: Method 1 validates that the new size is larger than the current size before attempting expansion.
Problem: VHD In Use by Running VM
expand vdisk requires the VHD to be completely detached. A running Hyper-V VM locks its VHD files.
Solution:
- Shut down the VM.
- Expand the VHD from the host using Method 1 or 3.
- Start the VM.
- Extend the partition from within the guest OS using Disk Management.
Windows Server 2012 R2+ and Windows 10+ support online VHDX expansion for Generation 2 VMs, the VM does not need to be shut down. Use Resize-VHD (Method 3) while the VM is running, then extend the partition from within the guest. This is NOT supported for Generation 1 VMs or VHD (non-VHDX) files.
Problem: Insufficient Host Disk Space for Fixed VHD
Expanding a fixed VHD by 100 GB requires 100 GB of immediately available space on the host drive. If the host drive doesn't have enough space, the expansion fails.
Solution: Method 2 checks host drive free space before attempting expansion and warns if space is tight.
Problem: Multiple Partitions in the VHD
select partition 1 followed by extend only extends the first partition. If the VHD contains multiple partitions (recovery, EFI, data), you need to select the correct partition number.
Solution: Before extending, list partitions to identify the correct one:
select vdisk file="path"
attach vdisk
list partition
Then select the data partition by its number: select partition 2 (or whichever is correct).
Best Practices and Rules
1. Always Extend After Expanding
Expanding without extending leaves unusable unallocated space inside the VHD. Method 1 performs both steps automatically. If expanding for a VM, extend from within the guest OS after booting.
2. Back Up Before Expanding
While expansion is generally safe, modifying the VHD's internal geometry carries risk. Keep a backup of the VHDX file before modifying it, especially for production VMs.
3. Verify the VHD Is Detached
expand vdisk silently fails or produces errors if the VHD is mounted or in use. Shut down VMs, detach any DiskPart mounts, and close any applications that may have the file open.
4. Use Resize-VHD for Hyper-V Online Expansion
For Generation 2 VMs on Windows Server 2012 R2+, Resize-VHD supports online expansion, the VM stays running during the resize. This eliminates downtime for the expansion step (though the guest-side partition extension may still require Disk Management).
5. Log Every Expansion
Track VHD size changes for capacity planning:
echo [%date% %time%] Expanded %VHDPath% to %NewSizeMB% MB >> vhd_operations.log
Over time, this reveals which VMs or containers frequently need more space, candidates for larger initial sizing.
6. Plan for Growth
If a VHD has been expanded twice in the last 6 months, consider creating it with a larger initial size next time. Repeated expansion adds operational overhead and downtime.
Conclusions
Expanding VHD files is a routine operation in virtual environments, but the two-step nature (expand container + extend partition) is a frequent source of confusion. By automating both steps with proper validation, size comparison, host space verification, VHD type detection, and partition extension, you ensure that the expansion is complete and the space is actually usable. Combined with logging for capacity planning, this automation turns a manual, error-prone process into a reliable one-click operation.