How to Create a Virtual Hard Disk (VHD) in Batch Script
A Virtual Hard Disk is a file that acts exactly like a physical hard drive. You can use it to create isolated storage containers, build portable backups, or prepare disk images for Hyper-V virtual machines. Rather than purchasing new hardware, a Batch script can use the DiskPart utility to create a new VHD or VHDX file, format it, assign a drive letter, and make it ready for use, all from a single automated script.
This guide will explain how to create virtual disks programmatically using DiskPart from Batch.
Method 1: Creating a Fixed-Size VHD
A fixed-size VHD immediately allocates all the space on your physical disk. It is slower to create but provides the best I/O performance because the file does not need to grow during use.
@echo off
setlocal
set "VHDPath=%~1"
set "SizeMB=%~2"
set "DriveLetter=%~3"
set "Label=DataDrive"
if "%VHDPath%"=="" (
echo Usage: %~nx0 ^<vhd_path^> ^<size_mb^> [drive_letter] [label]
echo.
echo Examples:
echo %~nx0 C:\VDisks\DataVault.vhdx 2048
echo %~nx0 D:\Images\Backup.vhdx 10240 V
echo %~nx0 C:\VDisks\Storage.vhdx 5120 W MyStorage
endlocal
exit /b 1
)
if "%SizeMB%"=="" (
echo [ERROR] Size in MB is required. >&2
endlocal
exit /b 1
)
if "%DriveLetter%"=="" set "DriveLetter=V"
if not "%~4"=="" set "Label=%~4"
:: Verify admin privileges
net session >nul 2>&1
if errorlevel 1 (
echo [ERROR] DiskPart requires administrator privileges. >&2
echo Right-click and select "Run as administrator." >&2
endlocal
exit /b 1
)
:: Verify the target directory exists
for %%d in ("%VHDPath%") do set "VHDDir=%%~dpd"
if not exist "%VHDDir%" (
echo [INFO] Creating directory: %VHDDir%
mkdir "%VHDDir%"
if errorlevel 1 (
echo [ERROR] Could not create directory: %VHDDir% >&2
endlocal
exit /b 1
)
)
:: Verify the VHD file does not already exist
if exist "%VHDPath%" (
echo [ERROR] VHD file already exists: %VHDPath% >&2
echo Delete it first or choose a different path. >&2
endlocal
exit /b 1
)
:: Verify the drive letter is available
if exist %DriveLetter%:\ (
echo [ERROR] Drive letter %DriveLetter%: is already in use. >&2
endlocal
exit /b 1
)
:: Check available disk space (fixed VHDs need full allocation)
for /f "delims=" %%s in (
'powershell -NoProfile -Command ^
"$drive = '%VHDDir%'.Substring(0,2);" ^
"$free = (Get-PSDrive -Name $drive[0] -ErrorAction SilentlyContinue).Free;" ^
"if ($free) { [math]::Floor($free / 1MB) } else { 0 }"'
) do set "FreeMB=%%s"
if %FreeMB% leq %SizeMB% (
echo [ERROR] Insufficient disk space. Need %SizeMB% MB, have %FreeMB% MB free. >&2
endlocal
exit /b 1
)
echo [INFO] Creating %SizeMB% MB fixed VHD: %VHDPath%
echo [INFO] This may take a moment for large disks...
:: Create the DiskPart script
set "DPScript=%TEMP%\dp_create_%RANDOM%.txt"
(
echo create vdisk file="%VHDPath%" maximum=%SizeMB% type=fixed
echo select vdisk file="%VHDPath%"
echo attach vdisk
echo wait
echo create partition primary
echo format fs=ntfs label="%Label%" quick
echo assign letter=%DriveLetter%
) > "%DPScript%"
:: Execute DiskPart
diskpart /s "%DPScript%" >nul 2>&1
set "DPResult=%errorlevel%"
del "%DPScript%" 2>nul
if %DPResult% neq 0 (
echo [ERROR] DiskPart failed to create the VHD. >&2
:: Clean up partial VHD file
if exist "%VHDPath%" del "%VHDPath%" 2>nul
endlocal
exit /b 1
)
:: Verify the drive is accessible
timeout /t 2 >nul
if exist %DriveLetter%:\ (
echo [OK] VHD created and mounted at %DriveLetter%:\
echo [OK] File: %VHDPath% (%SizeMB% MB, NTFS, label "%Label%"^)
) else (
echo [WARNING] VHD created but %DriveLetter%:\ is not accessible. >&2
echo Check Disk Management for details. >&2
)
endlocal
exit /b 0
Fixed vs. Dynamic VHDs:
| Property | Fixed | Dynamic (Expandable) |
|---|---|---|
| Disk space allocation | Immediate, full size allocated at creation | On-demand, grows as data is written |
| Creation speed | Slower (writes zeros to fill the file) | Fast (creates small metadata file) |
| I/O performance | Better (no fragmentation, no growth overhead) | Slightly slower (file must grow during writes) |
| Physical disk usage | Always uses full maximum size | Uses only as much as the actual data |
| Best for | Production VMs, performance-critical workloads | Backups, test environments, unknown final size |
Method 2: Creating a Dynamic (Expandable) VHD
A dynamic VHD starts small and grows only as data is written. This is ideal for backup containers or test environments where you want to reserve a large maximum size without consuming the space immediately.
@echo off
setlocal
set "VHDPath=%~1"
set "MaxSizeMB=%~2"
set "DriveLetter=%~3"
set "Label=DynamicDrive"
if "%VHDPath%"=="" (
echo Usage: %~nx0 ^<vhd_path^> ^<max_size_mb^> [drive_letter] [label]
echo.
echo Example: %~nx0 D:\Backups\DynamicStore.vhdx 10240 X Backups
endlocal
exit /b 1
)
if "%MaxSizeMB%"=="" (
echo [ERROR] Maximum size in MB is required. >&2
endlocal
exit /b 1
)
if "%DriveLetter%"=="" set "DriveLetter=X"
if not "%~4"=="" set "Label=%~4"
net session >nul 2>&1
if errorlevel 1 (
echo [ERROR] Administrator privileges required. >&2
endlocal
exit /b 1
)
for %%d in ("%VHDPath%") do set "VHDDir=%%~dpd"
if not exist "%VHDDir%" mkdir "%VHDDir%"
if exist "%VHDPath%" (
echo [ERROR] VHD file already exists: %VHDPath% >&2
endlocal
exit /b 1
)
if exist %DriveLetter%:\ (
echo [ERROR] Drive letter %DriveLetter%: is already in use. >&2
endlocal
exit /b 1
)
echo [INFO] Creating dynamic VHD (max %MaxSizeMB% MB): %VHDPath%
set "DPScript=%TEMP%\dp_dyn_%RANDOM%.txt"
(
echo create vdisk file="%VHDPath%" maximum=%MaxSizeMB% type=expandable
echo select vdisk file="%VHDPath%"
echo attach vdisk
echo wait
echo create partition primary
echo format fs=ntfs label="%Label%" quick
echo assign letter=%DriveLetter%
) > "%DPScript%"
diskpart /s "%DPScript%" >nul 2>&1
set "DPResult=%errorlevel%"
del "%DPScript%" 2>nul
if %DPResult% neq 0 (
echo [ERROR] VHD creation failed. >&2
if exist "%VHDPath%" del "%VHDPath%" 2>nul
endlocal
exit /b 1
)
timeout /t 2 >nul
if exist %DriveLetter%:\ (
:: Show actual file size vs. maximum capacity
for %%f in ("%VHDPath%") do (
echo [OK] VHD created and mounted at %DriveLetter%:\
echo [OK] Maximum capacity: %MaxSizeMB% MB
echo [OK] Current file size: %%~zf bytes (grows as data is added^)
)
) else (
echo [WARNING] VHD created but drive is not accessible. >&2
)
endlocal
exit /b 0
Why dynamic VHDs show a small initial file size:
A newly created 10 GB dynamic VHDX occupies only a few MB on the physical disk. As you write data to the mounted drive, the VHDX file grows to accommodate it, up to the maximum specified during creation. The physical disk space is consumed gradually, not all at once.
Method 3: Create, Use, and Detach Workflow
A common pattern is creating a temporary VHD for a specific operation (staging files, creating a backup snapshot, building a deployment image) and then detaching it for storage or transport.
@echo off
setlocal
set "VHDPath=%~dp0deployment_image.vhdx"
set "DriveLetter=W"
set "SizeMB=4096"
set "SourceDir=%~1"
if "%SourceDir%"=="" (
echo Usage: %~nx0 ^<source_directory^>
echo.
echo Creates a VHD containing a copy of the source directory.
endlocal
exit /b 1
)
if not exist "%SourceDir%\" (
echo [ERROR] Source directory not found: %SourceDir% >&2
endlocal
exit /b 1
)
net session >nul 2>&1
if errorlevel 1 (
echo [ERROR] Administrator privileges required. >&2
endlocal
exit /b 1
)
:: =============================================
:: Step 1: Create and mount the VHD
:: =============================================
echo [1/3] Creating VHD: %VHDPath%
if exist "%VHDPath%" del "%VHDPath%" 2>nul
set "DPScript=%TEMP%\dp_deploy_%RANDOM%.txt"
(
echo create vdisk file="%VHDPath%" maximum=%SizeMB% type=expandable
echo select vdisk file="%VHDPath%"
echo attach vdisk
echo wait
echo create partition primary
echo format fs=ntfs label="DeployImage" quick
echo assign letter=%DriveLetter%
) > "%DPScript%"
diskpart /s "%DPScript%" >nul 2>&1
del "%DPScript%" 2>nul
timeout /t 2 >nul
if not exist %DriveLetter%:\ (
echo [ERROR] Failed to create and mount VHD. >&2
endlocal
exit /b 1
)
echo [OK] VHD mounted at %DriveLetter%:\
:: =============================================
:: Step 2: Copy files to the VHD
:: =============================================
echo [2/3] Copying files from "%SourceDir%" to %DriveLetter%:\...
robocopy "%SourceDir%" %DriveLetter%:\ /E /R:1 /W:1 /NJH /NJS /NP >nul
if %errorlevel% geq 8 (
echo [ERROR] File copy failed. >&2
goto :Detach
)
echo [OK] Files copied successfully.
:: =============================================
:: Step 3: Detach the VHD
:: =============================================
:Detach
echo [3/3] Detaching VHD...
set "DPScript=%TEMP%\dp_detach_%RANDOM%.txt"
(
echo select vdisk file="%VHDPath%"
echo detach vdisk
) > "%DPScript%"
diskpart /s "%DPScript%" >nul 2>&1
del "%DPScript%" 2>nul
echo [OK] VHD detached. File ready for transport: %VHDPath%
for %%f in ("%VHDPath%") do echo [OK] Final file size: %%~zf bytes
endlocal
exit /b 0
Why this pattern is useful:
- Deployment images: Package an application and its dependencies into a single portable file.
- Backup snapshots: Create a point-in-time copy of a directory in a self-contained format.
- Secure transport: The VHD can be encrypted with BitLocker after creation for secure file transfer.
How to Avoid Common Errors
Problem: Fixed VHD Larger Than Free Disk Space
Creating a 100 GB fixed VHD with only 50 GB free will fail. DiskPart may fail partway through, leaving a partial file.
Solution: Method 1 checks available disk space before creation. For uncertain sizes, use type=expandable (Method 2), which only consumes space as data is written.
Problem: VHD File Already Exists
DiskPart fails if the target file already exists. The error message is not always clear.
Solution: All methods in this guide check for existing files before creation and provide a descriptive error message.
Problem: Missing Format Step
If you create and attach a VHD without formatting, Windows sees a RAW disk and may display a "You need to format this disk" dialog, or the volume simply appears as unformatted in Disk Management.
Solution: Always include format fs=ntfs label="..." quick in your DiskPart script. All methods in this guide include this step.
Problem: Partial VHD File After Failed Creation
If DiskPart fails during creation (disk full, permission error), it may leave a partial or corrupt VHD file on disk.
Solution: Methods 1 and 2 clean up the partial file with del "%VHDPath%" 2>nul when DiskPart reports a failure.
Problem: wait Missing After attach vdisk
Without wait, the create partition command may execute before the disk is fully recognized, causing a "There is no disk selected" error.
Solution: Always include wait after attach vdisk in the DiskPart script.
Best Practices and Rules
1. Use VHDX Over VHD
The VHDX format (introduced with Windows 8/Server 2012) is superior in every way:
| Feature | VHD | VHDX |
|---|---|---|
| Maximum size | 2 TB | 64 TB |
| Block size | 2 MB | Configurable (up to 256 MB) |
| Power failure resilience | Limited | Metadata journaling protects against corruption |
| Performance | Good | Better (larger block alignment, less overhead) |
Use .vhd only if backward compatibility with Windows 7 or Server 2008 R2 is required.
2. Validate All Inputs Before DiskPart
DiskPart error messages are cryptic. Validate everything before calling it: verify the target directory exists, check that the VHD file doesn't already exist, confirm the drive letter is available, and ensure sufficient disk space for fixed VHDs.
3. Always Clean Up DiskPart Script Files
Use %RANDOM% in temp filenames and del them immediately after DiskPart returns, even on error paths. Accumulated DiskPart scripts in %TEMP% are a minor mess but could also expose path information.
4. Detach VHDs When Done
A mounted VHD locks the .vhdx file, preventing backup tools from copying it and Hyper-V from using it. Detach when your operations are complete.
5. Label Volumes Descriptively
Always use label="DescriptiveName" in the format command. When multiple VHDs are mounted, descriptive labels in File Explorer are much more useful than "New Volume."
6. Log Creation Operations
For compliance and tracking, log VHD creation events:
echo [%date% %time%] Created VHD: %VHDPath% (%SizeMB% MB, %Label%) >> "%~dp0vhd_operations.log"
Conclusions
Creating Virtual Hard Disks via Batch script provides a powerful foundation for portable storage, backup containers, deployment images, and virtual machine infrastructure. By automating DiskPart commands with proper validation, drive letter assignment, format configuration, and error handling, you gain the speed and consistency needed for professional deployments. This automation eliminates the manual steps and potential errors inherent in GUI-based disk management.