Introduction to Hyper-V Storage Migration on Windows Server 2019
Hyper-V Storage Migration moves a virtual machine’s storage — its VHD/VHDX files, configuration file, snapshot files, and smart paging file — from one storage location to another while the VM continues running. Unlike Live Migration (which moves a VM between hosts), Storage Migration moves only the storage while the VM remains on the same host. This is used to evacuate a failing disk, migrate from slower storage to faster NVMe/SSD, balance storage load across multiple volumes, or move VMs from local storage to a SAN. Windows Server 2019 supports storage migration as both a standalone operation and combined with host migration (Shared Nothing Live Migration).
Storage Migration vs Shared Nothing Live Migration
Storage Migration: VM stays on the same Hyper-V host. Only the storage files move. VM continues running throughout. Use this when you need to move VMs between storage volumes on the same host.
Shared Nothing Live Migration: VM moves to a different host AND storage moves simultaneously. VM continues running. Use this when you need to move a VM to a different host that doesn’t share storage with the source host.
Quick Storage Migration: Similar to storage migration but briefly pauses the VM during the final cutover phase (a few seconds downtime). Faster than live storage migration because it doesn’t need to maintain full consistency during the copy phase.
Prerequisites
# Verify Hyper-V is installed and the target VM exists
Get-VM | Select-Object Name, State, Status | Format-Table
# Check current VM storage paths
Get-VM -Name "WebServer01" | Get-VMHardDiskDrive |
Select-Object VMName, Path, ControllerType, ControllerNumber | Format-Table
Get-VM -Name "WebServer01" |
Select-Object Name,
@{n="ConfigPath";e={$_.ConfigurationLocation}},
@{n="SnapshotPath";e={$_.SnapshotFileLocation}},
@{n="SmartPagingPath";e={$_.SmartPagingFilePath}} | Format-List
Move All VM Storage to a New Location
The simplest storage migration moves the entire VM (all VHDs, config, and snapshots) to a new storage path while the VM is running:
# Move all VM storage to a new path (VM stays running)
Move-VMStorage -VMName "WebServer01" -DestinationStoragePath "E:HyperVVMsWebServer01"
# Monitor progress
Get-VM -Name "WebServer01" | Select-Object Name, Status | Format-List
# Status will show "Moving" during the operation
# Verify new storage location after completion
Get-VMHardDiskDrive -VMName "WebServer01" | Select-Object Path | Format-Table
Get-VM -Name "WebServer01" | Select-Object ConfigurationLocation, SnapshotFileLocation
Move Individual VHD Files
You can move specific VHD/VHDX files individually, leaving others in place. This is useful when moving only a data disk to a faster volume while keeping the OS disk on the existing storage:
# Get disk paths for the VM
$disks = Get-VMHardDiskDrive -VMName "WebServer01"
$disks | Select-Object Path, ControllerType, ControllerLocation | Format-Table
# Move only the data disk (controller location 1) to NVMe storage
$dataDiskPath = ($disks | Where-Object ControllerLocation -eq 1).Path
$newDataDiskPath = "D:FastSSDWebServer01" + (Split-Path $dataDiskPath -Leaf)
Move-VMStorage -VMName "WebServer01" `
-Vhds @(@{SourceFilePath=$dataDiskPath; DestinationFilePath=$newDataDiskPath})
Move VM Storage and Configuration Separately
You can specify different destination paths for VHDs, configuration files, snapshots, and smart paging files independently:
# Move everything to different locations by component
Move-VMStorage -VMName "WebServer01" `
-VirtualMachinePath "E:HyperVVMs" ` # Config XML location
-SnapshotFilePath "E:HyperVSnapshots" ` # Checkpoint files
-SmartPagingFilePath "E:HyperVPaging" ` # Smart paging files
-Vhds @(
@{SourceFilePath="C:HyperVWebServer01OS.vhdx";
DestinationFilePath="D:VHDsOSWebServer01-OS.vhdx"},
@{SourceFilePath="C:HyperVWebServer01Data.vhdx";
DestinationFilePath="E:VHDsDataWebServer01-Data.vhdx"}
)
Batch Storage Migration — Move Multiple VMs
When evacuating a failing disk or migrating all VMs from one storage volume to another, automate the process for all VMs:
# Find all VMs with storage on C: and migrate them to D:
$vmsToMigrate = Get-VM | Where-Object {
(Get-VMHardDiskDrive -VM $_).Path -like "C:*"
}
Write-Host "VMs to migrate: $($vmsToMigrate.Count)"
foreach ($vm in $vmsToMigrate) {
Write-Host "Migrating $($vm.Name)..."
$newPath = "D:HyperVVMs$($vm.Name)"
New-Item -ItemType Directory -Path $newPath -Force | Out-Null
try {
Move-VMStorage -VMName $vm.Name -DestinationStoragePath $newPath
Write-Host " $($vm.Name) : Migration complete"
} catch {
Write-Host " $($vm.Name) : FAILED - $_"
}
}
# Verify all VMs moved successfully
Get-VM | ForEach-Object {
$disks = Get-VMHardDiskDrive -VM $_
Write-Host "$($_.Name): $($disks.Path -join ', ')"
}
Storage Migration with Maximum Simultaneous Operations
# Check and configure maximum simultaneous storage migrations
Get-VMHost | Select-Object MaximumStorageMigrations
# Set maximum simultaneous storage migrations (default is 2)
# Higher values use more IOPS — set based on storage subsystem capacity
Set-VMHost -MaximumStorageMigrations 4
Move VM Storage When VM is Offline
If the VM is stopped, use Windows file copy or Robocopy for the VHD files, then update the VM configuration to point to the new path:
# For an offline VM, moving storage is faster with a direct file copy
Stop-VM -Name "DatabaseVM" -Force
# Copy VHDX files to new location
$source = "C:HyperVDatabaseVMDatabaseVM.vhdx"
$dest = "D:FastSSDDatabaseVMDatabaseVM.vhdx"
New-Item -ItemType Directory -Path (Split-Path $dest) -Force | Out-Null
Copy-Item -Path $source -Destination $dest -Force
# Update the VM configuration to point to new disk location
$drive = Get-VMHardDiskDrive -VMName "DatabaseVM" | Where-Object Path -eq $source
Set-VMHardDiskDrive -VMName "DatabaseVM" `
-ControllerType $drive.ControllerType `
-ControllerNumber $drive.ControllerNumber `
-ControllerLocation $drive.ControllerLocation `
-Path $dest
# Remove old file after verifying
Remove-Item -Path $source -Force
# Start VM
Start-VM -Name "DatabaseVM"
Monitor Storage Migration Performance
# Monitor storage migration with performance counters
Get-Counter "Hyper-V Virtual Storage Device(*)Bytes Read/sec" -SampleInterval 5 -MaxSamples 5
Get-Counter "Hyper-V Virtual Storage Device(*)Bytes Written/sec" -SampleInterval 5 -MaxSamples 5
# Check disk I/O to identify storage bottlenecks during migration
Get-Counter "PhysicalDisk(*)Disk Bytes/sec" -SampleInterval 5 -MaxSamples 5
# View VM storage migration events in Hyper-V event log
Get-WinEvent -LogName "Microsoft-Windows-Hyper-V-VMMS-Admin" |
Where-Object { $_.Message -match "storage migration" -or $_.Message -match "Move" } |
Select-Object -First 20 TimeCreated, Id, Message | Format-List
Verify Storage Migration Completion
# After migration, verify all VM files are in the expected location
$vm = Get-VM -Name "WebServer01"
Write-Host "Configuration: $($vm.ConfigurationLocation)"
Write-Host "Snapshots: $($vm.SnapshotFileLocation)"
Write-Host "Smart Paging: $($vm.SmartPagingFilePath)"
Get-VMHardDiskDrive -VMName "WebServer01" | ForEach-Object {
Write-Host "Disk: $($_.Path) - Exists: $(Test-Path $_.Path)"
}
Summary
Hyper-V Storage Migration on Windows Server 2019 is a non-disruptive operation that moves VM storage while VMs continue running. It can migrate all VM storage in a single command or target individual VHD files with separate destination paths for each component. For bulk evacuation of a failing disk, a PowerShell loop automates the process across all VMs. The Maximum Storage Migrations setting controls parallelism and should be tuned to match the I/O capacity of both the source and destination storage systems. Always verify VM storage paths after migration completes to confirm all files are at their expected locations.