Недавно переносил у нас рабочую Windows-ВМ с Hyper-V на Proxmox. Сама по себе задача довольно обычная, и если машину можно спокойно выключить на время миграции, рассказывать тут особо не о чем. Но в нашем случае исходная ВМ должна была продолжать работать, оригинальный VHDX нельзя было менять или отсоединять, а новую машину нельзя было подключать к рабочей сети, пока мы полностью не убедимся, что перенос прошел нормально.
Сам процесс я автоматизировал через AI-агента, и уже на первой попытке всплыл хороший пример того, почему такие задачи нельзя сводить к последовательному выполнению команд. Агент создал ВМ на Proxmox, запустил ее, получил от qm status состояние running и посчитал перенос завершенным, хотя сама Windows в этот момент вообще не загрузилась.
Дальше таких моментов всплыло еще больше. При переходе с SATA на VirtIO SCSI я получил INACCESSIBLE_BOOT_DEVICE, на другой машине пришлось отдельно восстанавливать BCD, а VHDX с 4K-секторами в итоге оказалось проще пересобрать на файловом уровне, чем пытаться дальше конвертировать поблочно.
В процессе первоначальный сценарий «скопировать диск → импортировать → запустить» постепенно превратился в нормальный регламент с чекпоинтами, отдельной проверкой загрузки Windows, изолированным первым стартом и откатом после неудачных попыток.
В статье покажу весь процесс именно в том порядке, в котором я через него прошел, включая варианты, которые не сработали. Думаю, это будет полезнее очередной инструкции по импорту VHDX, особенно если исходную ВМ тоже нельзя просто выключить на пару часов.
С чего начал
В голове все выглядело довольно просто.
Нужно было найти системный VHDX на Hyper-V, скопировать его на хранилище, создать ВМ на Proxmox, импортировать диск и запустить Windows.
Проблема в том, что исходная машина продолжает работать, а значит, ее диск во время копирования меняется. Плюс перед переносом нужно учитывать тип виртуального контроллера, GPT и EFI-раздел, состояние загрузчика Windows, размер логического и физического сектора, существующие чекпоинты Hyper-V и драйверы внутри гостевой ОС.
Да, это довольно базовая история, но когда процесс выполняется автоматически, все такие проверки приходится прописывать прям дотошно. Если человек после запуска ВМ увидит в консоли BSOD, он поймет, что что-то пошло не так, а вот автоматизация вполне может увидеть успешно завершившийся qm start и пойти дальше.
Поэтому до начала миграции я закрепил три условия:
Исходную ВМ не выключаем.
Оригинальный VHDX не изменяем и не отсоединяем.
Сеть новой ВМ не включаем, пока отдельно не проверим результат миграции.
Последний пункт нужен в том числе из-за того, что копия Windows может подняться с тем же IP, именем компьютера и работающими фоновыми сервисами. Получить две такие машины в одном сегменте во время проверки миграции точно не хотелось.
Первый запуск показал проблему в самом сценарии
В первой версии я сделал слишком “банальный” процесс, то есть агент создавал ВМ на Proxmox, запускал ее и проверял состояние:
qm start 9001 qm status 9001
В ответ Proxmox возвращал:status: running
На этом агент считал этап успешно завершенным.
Формально то все правильно - running означает что процесс виртуальной машины работает, но про состояние гостевой ОС он ничего не говорит. QEMU может быть запущен, пока Windows не видит загрузочный диск, висит на Boot Manager или уже получила BSOD.
После этого я разделил запуск виртуальной машины и загрузку Windows на два разных этапа и постепенно весь процесс миграции стал выглядеть так:

Для переходов между этапами появились отдельные условия:
checkpoint error → STOP
check failed → STOP
Windows BSOD → ROLLBACK
network approval → HUMAN
На первых итерациях загрузку Windows я проверял через скриншот консоли ВМ:
qm start 9001 sleep 120 qm screenshot 9001 /tmp/boot_check.ppm
sleep 120 здесь, конечно, довольно примитивное решение - в нормальном сценарии ожидание лучше заменить проверкой через guest-agent или состоянием внутри Windows, но мне на этом этапе было важнее перестать считать запуск процесса QEMU доказательством того, что гостевая ОС действительно загрузилась.
Как получить стабильную копию работающего диска
Обычное копирование активного VHDX здесь не подходило. Пока файл читается, работающая Windows продолжает менять его содержимое, поэтому разные части получившегося образа могут относиться к разным моментам времени.
Чтобы зафиксировать базовый диск и при этом не останавливать машину я использовал временный production checkpoint Hyper-V.
Перед его созданием агент сначала собирал текущее состояние:
Get-VM -Name "vm-billing-01" | Select Name, State, CheckpointType Get-VHD "D:\Hyper-V\vm-billing-01\Disks\vm-billing-01-sys.vhdx" | Select Path, Size, LogicalSectorSize, ParentPath
Здесь нужно проверить не только путь к диску, но и понять является ли файл базовым VHDX или частью существующей цепочки, какой размер сектора используется и нет ли уже созданных чекпоинтов. Вручную это кажется очевидным, но для автоматизации такую проверку лучше все таки прописывать подробно.
После сохранения исходного состояния я временно включал Production checkpoint и создавал их с понятным именем:
Set-VM ` -Name "vm-billing-01" ` -CheckpointType ProductionOnly Checkpoint-VM ` -Name "vm-billing-01" ` -SnapshotName "MIG_vm-billing-01_C_tmp"
После создания чекпоинтов Hyper-V продолжает работу через дочерний .avhdx, поэтому базовый VHDX в этот момент перестает меняться и его можно скопировать, не выключая исходную машину. Я использовал именно Production checkpoint, чтобы перед созданием точки Hyper-V задействовал VSS внутри Windows, а не просто снимал состояние диска.
Для копирования я использовал robocopy:
robocopy ` "D:\Hyper-V\vm-billing-01\Disks" ` "\\nas-backup\vm-archive\vm-billing-01" ` vm-billing-01-sys.vhdx ` /MT:16 ` /LOG+:C:\Temp\mig_copy.log
Сам выбор именно robocopy здесь не особенно принципиален. Мне было важно получать лог, понятный exit code, возможность повторить операцию и нормально проверить результат.
Тут есть небольшой нюанс с exit code самого robocopy. Его нельзя проверять по обычной логике, где любой ненулевой код считается ошибкой. Значения ниже 8 могут означать успешно выполненное копирование с дополнительными условиями, а ошибкой считается код 8 и выше.
Еще один небольшой момент связан с динамическими VHDX. Диск с виртуальной емкостью 70 ГБ на файловой системе может занимать заметно меньше, поэтому я сравнивал копию с исходником по фактическому размеру файла и параметрам VHDX, а не пытался сопоставлять размер файла с виртуальной емкостью диска.
Только после проверки полученной копии временный чекпоинт удалялся, Hyper-V выполнял слияние изменений, а исходная настройка чекпоинтов возвращалась:
Remove-VMSnapshot ` -VMName "vm-billing-01" ` -Name "MIG_vm-billing-01_C_tmp" Set-VM ` -Name "vm-billing-01" ` -CheckpointType Disabled Get-VM "vm-billing-01" | Select State Get-VMSnapshot -VMName "vm-billing-01"
Сначала я хотел удалять временный чекпоинт сразу после завершения копирования, но в итоге оставил проверку образа перед удалением. Если с копией что-то не так, удобнее обнаружить это до того как Hyper-V уже начал возвращать исходный диск в обычное состояние.
Импортируем диск и пока не трогаем VirtIO
На стороне Proxmox я сначала проверял, что скопированный VHDX вообще нормально читается:
qemu-img info \ -f vhdx \ /mnt/pve/nas-archive/vm-billing-01/target_512.vhdx qemu-img check \ -f vhdx \ /mnt/pve/nas-archive/vm-billing-01/target_512.vhdx
Это еще ничего не говорит о загрузке Windows, проверка подтверждает только то, что файл читается и структура VHDX не развалилась при копировании. Поэтому разницу между “образ корректный” и “Windows загрузится” дальше пришлось учитывать отдельно.
Если базовые проверки проходили, диск импортировался в хранилище Proxmox:
qm importdisk \ 9001 \ /mnt/pve/nas-archive/vm-billing-01/target_512.vhdx \ rpool
Первый запуск я делал через SATA:
qm set 9001 \ --sata0 rpool:vm-9001-disk-1,size=65G qm set 9001 --boot order=sata0
Да, SATA здесь выглядит довольно консервативно, но на первом запуске мне была важнее совместимость, чем производительность, поэтому я пошел таким путем. Windows обычно может загрузиться с таким контроллером без предварительной подготовки, а уже после успешного старта можно спокойно заниматься VirtIO.
Сетевую карту я добавлял сразу, но виртуальный линк оставлял выключенным:
qm set 9001 \ --net0 virtio=AA:BB:CC:DD:EE:01,bridge=vmbr0,link_down=1
До этого момента новая ВМ вообще не должна общаться с рабочей сетью. Сначала нужно убедиться, что Windows нормально загружается в изоляции и только потом разбираться с сетевой конфигурацией.
Почему нельзя сразу перевести системный диск на VirtIO SCSI
После успешной загрузки на SATA я попробовал подключить системный диск через VirtIO SCSI и получил INACCESSIBLE_BOOT_DEVICE.
Причина оказалась довольно простой - само наличие файлов VirtIO-драйвера внутри Windows еще не означает, что система подготовлена к загрузке с нового контроллера, поэтому сначала я дал уже загруженной Windows увидеть VirtIO SCSI на отдельном временном диске. После того как контроллер появился в системе и драйвер нормально подхватился, уже переключал на него системный диск.

Рабочая последовательность в итоге получилась такой:
Загрузить Windows с системным диском на SATA.
Подключить
virtio-winISO.Установить и проверить
vioscsi.Подключить небольшой временный диск через VirtIO SCSI.
Загрузить Windows и убедиться, что контроллер и новое устройство определились.
Штатно выключить ВМ.
Удалить временный диск и SATA-подключение.
Подключить системный диск уже через SCSI.
Еще раз загрузить Windows и проверить результат.
В командах это выглядело так:
# Временный SCSI-диск, чтобы Windows увидела контроллер qm set 9001 --scsi0 rpool:vm-9001-tmp,size=1G qm start 9001 # Здесь в рабочем сценарии проверяем, # что контроллер определился внутри Windows qm shutdown 9001 # Только после этого переключаем системный диск qm set 9001 --delete sata0 --delete scsi0 qm set 9001 --scsi0 rpool:vm-9001-disk-1,size=65G qm set 9001 --boot order=scsi0 qm start 9001 qm screenshot 9001 /tmp/boot_check.ppm
В одной из первых попыток этот промежуточный этап был пропущен, после чего Windows и упала с INACCESSIBLE_BOOT_DEVICE. Конфигурацию пришлось откатить из заранее сохраненного бэкапа, а переход через временный SCSI-диск после этого стал обязательной частью сценария.
На другой ВМ пришлось восстанавливать загрузчик
Следующая проблема появилась уже на другой машине. Сам диск импортировался без ошибок, qemu-img тоже ничего подозрительного не показывал, но Windows до нормальной загрузки не доходила.
Можно было начать вручную редактировать BCD, но на подключенном диске легко запутаться в буквах томов и EFI-разделах, поэтому я сначала решил восстановить загрузочные записи штатными инструментами Windows.
VHDX подключил к Windows-хосту, назначил буквы разделам и выполнил:
bcdboot ` W:\Windows ` /s S: ` /f UEFI ` /l ru-RU
В этом примере W: - раздел с Windows, а S: - EFI-раздел.
После этого результат можно проверить через хранилище BCD:
bcdedit ` /store S:\EFI\Microsoft\Boot\BCD
Если проблема действительно в BCD или EFI-записях, мне такой подход кажется надежнее ручной правки. Сначала даем самой Windows пересоздать загрузочную конфигурацию штатной утилитой, а уже если это не помогает, идем разбираться дальше.
Больше всего времени забрал VHDX с 4K-секторами
С отдельной ВМ стандартный сценарий снова перестал работать из-за диска с логическими секторами по 4 КБ. Здесь пришлось попробовать три разных подхода и как раз на этом кейсе хорошо видно, почему успешный qemu-img check сам по себе почти ничего не говорит о том, загрузится ли Windows.
Попытка №1. Поблочная копия в диск с секторами 512 байт
Первым вариантом была поблочная копия PhysicalDrive в новый диск с секторами 512 байт с последующей перезаписью GPT.
После этого qemu-img check завершался без ошибок, но Windows останавливалась с UNMOUNTABLE_BOOT_VOLUME.
На этом варианте хорошо проявилась та же проблема, что и в начале статьи. Формальная проверка прошла успешно, но проверяла она не то, что мне на самом деле было нужно. Контейнер VHDX оставался структурно корректным, однако его содержимое от этого не стало совместимым с загрузкой Windows.
Поэтому этот вариант дальше использовать не стал.
Попытка №2. Передать параметры 4K через QEMU
Следующим вариантом я попробовал передать QEMU параметры, описывающие 4K-секторы. Windows с ними загрузилась, поэтому для диагностики способ оказался полезным.
Оставлять такую конфигурацию для постоянной эксплуатации я не хотел. Нестандартное подключение диска усложняет сопровождение, резервное копирование, повторное развертывание ВМ и перенос конфигурации между узлами. Если можно получить обычный диск, который Proxmox будет использовать без дополнительных параметров, для меня это предпочтительнее.
Попытка №3. Пересобрать диск на файловом уровне
В итоге я отказался от попыток исправить диск поблочно и создал новый фиксированный VHDX с логическими и физическими секторами по 512 байт:
New-VHD ` -Path "E:\work\target_512.vhdx" ` -Fixed ` -SizeBytes 70GB ` -LogicalSectorSizeBytes 512 ` -PhysicalSectorSizeBytes 512 Mount-VHD "E:\work\source.vhdx" -ReadOnly Mount-VHD "E:\work\target_512.vhdx"
На новом диске заново создал разделы в том же порядке, что и на исходном:
Recovery.
EFI.
MSR.
Windows.
В рабочем скрипте используются полные GUID типов GPT-разделов. Здесь сокращаю их только для компактности:
$dn = 7 New-Partition -DiskNumber $dn -Size 500MB -GptType '{de94bba4-…}' # Recovery New-Partition -DiskNumber $dn -Size 100MB -GptType '{c12a7328-…}' # EFI New-Partition -DiskNumber $dn -Size 128MB -GptType '{e3c9e316-…}' # MSR $os = New-Partition -DiskNumber $dn -UseMaximumSize
Дальше назначил буквы разделам:
$SourceOS = 'G:' $TargetOS = 'W:' $TargetEFI = 'S:' Set-Partition -DiskNumber $dn ` -PartitionNumber $os.PartitionNumber ` -NewDriveLetter $TargetOS Format-Volume -DriveLetter $TargetOS -NewFileSystemLabel OS
EFI-раздел подключался отдельно:
$efi = Get-Partition -DiskNumber $dn | Where-Object GptType -eq '{c12a7328-…}' Set-Partition -DiskNumber $dn ` -PartitionNumber $efi.PartitionNumber ` -NewDriveLetter $TargetEFI
После этого файлы Windows переносились с сохранением атрибутов и ACL:
robocopy "$SourceOS\" "$TargetOS\" ` /MIR /COPYALL /DCOPY:DAT /B /XJ ` /R:1 /W:1 /MT:32
После переноса оставалось заново создать загрузчик, а затем отключить оба VHDX:
bcdboot "$TargetOS\Windows" /s $TargetEFI /f UEFI /l ru-RU Dismount-VHD "E:\work\source.vhdx" Dismount-VHD "E:\work\target_512.vhdx"
Этот вариант в итоге и подошел для эксплуатации. Он требует больше времени, чем поблочная конвертация, зато на выходе получается обычный VHDX с понятной для Proxmox геометрией, без дополнительных параметров QEMU. Его проще проверять, резервировать и при необходимости подключать повторно.
После этого кейса я бы уже не рассчитывал, что поблочная конвертация 4K-диска в 512 автоматически решит проблему. Если образ формально исправен, а Windows все равно не загружается, иногда проще пересобрать разделы и перенести файлы, чем дальше пытаться исправлять сам контейнер.
Что еще всплыло уже во время автоматизации
Когда основной сценарий заработал, осталось несколько менее заметных вещей, которые тоже пришлось учесть.
В интерактивной PowerShell-сессии удобно передавать между командами объекты MSFT_Partition, но в scheduled task это оказалось менее надежно. Объект не всегда нормально сериализовался и возвращался на следующем шаге, поэтому в автоматизированном сценарии я перешел на обычные числовые значения:
-DiskNumber 7 -PartitionNumber 4
Для временных томов я использовал буквы ближе к концу алфавита.
Тут ничего необычного нет, просто так меньше шанс пересечься с буквами, которые Windows уже назначила исходному диску или служебным разделам.Похожая история получилась с чекпоинтами.
Если временный чекпоинт с ожидаемым именем уже существует и действительно удерживает нужный базовый диск, повторно создавать его не нужно. Дополнительная цепочка здесь ничего полезного не дает и только усложняет восстановление после ошибки.
Когда все этапы были пройдены, я еще раз проверял конфигурацию целевой ВМ и отдельно смотрел, что сеть по-прежнему остается отключенной:
qm config 9001 | grep -E 'boot|scsi0|link_down'
После этого отдельно проверял исходную машину на Hyper-V:
Get-VM "vm-billing-01" | Select State
link_down=1 снимался только после отдельного разрешения инженера, чтобы копия рабочей машины не могла автоматически оказаться в одном сегменте с оригиналом.
Во что в итоге превратился первоначальный сценарий
Первая версия скилла описывала в основном идеальный путь. Нужно было получить VHDX, импортировать его, запустить новую ВМ и проверить результат.
После нескольких реальных попыток в сценарии появились отдельные ветки для восстановления загрузчика, работы с 4K-секторами, перехода с SATA на VirtIO SCSI, проверки сети и отката конфигурации после неудачного запуска.
Но главным изменением для меня стало не количество этих веток. Пришлось четко определить, что именно считается успешным результатом на каждом этапе.
В начале процесса у меня фактически было четыре разных состояния, которые автоматизация воспринимала почти одинаково:
файл скопирован
≠
образ читается
≠
ВМ запущена
≠
Windows загрузилась
Для человека разница очевидна и в этом как раз кроется проблема. Мы часто не описываем такие проверки, потому что сами выполняем их почти автоматически. Если на экране BSOD, никто не станет считать миграцию успешной только из-за того, что qm start вернул нормальный exit code.
С агентом эти очевидные проверки приходится превращать в конкретные условия перехода к следующему шагу.
В итоге процесс у меня сейчас сводится к нескольким контрольным точкам:
Этап |
Что проверяю |
Что происходит при ошибке |
Исходное состояние |
Настройки сохранены, исходная ВМ продолжает работать |
STOP |
Копирование |
Production checkpoint создан, VHDX скопирован и проверен |
STOP |
Образ |
qemu-img info и qemu-img check проходят |
STOP |
Первый запуск |
Windows загрузилась на SATA без сети |
ROLLBACK |
SCSI / 4K |
Контроллер подготовлен, Windows снова загрузилась |
ROLLBACK |
Сеть |
Гостевая ОС полностью проверена |
HUMAN |
Подключение сети я специально оставил за человеком. Даже если все предыдущие проверки прошли, мне не нужен сценарий, в котором агент самостоятельно решает выпустить копию рабочей машины в тот же сегмент, где все еще работает оригинал.
Что я вынес из этой миграции
Если придется еще раз переносить работающую Windows-ВМ с Hyper-V на Proxmox, я бы уже не начинал непосредственно с импорта диска. Сначала стоит посмотреть на GPT/UEFI, текущий контроллер, logical и physical sector size, цепочку чекпоинтов, наличие VirtIO-драйверов и заранее решить, как именно будет подтверждаться загрузка Windows.
Звучит довольно очевидно, но именно такие очевидные вещи чаще всего теряются, когда длинный процесс начинают автоматизировать. Первая версия моего сценария тоже умела скопировать диск, импортировать его и запустить ВМ, просто этого оказалось недостаточно.
Самая неприятная ошибка здесь не команда, которая завершилась с ненулевым exit code. Ее хотя бы легко поймать и остановить процесс. Гораздо хуже, когда каждая команда формально выполнилась успешно, автоматизация дошла до конца, а итоговая система при этом не работает.
У меня весь этот разбор как раз начался с одного вполне корректного status: running
На этом все, спасибо за внимание, рад буду комментариям или вопросам.
Комментарии (9)

osmanpasha
22.09.2026 14:21А как вообще современная винда относится к таким переездам? Раньше рекомендовалось делать sysprep, чтобы она отвязалась от аппаратных идентификаторов и при следующей загрузке привязалась к новым. На горячую это, конечно, нельзя было сделать.

overslepter
22.09.2026 14:21А ничего не поменялось. Если очень нужно то так же нужно удалять драйвера, подготавливать к вирт окружению и выключать, исходя из того что включаться образ уже будет в виртуальном окружении. В случае же статьи и вырожденного случая конверта виртуальной в виртуалку - все проще, о чем ТС и пишет.

RomanOpenclaw Автор
22.09.2026 14:21Все отлично переехало и без sysprep.
Sysprep - это доп.настройка, ввод в домен и прочее. В данном случае это не надо.

dimsoft
22.09.2026 14:21Поднимаем на Proxmox smb 3.x шару, на hyper- v мигрируем vhdx на неё, интегрируем через dism драйвера. Запускаем на новом месте

RomanOpenclaw Автор
22.09.2026 14:21Скорее всего так и есть, но без тестов и конкретного кейса это теория. У меня не стояла задача, придумать все варианты. Мне надо было просто максимально быстро и безболезненно перенести ВМ с одного гипервизора на другой. На проверку гипотез и выбор лучшего варианта времени не было.
В конце концов, выбранный вариант рабочий, поэтому и решил поделиться.
Спасибо за приведенный альтернативный вариант. Если будет аналогичная задача опробую и ваш путь.

Mikhalich93
22.09.2026 14:21Мигрирую с Vmware через промежуточный NFS датастор который подключен к VMware и Proxmox одновременно:
Подробное описание этапов
Плейбук выполняется в 63 задачи, сгруппированные в 12 логических блоков. Ниже — подробный разбор каждого блока.
Блок 1 — Discovery (только чтение, выполняется всегда)
ЗадачаЧто делает
VMware | Inspect VMsgovcvm.info-jsonсобирает: имя, BIOS (efi/bios), cores, memory, IP из VMware Tools, MAC-адреса всех NIC, портгруппы, пути к VMDK-дискам, иguestFullName(строка ОС из VMware Tools — используется для авто-определенияwin11)VMware | Collect unique dvPortGroup keysРезолвит ключи distributed portgroup (dvPortGroup-XXXX) в человекочитаемые имена (DPortGroup-VLAN(13)) одним batch-запросом на все ВМBuild windows_vmsСобирает финальную структуру данных на каждую ВМ: имя, VMID, bios, cores, memory, MAC, bridge/vlan (из имени портгруппы или ручного маппинга), IP, диски, snapshot-имя, и вычисленныйostype(см. раздел Автоопределение типа ОС)Assert: VM name + NIC + disks resolvedВалидация — не даёт продолжить если что-то не резолвилось (пустой MAC, нет дисков и т.д.)Блок 2 — VMID allocation
ЗадачаЧто делает
Proxmox | Allocate VMIDpvesh get /cluster/nextid— тот же механизм что использует Proxmox GUI. Отслеживает уже выделенные ID в рамках одного запуска плейбука, чтобы не выдать два одинаковых VMID при батч-миграции нескольких ВМ подрядБлок 3 — GuestOps подготовка (через VMware Tools)
ЗадачаЧто делает
GuestOps | mkdir migration dirСоздаётC:\Tempв гостевой ОС.retries: 10, delay: 5— VMware Tools может быть ещё не готов сразу после старта ВМGuestOps | Upload PS scriptsЗагружает 5 PowerShell-скриптов (килобайты) черезgovc guest.upload. Важно: архив с драйверами (virtio-drivers.zip, 30-40 МБ) больше НЕ загружается — драйверы идут через ISO (см. ниже)GuestOps | Mount virtio-win ISO as CD-ROMgovc device.cdrom.insert+govc device.connectмонтируетvirtio-win.isoс NFS-датастора как CD-ROM. Мгновенно — нет передачи по сетиGuestOps | Run preparation scriptЗапускаетguestops-prepare-windows.ps1(см. описание скрипта ниже)Pull network-before.jsonЗабирает сохранённые сетевые параметры ВМ (IP/маска/шлюз/DNS) с гостя на Ansible-хост для дальнейшего использования на Proxmox-сторонеБлок 4 — Snapshot и svMotion
ЗадачаЧто делает
VMware | List/remove old snapshotsУдаляет снапшоты от предыдущих неудачных попыток миграции этой же ВМVMware | Create snapshotСоздаёт снапшотpre-proxmox-migration-YYYYMMDD-HHmmss(страховка перед cutover)GuestOps | Eject virtio-win ISO before svMotionОтключает и извлекает ISO — VMware не даст перенести ВМ с примонтированным файлом с датастораVMware | svMotion to stagingПеремещает VMDK на NFS staging-датастор, видимый и vSphere, и ProxmoxБлок 5 — Cutover gate + GuestOps cleanbreak
ЗадачаЧто делает
Cutover gateSafety gate. Еслиexecute_cutover=false— плейбук останавливается здесь с понятным сообщением. Всё что выше (подготовка драйверов, snapshot, svMotion) уже выполнено, но сама ВМ ещё не выключена и не тронута необратимоGuestOps | CleanBreak (remove VMware Tools) + shutdownЗапускаетguestops-cleanbreak-shutdown.ps1— нейтрализует VMware Tools, освобождает IP, выключает ВМ (подробности ниже)VMware | Wait poweredOffЖдёт до 5 минут корректного выключения, затем принудительно останавливает если не выключилась самаБлок 6 — Создание ВМ в Proxmox
ЗадачаЧто делает
Proxmox | Create shell VM if missingqm createс параметрами:cpu=host,machine=q35,scsihw=virtio-scsi-single,agent=enabled=1,--ostypeберётся индивидуально для каждой ВМ (win10/win11), сетевой адаптерvirtioс нужным bridge/VLAN/MACProxmox | Add EFI disk (UEFI only, once)Если BIOS=ovmf — добавляетefidisk0(pre-enrolled-keys=0для совместимости с Secure Boot)Proxmox | Add vTPM for Win11 (swtpm v2.0)Еслиostype=win11и BIOS=ovmf — автоматически добавляетtpmstate0(программный TPM 2.0 через Proxmoxswtpm). Не выполняется для win10Блок 7 — Импорт диска и Phase A boot
ЗадачаЧто делает
Proxmox | Import disk from stagingqm disk importконвертирует VMDK → формат хранилища (Ceph RBD)Proxmox | Attach dummy disk (scsi0) + main disk (sata0)Основной диск подключается какsata0(загрузка через встроенный AHCI-контроллер), плюс создаётся временный 1 ГБ "dummy" диск какscsi0— единственная цель этого диска: заставить Windows через PnP обнаружить virtio-scsi контроллер и установитьvioscsi.sysProxmox | Start VM (Phase A)Запускает ВМ в конфигурации Phase AProxmox | Phase A: wait for QGAЖдёт до 90 попыток × 10 сек = 15 минут появления QEMU Guest Agent. Долгий таймаут учитывает возможную установку Windows Update при первой загрузке с рестартомProxmox | Verify vioscsi RunningЧерез QGA проверяет что службаvioscsiдействительно поднялась (значит PnP отработал и драйвер встал)Блок 8 — Настройка сети через QGA
ЗадачаЧто делает
QGA | Configure static IP on VirtIO-Net (retry-safe)Пушит и запускаетqga-configure-network.ps1с параметрами IP/маска/шлюз/DNS, взятыми изnetwork-before.json. Адаптер не переименовывается (оставлен как есть). Результат подтверждается через маркер-файлqga-net-result.txt(устойчиво к разрыву virtio-serial канала во время смены IP)Блок 9 — Phase B: flip дисков
ЗадачаЧто делает
Proxmox | Phase B: flip disks SATA -> SCSIВыключает ВМ, удаляет dummy-дискscsi0, перемещает основной дискsata0 → scsi0, выставляетboot order=scsi0. Автоматически подчищает мелкие "unused"-диски (<2 ГБ) оставшиеся от dummy/efidiskProxmox | Start VM (Phase B - final boot)Финальный старт уже полностью на virtio-scsiProxmox | Phase B: wait for QGA (final boot)До 60 попыток × 10 сек = 10 минут ожидания финальной загрузкиБлок 10 — Пост-обработка внутри гостя
ЗадачаЧто делает
GuestOps | Push + runfix-vmtools-rdp-error.ps1Убираетvmtoolsdиз автозагрузки — устраняет ошибку0xc0000096при входе по RDP после миграцииБлок 11 — Smoke-тест
ЗадачаЧто делает
Smoke | ICMP (best-effort)До 6 попыток × 5 сек пинга.failed_when: false— ICMP часто блокируется брандмауэром Windows, это не признак поломкиSmoke | Fallback - verify IP via QGA when ICMP failedЕсли пинг не прошёл — спрашивает у QGA напрямую (Get-NetIPAddress) есть ли нужный IP на интерфейсе. Это источник правды, не зависящий от файрволаSmoke | Report network status summaryПонятная сводка по каждой ВМ: ICMP OK / ICMP заблокирован (но сеть настроена)Smoke | TCP portsПроверка доступности портов изcheck_ports(по умолчанию 3389/RDP)Блок 12 — Регистрация в Proxmox HA
ЗадачаЧто делает
HA | Register VM in Proxmox HAha-manager add vm:VMID --state started. Идемпотентно — пропускает если ВМ уже в HA. Можно отключить флагом-e enable_ha=falseHA | Verify HA status/HA | Show HA statusПроверяет и выводит текущий статус ВМ в HA-менеджере

Mikhalich93
22.09.2026 14:21промежуточный датастор позволяет быстро импортировать диск в скелет ВМ , к тому же ВМ все лежат на VSAN а Proxmox не поддерживает миграцию с VSAN поэтому он выручает, все задачи с гостевой ОС по максимуму делаю через VMGuesttools и Qemu guest agent тогда не нужно открывать порты SSH, WinRM и можно мигрировать с выключенной сетью, к примеру clone или как вы для проверки
Junecat
У Вас присутствовало пофайловое копирование исходного диска на новый… а нельзя было создать софт рейд уровня 1, а потом «разбить зеркало», и подключить его половинку к новой машине?
RomanOpenclaw Автор
Привет! Это слишком сложно и замороченно. Поэтому я такой способ не выбрал. В моем случае хотелось обойтись штатными средствами Hyper-V и при этом не менять оригинальный VHDX, поэтому Production checkpoint оказался наиболее простым способом зафиксировать его состояние.