Hyper-V 虚拟机迁移至移动硬盘实战笔记
目标:把 Hyper-V 上的若干台虚拟机从内置盘迁移到移动硬盘上长期运行,腾出内置盘空间;外置盘按 VM 名分目录组织(与你 D: 现存结构对齐),同时保留"可拔盘带去别的 Win11 复用"的特性
核心思路:用Move-VM一条命令指定每台 VM 的新路径(VHDX / 配置 / 快照三处都自定义),不动.vmcx任何字节,整台 VM 的资产即可在外置盘上重新落位并跑起来
可选副产物:跑完Move-VM后还可再跑一次Export-VM,在外置盘另一路径下生成 Hyper-V 标准便携包——同时拥有一份"插盘即可导入"的全机器版本
工作示例:本文以 7 台 RHEL8.10 集群节点(RHEL8-Node1~RHEL8-Node7)为主线示例,所有步骤对任意 Hyper-V VM 通用
适用系统:Windows 11 / 10 + Hyper-V(Windows Server 同思路);移动硬盘 NTFS 或 exFAT 均可
更新日期:2026-07-31
目录
- 一、本教程解决什么问题
- 二、总体方案:Move-VM + 可选 Export-VM
- 三、准备工作
- 四、Move-VM 操作
- 五、验证
- 六、清理源机器内置盘
- 七、偶尔换机:到新机器上重建 Hyper-V 注册
- 八、移动硬盘使用注意事项
- 九、常见问题排查
- 十、命令速查
- 十一、总结
- 附录:本文工作示例速查(7 台 RHEL8 节点)
一、本教程解决什么问题
把 Hyper-V 虚拟机搬到外置盘上,通常背后是这两类诉求——本教程给出的方案对它们同时覆盖:
| 🎯 诉求 | 📝 说明 |
|---|---|
| 本机磁盘空间紧张 | 内置盘剩余空间告急;让 VM 文件跑到外置盘上、内置盘腾出来 |
| 可换机 / 异地使用 | 拔下盘带到另一台装有 Hyper-V 的 Win11 上插上就能用 |
Move-VM 是 Hyper-V 为"在 Hyper-V 主机内搬迁 VM 资产"设计的官方命令——它能精确指定每台 VM 在新位置的 VHDX / 配置 / 快照路径,且不修改任何 .vmcx 内容(只是更新 Hyper-V 注册里的路径指针)。整台 VM 在外置盘上的最终目录结构可以和原内置盘完全同形,便于排查和维护。
二、总体方案:Move-VM + 可选 Export-VM
主流程就两步:
| 🔢 步骤 | 🛠️ 动作 | 📦 工具 |
|---|---|---|
| ① Move-VM | 把 VM 的 VHDX、配置文件、快照一次性移到外置盘的指定路径;移动完 VM 当场可跑 | Move-VM |
| ② 清理源盘 | 删除内置盘上的旧文件腾出空间 | Remove-Item 文件删除 |
如果还想"换机时即插即用"或者"多一份离线归档",在 ① 之后加一步:
| 🔢 步骤 | 🛠️ 动作 | 📦 工具 |
|---|---|---|
| ③ (可选)Export-VM | 在外置盘另一目录下生成每台 VM 的自包含标准包——本身"插盘即可导入" | Export-VM |
③ 是可选的——
Move-VM后的结构换机时通过Import-VM -Register重建一次即可(多 1 分钟),但不少用户更希望"插盘就能用"。看个人需要。
Move-VM 之后,外置盘上的目录结构长这样
VHDX 部分(你能自定义到这层):
F:\Virtual Hard Disks\
├── RHEL8-Node1\
│ └── RHEL8-Node1.vhdx ← 和你 D: 上的目录结构对齐
├── RHEL8-Node2\
│ └── RHEL8-Node2.vhdx
├── ...
└── RHEL8-Node7\
└── RHEL8-Node7.vhdx
VM 配置 / 快照部分(Hyper-V 固定用 GUID 子目录):
F:\Hyper-V\
├── Virtual Machines\
│ └── <VM的 GUID>\
│ ├── <VM的 GUID>.vmcx ← VM 配置
│ ├── <VM的 GUID>.vmgs ← 虚机密钥
│ └── <VM的 GUID>.VMRS ← 状态文件
└── Snapshots\
└── <快照 GUID>\
└── <快照 GUID>.avhdx ← 快照(每台 VM 自己的子目录)
VHDX 部分按
<VM名>\<VM名>.vhdx排好,是因为这条路径在Move-VM里由你显式指定。
VM 配置 / 快照部分是 Hyper-V 自身用 GUID 命名(但一台 VM 一个 GUID 子目录,本质上还是每台 VM 独占自己的子目录)。
直观对比 D: 上的现状
| 路径类型 | 📍 D: 上原位置 | 🎯 F: 上新位置(Move-VM 后) |
|---|---|---|
| VHDX | D:\Virtual Hard Disks\<VM名>\<VM名>.vhdx |
F:\Virtual Hard Disks\<VM名>\<VM名>.vhdx |
| VM 配置 | D:\Hyper-V\Virtual Machines\<GUID>\*.vmcx |
F:\Hyper-V\Virtual Machines\<GUID>\*.vmcx |
| 快照 | D:\Hyper-V\Snapshots\<GUID>\*.avhdx |
F:\Hyper-V\Snapshots\<GUID>\*.avhdx |
完成 ① ② 之后:内置盘释放几十 GB、外置盘有"按 VM 名分目录"的整洁结构、拔盘到任何 Win11 上 5 分钟内重建注册可用。
三、准备工作
3.1 关停目标 VM
Move-VM 要求 VM 在 Off 状态(Saved 也可)。Running 状态的 VM 必须先关停。
第 1 步:先看清本机有哪些 VM——避免后面 $vmNames 写错名。
# PowerShell(右键以管理员身份运行)
# 名字 + 状态 + 当前 VHD 路径(足够决定怎么 Move-VM)
Get-VM | Format-Table Name, State,
@{N='VHDPath';E={(Get-VMHardDiskDrive -VMName $_.Name).Path}} -AutoSize -Wrap
参考输出(你的实际列表可能不同):
Name State VHDPath
---- ----- -------
RHEL8-Node1 Off D:\Virtual Hard Disks\RHEL8-Node1\RHEL8-Node1.vhdx
...
Win11-Test Off D:\Virtual Hard Disks\Win11-Test.vhdx
虚拟机完成初始化 Off ...
不显示"配置目录"列:Hyper-V 的
ConfigurationLocation属性在本机返回的是"声明路径"(如D:\Hyper-V),跟.vmcx真实位置(D:\Hyper-V\Virtual Machines\<GUID>\)对不上,没参考价值。需要时用 9 章 FAQ 第二条里的命令查。
第 2 步:填好 $vmNames(两种写法选一种):
# 方法 A:手写(适合"我已经知道名字")
$vmNames = @("RHEL8-Node1","RHEL8-Node2","RHEL8-Node3","RHEL8-Node4",
"RHEL8-Node5","RHEL8-Node6","RHEL8-Node7")
# 方法 B:按通配符自动提取(适合"按名字模式批量挑")
# 例:挑出所有 RHEL8-* 命名的 VM
$vmNames = (Get-VM | Where-Object { $_.Name -like "RHEL8-*" }).Name
更多写法见 3.4 节。
第 3 步:看一眼当前状态:
Get-VM -Name $vmNames | Select-Object Name, State | Format-Table -AutoSize
# 一键关停
Get-VM -Name $vmNames | Where-Object State -eq "Running" | Stop-VM -Force
# 再确认一遍
Get-VM -Name $vmNames | Where-Object State -ne "Off" | Select Name, State
最后一条命令没有输出 = 全部 Off,可进入下一步。
3.2 在外置盘上"镜像"出 D: 的目录结构
Move-VM 不会自动在目标位置建中间目录——你要的目标位置必须先建好。
# 外置盘根:本文统一用 F: 作演示;你的移动硬盘是哪个盘符就改哪个
# 三个顶层目录镜像 D: 上的命名
New-Item -ItemType Directory -Path "F:\Virtual Hard Disks" -Force
New-Item -ItemType Directory -Path "F:\Hyper-V\Virtual Machines" -Force
New-Item -ItemType Directory -Path "F:\Hyper-V\Snapshots" -Force
# 为每台待迁 VM 预先建好 VHDX 子目录(Move-VM 不会自动建)
foreach ($name in $vmNames) {
New-Item -ItemType Directory -Path "F:\Virtual Hard Disks\$name" -Force | Out-Null
}
配
Move-VM后 VHDX 会出现在<VM名>\<VM名>.vhdx——子目录是<VM名>\,VHDX 文件名同 VM 名;和你 D: 上的结构一样。
验证目录已建好:
"===== F: 顶层 ====="
Get-ChildItem "F:\" | Where-Object PSIsContainer
"===== VM 子目录 ====="
$vmNames | ForEach-Object {
$exists = Test-Path "F:\Virtual Hard Disks\$_"
" $_ → $exists"
}
3.3 关于快照:先决定保留还是合并
Move-VM 会完整保留所有检查点(.avhdx),快照结构和父 VHDX 一起迁移,不丢不破。
但如果某些快照只是"临时中间态"可以先合并:
| 🛤️ 路线 | 🛠️ 操作 | 🎯 适合 |
|---|---|---|
| A. 带快照 Move(默认) | 直接 Move-VM,快照自动跟着搬 |
检查点里有重要中间状态 |
| B. 先合并再 Move | Hyper-V 管理器 → 选中 VM → 检查点面板 → 选中检查点 → 删除(合并进父 VHDX) | 检查点只是临时态 |
# Move 前列出所有目标 VM 的检查点(看清状态再做决策)
foreach ($name in @("VM1", "VM2", "VM3")) {
Write-Host "==== $name ===="
Get-VMSnapshot -VMName $name | Select Name, SnapshotType, CreationTime | Format-Table -AutoSize
}
3.4 待导出 VM 列表的确定
Move-VM 按 VM 为单位迁移——在写 $vmNames 之前,先看清本机当前 Hyper-V 上有哪些 VM。
第一步:先查(看清本机 VM 列表)
用 3.1 节 第 1 步那个命令直接复用即可:
Get-VM | Format-Table Name, State,
@{N='VHDPath';E={(Get-VMHardDiskDrive -VMName $_.Name).Path}} -AutoSize -Wrap
第二步:筛选(决定要迁哪些)
# 方法 A:手写名单(最适合"我已经知道名字"的情况)
$vmNames = @("RHEL8-Node1","RHEL8-Node2","RHEL8-Node3","RHEL8-Node4",
"RHEL8-Node5","RHEL8-Node6","RHEL8-Node7")
# 方法 B:从全量列表按通配符筛(适合"知道名字模式"的情况)
$vmNames = (Get-VM | Where-Object Name -like "RHEL8-Node*").Name
# 方法 C:组合(手动 + 筛选)
$vmNames = @("Win11-Test") + (Get-VM | Where-Object Name -like "RHEL8-Node*").Name
# 方法 D:迁移所有 Off 状态的 VM(适合"先一股脑都带走,到新机器再删")
$vmNames = (Get-VM | Where-Object State -eq "Off").Name
# 排除不要的(比如一堆中文命名的测试 VM):
$vmNames = (Get-VM | Where-Object Name -notmatch "^[一-龥]" -and State -eq "Off").Name
第三步:确认($vmNames 是否符合预期)
# 看看最终决定要导哪些 VM
"===== 待迁移 VM 列表($($vmNames.Count) 台) ====="
$vmNames | ForEach-Object { " $_" }
""
# 对比"Get-VM -Name $vmNames"匹配到的实际数量
"===== Get-VM 匹配核验 ====="
$matched = Get-VM -Name $vmNames
$matched | Select-Object Name, State | Format-Table -AutoSize
""
"匹配数:$($matched.Count) / $vmNames.Count"
if ($matched.Count -ne $vmNames.Count) {
Write-Warning "匹配数和列表数量不一致,请检查写错的 VM 名" -WarningAction Continue
}
同名 VM 冲突:如果目标机器(换机场景)已经有同名 VM 在跑,迁过去后需
Import-VM -GenerateNewId拿新 GUID 避免冲突。
是否每台都要迁?
源机器上若有"克隆自同一母版的多台 VM"(如本文的 7 台 RHEL8 节点全部由 Node1 克隆),存在权衡:
| 🔢 数量 | 💰 外置盘占用 | 🎯 评估 |
|---|---|---|
| 每台全 Move(推荐先这样做) | N × 单台 VHDX | 每台差异化状态都保留;适合"已经按节点装好不同应用"的场景 |
| 只 Move 母版 + 新机器上克隆 | 1 × 单台 VHDX | 仅适合源机器还没做差异化部署 |
本文示例的 7 台 RHEL8 节点默认全 Move——按 RHEL 8.10 集群笔记规划,Node3-5 装 MongoDB、Node1-2 装 Tomcat、Node6-7 装 Nginx,差异化部署后每台不可重建,必须全 Move。
3.5 盘符策略:提前想好跨机器的稳定性
核心难点:VM 配置里写死的 VHDX 路径包含盘符。移动硬盘在不同机器上盘符可能不一样(从 F: 变 G:),VM 注册的引用就失效了。
推荐做法:所有目标机器上手动固定盘符。
- 右键"此电脑" → 管理 → 磁盘管理
- 右键移动硬盘的分区 → 更改驱动器号和路径 → 更改
- 选
F:(或你定的固定盘符)→ 确定
简单粗暴,但每次插新机器都要手动分配一次。如果只在固定 2-3 台机器间切换,最省事。
本文统一以F:为例;你的实际盘符把脚本里的F:整体替换为你的盘符即可。
四、Move-VMStorage 操作
本章使用的 cmdlet 是
Move-VMStorage,不是Move-VM。
Move-VM设计上是给"跨主机"搬迁用的——同主机搬迁 VHDX 时即使用-DestinationHost $env:COMPUTERNAME也无法绕过 VMMS 兼容性校验,会报"序列不包含任何匹配元素"。Move-VMStorage是为同主机搬迁设计的官方 cmdlet,没有这个问题,且会自动改写.vmcx里的 VHDX 路径引用。
4.1 单台 Move-VMStorage 命令模板
前置:4.5 节 已合并完所有检查点(VHDX 是 <VM名>.vhdx 无 GUID 后缀)。
以 RHEL8-Node1 为例,用三参数组合精确控制目标路径:
$name = "RHEL8-Node1"
# 当前 VHDX 在 D: 上的真实路径(脚本里读出来,避免硬编码)
$vhdCurrent = (Get-VMHardDiskDrive -VMName $name).Path
Write-Host "源: $vhdCurrent"
# F: 顶层建目标子目录
New-Item -ItemType Directory -Path "F:\Virtual Hard Disks\$name" -Force | Out-Null
# 三参数分开指定:VHDX 平铺 + 配置/快照走标准位置
# 注意:-VirtualMachinePath / -SnapshotFilePath 参数**不要加** `Virtual Machines\` / `Snapshots\` 后缀
# Move-VMStorage 会自动追加这两个子目录——详见下面的"Magic 子目录"说明
Move-VMStorage -VMName $name `
-Vhds @(@{
SourceFilePath = $vhdCurrent
DestinationFilePath = "F:\Virtual Hard Disks\$name\$name.vhdx"
}) `
-VirtualMachinePath "F:\Hyper-V" `
-SnapshotFilePath "F:\Hyper-V"
| 参数 | 📝 含义 |
|---|---|
-VMName |
目标 VM 名 |
-Vhds @{...} |
hashtable 精确指定每块磁盘的源/目标路径。源和目标文件名必须一致——否则报"源文件名和目标文件名必须匹配" |
-VirtualMachinePath |
.vmcx / .vmgs / .VMRS 新位置(Hyper-V 自动在该目录下建 <GUID>\ 子目录) |
-SnapshotFilePath |
.avhdx 新位置(每个快照一个 <GUID>\ 子目录) |
Move-VMStorage 完成后,外置盘上的实际结构是:
F:\
├── Virtual Hard Disks\
│ ├── RHEL8-Node1\
│ │ └── RHEL8-Node1.vhdx
│ ├── RHEL8-Node2\
│ │ └── RHEL8-Node2.vhdx
│ └── ...
└── Hyper-V\
├── Virtual Machines\
│ └── <VM 的 GUID>\
│ ├── <VM 的 GUID>.vmcx
│ ├── <VM 的 GUID>.vmgs
│ └── <VM 的 GUID>.VMRS
└── Snapshots\
└── <快照 GUID>\
└── <快照 GUID>.avhdx
这正是 D: 上的目录结构镜像——
Virtual Hard Disks\<VM名>\<VM名>.vhdx平铺,加上Hyper-V\Virtual Machines\<GUID>\*.vmcx与Hyper-V\Snapshots\<GUID>\*.avhdx三个独立的顶层位置。
单台验证:
Get-VM -Name "RHEL8-Node1" | ForEach-Object {
[PSCustomObject]@{
VM = $_.Name
VHD = (Get-VMHardDiskDrive -VMName $_.Name).Path
}
}
期望 VHD = F:\Virtual Hard Disks\RHEL8-Node1\RHEL8-Node1.vhdx。
启动一台验证:
Start-VM -Name "RHEL8-Node1"
Start-Sleep 30
Get-VM -Name "RHEL8-Node1" | Select-Object Name, State
State 应显示 Running。
不用 -Vhds hashtable 的简化版(结构会嵌套一层)
如果不在意中间多一层 Virtual Hard Disks\(即 VHDX 最终路径是 F:\Virtual Hard Disks\<VM名>\Virtual Hard Disks\<VM名>.vhdx),可以用更简单的写法:
Move-VMStorage -VMName "RHEL8-Node1" `
-DestinationStoragePath "F:\Virtual Hard Disks"
但这不是你想要的镜像 D: 结构。本文主推 -Vhds hashtable 写法。
4.2 批量 Move-VMStorage 脚本(推荐)
前置:先合并 7 台快照。
$vmNames = @(
"RHEL8-Node1","RHEL8-Node2","RHEL8-Node3","RHEL8-Node4",
"RHEL8-Node5","RHEL8-Node6","RHEL8-Node7"
)
$externalDrive = "F:" # ← 你的移动硬盘盘符
foreach ($name in $vmNames) {
$start = Get-Date
Write-Host "==== 迁移:$name ====" -ForegroundColor Cyan
$vhdCurrent = (Get-VMHardDiskDrive -VMName $name).Path
# 没合并好的跳过,避免文件名不匹配报错
if ($vhdCurrent -notlike "*\$name.vhdx") {
Write-Warning "$name VHDX 还没合并好($vhdCurrent),跳过——先去 [4.5 节](#sec45) 合并" -WarningAction Continue
continue
}
New-Item -ItemType Directory -Path "$externalDrive\Virtual Hard Disks\$name" -Force | Out-Null
Move-VMStorage -VMName $name `
-Vhds @(@{
SourceFilePath = $vhdCurrent
DestinationFilePath = "$externalDrive\Virtual Hard Disks\$name\$name.vhdx"
}) `
-VirtualMachinePath "$externalDrive\Hyper-V" `
-SnapshotFilePath "$externalDrive\Hyper-V" `
-ErrorAction Stop
# Move-VMStorage 每次会自动创建一个 saved state 检查点
# 删掉它,让 VHDX 变回干净的 .vhdx
Remove-VMSnapshot -VMName $name -Confirm:$false
Write-Host " ✅ 完成($((Get-Date) - $start).ToString('mm\:ss'))" -ForegroundColor Green
}
每台 12 GB VHDX 在 USB 3.0 上约 3-5 分钟;7 台整体约 25-40 分钟。不要中途拔盘。
反向:从 F: 迁回 D:(用上面的脚本改四行即可)
正向脚本只改四处就能反向——把 SourceFilePath 的 D: 路径去掉(脚本里自动读)、DestinationFilePath 改 D:、-VirtualMachinePath 改 D:\Hyper-V、-SnapshotFilePath 改 D:\Hyper-V:
$vmNames = "RHEL8-Node1","RHEL8-Node2","RHEL8-Node3","RHEL8-Node4",
"RHEL8-Node5","RHEL8-Node6","RHEL8-Node7"
foreach ($name in $vmNames) {
$start = Get-Date
Write-Host "==== 反向迁回:$name ====" -ForegroundColor Cyan
$vhdCurrent = (Get-VMHardDiskDrive -VMName $name).Path
if ($vhdCurrent -notlike "*\$name.vhdx") {
Write-Warning "$name VHDX 不干净($vhdCurrent),跳过——先合并 saved state" -WarningAction Continue
continue
}
# 关停
Stop-VM -Name $name -Force -ErrorAction SilentlyContinue
# D: 顶层建目标子目录
New-Item -ItemType Directory -Path "D:\Virtual Hard Disks\$name" -Force | Out-Null
# 反向 Move-VMStorage(参数缩短一层 + 目标路径反过来)
Move-VMStorage -VMName $name `
-Vhds @(@{
SourceFilePath = $vhdCurrent
DestinationFilePath = "D:\Virtual Hard Disks\$name\$name.vhdx"
}) `
-VirtualMachinePath "D:\Hyper-V" `
-SnapshotFilePath "D:\Hyper-V" `
-ErrorAction Stop
# 删 Move-VMStorage 自带的 saved state
Remove-VMSnapshot -VMName $name -Confirm:$false
Write-Host " ✅ 完成($((Get-Date) - $start).ToString('mm\:ss')))" -ForegroundColor Green
}
Move-VMStorage 的"Magic 子目录"行为
Move-VMStorage 的 -VirtualMachinePath 和 -SnapshotFilePath 参数会自动追加子目录:
| 参数值 | 实际生成的目录 |
|---|---|
-VirtualMachinePath "F:\Hyper-V" |
F:\Hyper-V\Virtual Machines\<VM的 GUID>\*.vmcx |
-VirtualMachinePath "F:\Hyper-V\Virtual Machines" |
F:\Hyper-V\Virtual Machines\Virtual Machines\<VM的 GUID>\*.vmcx ← 多一层 |
-SnapshotFilePath "F:\Hyper-V" |
F:\Hyper-V\Snapshots\<快照 GUID>\*.avhdx |
不要在参数里加
Virtual Machines\或Snapshots\后缀——这是文档早期版本的一个坑,会导致F:\Hyper-V\Virtual Machines\Virtual Machines\<GUID>\这种嵌套结构。
Move-VMStorage 的 saved state 副作用
每次跑 Move-VMStorage 都会自动创建一个 saved state 检查点(为了搬迁过程的数据安全),导致 VHDX 立刻变成 <VM名>_<GUID>.avhdx。
每次 Move-VMStorage 之后必须删 saved state,VHDX 才回到 <VM名>.vhdx:
Remove-VMSnapshot -VMName "X" -Confirm:$false
4.3 Hyper-V 管理器方式
Hyper-V 管理器 → 选中 VM → 右侧"操作" → 移动...
| 步骤 | ✅ 选择 |
|---|---|
| 1 | 移动虚拟机的所有数据 |
| 2 | 路径 1(VM 配置):F:\Hyper-V\ ← 不加 Virtual Machines\ 后缀(Move-VMStorage 会自动加) |
| 3 | 路径 2(快照):F:\Hyper-V\ ← 不加 Snapshots\ 后缀 |
| 4 | 路径 3(VHDX):F:\Virtual Hard Disks\<VM名>\ |
| 5 | 摘要核对 → 完成 |
GUI 对单台直观,但 7+ 台逐台点效率低,PowerShell 批量脚本更适合。
4.4 (可选)再跑一次 Export-VM 生成便携归档
这一步可选——只为"换机时插盘就能用"加一份 Hyper-V 标准便携包。
你想等换机时再Import-VM -Register重建注册(多 1 分钟),可以跳过。
$vmNames = @(
"RHEL8-Node1","RHEL8-Node2","RHEL8-Node3","RHEL8-Node4",
"RHEL8-Node5","RHEL8-Node6","RHEL8-Node7"
)
$archiveRoot = "F:\Hyper-V-Archive" # 与 Move-VM 落地的 F:\Hyper-V\ 平级,互不干扰
New-Item -ItemType Directory -Path $archiveRoot -Force | Out-Null
foreach ($name in $vmNames) {
$start = Get-Date
Write-Host "==== 导出便携包:$name ====" -ForegroundColor Cyan
# 注意:Export-VM 永远从当前 Hyper-V 注册的路径(即 F: 上 Move-VM 后的位置)出发
Export-VM -Name $name -Path $archiveRoot -ErrorAction Stop
Write-Host "==== 完成:$name($((Get-Date) - $start).ToString('mm\:ss')))====" -ForegroundColor Green
}
双产物目录对照:
| 📦 类型 | 📁 路径 | ⚡ 作用 |
|---|---|---|
Move-VM 落地(活存储) |
F:\Hyper-V\ + F:\Virtual Hard Disks\ |
当前机器 Hyper-V 直接跑 |
Export-VM 便携包 |
F:\Hyper-V-Archive\ |
换机时复制即导入,无需再重新导出 |
这一步会再花 25-40 分钟复制一份。要不要做取决于你"对插盘即用的需求有多强"。
4.5 关键前置:合并检查点后再 Move
这一步默认强烈推荐先做——
Move-VM跑之前,所有待迁 VM 应当处于"快照全部合并回主磁盘"的干净状态。
如果 VM 还有活动检查点(.avhdx),Move-VM仍然能迁,但搬过去的磁盘是"快照态"而不是"主态"——迁移后磁盘文件命名也不直观。
为什么需要合并
Get-VMHardDiskDrive 返回的是 VM 当前活动磁盘的路径——如果 VM 有活动检查点,这个路径会指向 .avhdx(带 GUID 后缀的快照磁盘),而不是 <VM名>.vhdx(主磁盘)。这两种状态的差别:
| 📊 状态 | 🛠️ 路径形态 | 📝 Move-VM 后效果 |
|---|---|---|
| 已合并 | D:\Virtual Hard Disks\<VM名>\<VM名>.vhdx |
迁移过去是干净的"主磁盘",文件名直观 |
| 有快照 | D:\Virtual Hard Disks\<VM名>\<VM名>_<GUID>.avhdx |
迁移过去是"快照态磁盘",主磁盘 .vhdx 留在 D: 成为孤儿 |
检查当前快照状态
# 列出所有目标 VM 当前的"活动磁盘路径"
Get-VM | Where-Object Name -in $vmNames |
Select-Object Name,
@{N='CurrentDisk';E={(Get-VMHardDiskDrive -VMName $_.Name).Path}} |
Format-Table -AutoSize -Wrap
如果 CurrentDisk 列含 <VM名>_<GUID>.avhdx(带 GUID 后缀的 .avhdx)——需要先合并。
合并检查点
# 直接按 VMName 删——不用 Get-VMSnapshot 枚举,避开 pipeline quirk
foreach ($name in $vmNames) {
Write-Host "==== $name:合并检查点 ====" -ForegroundColor Yellow
Remove-VMSnapshot -VMName $name -Confirm:$false
}
PowerShell 提示:
Get-VMSnapshot -VMName "X"在某些环境下 pipeline 行为异常——返回其他 VM 的快照或报"通配符模式无效"。最稳的是直接给Remove-VMSnapshot -VMName X不带-Name参数——-Name默认通配,删除该 VM 的所有 saved state。
合并是 Hyper-V 后台异步操作,不要立即 Move-VM,先等合并完成。
等待合并完成
# 方法 1:粗略轮询(VM State 会在合并中短暂变为 Merging / Saving)
while ($true) {
$inProgress = Get-VM | Where-Object State -in "Merging", "Saving", "Stopping"
if (-not $inProgress) { break }
Write-Host "等待合并完成:$($inProgress.Name -join ', ')"
Start-Sleep 30
}
Write-Host "所有合并完成 ✅" -ForegroundColor Green
# 方法 2:看事件日志确认合并完成
Get-WinEvent -LogName "Microsoft-Windows-Hyper-V-VMMS-Admin" -MaxEvents 200 |
Where-Object { $_.Message -match 'merged|merge' } |
Select-Object TimeCreated, Message | Format-List
验证合并结果
# 期望:每台 VM 的活动磁盘都回到 "<VM名>.vhdx"(不带 GUID 后缀)
Get-VM | Where-Object Name -in $vmNames |
Select-Object Name,
@{N='CurrentDisk';E={(Get-VMHardDiskDrive -VMName $_.Name).Path}} |
Format-Table -AutoSize -Wrap
✅ 看到 CurrentDisk = D:\Virtual Hard Disks\<VM名>\<VM名>.vhdx 即可以进入 4.1 节 / 4.2 节 跑 Move-VM。
不要跳过这一步直接 Move-VMStorage——
Move-VMStorage -Vhdshashtable 要求源/目标文件名完全一致(否则报"源文件名和目标文件名必须匹配")。如果源 VHDX 是<VM名>_<GUID>.avhdx,目标路径写<VM名>.vhdx就会报错。
五、验证
5.1 确认所有路径已迁移到外置盘
Get-VM | Where-Object Name -in $vmNames | ForEach-Object {
[PSCustomObject]@{
VM = $_.Name
VHD = (Get-VMHardDiskDrive -VMName $_.Name).Path
}
} | Format-Table -AutoSize
期望:
| 列 | 期望内容 |
|---|---|
Config |
全是 F:\Hyper-V\Virtual Machines |
VHD |
全是 F:\Virtual Hard Disks\<VM名>\<VM名>.vhdx |
任一台 VHD 还显示 D::回去对 4.1 节 检查 -DestinationStoragePath 写对,或源 VM 是否真的 Off。
5.2 启动一台 VM 验证
Start-VM -Name "RHEL8-Node1"
Start-Sleep 30
Get-VM -Name "RHEL8-Node1" | Select-Object Name, State
State 应显示 Running。VM 内核对一下:
# Linux VM
ip addr show eth0 # 应有原配置 IP(如 192.168.100.101)
lsblk # 磁盘标识不变
# Windows VM
Get-Disk
Get-NetIPAddress
跑通一台再批量启动剩余 VM:
Get-VM -Name $vmNames | Where-Object State -eq "Off" | Start-VM
六、清理源机器内置盘
先验证 第 5 章 全部通过、所有 VM 跑起来都没问题,再删源机器上的旧文件。
这一步决定 D: 上的空间能不能真的腾出来。
# 1. 再次确认:要删的 VM 的 VHDX 已在 F:(不在 D:)
foreach ($name in $vmNames) {
$vhd = (Get-VMHardDiskDrive -VMName $name).Path
$onE = $vhd -like "F:\*"
" $name → VHD 在 F: = $onE ($vhd)"
}
# 2. 关闭源机器上的所有 VM(防止删注册时报 in use)
Get-VM -Name $vmNames | Stop-VM -Force
# 3. 删除源机器上的 VM 注册(仅删 D: 上的那份,不会影响外置盘上 Move-VM 的注册)
# 注意:因为 Move-VM 在当前机器上"重新注册"了 VM,注册只此一份,路径已是 F:
# 所以此时 Get-VM 看到的"原始 VM 注册"其实就是 F: 上的注册,**不能直接 Remove-VM**
#
# 实际做法:源注册 = F: 上的注册,**你不需要 Remove-VM**。直接跳到删文件即可。
# Hyper-V 看到 VHDX 在 F:,注册也指向 F:,移除 D: 文件不影响 VM 运行。
# 4. 删除 D: 上的 VM 配置目录
Remove-Item -Recurse -Force "D:\Hyper-V\Virtual Machines\*"
# 5. 删除 D: 上的快照目录
Remove-Item -Recurse -Force "D:\Hyper-V\Snapshots\*"
# 6. 删除 D: 上的 VHDX
foreach ($name in $vmNames) {
Remove-Item -Recurse -Force "D:\Virtual Hard Disks\$name"
}
验证 D: 已腾出空间:
Get-ChildItem "D:\Hyper-V\Virtual Machines" -ErrorAction SilentlyContinue
Get-ChildItem "D:\Virtual Hard Disks" -ErrorAction SilentlyContinue
两条命令都没有输出(或只剩 D:\ISOs\ 不需要迁的目录)= 内置盘已释放。
⚠️ 重要:Move-VM 后源机器 Hyper-V 注册直接指向 F:,不需要
Remove-VM。D: 上残留的配置文件和 VHDX 已经是"垃圾",可以安全删除。
如果发现Remove-Item报"文件被占用",那是源机器快照还残留——回 3.3 节 把快照先合并。
七、偶尔换机:到新机器上重建 Hyper-V 注册
Move-VM落地后的结构不是 Hyper-V 标准 Export 包——它在 F: 上放着"漂亮的分目录"VM 资产,但每台 VM 的 Hyper-V 注册还留在当前机器上。
换到新机器时,必须通过Import-VM -Register让新机器"认识"这些 VM。
7.1 把盘带过去 + 固定盘符
7.2 Import-VM -Register 重建注册
新机器上首次操作:先确认 Hyper-V 角色已启用,然后用 PowerShell(管理员)跑:
$vmsPath = "F:\Hyper-V\Virtual Machines"
# 收齐所有 .vmcx,遍历注册
Get-ChildItem $vmsPath -Directory | ForEach-Object {
$vmcx = Get-ChildItem $_.FullName -Filter "*.vmcx" | Select-Object -First 1
if ($vmcx) {
Import-VM -Path $vmcx.FullName -Register -VirtualMachinePath $vmsPath
Write-Host "已注册:$($vmcx.BaseName)"
}
}
⚠️ 上面的
Get-ChildItem -First 1是简化演示——多台 VM 时按"每台 VM 的 GUID 子目录"配对注册,更精确。
更精确方式:在源机器上跑 Get-VM | Where Name -in $vmNames | Select Id, Name,把每台 VM 的 GUID 记下来带走;换机后按 GUID 找对应 .vmcx。
# 源机器:记 GUID
"GUID 列表"
Get-VM | Where-Object Name -in $vmNames | Select-Object Id, Name | Format-Table -AutoSize
# 新机器:按 GUID 找 .vmcx 注册
foreach ($vmId in @("GUID1","GUID2","GUID3")) {
$vmcx = Get-ChildItem "F:\Hyper-V\Virtual Machines\$vmId" -Filter "$vmId.vmcx" -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($vmcx) {
Import-VM -Path $vmcx.FullName -Register -VirtualMachinePath $vmsPath
}
}
如果新机器已经在跑同名 VM:
# 用新 GUID 导入(避免冲突)
Import-VM -Path $vmcx.FullName -Register -GenerateNewId -VirtualMachinePath $vmsPath
# 或先删同名 VM(如果新机器上同名 VM 不重要)
Get-VM -Name "VM1" | Remove-VM -Force
Import-VM -Path $vmcx.FullName -Register -VirtualMachinePath $vmsPath
7.3 启动验证 + 网络收尾
Start-VM -Name "RHEL8-Node1"
Get-VM -Name "RHEL8-Node1"
# State 应显示 Running
网络收尾(跟 Export-VM 流程同样要做):
| 🛠️ 现象 | 📝 处理 |
|---|---|
| "找不到虚拟交换机 InternalSwitch" | 新机器 Hyper-V 管理器建同名交换机 |
| VM 网卡 "Identifying..." 持续数十秒 | 正常:Hyper-V 在重新生成虚拟 MAC |
| VM 内网络配置丢失 / 上不了网 | 重建宿主侧 InternalSwitch + NAT(参考本机原 Hyper-V 集群笔记) |
| 7 台 VM 启动后 IP 冲突 / 网关不通 | 宿主 NAT 没在新机器复刻,按集群笔记复刻 |
具体的虚拟交换机、NAT、nmcli reinit 等步骤在原 RHEL 8.10 安装笔记的第五章有完整流程,新机器上同样要走一遍。
八、移动硬盘使用注意事项
| ⚠️ 风险 | 📝 说明 | 💡 缓解 |
|---|---|---|
| 活外置化后拔出 = VM 立即丢失 | 第六章 完成后,本机 VM 全跑在外置盘上;拔盘当机(数据可能损坏) | 日常使用盘不要拔;每次拔盘前 Stop-VM -Force 所有 VM |
| Move-VM 过程中拔出 | USB 突然断电,正在写入的 VHDX 可能损坏 | Move-VM 期间盘不动 |
| 机械硬盘掉盘 | 老式移动硬盘 USB 取电不足,重负载时可能识别失败 | Y 线或独立供电硬盘盒;带盘换机器前先 chkdsk F: /f |
| 不同机器盘符变化 | 拔插顺序变了,盘符从 F: 变 G: | 用 3.5 节 方案——每台机器手动固定 F: |
| 速度:移动硬盘比内置 NVMe 慢 | USB 3.0 HDD 写盘 ~30-80 MB/s,内置 NVMe 可达 3000 MB/s | I/O 密集 VM(数据库、备份)仍跑外置盘;性能敏感可改外置 SSD |
| 碎片化 | 移动硬盘上的大文件长期写入会比较碎 | NTFS 移动硬盘定期碎片整理;exFAT 不需要 |
| 导出包 / 数据完整性 | 80-100 GB 数据复制中途可能出错 | Move-VM 后做 第五章 验证;另一块盘同步 robocopy /MIR 备份 |
文件系统选择:exFAT 跨平台好;NTFS 支持稀疏文件、ACL、VHDX 元数据略快。
新出厂移动硬盘通常是 exFAT——不必为了迁 VM 重格式化。
九、常见问题排查
| ❌ 问题 | 🔍 原因 | 🛠️ 解决 |
|---|---|---|
Move-VM 报 "VM is not in Off state" |
还有 VM 没关停 | 见 3.1 节;逐台 Stop-VM -Force |
Move-VM 报 "Insufficient permissions" |
PowerShell 没以管理员身份运行 | 重开 PowerShell(右键 → "以管理员身份运行") |
Move-VM 报 "Path not found" |
目标子目录 <VM名>\ 没建 |
见 3.2 节,先 New-Item |
Move-VM 报 "File already exists" |
目标路径已有同名文件 | 删除目标文件再 Move;或换一个目标文件名 |
| Move-VM 后某台 VM 还是 Off 看不到路径变更 | -DestinationStoragePath 路径写错(含空格 / 大小写不对) |
对照 VM 设置 → 硬盘里的真实 VHD 路径修正 |
| Move-VM 卡住或超时 | USB 中途断开 / 移动硬盘 IO 抖动 | 别动;30 分钟以上无进展可 Ctrl+C 终止重试 |
| 启动 VM 报错"虚拟硬盘找不到" | 跨机盘符变化 | 见 3.5 节;在新机器固定盘符 |
启动时报"找不到虚拟交换机 XXX" |
新机器没建同名虚拟交换机 | 在新机器 Hyper-V 管理器建同名交换机 |
Import-VM -Register 报 "VM with same name already exists" |
新机器同名 VM 已在注册 | 见 7.2 节,GenerateNewId 或先删再导入 |
| 7 台 VM 启动后 IP 冲突 / 网关不通 | 原机器 NAT / 虚拟交换机没在新机器复刻 | 按本机原 Hyper-V 集群笔记复刻 InternalSwitch + 宿主 NAT |
| Move-VM 后 D: 上的文件删不掉,报"in use" | 源机器快照还残留 / VM 还在运行 | Stop-VM -Force 全部 → 重试;先合并快照再删 |
| 想把已经在 F: 上的 VM 移回 D: | 活外置化的"反向操作" | 用 4.2 节 末尾的反向脚本——把 4 个路径参数里的 F: 换成 D: 即可 |
| 想"在线 Move 不停 VM" | Move-VM 不支持 |
把 VM 短暂 Off(维护窗口内);或用 Hyper-V 复制(Replica)替代 |
| 想"换机器插盘即用,不要 5 分钟 Import" | Move-VM 模式做不到"零操作插盘" | 跑 4.4 节 加做一次 Export-VM,便携包可零操作导入 |
Get-VM | Select ConfigurationLocation 返回的路径和真实 .vmcx 位置对不上(如显示 D:\Hyper-V 但实际在 D:\Hyper-V\Virtual Machines\<GUID>\) |
ConfigurationLocation 在自定义默认 VM 路径后,会显示 Hyper-V 设置中的"声明位置"而不是 .vmcx 真实位置 |
不要把 ConfigurationLocation 当真实路径用。真实位置用 Get-ChildItem "D:\Hyper-V\Virtual Machines" -Recurse -Filter "*.vmcx" 找;Move-VM 也用 -VirtualMachinePath 参数指定目标,不需要读这个属性 |
Get-VMHardDiskDrive 返回的是 .avhdx 而不是 .vhdx |
VM 当前正指向"快照磁盘"(活动检查点未合并) | 跑 4.5 节 合并检查点;或脚本里把源路径改为 (Get-VMHardDiskDrive -VMName $name).Path 动态拿 |
Move-VM 报 AmbiguousParameterSet 或 序列不包含任何匹配元素 |
Move-VM 设计上只服务"跨主机搬迁"——同主机场景(包括加 -DestinationHost $env:COMPUTERNAME)都会触发问题;前者是参数集挑不出来,后者是 VMMS 兼容性校验的内部异常 |
改用 Move-VMStorage——专为同主机搬迁设计,自动改写 .vmcx 里的 VHDX 路径引用,没有上述问题。例:Move-VMStorage -VMName "X" -DestinationStoragePath "F:\..." |
Move-VMStorage -Vhds 报"源文件名和目标文件名必须匹配" |
-Vhds hashtable 要求源/目标绝对路径里文件名部分完全一致——例如源 RHEL8-Node1.vhdx 目标也得是 RHEL8-Node1.vhdx,不能改名 |
先把 VHDX 路径里文件名搞清楚;如果源是 <VM名>_<GUID>.avhdx(快照未合并),先跑 4.5 节 合并成 <VM名>.vhdx 再迁 |
跑完 Move-VMStorage 后,F:\Hyper-V\Virtual Machines\Virtual Machines\<GUID>\*.vmcx(多一层 Virtual Machines\Virtual Machines\) |
-VirtualMachinePath 参数不要带 Virtual Machines\ 后缀——Move-VMStorage 会自动追加。Snapshots 同理 |
参数改成 -VirtualMachinePath "F:\Hyper-V"(不带后缀);快照改成 -SnapshotFilePath "F:\Hyper-V" |
跑完 Move-VMStorage 后,VHDX 立刻变成 <VM名>_<GUID>.avhdx(带 GUID 后缀) |
Move-VMStorage 自动创建一个 saved state 检查点保证搬迁事务安全——副作用是 VHDX 变成快照态 |
每次跑完 Move-VMStorage 立刻删 saved state:Remove-VMSnapshot -VMName X -Confirm:$false(不带 -Name 参数,让 cmdlet 用默认通配) |
十、命令速查
10.1 全流程("开箱即用"模板)
保存为 migrate-vms-to-usb.ps1,按需修改 $vmNames 和 $externalDrive:
# ===== 0. 先看清本机有哪些 VM(决定 $vmNames 怎么写) =====
Get-VM | Format-Table Name, State,
@{N='VHDPath';E={(Get-VMHardDiskDrive -VMName $_.Name).Path}} -AutoSize -Wrap
# ===== 1. 配置 =====
# $vmNames 两种写法选一种:
# 方法 A:手写名单(适合"我已经知道名字")
# 方法 B:按通配符自动提取(适合"按名字模式批量挑")
# 方法 A 写法:
# $vmNames = @("RHEL8-Node1","RHEL8-Node2","RHEL8-Node3","RHEL8-Node4",
# "RHEL8-Node5","RHEL8-Node6","RHEL8-Node7")
# 方法 B 写法(按名字模式自动挑):
$vmNames = (Get-VM | Where-Object { $_.Name -like "RHEL8-*" }).Name
$externalDrive = "F:" # ← 移动硬盘盘符(见 3.5 节)
# ===== 1.5 确认 $vmNames 与 Get-VM 一致 =====
$matched = Get-VM -Name $vmNames
""
"匹配数:$($matched.Count) / $($vmNames.Count)"
if ($matched.Count -ne $vmNames.Count) {
Write-Warning "数量不一致!请回 3.4 节检查写错的 VM 名" -WarningAction Continue
return
}
# ===== 2. 关停所有目标 VM =====
Get-VM -Name $vmNames | Where-Object State -eq "Running" | Stop-VM -Force
# ===== 3. 合并所有快照([4.5 节](#sec45))=====
# 必备前置:VHDX 必须是 "<VM名>.vhdx" 无 GUID 后缀
foreach ($name in $vmNames) {
Remove-VMSnapshot -VMName $name -Confirm:$false
}
Write-Host "快照合并已发起,等合并完成再继续(看 Hyper-V 管理器)"
Write-Host "Press Enter when ready..." -ForegroundColor Yellow
Read-Host
# ===== 4. Move-VMStorage 批量:三参数分开指定 =====
foreach ($name in $vmNames) {
$start = Get-Date
Write-Host "==== 迁移:$name ====" -ForegroundColor Cyan
$vhdCurrent = (Get-VMHardDiskDrive -VMName $name).Path
# 没合并好的跳过,避免 Move-VMStorage -Vhds 报"文件名不匹配"
if ($vhdCurrent -notlike "*\$name.vhdx") {
Write-Warning "$name VHDX 还没合并好($vhdCurrent),跳过——回 [4.5 节](#sec45)" -WarningAction Continue
continue
}
# F: 顶层建目标子目录
New-Item -ItemType Directory -Path "$externalDrive\Virtual Hard Disks\$name" -Force | Out-Null
Move-VMStorage -VMName $name `
-Vhds @(@{
SourceFilePath = $vhdCurrent
DestinationFilePath = "$externalDrive\Virtual Hard Disks\$name\$name.vhdx"
}) `
-VirtualMachinePath "$externalDrive\Hyper-V" `
-SnapshotFilePath "$externalDrive\Hyper-V" `
-ErrorAction Stop
# Move-VMStorage 自动创建 saved state,删掉它让 VHDX 回到干净的 .vhdx
Remove-VMSnapshot -VMName $name -Confirm:$false
Write-Host " ✅ 完成($((Get-Date) - $start).ToString('mm\:ss'))" -ForegroundColor Green
}
# ===== 5. 验证所有 VM 路径都在 F: =====
Get-VM | Where-Object Name -in $vmNames | ForEach-Object {
[PSCustomObject]@{
VM = $_.Name
VHD = (Get-VMHardDiskDrive -VMName $_.Name).Path
}
} | Format-Table -AutoSize
# ===== 6. 启动一台验证 =====
Start-VM -Name $vmNames[0]
Start-Sleep 30
Get-VM -Name $vmNames[0] | Select-Object Name, State
Write-Host "`n✅ Move-VMStorage 完成。下一阶段:'" -ForegroundColor Green -NoNewline
Write-Host "第六章清理源盘" -ForegroundColor Yellow -NoNewline
Write-Host "' 或 '" -ForegroundColor Green -NoNewline
Write-Host "第七章换机" -ForegroundColor Yellow -NoNewline
Write-Host "'。" -ForegroundColor Green
10.2 反向 Move-VMStorage:从 F: 移回 D: (如需)
$vmNames = @("VM1", "VM2", "VM3")
foreach ($name in $vmNames) {
$vhdCurrent = (Get-VMHardDiskDrive -VMName $name).Path
Get-VM -Name $name | Stop-VM -Force
Move-VMStorage -VMName $name `
-Vhds @(@{
SourceFilePath = $vhdCurrent
DestinationFilePath = "D:\Virtual Hard Disks\$name\$name.vhdx"
}) `
-VirtualMachinePath "D:\Hyper-V" `
-SnapshotFilePath "D:\Hyper-V"
}
10.3 新机器重建注册
$vmsPath = "F:\Hyper-V\Virtual Machines"
# 收齐所有 .vmcx,遍历注册
Get-ChildItem $vmsPath -Directory | ForEach-Object {
$vmcx = Get-ChildItem $_.FullName -Filter "*.vmcx" | Select-Object -First 1
if ($vmcx) {
Import-VM -Path $vmcx.FullName -Register -VirtualMachinePath $vmsPath
Write-Host "已注册:$($vmcx.BaseName)"
}
}
10.4 关键点速查
| 🔢 阶段 | ✅ 必做 | 🚫 不做 |
|---|---|---|
| 准备 | 全部 VM Off;建 F: 镜像目录结构;规划好盘符跨机器一致性 | 直接 Move |
| Move | 先 4.5 合并快照使 VHDX 变 .vhdx;用 Move-VMStorage -Vhds @{...} hashtable + -VirtualMachinePath + -SnapshotFilePath 三参数组合精确指定路径 |
想改 VHDX 文件名(Move-VMStorage 不允许改名,会报"源/目标文件名必须匹配") |
| 验证 | Get-VM 输出路径已全在 F:;启动一台 VM 跑通再批量 |
不验证直接删 D: 旧文件 |
| 清源盘 | 验证全过后再删;Remove-Item -Recurse -Force |
在 VM 还 Running 时删 D: 文件 |
| 换机 | 在新机器固定盘符为 F:;用 Import-VM -Register 重建;如冲突用 -GenerateNewId |
假设盘符自动一致 |
| 拔盘 | 拔之前 Stop-VM -Force 全部 |
拔盘期间 VM 在 Running |
10.5 一次性脚本:阶段 0 到阶段 3
把整个迁移流程压成 4 个顺序执行的代码块。别跳步骤——先跑阶段 0 确认环境 OK,再单台试阶段 1,验证后跑阶段 2 批量,最后阶段 3 清理。
每跑完一段把输出贴回来确认,再跑下一段。
阶段 0:环境预检
# A) 看 VM 列表 + 当前 VHD 状态
Get-VM | Where-Object Name -like "RHEL8-Node*" |
Format-Table Name, State,
@{N='VHD';E={(Get-VMHardDiskDrive -VMName $_.Name).Path}} -AutoSize -Wrap
# B) 合并所有检查点(必备前置:VHDX 必须变 `.vhdx` 无 GUID 后缀)
foreach ($name in "RHEL8-Node1","RHEL8-Node2","RHEL8-Node3","RHEL8-Node4",
"RHEL8-Node5","RHEL8-Node6","RHEL8-Node7") {
Remove-VMSnapshot -VMName $name -Confirm:$false
}
# 等合并完成(看 Hyper-V 管理器或事件日志)——5-15 分钟
# C) 在 F: 上建好顶层目录(脚本里后面会自动建 <VM名> 子目录)
$externalDrive = "F:"
New-Item -ItemType Directory -Path "$externalDrive\Virtual Hard Disks" -Force | Out-Null
# D) 关停所有目标 VM
$vmNames = (Get-VM | Where-Object { $_.Name -like "RHEL8-Node*" }).Name
Get-VM -Name $vmNames | Where-Object State -eq "Running" | Stop-VM -Force
"✅ 阶段 0 完成"
阶段 1:单台测试(先迁 RHEL8-Node1 一台验证链路)
$testName = "RHEL8-Node1"
# 当前 VHDX 在 D: 上的真实路径(避免硬编码)
$vhdCurrent = (Get-VMHardDiskDrive -VMName $testName).Path
Write-Host "源: $vhdCurrent"
# F: 顶层建目标子目录
New-Item -ItemType Directory -Path "F:\Virtual Hard Disks\$testName" -Force | Out-Null
# 三参数分开指定:VHDX 平铺 + 配置/快照走标准位置
# 参数**不要加** Virtual Machines\ / Snapshots\ 后缀——Move-VMStorage 会自动追加
Move-VMStorage -VMName $testName `
-Vhds @(@{
SourceFilePath = $vhdCurrent
DestinationFilePath = "F:\Virtual Hard Disks\$testName\$testName.vhdx"
}) `
-VirtualMachinePath "F:\Hyper-V" `
-SnapshotFilePath "F:\Hyper-V"
# Move-VMStorage 自动创建 saved state,删掉它让 VHDX 回到干净的 .vhdx
Remove-VMSnapshot -VMName $testName -Confirm:$false
# 验证:路径已迁到 F:
Get-VM -Name $testName | Format-Table Name, State,
@{N='VHD';E={(Get-VMHardDiskDrive -VMName $_.Name).Path}} -AutoSize
# 启动验证
Start-VM -Name $testName
Start-Sleep 30
Get-VM -Name $testName | Select Name, State
期望:VHD = F:\Virtual Hard Disks\RHEL8-Node1\RHEL8-Node1.vhdx,State 显示 Running。
阶段 2:批量迁移(单台 OK 后跑)
$vmNames = "RHEL8-Node2","RHEL8-Node3","RHEL8-Node4",
"RHEL8-Node5","RHEL8-Node6","RHEL8-Node7"
foreach ($name in $vmNames) {
$start = Get-Date
Write-Host "==== $name ====" -ForegroundColor Cyan
$vhdCurrent = (Get-VMHardDiskDrive -VMName $name).Path
# 没合并好的跳过——避免 Move-VMStorage -Vhds 报"文件名不匹配"
if ($vhdCurrent -notlike "*\$name.vhdx") {
Write-Warning "$name VHDX 还没合并好($vhdCurrent),跳过——回 4.5" -WarningAction Continue
continue
}
New-Item -ItemType Directory -Path "F:\Virtual Hard Disks\$name" -Force | Out-Null
Move-VMStorage -VMName $name `
-Vhds @(@{
SourceFilePath = $vhdCurrent
DestinationFilePath = "F:\Virtual Hard Disks\$name\$name.vhdx"
}) `
-VirtualMachinePath "F:\Hyper-V" `
-SnapshotFilePath "F:\Hyper-V" `
-ErrorAction Stop
# Move-VMStorage 自动创建 saved state,删掉它让 VHDX 回到干净的 .vhdx
Remove-VMSnapshot -VMName $name -Confirm:$false
Write-Host " ✅ 完成($((Get-Date) - $start).ToString('mm\:ss'))" -ForegroundColor Green
}
# 全部验证
Get-VM | Where-Object Name -like "RHEL8-Node*" |
Format-Table Name, State,
@{N='VHD';E={(Get-VMHardDiskDrive -VMName $_.Name).Path}} -AutoSize
阶段 3:清理 D: 上的旧文件(全部 OK 后跑)
# 先确认 7 台 VHD 都在 F:
$inF = Get-VM | Where-Object Name -like "RHEL8-Node*" |
Where-Object { (Get-VMHardDiskDrive -VMName $_.Name).Path -like "F:\*" }
"在 F: 的 VM 数量: $($inF.Count) / 7"
if ($inF.Count -ne 7) {
Write-Warning "还没全部迁完,先别清 D:" -WarningAction Continue
return
}
# 全 OK,清 D:
Remove-Item -Recurse -Force "D:\Hyper-V\Virtual Machines\*"
Remove-Item -Recurse -Force "D:\Hyper-V\Snapshots\*" -ErrorAction SilentlyContinue
foreach ($name in "RHEL8-Node1","RHEL8-Node2","RHEL8-Node3","RHEL8-Node4",
"RHEL8-Node5","RHEL8-Node6","RHEL8-Node7") {
Remove-Item -Recurse -Force "D:\Virtual Hard Disks\$name"
}
"D: 清理完成,$($inF.Count) 台 VM 已完全迁到 F:"
进度回报点
每跑完一个阶段后,把这段输出贴回来确认状态:
Get-VM | Where-Object Name -like "RHEL8-Node*" |
Format-Table Name, State,
@{N='VHD';E={(Get-VMHardDiskDrive -VMName $_.Name).Path}} -AutoSize -Wrap
十一、总结
| 🔢 步骤 | 🛠️ 操作 | 📌 关键点 |
|---|---|---|
| 1 | 关停目标 VM | Stop-VM -Force 全部目标 VM |
| 2 | 在 F: 上建镜像目录 | F:\Hyper-V\Virtual Machines\ / Snapshots\ / Virtual Hard Disks\ + 每台 <VM名>\ |
| 3 | Move-VM 逐台迁 |
用 -DestinationHost $env:COMPUTERNAME -IncludeStorage -DestinationStoragePath 搬 VHDX,再 Copy 配置 + Remove-VM + Import-VM -Register 重建 |
| 4 | 验证路径全在 F: | Get-VMHardDiskDrive 输出对照检查 |
| 5 | 启动一台 VM 跑通 | 确认 IP / 网关 / 磁盘 |
| 6 | 删除 D: 上的旧文件 | 验证全过后再删;不删注册(Move-VM 已重指) |
| 7 | (可选)Export-VM 加做便携包 | 换机时插盘即用,省去 Import-VM 步骤 |
| 8 | (偶尔)换机 | 拔盘到新机器 → 固定盘符 → Import-VM -Register |
核心要点:
Move-VM是 Hyper-V 官方"在主机内搬迁 VM"命令,配合-DestinationHost $env:COMPUTERNAME绕过单主机场景的参数集歧义;VM 配置目录通过 Copy + Remove/Import 的组合搬移到新位置。
主风险点:Move 期间不要拔盘;跨机器前要固定盘符;删除 D: 旧文件前先验证 F: 跑通。
附录:本文工作示例速查(7 台 RHEL8 节点)
仅给"恰好是 7 台 RHEL8 集群节点"的读者参考。VM 场景不一样时跳过本节。
| 🖥️ VM 名 | 🎭 角色(按 RHEL 8.10 集群笔记规划) | 🌐 原 IP |
|---|---|---|
RHEL8-Node1 |
Tomcat-1 | 192.168.100.101 |
RHEL8-Node2 |
Tomcat-2 | 192.168.100.102 |
RHEL8-Node3 |
MongoDB-1 | 192.168.100.103 |
RHEL8-Node4 |
MongoDB-2 | 192.168.100.104 |
RHEL8-Node5 |
MongoDB-3 | 192.168.100.105 |
RHEL8-Node6 |
Nginx-1 | 192.168.100.106 |
RHEL8-Node7 |
Nginx-2 | 192.168.100.107 |
代入本文主线的具体参数:
$vmNames = @(
"RHEL8-Node1","RHEL8-Node2","RHEL8-Node3","RHEL8-Node4",
"RHEL8-Node5","RHEL8-Node6","RHEL8-Node7"
)
$externalDrive = "F:" # 你的移动硬盘盘符
7 台 RHEL8 节点的特殊注意事项:
- VHDX 体量:约 80-100 GB(每台 ~12 GB VHDX)
- 快照:通常已清理过(克隆完成后),如有保留无影响
- 网络:源机器的
InternalSwitch+ 宿主 NAT(192.168.100.1网关)需在每台新机器复刻——只跟外置盘走的是 VM,宿主侧网络配置(虚拟交换机、NAT)不跟 - Red Hat 订阅:Developer Subscription 支持 16 台机器,每台 VM 独立 system identity。换机后用同一账号即可,
subscription-manager identity在 VM 内能验证
更新时间:2026-07-31
适用环境:Windows 11 / 10 + Hyper-V;移动硬盘 NTFS 或 exFAT 均可
工作示例:7 台 RHEL 8.10 集群节点(RHEL8-Node1 ~ RHEL8-Node7)
主推方案:Move-VM + 路径自定义(<VM名>\<VM名>.vhdx)

浙公网安备 33010602011771号