Hyper‑V : fusionner plusieurs AVHDX « branches sœurs » sans perte de données (Windows Server 2022, Veeam, PowerShell)

Chaîne AVHDX divergente dans Hyper‑V (Windows Server 2022) : cinq checkpoints 5‑9 pointant vers le même parent (4) perturbent Veeam et font « disparaître » des données. Voici des procédures sûres, scripts PowerShell et check‑list pour fusionner sans perte et remettre la sauvegarde d’équerre.

Sommaire

Contexte et problème à résoudre

Vous administrez une VM Hyper‑V (Windows Server 2022) qui dispose d’une chaîne de disques différenciés (checkpoints) longue et, surtout, divergente. Le tronc est linéaire (AVHDX 1 → 2 → 3 → 4), mais cinq autres diff (5 à 9) pointent tous vers le même parent (4). Cette topologie n’est pas un simple « collier de perles » mais un arbre : chaque AVHDX 5‑9 est une branche sœur. Dans ce cas de figure, un merge séquentiel classique écrase le parent à chaque passage et rend les autres enfants illisibles. Effet de bord observé : Veeam sauvegarde un partage de fichiers mais perd la cohérence de la chaîne, d’où une « disparition » apparente de données.

VHDX (base)
└─ AVHDX 1
   └─ AVHDX 2
      └─ AVHDX 3
         └─ AVHDX 4  <-- parent commun
            ├─ AVHDX 5
            ├─ AVHDX 6
            ├─ AVHDX 7
            ├─ AVHDX 8
            └─ AVHDX 9

Pourquoi cette topologie casse la sauvegarde

  • Règle des disques différenciés : un AVHDX est un delta qui dépend de l’empreinte exacte de son parent. Si le parent change après la création de l’enfant, l’enfant ne « reconnaît » plus son parent.
  • Branches sœurs : 5‑9 sont indépendants les uns des autres. Fusionner 9→4 modifie 4 ; dès lors, 8/7/6/5 ne correspondent plus au parent attendu : montages impossibles, erreurs d’inspection, données inaccessibles.
  • Veeam & checkpoints : selon le mode de sauvegarde et l’état des checkpoints (production vs standard, résidus, orphelins), Veeam peut ne plus suivre correctement la chaîne et ne capturer qu’un snapshot logique du partage, pas la totalité des deltas utiles.

Diagnostic rapide : établir la cartographie exacte

Avant toute action destructive, dressez l’état des lieux hors ligne (VM arrêtée si possible) :

  1. Inventaire des fichiers : repérez le répertoire de stockage de la VM (CSV, SMB 3, volume local). Sauvegardez la liste des VHDX/AVHDX.
dir D:\VMs\VM01\*.vhdx,*.avhdx /s > D:\VMs\VM01\_inventaire.txt
  1. Inspection PowerShell : récupérez ParentPath, dates et tailles.
$root = 'D:\VMs\VM01'
Get-ChildItem $root -Filter *.avhdx -Recurse |
  Sort-Object LastWriteTime |
  ForEach-Object {
    $v = Get-VHD -Path $_.FullName
    [pscustomobject]@{
      Child             = $_.FullName
      ParentPath        = $v.ParentPath
      IsDifferencing    = $v.VhdFormat
      FileSizeGB        = [math]::Round($v.FileSize/1GB,2)
      VirtualSizeGB     = [math]::Round($v.Size/1GB,2)
      Modified          = $_.LastWriteTime
    }
  } | Format-Table -Auto
  1. Identifier la branche utile : triez par LastWriteTime pour repérer l’AVHDX le plus récent (souvent 9). Montez chaque branche en lecture seule pour confirmer la présence de données uniques.
# Exemple de montage RO d'une branche
$path = 'D:\VMs\VM01\AVHDX9.avhdx'
$vh = Mount-VHD -Path $path -ReadOnly -PassThru
$disk = $vh | Get-Disk
# Attribuer une lettre de lecteur si besoin
$part = $disk | Get-Partition | Where-Object DriveLetter -ne $null | Select-Object -First 1
if (-not $part) { ($disk | Get-Partition | Select-Object -First 1) | Add-PartitionAccessPath -AssignDriveLetter }
Get-Volume -DiskNumber $disk.Number
# ...inspection...
Dismount-VHD -Path $path

Difficultés identifiées

  1. Chaîne divergente : les disques 5‑9 ne forment pas une suite linéaire mais des branches sœurs — un « merge en série » échoue mécaniquement.
  2. Risque de perte : fusionner successivement des enfants vers le même parent écrase l’état du parent après chaque merge. Les autres enfants deviennent orphelins.
  3. Sauvegarde incomplète : la sauvegarde « voit » le partage mais ne suit plus la totalité de la chaîne différenciée, d’où lacunes et « données manquantes ».

Stratégies de fusion comparées

ApprochePrincipeOrdre conseilléAvantagesLimites
Fusion sélective (réponse initiale)Choisir le plus récent (9) et le fusionner dans 4, puis 8→4, etc.9→4, 8→4, 7→4, 6→4, 5→4Rapide si l’on ne garde qu’une brancheÉcrase chaque fois le parent 4 ; les autres branches deviennent orphelines.
Fusion vers un nouveau disque (méthode sûre)Merge-VHD -Path <enfant> -DestinationPath <nouveau.vhdx> -Mode FullUn enfant à la fois, ordre indifférentGarde une copie intégrale de chaque branche ; zéro écrasementNécessite plus d’espace et de temps.
Méthode « copie puis comparaison » (solution réellement appliquée)1) Sauvegarder 4. 2) Fusionner 9→4. 3) Monter le résultat, copier les écarts. 4) Restaurer 4 et recommencer avec 8, 7, …9→4, restauration, 8→4, etc.Permet de récupérer toutes les donnéesTrès fastidieux si beaucoup de disques.

Procédure recommandée (sûre) : Fusionner chaque branche vers un nouveau VHDX

Objectif : conserver chaque branche intacte, produire des disques « pleins » autonomes, puis choisir en connaissance de cause la consolidation finale.

Impératif : arrêtez la VM, désactivez toute tâche de sauvegarde et copiez à froid tous les fichiers VHDX/AVHDX. Conservez ces sauvegardes jusqu’à validation complète.

Étapes détaillées

  1. Préparer l’espace : prévoir un volume libre ≥ à la taille logique utilisée par chaque branche (souvent proche de la taille disque invitée). Laisser 10‑20 % de marge pour la consolidation.
  2. Fusionner une branche : répétez pour 5, 6, 7, 8, 9 (ordre indifférent).
# Dossier de sortie
$dest = 'D:\VMs\VM01\Branches-merge'
New-Item -ItemType Directory -Path $dest -Force | Out-Null

# Exemple pour AVHDX9

$child = 'D:\VMs\VM01\AVHDX9.avhdx'
$out   = Join-Path $dest 'VM01-branch9-merged.vhdx'
Merge-VHD -Path $child -DestinationPath $out -Mode Full -Confirm:$false 
  1. Vérifier le disque résultant : inspectez, montez en lecture seule, contrôlez partitions et données.
Get-VHD -Path $out | Format-List *
$vh = Mount-VHD -Path $out -ReadOnly -PassThru
Get-Volume -DiskNumber ($vh | Get-Disk).Number
Dismount-VHD -Path $out
  1. (Option) Optimiser/Convertir : si besoin de compacter :
Optimize-VHD -Path $out -Mode Full
  1. Répéter pour toutes les branches. Vous obtenez une collection de VHDX autonomes (aucun ParentPath).
  2. Choisir la version fonctionnelle : montez chaque VHDX, listez les différences (date/volume) et retenez celui qui contient toutes les données requises.
  3. Attacher à la VM : remplacez le disque système ou data de la VM par le VHDX retenu. Conservez les autres en archivage jusqu’à la fin de la recette.

Variantes en GUI

  • Hyper‑V ManagerEdit Disk → sélection de l’AVHDX enfant → MergeTo a new virtual hard disk. Répétez pour 5‑9.
  • Inspect Disk (GUI) : utile pour valider le ParentPath et l’intégrité avant toute opération.

Procédure appliquée « copie puis comparaison » : récupérer toutes les données sans cloner chaque branche

Cette méthode est pertinente si l’espace est contraint ou si vous devez consolider rapidement tout le contenu utile en partant d’un parent commun (4).

  1. Copie de sécurité du parent 4 (fichier physique) et point de restauration hors ligne.
  2. Merge 9 → 4 (vers le parent). Attention : 5‑8 deviendront alors orphelins temporairement.
  3. Monter le résultat (ex‑4) ; copier seulement les écarts vers un espace de consolidation.
# Exemple de copie «&nbsp;diff only&nbsp;» prudente (pas de suppression côté destination)
robocopy X:\ Y:\ /E /XO /XN /XC /COPY:DAT /DCOPY:T /R:1 /W:1 /MT:16 /NFL /NDL /NP /LOG:robocopy-9.log
  1. Restaurer le parent 4 à son état précédent (copie à froid effectuée à l’étape 1).
  2. Répéter avec 8 → 4, 7 → 4, 6 → 4, 5 → 4, en copiant à chaque fois les ajouts/modifications vers l’espace de consolidation.
  3. À la fin, fabriquer un VHDX final (nouveau disque) et y déposer les données consolidées, ou fusionner la branche la plus fidèle et y injecter les fichiers restants.

Astuce : faites un « dry‑run » avant chaque copie :

robocopy X:\ Y:\ /E /L /XO /XN /XC

L’option /L liste sans copier ; vérifiez les écarts détectés.

Procédure expéditive « fusion sélective » : quand elle peut suffire

Si vous avez identifié une branche unique à conserver (ex. 9 est la vérité fonctionnelle) et que les autres sont obsolètes :

  1. Sauvegarder 4 et 9.
  2. Merge 9 → 4 (vers le parent) : le nouvel état de 4 devient l’état final.
  3. Déclarer 5‑8 obsolètes et archiver les fichiers (ne pas tenter de les monter sur le nouveau 4).
  4. Relancer une sauvegarde complète.

Avantage : rapidité. Risque : si une petite portion de données utiles existait dans 8 (mais pas dans 9), elle sera perdue. Ne choisissez cette voie que si vous êtes certain de la redondance.

Recommandations pratiques incontournables

  1. Sauvegarde avant toute manipulation : conservez chaque AVHDX + le parent VHDX original (copie à froid).
  2. Privilégier « Merge to New VHD » (Hyper‑V Manager) ou Merge‑VHD -Mode Full avec un chemin de destination différent ; ainsi chaque branche reste intacte.
  3. Identifier la branche utile :
    • Get‑VHD -Path *.avhdx | Sort‑Object LastWriteTime pour repérer le plus récent.
    • Montez chaque branche, inspectez son contenu et n’en gardez qu’une si les autres ne contiennent pas de données uniques.
  4. Travailler hors ligne (VM arrêtée) : simplifie et sécurise l’opération. La fusion « live » n’est utile que si le service doit absolument rester disponible.
  5. Nettoyage post‑fusion :
    • Vérifiez la cohérence avec Inspect Disk (GUI) ou Get‑VHD.
    • Détruisez les checkpoints résiduels (Remove‑VMSnapshot).
    • Relancez une sauvegarde complète (full) pour repartir sur une base saine.
  6. Prévention :
    • Automatisez la suppression des checkpoints après chaque sauvegarde (scripts PowerShell ou paramètres du logiciel de backup).
    • Surveillez la présence d’AVHDX orphelins (rapport quotidien).
    • Assurez un espace disque suffisant pour éviter des chaînes trop profondes.

Check‑list opérationnelle (pas à pas)

ÉtapeCommande/ActionCritère de succèsPlan B
Arrêt de la VMStop‑VM <NomVM>État OffPause cluster si CSV, maintenance host
InventaireGet‑ChildItem *.vhdx,*.avhdxListe complète sauvegardéeShadow copy du volume de stockage
CartographieGet‑VHD -Path *.avhdxParentPath validéGUI : Inspect Disk
Choix stratégieFusion vers nouveau VHDXPas d’écrasement de 4Méthode copie‑comparaison
Merge brancheMerge‑VHD -Mode FullVHDX autonome (ParentPath vide)Hyper‑V Manager → Edit Disk → Merge
VérificationMount‑VHD (RO), Get‑VolumePartitions lisiblesCHKDSK / Scan offline
Rattacher à la VMSet‑VMHardDiskDriveDémarrage OKRevenir à l’ancien disque, log console
NettoyageRemove‑VMSnapshot, purge AVHDXPlus aucun AVHDXArchiver 30 jours
Full backupExécuter job fullChaîne saineExport‑VM (clone) avant full

Scripts utiles (audit et merge)

Audit des AVHDX orphelins et de la chronologie

$root = 'D:\VMs\VM01'
Get-ChildItem $root -Filter *.avhdx -Recurse |
  ForEach-Object {
    $v = Get-VHD -Path $_.FullName
    [pscustomobject]@{
      AVHDX        = $_.Name
      ParentExists = if ($v.ParentPath) { Test-Path $v.ParentPath } else { $false }
      ParentPath   = $v.ParentPath
      FileSizeGB   = [math]::Round($v.FileSize/1GB,2)
      Modified     = $_.LastWriteTime
    }
  } | Sort-Object Modified | Format-Table -Auto

Fonction d’industrialisation d’un merge « vers nouveau VHDX »

function Merge-BranchToNewVhd {
  param(
    [Parameter(Mandatory=$true)] [string] $ChildPath,
    [Parameter(Mandatory=$true)] [string] $DestinationFolder
  )
  if (-not (Test-Path $ChildPath)) { throw "Introuvable : $ChildPath" }
  if (-not (Test-Path $DestinationFolder)) { New-Item -ItemType Directory -Path $DestinationFolder | Out-Null }

$baseName = [IO.Path]::GetFileNameWithoutExtension($ChildPath)
$dest = Join-Path $DestinationFolder "$baseName-merged.vhdx"

Write-Host "Fusion de $ChildPath → $dest"
Merge-VHD -Path $ChildPath -DestinationPath $dest -Mode Full -Confirm:$false
$info = Get-VHD -Path $dest
if ($info.ParentPath) { throw "Le disque $dest n'est pas autonome (ParentPath ≠ vide)" }
return $dest
}

# Exemple d'usage

$targets = @(
'D:\VMs\VM01\AVHDX5.avhdx',
'D:\VMs\VM01\AVHDX6.avhdx',
'D:\VMs\VM01\AVHDX7.avhdx',
'D:\VMs\VM01\AVHDX8.avhdx',
'D:\VMs\VM01\AVHDX9.avhdx'
)
$destFolder = 'D:\VMs\VM01\Merged'
$merged = foreach ($t in $targets) { Merge-BranchToNewVhd -ChildPath $t -DestinationFolder $destFolder }
$merged 

Remettre la VM en production (consolidation finale)

  1. Choisir le disque final parmi les VHDX fusionnés (ou le VHDX reconstruit via copie‑comparaison) : montez‑le, faites un ultime contrôle des données et de la cohérence applicative (bases, journaux).
  2. Basculer la VM :
    • Retirer en toute sécurité le disque actuel (checkpoint) du contrôleur virtuel.
    • Attacher le VHDX final (Set‑VMHardDiskDrive ou GUI).
    • Vérifier l’ordre de boot (UEFI/Gen2) si c’est le disque système.
  3. Démarrage et tests : services, journaux d’événements, intégrité applicative.
  4. Nouvelle sauvegarde complète : créez un point zéro propre pour la nouvelle chaîne.

Nettoyage post‑fusion

  • Inspect Disk (ou Get‑VHD) : confirmez qu’aucun AVHDX n’est encore rattaché à la VM.
  • Détruire les checkpoints résiduels : Get‑VMSnapshot -VMName <VM> | Remove‑VMSnapshot.
  • Archiver puis supprimer les anciens AVHDX/VHDX non utilisés après une période de sécurité (ex. 30 jours).

Prévention : durcir le runbook pour ne plus revivre ça

  • Politique de checkpoints : jamais de checkpoints persistants. Les checkpoints de production ne doivent vivre que le temps d’une sauvegarde.
  • Jobs de sauvegarde : configurez la suppression des checkpoints en fin de job et des alertes en cas d’échec de consolidation.
  • Surveillance quotidienne : un script qui reporte toute présence d’AVHDX > 24 h ou toute divergence de parent.
  • Capacité : gardez 20–30 % d’espace libre sur les volumes qui hébergent VHDX/AVHDX (les merges grossissent temporairement).
  • Procédure CSV/Cluster : standardisez les étapes (pause du rôle, orchestration multi‑nœuds) pour éviter des accès concurrents lors des merges.

FAQ express

Q : Puis‑je fusionner plusieurs enfants directement dans le même parent ?
R : Techniquement oui, mais chaque fusion écrase l’état du parent. Les autres enfants deviennent orphelins. C’est dangereux si des données uniques existent dans ces branches.

Q : L’ordre des merges a‑t‑il de l’importance ?
R : Oui uniquement si vous mergez vers le parent (ex. 9→4, puis 8→4). Si vous mergez vers un nouveau VHDX, l’ordre est indifférent.

Q : Comment vérifier qu’un VHDX final est autonome ?
R : Get‑VHD -Path <fichier.vhdx> doit afficher un ParentPath vide (ou non applicable). L’inspection GUI « Inspect Disk » doit confirmer l’absence de parent.

Q : Puis‑je faire tout cela « à chaud » ?
R : Mieux vaut l’éviter. La fusion « live » augmente les risques (accès concurrent, I/O). Privilégiez une fenêtre de maintenance.

Points clés à retenir

  • Lorsque plusieurs AVHDX partagent le même parent, ils sont indépendants ; fusionner l’un modifie (ou remplace) le parent et rend les autres impropres à la lecture.
  • La méthode la plus sûre consiste à fusionner chaque branche vers un nouveau disque avant de décider de la consolidation finale.
  • Le choix de l’ordre (du plus récent au plus ancien) n’a d’impact que si l’on souhaite écraser le parent ; sinon, l’ordre importe peu.
  • Documenter et automatiser la gestion des checkpoints réduit radicalement le risque de répéter ce scénario.

Modèle de procédure (prêt à l’emploi)

# 0) Sauvegarde à froid des VHDX/AVHDX
# 1) Cartographie
Get-ChildItem *.avhdx | ForEach-Object { Get-VHD -Path $_.FullName | Select Path, ParentPath }

# 2) Fusion sûre pour chaque branche (5-9)

$branches = 5..9 | ForEach-Object { "D:\VMs\VM01\AVHDX$_.avhdx" }
$dest = "D:\VMs\VM01\Merged"
$merged = @()
foreach ($b in $branches) {
$name = [IO.Path]::GetFileNameWithoutExtension($b)
$out  = Join-Path $dest "$name-merged.vhdx"
Merge-VHD -Path $b -DestinationPath $out -Mode Full
$merged += $out
}

# 3) Contrôle d'autonomie

$merged | ForEach-Object { Get-VHD -Path $_ | Select Path, ParentPath }

# 4) Choix du disque final, attachement à la VM

# 5) Remove-VMSnapshot, purge des résidus

# 6) Full backup

Estimation d’espace et de temps : savoir dimensionner

Un merge vers un nouveau VHDX requiert approximativement l’espace de la taille logique utilisée par le disque invité, plus une marge pour l’overhead VHDX. Si votre VM a un disque de 500 Go dont 300 Go occupés, prévoyez ~320 Go libres par branche à fusionner. Ajoutez 10–20 % de marge pour être confortable lors des vérifications et copies complémentaires. Le temps dépend du support (I/O séquentielles), du nombre de fichiers fragmentés et de la compression/déduplication éventuelle (évitez de merger directement sur un volume dédupliqué).

Erreurs fréquentes à éviter

  • Reconnecter un enfant sur le mauvais parent (block size/ID différents) : corruption immédiate. Toujours valider ParentPath avec Get‑VHD.
  • Exécuter un job de sauvegarde pendant la consolidation : risque de nouveaux checkpoints au milieu du merge.
  • Manque d’espace en cours d’opération : provoque des échecs partiels et des disques « à moitié » fusionnés.
  • Supprimer des AVHDX avant validation : conservez‑les jusqu’à la fin de la recette et la réussite du full backup.
  • Oublier les volumes CSV/SMB : coordonnez avec le cluster et les verrous de fichiers. Utilisez une fenêtre de maintenance.

En synthèse : face à un arbre de checkpoints où plusieurs AVHDX (5‑9) partagent le parent 4, traitez chaque branche comme une réalité alternative indépendante. Fusionnez‑les vers de nouveaux VHDX, comparez, puis consolidez sereinement. C’est un peu plus long… et infiniment plus sûr.

Sommaire