J’ai une infrastructure virtualisée sous Hyper-V avec un serveur de backup/DRP séparé. La stack complète tourne sur 19 VMs : deux domaines Active Directory avec approbation, des DNS forwarders Linux (BIND9), du RADIUS, du monitoring, des services applicatifs… bref, pas un truc qu’on peut se permettre de remettre en route n’importe comment.

L’objectif du projet était simple à énoncer, complexe à réaliser :

Basculer toute l’infra sur le serveur DRP en un minimum d’actions, avec continuité de service garantie (en tout cas ne pas couper l’aces a internet) puis revenir en prod proprement.

Spoiler : ça m’a pris plusieurs itérations, quelques sueurs froides et une bonne dose de PowerShell pour y arriver. Quelques litres de café aussi…


Architecture

HYPERV1 (Hyper-V prod)
├── 2 domaines AD avec approbation
│   ├── Domain1 : SRV-PDC1 (PDC + DNS intégré AD)
│   │             SRV-DC1  (DC secondaire + DNS intégré AD)
│   └── Domain2 : SRV-PDC2 (PDC + DNS intégré AD + DHCP)
│                 SRV-DC2  (DC secondaire + DNS intégré AD + DHCP)
├── 2 DNS forwarders Linux BIND9 + NTP
│   ├── SRV-DNS1 (DNS forwarder primaire + NTP)
│   └── SRV-DNS2 (DNS forwarder secondaire + NTP)
├── RADIUS, Proxy, SMTP, SIEM, Monitoring...
└── 3 workstations virtuelles

HYPERV2 (Hyper-V DRP)
└── Veeam B&R 12 + réplicas NVMe de toutes les VMs

La réplication tourne 4 fois par semaine avec une rétention de 2 restore points — suffisant pour garantir un RPO de 24h maximum.


Les deux scénarios

Dès le départ, j’ai identifié deux cas d’usage radicalement différents :

FULL DRP — Crash réel La prod est morte. On bascule tout immédiatement. Les PDC démarrent en premier, le reste suit. Pas de continuité à gérer puisque tout est déjà down.

MCO DRP — Maintenance planifiée La prod tourne encore. On doit basculer sans interruption de service. Là, l’ordre de démarrage devient critique — il faut toujours avoir au moins un DC par domaine et un DNS forwarder actifs quelque part sur le réseau.

Cette distinction a imposé la création de deux Failover Plans distincts dans Veeam, avec des ordres de démarrage différents.


Le Failover Plan MCO : l’ordre qui change tout

Pour la maintenance planifiée, la séquence correcte est la suivante :

Le principe repose sur deux groupes qui basculent en alternance, garantissant qu’à chaque instant Domain1, Domain2 et le DNS ont toujours au moins un service actif quelque part sur le réseau.

Groupe 1 — PDC Domain1 + DC secondaire Domain2 + DNS forwarder primaire : SRV-PDC1 · SRV-DC2 · SRV-DNS1

Groupe 2 — DC secondaire Domain1 + PDC Domain2 + DNS forwarder secondaire : SRV-DC1 · SRV-PDC2 · SRV-DNS2

┌────────────────────────────────────────────────────────────────────────────────────────┐
│                     HYPERV1 (prod)                     HYPERV2 (DRP)                   │
│                  Domain1  Domain2  DNS              Domain1  Domain2  DNS              │
├────────────────────────────────────────────────────────────────────────────────────────┤
│ Départ     SRV-PDC1 ✅  SRV-PDC2 ✅  DNS1 ✅             —        —        —          │
│            SRV-DC1  ✅  SRV-DC2  ✅  DNS2 ✅                                          │
├────────────────────────────────────────────────────────────────────────────────────────┤
│ Etape 1    SRV-PDC1 ⬇️  SRV-PDC2 ✅  DNS1 ⬇️             —        —        —          │
│ Groupe 1   SRV-DC1  ✅  SRV-DC2  ⬇️  DNS2 ✅                                          │
│ down H1    ↑DC1+PDC2+DNS2 encore UP sur HYPERV1 → continuité garantie ✅               │
├────────────────────────────────────────────────────────────────────────────────────────┤
│ Etape 2    SRV-DC1  ✅  SRV-PDC2 ✅  DNS2 ✅     SRV-PDC1 🔄  SRV-DC2 🔄  DNS1 🔄   │
│ Groupe 1   ↑Groupe 2 reste UP sur HYPERV1            ↑Groupe 1 démarre sur HYPERV2     │
│ up H2      ↑Failover Plan MCO-DRP Wave 1 lancé                                         │
├────────────────────────────────────────────────────────────────────────────────────────┤
│ Etape 3    SRV-DC1  ✅  SRV-PDC2 ✅  DNS2 ✅   SRV-PDC1 ✅  SRV-DC2 ✅  DNS1 ✅     │
│ Groupe 1   ↑Groupe 2 encore UP sur HYPERV1          ↑Groupe 1 confirmé Running H2      │
│ confirmé                                                                               │
├────────────────────────────────────────────────────────────────────────────────────────┤
│ Etape 4    SRV-DC1  ⬇️  SRV-PDC2 ⬇️  DNS2 ⬇️   SRV-PDC1 ✅  SRV-DC2 ✅  DNS1 ✅     │ 
│ Groupe 2   ↑Groupe 2 éteint sur HYPERV1            ↑PDC1+DC2+DNS1 up sur HYPERV2       │
│ down H1                                             → continuité garantie ✅          │
├────────────────────────────────────────────────────────────────────────────────────────┤
│ Etape 5    —                                    SRV-PDC1 ✅  SRV-DC2 ✅  DNS1 ✅      │
│ Groupe 2                                        SRV-DC1  ✅  SRV-PDC2 ✅  DNS2 ✅     │
│ up H2                                                  ↑Tout sur HYPERV2 ✅           │
└────────────────────────────────────────────────────────────────────────────────────────┘

À chaque étape, Domain1 a toujours un DC actif, Domain2 aussi, et le DNS forwarder répond toujours. Aucune interruption de service AD, DNS ou DHCP pendant toute la durée de la bascule.


Le script Start-DRP.ps1

Tout est automatisé depuis HYPERV2 en PowerShell. Le script gère les deux modes via un menu interactif :

.\Start-DRP.ps1
============================================================
  PROCEDURE DRP - Choisissez le mode de bascule
============================================================

  [1] CRASH  - Prod en panne, démarrage immédiat
               Utilise le Failover Plan : FULL DRP

  [2] MCO    - Maintenance planifiée, continuité garantie
               Utilise le Failover Plan : MCO DRP

  [3] MCO + Skip  - MCO sans réplication préalable
============================================================
Votre choix (1/2/3) :

Après confirmation, le script enchaîne automatiquement :

  1. Réplication manuelle — dernière synchro avant bascule
  2. Flag anti-shutdown — bloque le post-script qui éteint HYPERV1 après réplication
  3. Shutdown ordonné des VMs non critiques (waves 5→3)
  4. En mode MCO : extinction du Groupe 1, lancement du plan, attente ping Groupe 1, extinction Groupe 2
  5. En mode CRASH : extinction de tout, lancement du plan

La vérification du démarrage des réplicas se fait via Hyper-V local (Get-VM SRV-DC1_VeeamReplica) et non par le réseau — évite les faux positifs quand la même IP répond encore depuis HYPERV1.


Le retour en prod : Start-FailbackToProd.ps1

C’est là que les choses se sont compliquées. Plusieurs versions ont été nécessaires pour arriver à quelque chose de propre.

Les pièges Veeam 12

Piège 1 — Get-VBRJobSession n’existe plus Remplacée par Get-VBRSession avec un filtre sur JobName.

Piège 2 — Le commit sur le mauvais restore point Après un Start-VBRHvReplicaFailback, Veeam crée un nouveau restore point (index 0). Si on passe ce RP à Stop-VBRHvReplicaFailback pour committer, la VM se retrouve bloquée en LockedItem. Il faut committer sur l’index 1 — le RP d’avant le failback.

# Index 0 = nouveau RP créé par le failback → NE PAS committer ça
# Index 1 = ancien RP Failover → c'est lui qu'on committe
$rpCommit = Get-VBRRestorePoint |
    Where-Object { $_.IsReplica() -and $_.VmName -eq $vmName } |
    Sort-Object CreationTime -Descending |
    Select-Object -Skip 1 -First 1

Stop-VBRHvReplicaFailback -RestorePoint $rpCommit

Piège 3 — Le Failover Plan qui redémarre les réplicas Tant que le Failover Plan est actif, il redémarre automatiquement les réplicas après chaque commit. La solution : utiliser Stop-VBRReplicaFailover individuellement sur chaque VM avant de lancer son failback. Ça éteint proprement le replica sans toucher aux autres.

# Undo individuel — n'affecte pas les autres VMs du plan
Stop-VBRReplicaFailover -RestorePoint $rpFailover

Piège 4 — Le VHDX encore verrouillé Après le commit, Hyper-V n’a pas encore libéré le fichier VHDX. Un Start-VM immédiat échoue avec “Le processus ne peut pas accéder au fichier”. Solution : attendre 15 secondes entre le commit et le démarrage de la VM.

La séquence finale pour chaque VM

1. Stop-VBRReplicaFailover (index 0)
   → Eteint proprement le replica sur HYPERV2

2. Start-VBRHvReplicaFailback (SANS RunAsync = bloquant)
   → Resync complète vers HYPERV1
   → Veeam crée un nouveau RP

3. Stop-VBRHvReplicaFailback (index 1)
   → Commit

4. Start-Sleep 15
   → Libération VHDX

5. Start-VM sur HYPERV1
   → Délai wave → VM suivante

L’ordre de retour pour la continuité AD/DNS

SRV-PDC1  (PDC Domain1 + DNS AD)          120s
SRV-PDC2  (PDC Domain2 + DNS AD + DHCP)    90s
SRV-DNS1  (DNS forwarder + NTP)            60s
SRV-DC1   (DC2 Domain1 + DNS AD)           60s
SRV-DC2   (DC2 Domain2 + DNS AD + DHCP)    60s
SRV-DNS2  (DNS forwarder + NTP)            30s
... services applicatifs ...
... workstations ...

La logique est identique au MCO DRP : à chaque instant, au moins un DC par domaine et un DNS sont actifs quelque part sur le réseau.


Les bugs qui m’ont coûté du temps

Le module Hyper-V pas chargé dans le contexte script

Get-VM fonctionnait parfaitement en session interactive mais retournait null dans le script. Cause : le module Hyper-V n’est pas chargé automatiquement dans un contexte PowerShell non-interactif. Fix :

Import-Module Hyper-V -ErrorAction Stop -WarningAction SilentlyContinue

L’état .State retourné comme PSObject via WinRM

Quand on interroge l’état d’une VM via Invoke-Command, .State retourne un objet PSObject et non une string. La comparaison $state -eq "Off" échoue silencieusement. Fix :

(Get-VM -Name $name).State.ToString()

Get-VBRSession demande un paramètre obligatoire

Contrairement à ce que laisse penser la documentation, Get-VBRSession sans paramètre ouvre un prompt interactif. Il faut passer -ErrorAction SilentlyContinue pour éviter le blocage dans un script automatisé.

Le deadlock MCO

Première implémentation : le script attendait que les réplicas soient Running sur HYPERV2 avant de les éteindre sur HYPERV1. Problème : Veeam ne démarre pas un replica tant que la VM source tourne. Résultat : deadlock infini. La solution était de les éteindre avant de lancer le Failover Plan, puis d’attendre leur démarrage.


Résultats

Après une journée de tests et d’itérations, les deux scripts fonctionnent en production :

Scénario Temps total Interruption AD/DNS
MCO DRP (bascule) ~8 min 0 seconde
Failback MCO ~25 min 0 seconde
FULL DRP (crash) ~3 min Non applicable

Le temps de failback est plus long car chaque VM est traitée séquentiellement avec sa resync individuelle — c’est le prix de la continuité de service.


Ce que j’aurais fait différemment

Tester sur une VM isolée avant de tout automatiser. Chaque piège Veeam (commit index 1, RunAsync vs bloquant, Stop-VBRReplicaFailover) aurait pu être découvert sur une seule VM avant de l’intégrer dans le script complet.

Documenter les commandes Veeam PowerShell au fur et à mesure. La documentation officielle est incomplète sur certains points (comportement de Start-VBRHvReplicaFailback avec -RunAsync, gestion du RP après failback). Les forums Veeam sont plus fiables.


Les scripts

Les deux scripts sont conçus pour tourner depuis HYPERV2 (le serveur DRP). Ils incluent un changelog versionné, un mode -WhatIf pour simuler sans action, et des logs horodatés complets dans C:\Scripts\DRP\Logs\.

Start-DRP.ps1

Bascule de HYPERV1 (prod) vers HYPERV2 (DRP). Menu interactif au lancement.

# Usage
.\Start-DRP.ps1                           # Menu interactif
.\Start-DRP.ps1 -Mode MCO                 # MCO direct
.\Start-DRP.ps1 -Mode CRASH               # Crash direct
.\Start-DRP.ps1 -Mode MCO -SkipReplication # MCO sans réplication
# =============================================================================
# Start-DRP.ps1
# Script de bascule DRP complet depuis GIEDI PRIME
#
# Deux modes de bascule :
#
#   MODE CRASH (defaut) :
#     Utilise quand la prod est en panne ou inaccessible.
#     Demarre toutes les VMs sur HYPERV2 via le Failover Plan "FULL-DRP"
#     sans se preoccuper de la continuite de service.
#     Etapes :
#       1. Replication Veeam
#       2. Shutdown toutes les VMs sur HYPERV1
#       3. Lancement Failover Plan "FULL-DRP"
#
#   MODE MCO (maintenance planifiee) :
#     Utilise pour une maintenance programmee avec continuite de service.
#     Garantit qu'un DC par domaine et un DNS sont toujours up pendant la bascule.
#     Etapes :
#       1. Replication Veeam
#       2. Shutdown waves 5/4/3 sur HYPERV1
#       3. Lancement Failover Plan "MCO-DRP"
#       4. Attente Groupe 1 Running sur HYPERV2 (SRV-DC1+SRV-DC2+SRV-DNS1)
#          puis extinction Groupe 1 sur HYPERV1
#       5. Attente Groupe 2 Running sur HYPERV2 (SRV-PDC1+SRV-PDC2+SRV-DNS2)
#          puis extinction Groupe 2 sur HYPERV1
#
# Prerequis :
#   - WinRM actif sur HYPERV1
#   - Droits admin sur HYPERV1
#   - Veeam Backup & Replication console installee sur GIEDI PRIME
#   - Script a lancer en PowerShell Administrator sur GIEDI PRIME
#   - Failover Plans "FULL-DRP" et "MCO-DRP" crees dans Veeam
#
# Usage :
#   .\Start-DRP.ps1                          -> Mode CRASH + replication
#   .\Start-DRP.ps1 -SkipReplication         -> Mode CRASH sans replication
#   .\Start-DRP.ps1 -Mode MCO                -> Mode MCO + replication
#   .\Start-DRP.ps1 -Mode MCO -SkipReplication -> Mode MCO sans replication
#
# =============================================================================
# WARNING  MAINTENANCE DU SCRIPT - LIRE AVANT TOUTE MODIFICATION
# =============================================================================
#
# A chaque ajout ou suppression d'une VM dans l'infrastructure :
#
#   1. Mettre a jour les Failover Plans "FULL-DRP" et "MCO-DRP" dans Veeam
#   2. Mettre a jour $VMShutdownNonCritical, $MCOGroupe1 et $MCOGroupe2
#   3. Mettre a jour le CHANGELOG
#
# Rappel ordre FULL-DRP :
#   Wave 1 : SRV-PDC1 (120s) > SRV-PDC2 (90s) > SRV-DNS1 (60s)
#   Wave 2 : SRV-DC1 (60s)   > SRV-DC2 (60s)         > SRV-DNS2 (30s)
#   Wave 3 : SRV-RADIUS (45s)  > SRV-PROXY (30s)        > SRV-SMTP (30s)  > SRV-PASSBOLT (30s)
#   Wave 4 : SRV-SIEM (45s)    > SRV-MONITORING (45s)         > SRV-WSUS (30s)
#            > SRV-PRINT (30s)    > SRV-PKI (20s)
#   Wave 5 : SRV-PXE (20s)  > WS-01 (20s)            > WS-02 (20s)
#            > WS-03 (20s)
#
# Rappel ordre MCO-DRP :
#   Wave 1 : SRV-DC1 (90s)   > SRV-DC2 (90s)         > SRV-DNS1 (60s)
#   Wave 2 : SRV-PDC1 (120s) > SRV-PDC2 (90s) > SRV-DNS2 (30s)
#   Wave 3 : SRV-RADIUS (45s)  > SRV-PROXY (30s)        > SRV-SMTP (30s)  > SRV-PASSBOLT (30s)
#   Wave 4 : SRV-SIEM (45s)    > SRV-MONITORING (45s)         > SRV-WSUS (30s)
#            > SRV-PRINT (30s)    > SRV-PKI (20s)
#   Wave 5 : SRV-PXE (20s)  > WS-01 (20s)            > WS-02 (20s)
#            > WS-03 (20s)
#
# =============================================================================
# CHANGELOG
# =============================================================================
# 2026-03-25 - v1.0 - Version initiale - 18 VMs
# 2026-03-25 - v1.1 - Correction nom du job : ReplicaVM-HYPERV1_Dayly
# 2026-03-25 - v1.2 - Ajout flag DRP
# 2026-03-28 - v1.3 - Import-Module au lieu de Add-PSSnapin
# 2026-03-28 - v1.4 - Correction detection fin de job + -SkipReplication
# 2026-03-28 - v1.5 - Correction Get-VBRSession
# 2026-03-28 - v1.6 - Correction .ToString() sur State via WinRM
# 2026-03-28 - v1.7 - Ajout verification NETLOGON DC critiques (mauvaise logique)
# 2026-03-28 - v1.8 - Correction Get-VBRSession -ErrorAction SilentlyContinue
# 2026-03-28 - v1.9 - Refactoring logique DC (mauvais ordre)
# 2026-03-28 - v2.0 - Refactoring complet logique DC/DNS par paires
# 2026-03-28 - v2.1 - Ajout mode MCO-DRP avec continuite de service garantie
#                     Groupe 1 (SRV-DC1+SRV-DC2+SRV-DNS1) demarre sur HYPERV2
#                     puis s'eteint sur HYPERV1 avant Groupe 2
#                     Verification via Hyper-V local (Get-VM _VeeamReplica)
#                     pour eviter tout conflit nom/IP pendant la bascule
# 2026-03-28 - v2.2 - Ajout menu interactif si -Mode non specifie
#                     Confirmation avant lancement
# 2026-03-28 - v2.3 - Correction null state sur Get-VM
# 2026-03-28 - v2.4 - Correction deadlock MCO :
#                     Groupe 1 s'eteint sur HYPERV1 AVANT le Failover Plan
#                     Le Failover Plan demarre Groupe 1 sur HYPERV2
#                     Puis Groupe 2 s'eteint sur HYPERV1
# =============================================================================

param(
    [ValidateSet("CRASH","MCO")]
    [string]$Mode = "",
    [switch]$SkipReplication
)

# --- MENU INTERACTIF ---------------------------------------------------------
# Si Mode non specifie en parametre, afficher le menu de selection

if ($Mode -eq "") {
    Write-Host ""
    Write-Host "============================================================" -ForegroundColor Cyan
    Write-Host "  PROCEDURE DRP - Choisissez le mode de bascule" -ForegroundColor Cyan
    Write-Host "============================================================" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "  [1] CRASH        " -ForegroundColor Red -NoNewline
    Write-Host "- Prod en panne, demarrage immediat sur GIEDI PRIME"
    Write-Host "               Utilise le Failover Plan : FULL-DRP"
    Write-Host ""
    Write-Host "  [2] MCO          " -ForegroundColor Yellow -NoNewline
    Write-Host "- Maintenance planifiee, continuite de service garantie"
    Write-Host "               Utilise le Failover Plan : MCO-DRP"
    Write-Host ""
    Write-Host "  [3] MCO + Skip   " -ForegroundColor Yellow -NoNewline
    Write-Host "- MCO sans replication (replicas deja a jour)"
    Write-Host "               Utilise le Failover Plan : MCO-DRP"
    Write-Host ""
    Write-Host "============================================================" -ForegroundColor Cyan
    Write-Host ""
    $choix = Read-Host "Votre choix (1/2/3)"

    switch ($choix) {
        "1" {
            $Mode = "CRASH"
            Write-Host ""
            Write-Host "Mode CRASH selectionne." -ForegroundColor Red
        }
        "2" {
            $Mode = "MCO"
            Write-Host ""
            Write-Host "Mode MCO selectionne." -ForegroundColor Yellow
        }
        "3" {
            $Mode = "MCO"
            $SkipReplication = $true
            Write-Host ""
            Write-Host "Mode MCO + SkipReplication selectionne." -ForegroundColor Yellow
        }
        default {
            Write-Host ""
            Write-Host "Choix invalide. Arret du script." -ForegroundColor Red
            exit 1
        }
    }

    # Confirmation avant de lancer
    Write-Host ""
    Write-Host "============================================================" -ForegroundColor Cyan
    Write-Host "  CONFIRMATION" -ForegroundColor Cyan
    Write-Host "============================================================" -ForegroundColor Cyan
    Write-Host "  Mode         : $Mode" -ForegroundColor White
    Write-Host "  Failover Plan: $(if ($Mode -eq 'MCO') { 'MCO-DRP' } else { 'FULL-DRP' })" -ForegroundColor White
    Write-Host "  Replication  : $(if ($SkipReplication) { 'IGNOREE' } else { 'OUI' })" -ForegroundColor White
    Write-Host "  Hote prod    : HYPERV1" -ForegroundColor White
    Write-Host "============================================================" -ForegroundColor Cyan
    Write-Host ""
    $confirm = Read-Host "Confirmer le lancement ? (O/N)"
    if ($confirm -notmatch "^[Oo]$") {
        Write-Host "Annule par l'utilisateur." -ForegroundColor Yellow
        exit 0
    }
}

# --- CONFIGURATION -----------------------------------------------------------
$ScriptVersion        = "2.8"
$VeeamReplicaJobName  = "ReplicaVM-HYPERV1_Dayly"
$VeeamFailoverPlan    = if ($Mode -eq "MCO") { "MCO-DRP" } else { "FULL-DRP" }
$ProdHost             = "HYPERV1"
$LogFile              = "C:\Scripts\DRP\Logs\DRP_${Mode}_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
$ShutdownTimeout      = 300   # Secondes max pour attendre le shutdown d'une VM (5 min)
$ReplicationTimeout   = 7200  # Secondes max pour attendre la fin de la replication (2h)
$VMReadyTimeout       = 600   # Secondes max pour attendre qu'une VM soit Running sur HYPERV2 (10 min)
$VeeamModule          = "C:\Program Files\Veeam\Backup and Replication\Console\Veeam.Backup.PowerShell.dll"

# VMs non critiques - shutdown dans les 2 modes (waves 5, 4, 3)
$VMShutdownNonCritical = @(
    # Wave 5 - Workstations
    "WS-03", "WS-02", "WS-01",
    # Wave 4 - Services applicatifs
    "SRV-PXE", "SRV-PKI", "SRV-PRINT", "SRV-WSUS", "SRV-MONITORING", "SRV-SIEM",
    # Wave 3 - Services reseau
    "SRV-PASSBOLT", "SRV-SMTP", "SRV-PROXY", "SRV-RADIUS"
)

# Mode CRASH - shutdown complet waves 2 et 1 (apres non critiques)
$VMShutdownCrash = @(
    "SRV-DNS2", "SRV-DC2", "SRV-DC1",
    "SRV-DNS1", "SRV-PDC2", "SRV-PDC1"
)

# Mode MCO - Groupe 1 : DC secondaires + DNS primaire
# Demarrent en Wave 1 du MCO-DRP → s'eteignent sur HYPERV1 une fois up sur HYPERV2
# (leurs homologues SRV-PDC1, SRV-PDC2, SRV-DNS2 restent up sur HYPERV1)
$MCOGroupe1 = @("SRV-DC1", "SRV-DC2", "SRV-DNS1")

# Mode MCO - Groupe 2 : DC primaires + DNS failover
# Demarrent en Wave 2 du MCO-DRP → s'eteignent sur HYPERV1 une fois up sur HYPERV2
$MCOGroupe2 = @("SRV-PDC1", "SRV-PDC2", "SRV-DNS2")

# --- FONCTIONS ---------------------------------------------------------------

function Write-Log {
    param([string]$Message, [string]$Level = "INFO")
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $line = "[$timestamp] [$Level] $Message"
    Write-Host $line -ForegroundColor $(switch ($Level) {
        "INFO"    { "Cyan" }
        "OK"      { "Green" }
        "WARN"    { "Yellow" }
        "ERROR"   { "Red" }
        default   { "White" }
    })
    Add-Content -Path $LogFile -Value $line
}

function Wait-VMOff {
    param([string]$VMName, [int]$TimeoutSec = $ShutdownTimeout)
    $elapsed = 0
    while ($elapsed -lt $TimeoutSec) {
        $state = Invoke-Command -ComputerName $ProdHost -ScriptBlock {
            param($name)
            (Get-VM -Name $name -ErrorAction SilentlyContinue).State.ToString()
        } -ArgumentList $VMName
        if ($state -eq "Off") { return $true }
        Start-Sleep -Seconds 5
        $elapsed += 5
    }
    return $false
}

function Stop-VMProprement {
    param([string]$VMName)
    $vmState = Invoke-Command -ComputerName $ProdHost -ScriptBlock {
        param($name)
        $v = Get-VM -Name $name -ErrorAction SilentlyContinue
        if ($v) { $v.State.ToString() } else { "NotFound" }
    } -ArgumentList $VMName

    if ($vmState -eq "NotFound") {
        Write-Log "VM '$VMName' introuvable sur $ProdHost, ignoree" "WARN"
        return
    }
    if ($vmState -eq "Off") {
        Write-Log "VM '$VMName' deja eteinte, ignoree" "OK"
        return
    }

    Write-Log "Arret de '$VMName' (etat: $vmState)..."
    Invoke-Command -ComputerName $ProdHost -ScriptBlock {
        param($name)
        Stop-VM -Name $name -Force -ErrorAction SilentlyContinue
    } -ArgumentList $VMName

    $isOff = Wait-VMOff -VMName $VMName
    if ($isOff) {
        Write-Log "VM '$VMName' eteinte proprement" "OK"
    } else {
        Write-Log "VM '$VMName' n'a pas repondu, power off force..." "WARN"
        Invoke-Command -ComputerName $ProdHost -ScriptBlock {
            param($name)
            Stop-VM -Name $name -TurnOff -Force -ErrorAction SilentlyContinue
        } -ArgumentList $VMName
        Start-Sleep -Seconds 10
        $finalState = Invoke-Command -ComputerName $ProdHost -ScriptBlock {
            param($name)
            (Get-VM -Name $name).State.ToString()
        } -ArgumentList $VMName
        if ($finalState -eq "Off") {
            Write-Log "VM '$VMName' eteinte de force" "OK"
        } else {
            Write-Log "VM '$VMName' impossible a eteindre ! (etat: $finalState)" "ERROR"
        }
    }
}

function Wait-VMRunningLocal {
    # Verifie que la VM replica est Running sur HYPERV2 via Hyper-V local
    # On verifie le nom _VeeamReplica directement sur l hyperviseur local
    # Pas de dependance reseau — evite tout conflit avec la VM source sur HYPERV1
    param([string]$VMName, [int]$TimeoutSec = $VMReadyTimeout)
    $replicaName = "${VMName}_VeeamReplica"
    $elapsed = 0
    Write-Log "Attente '$replicaName' Running sur GIEDI PRIME (Hyper-V local)..." "WARN"
    while ($elapsed -lt $TimeoutSec) {
        $vm = Get-VM -Name $replicaName -ErrorAction SilentlyContinue
        $state = if ($vm) { $vm.State.ToString() } else { "NotFound" }
        if ($state -eq "Running") {
            Write-Log "'$VMName' confirme Running sur GIEDI PRIME" "OK"
            return $true
        }
        Write-Log "'$VMName' pas encore Running (etat: $state) — $([math]::Round($elapsed/60,1)) min" "WARN"
        Start-Sleep -Seconds 15
        $elapsed += 15
    }
    Write-Log "'$VMName' pas Running apres $($TimeoutSec/60) min — on continue quand meme" "ERROR"
    return $false
}

function Wait-GroupeRunning {
    param([string[]]$VMNames)
    Write-Log "Attente que toutes les VMs du groupe soient Running sur GIEDI PRIME..."
    $allReady = $true
    foreach ($vmName in $VMNames) {
        $ready = Wait-VMRunningLocal -VMName $vmName
        if (-not $ready) { $allReady = $false }
    }
    return $allReady
}

# --- INITIALISATION ----------------------------------------------------------

$logDir = Split-Path $LogFile
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }

Write-Log "============================================================"
Write-Log "DEBUT DE LA PROCEDURE DRP"
Write-Log "Version      : $ScriptVersion"
Write-Log "Mode         : $Mode"
Write-Log "Failover Plan: $VeeamFailoverPlan"
Write-Log "Job Veeam    : $VeeamReplicaJobName"
Write-Log "Hote prod    : $ProdHost"
if ($SkipReplication) { Write-Log "Replication  : ignoree (-SkipReplication)" "WARN" }
Write-Log "============================================================"

# Charger le module Hyper-V (necessaire pour Get-VM dans le contexte script)
Write-Log "Chargement du module Hyper-V..."
try {
    Import-Module Hyper-V -ErrorAction Stop -WarningAction SilentlyContinue
    Write-Log "Module Hyper-V charge" "OK"
} catch {
    Write-Log "Impossible de charger le module Hyper-V : $_" "ERROR"
    exit 1
}

# Charger le module Veeam
Write-Log "Chargement du module Veeam PowerShell..."
try {
    Import-Module $VeeamModule -ErrorAction Stop -WarningAction SilentlyContinue
    Write-Log "Module Veeam charge" "OK"
} catch {
    Write-Log "Impossible de charger le module Veeam : $_" "ERROR"
    exit 1
}

# Verifier WinRM sur HYPERV1
try {
    Invoke-Command -ComputerName $ProdHost -ScriptBlock { $env:COMPUTERNAME } -ErrorAction Stop | Out-Null
    Write-Log "Connexion WinRM vers $ProdHost OK" "OK"
} catch {
    Write-Log "Impossible de se connecter a $ProdHost via WinRM : $_" "ERROR"
    exit 1
}

# --- ETAPE 1 : REPLICATION ---------------------------------------------------

if ($SkipReplication) {
    Write-Log "------------------------------------------------------------"
    Write-Log "ETAPE 1 : Replication ignoree (-SkipReplication)" "WARN"
} else {
    Write-Log "------------------------------------------------------------"
    Write-Log "ETAPE 1 : Lancement du job de replication '$VeeamReplicaJobName'"

    $flagFile = "C:\Scripts\DRP\DRP_MODE.flag"
    try {
        Invoke-Command -ComputerName $ProdHost -ScriptBlock {
            param($f) New-Item -Path $f -ItemType File -Force | Out-Null
        } -ArgumentList $flagFile
        Write-Log "Flag DRP cree sur $ProdHost : $flagFile" "OK"
    } catch {
        Write-Log "Impossible de creer le flag DRP sur $ProdHost : $_" "ERROR"
        exit 1
    }

    try {
        $job = Get-VBRJob -Name $VeeamReplicaJobName -ErrorAction Stop
    } catch {
        Write-Log "Job de replication introuvable : $_" "ERROR"
        Invoke-Command -ComputerName $ProdHost -ScriptBlock {
            param($f) Remove-Item $f -Force -ErrorAction SilentlyContinue
        } -ArgumentList $flagFile
        exit 1
    }

    if ($job.IsRunning) {
        Write-Log "Le job est deja en cours, attente de fin..." "WARN"
    } else {
        Start-VBRJob -Job $job | Out-Null
        Write-Log "Job de replication demarre" "OK"
    }

    Write-Log "Attente du demarrage effectif du job..."
    Start-Sleep -Seconds 20

    Write-Log "Attente de la fin de la replication (timeout $($ReplicationTimeout/60) min)..."
    $elapsed = 0
    $success = $false
    while ($elapsed -lt $ReplicationTimeout) {
        $job = Get-VBRJob -Name $VeeamReplicaJobName
        if (-not $job.IsRunning) {
            $lastSession = Get-VBRSession -ErrorAction SilentlyContinue |
                Where-Object { $_.JobName -eq $VeeamReplicaJobName } |
                Sort-Object CreationTime -Descending |
                Select-Object -First 1
            if ($lastSession -and ($lastSession.Result -eq "Success" -or $lastSession.Result -eq "Warning")) {
                Write-Log "Replication terminee avec succes (Result: $($lastSession.Result))" "OK"
                $success = $true
                break
            } elseif ($lastSession -and $lastSession.Result -ne "" -and $lastSession.Result -ne "None") {
                Write-Log "Replication terminee en ERREUR (Result: $($lastSession.Result))" "ERROR"
                break
            } else {
                Write-Log "Job demarre, en attente du resultat..." "INFO"
                Start-Sleep -Seconds 30
                $elapsed += 30
                continue
            }
        }
        Start-Sleep -Seconds 30
        $elapsed += 30
        Write-Log "Replication en cours... ($([math]::Round($elapsed/60,1)) min ecoulees)"
    }

    if (-not $success) {
        Write-Log "La replication a echoue ou depasse le timeout. Arret du script." "ERROR"
        Write-Log "Relancer avec -SkipReplication si les replicas sont a jour." "ERROR"
        exit 1
    }
}

# --- ETAPE 2 : SHUTDOWN VMs NON CRITIQUES ------------------------------------

Write-Log "------------------------------------------------------------"
Write-Log "ETAPE 2 : Shutdown VMs non critiques sur $ProdHost (waves 5/4/3)"

foreach ($vmName in $VMShutdownNonCritical) {
    Stop-VMProprement -VMName $vmName
}

# --- ETAPE 3 : SHUTDOWN DC/DNS + LANCEMENT FAILOVER PLAN --------------------

Write-Log "------------------------------------------------------------"

if ($Mode -eq "CRASH") {
    # Mode CRASH : on eteint tout puis on lance le Failover Plan
    Write-Log "ETAPE 3 : Mode CRASH — Shutdown DC/DNS sur $ProdHost"
    foreach ($vmName in $VMShutdownCrash) {
        Stop-VMProprement -VMName $vmName
    }

    Write-Log "------------------------------------------------------------"
    Write-Log "ETAPE 4 : Lancement du Failover Plan '$VeeamFailoverPlan'"
    try {
        $fp = Get-VBRFailoverPlan -Name $VeeamFailoverPlan -ErrorAction Stop
        Start-VBRFailoverPlan -FailoverPlan $fp | Out-Null
        Write-Log "Failover Plan lance — VMs en cours de demarrage sur GIEDI PRIME" "OK"
    } catch {
        Write-Log "Erreur au lancement du Failover Plan : $_" "ERROR"
        exit 1
    }

} else {
    # Mode MCO : sequence en 2 groupes avec continuite de service
    #
    # Sequence :
    #   1. Eteindre Groupe 1 sur HYPERV1 (SRV-DC1+SRV-DC2+SRV-DNS1)
    #      SRV-PDC1+SRV-PDC2+SRV-DNS2 encore up sur HYPERV1 -> continuite AD/DNS
    #   2. Lancer le Failover Plan MCO-DRP
    #      -> Wave 1 demarre SRV-DC1+SRV-DC2+SRV-DNS1 sur HYPERV2
    #   3. Attendre Groupe 1 Running sur HYPERV2
    #   4. Eteindre Groupe 2 sur HYPERV1 (SRV-PDC1+SRV-PDC2+SRV-DNS2)
    #      SRV-DC1+SRV-DC2+SRV-DNS1 up sur HYPERV2 -> continuite AD/DNS

    Write-Log "ETAPE 3 : Mode MCO — Extinction Groupe 1 sur $ProdHost"
    Write-Log "Groupe 1 : SRV-DC1 + SRV-DC2 + SRV-DNS1"
    Write-Log "SRV-PDC1 + SRV-PDC2 + SRV-DNS2 restent up sur $ProdHost -> continuite AD/DNS" "WARN"

    foreach ($vmName in $MCOGroupe1) {
        Stop-VMProprement -VMName $vmName
    }
    Write-Log "Groupe 1 eteint sur $ProdHost" "OK"

    Write-Log "------------------------------------------------------------"
    Write-Log "ETAPE 4 : Lancement du Failover Plan '$VeeamFailoverPlan'"
    try {
        $fp = Get-VBRFailoverPlan -Name $VeeamFailoverPlan -ErrorAction Stop
        Start-VBRFailoverPlan -FailoverPlan $fp | Out-Null
        Write-Log "Failover Plan lance — Wave 1 demarre Groupe 1 sur GIEDI PRIME" "OK"
    } catch {
        Write-Log "Erreur au lancement du Failover Plan : $_" "ERROR"
        exit 1
    }

    Write-Log "------------------------------------------------------------"
    Write-Log "ETAPE 5 : Attente Groupe 1 Running sur GIEDI PRIME"
    Write-Log "Groupe 1 : SRV-DC1 + SRV-DC2 + SRV-DNS1"
    Wait-GroupeRunning -VMNames $MCOGroupe1 | Out-Null

    Write-Log "------------------------------------------------------------"
    Write-Log "ETAPE 6 : Extinction Groupe 2 sur $ProdHost"
    Write-Log "Groupe 2 : SRV-PDC1 + SRV-PDC2 + SRV-DNS2"
    Write-Log "Groupe 1 up sur GIEDI PRIME -> continuite AD/DNS garantie" "WARN"

    foreach ($vmName in $MCOGroupe2) {
        Stop-VMProprement -VMName $vmName
    }
    Write-Log "Groupe 2 eteint sur $ProdHost" "OK"
}

# --- ETAPE 5 : VERIFICATION FINALE -------------------------------------------

Write-Log "------------------------------------------------------------"
Write-Log "ETAPE 5 : Verification finale — toutes les VMs Off sur $ProdHost"

$allOff = $true
$vmStates = Invoke-Command -ComputerName $ProdHost -ScriptBlock {
    Get-VM | Select-Object Name, @{N="State";E={$_.State.ToString()}}
}

foreach ($vm in $vmStates) {
    if ($vm.State -ne "Off") {
        Write-Log "VM '$($vm.Name)' encore en etat '$($vm.State)'" "ERROR"
        $allOff = $false
    } else {
        Write-Log "VM '$($vm.Name)' : Off" "OK"
    }
}

if (-not $allOff) {
    Write-Log "Certaines VMs pas eteintes — verifiez manuellement" "WARN"
} else {
    Write-Log "Toutes les VMs sont Off sur $ProdHost" "OK"
}

# --- FIN ---------------------------------------------------------------------

Write-Log "============================================================"
Write-Log "PROCEDURE DRP $Mode TERMINEE"
Write-Log "Surveillez le demarrage des VMs dans la console Veeam"
Write-Log "Log complet : $LogFile"
Write-Log "============================================================"

Start-FailbackToProd.ps1

Retour de HYPERV2 (DRP) vers HYPERV1 (prod). Menu interactif au lancement.

# Usage
.\Start-FailbackToProd.ps1          # Menu interactif
.\Start-FailbackToProd.ps1 -WhatIf  # Dry run
# =============================================================================
# Start-FailbackToProd.ps1
# Script de retour en production depuis GIEDI PRIME
#
# Principe de continuite de service :
#   Les VMs sont traitees par PAIRES pour garantir qu'un DC par domaine
#   et un DNS sont toujours up pendant toute la duree du failback.
#
#   Pour chaque VM dans l'ordre :
#   1. Stop-VBRReplicaFailover   → Undo individuel, eteint le replica sur HYPERV2
#                                  sans toucher aux autres VMs du Failover Plan
#   2. Start-VBRHvReplicaFailback (bloquant) → resync vers HYPERV1
#   3. Stop-VBRHvReplicaFailback (index 1)   → commit
#   4. Attente 15s liberation VHDX
#   5. Start-VM sur HYPERV1
#   6. Delai wave → VM suivante
#
#   Exemple Domain1 (sans interruption) :
#   SRV-PDC1  undo → failback → commit → start HYPERV1 (120s)
#                SRV-DC1 encore Failover HYPERV2 → D1 couvert ✅
#   SRV-DC1   undo → failback → commit → start HYPERV1 (60s)
#                SRV-PDC1 up HYPERV1 → D1 couvert ✅
#
# Prerequis :
#   - WinRM actif sur HYPERV1
#   - Droits admin sur HYPERV1
#   - Veeam Backup & Replication console installee sur GIEDI PRIME
#   - Script a lancer en PowerShell Administrator sur GIEDI PRIME
#   - Les VMs DRP doivent etre en etat Failover dans Veeam
#
# Usage :
#   .\Start-FailbackToProd.ps1          -> execution reelle (menu interactif)
#   .\Start-FailbackToProd.ps1 -WhatIf  -> dry run (simulation sans action)
#
# =============================================================================
# WARNING  MAINTENANCE DU SCRIPT - LIRE AVANT TOUTE MODIFICATION
# =============================================================================
#
# A chaque ajout ou suppression d'une VM dans l'infrastructure :
#
#   1. Mettre a jour $VMStartOrder ci-dessous
#      Respecter l'ordre par PAIRES pour la continuite DC/DNS :
#      - PDC domaine X en premier, puis DC secondaire domaine X
#      - DNS primaire en premier, puis DNS secondaire
#
#   2. Mettre a jour le CHANGELOG
#
# Rappel de l'ordre (paires pour continuite de service) :
#   Wave 1 : SRV-PDC1 (120s) > SRV-PDC2 (90s) > SRV-DNS1 (60s)
#   Wave 2 : SRV-DC1 (60s)   > SRV-DC2 (60s)         > SRV-DNS2 (30s)
#   Wave 3 : SRV-RADIUS (45s)  > SRV-PROXY (30s)        > SRV-SMTP (30s) > SRV-PASSBOLT (30s)
#   Wave 4 : SRV-SIEM (45s)    > SRV-MONITORING (45s)         > SRV-WSUS (30s)
#            > SRV-PRINT (30s)    > SRV-PKI (20s)
#   Wave 5 : SRV-PXE (20s)  > WS-01 (20s)            > WS-02 (20s) > WS-03 (20s)
#
# =============================================================================
# CHANGELOG
# =============================================================================
# 2026-03-28 - v1.0 - Version initiale
# 2026-03-28 - v1.1 - Refactoring complet : logique VM par VM
# 2026-03-28 - v1.2 - Ajout verification DC avant commit
# 2026-04-03 - v1.3 - Ajout SRV-PASSBOLT (Passbolt) en Wave 3 apres SRV-SMTP (30s)
# 2026-04-03 - v1.4 - Suppression RunAsync, commit index 1, Undo global, delai 15s
# 2026-04-03 - v1.5 - Correction detection plan actif via restore points
# 2026-04-03 - v1.6 - Simplification logique Undo
# 2026-04-03 - v1.7 - Ajout menu interactif MCO/CRASH
# 2026-04-03 - v1.8 - Ordre par paires, suppression Undo global
# 2026-04-03 - v1.9 - Stop-VBRReplicaFailover individuel avant chaque failback
# 2026-04-03 - v2.0 - Ordre final : SRV-PDC1 > SRV-PDC2 > SRV-DNS1 > SRV-DC1
#                     > SRV-DC2 > SRV-DNS2 > services > workstations Remplacement Undo global par Stop-VBRReplicaFailover
#                     individuel avant chaque failback
#                     Garantit que le Failover Plan ne redemarre pas le replica
#                     apres le commit. Zero interruption de service.
# =============================================================================

param(
    [ValidateSet("MCO","CRASH")]
    [string]$Mode = "",
    [switch]$WhatIf
)

# --- MENU INTERACTIF ---------------------------------------------------------
if ($Mode -eq "") {
    Write-Host ""
    Write-Host "============================================================" -ForegroundColor Cyan
    Write-Host "  PROCEDURE FAILBACK TO PRODUCTION" -ForegroundColor Cyan
    Write-Host "============================================================" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "  [1] FAILBACK MCO    " -ForegroundColor Yellow -NoNewline
    Write-Host "- Retour apres maintenance planifiee"
    Write-Host "               Failover Plan : MCO-DRP"
    Write-Host ""
    Write-Host "  [2] FAILBACK CRASH  " -ForegroundColor Red -NoNewline
    Write-Host "- Retour apres desastre"
    Write-Host "               Failover Plan : FULL-DRP"
    Write-Host ""
    Write-Host "============================================================" -ForegroundColor Cyan
    Write-Host ""
    $choix = Read-Host "Votre choix (1/2)"

    switch ($choix) {
        "1" { $Mode = "MCO";   Write-Host "`nMode FAILBACK MCO selectionne."   -ForegroundColor Yellow }
        "2" { $Mode = "CRASH"; Write-Host "`nMode FAILBACK CRASH selectionne." -ForegroundColor Red }
        default {
            Write-Host "`nChoix invalide. Arret du script." -ForegroundColor Red
            exit 1
        }
    }

    Write-Host ""
    Write-Host "============================================================" -ForegroundColor Cyan
    Write-Host "  CONFIRMATION" -ForegroundColor Cyan
    Write-Host "============================================================" -ForegroundColor Cyan
    Write-Host "  Mode         : FAILBACK $Mode" -ForegroundColor White
    Write-Host "  Hote prod    : HYPERV1" -ForegroundColor White
    Write-Host "============================================================" -ForegroundColor Cyan
    Write-Host ""
    $confirm = Read-Host "Confirmer le lancement ? (O/N)"
    if ($confirm -notmatch "^[Oo]$") {
        Write-Host "Annule par l'utilisateur." -ForegroundColor Yellow
        exit 0
    }
}

# --- CONFIGURATION -----------------------------------------------------------
$ScriptVersion   = "2.0"
$ProdHost        = "HYPERV1"
$LogFile         = "C:\Scripts\DRP\Logs\FAILBACK_${Mode}_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
$VeeamModule     = "C:\Program Files\Veeam\Backup and Replication\Console\Veeam.Backup.PowerShell.dll"

# Ordre de demarrage prod
$VMStartOrder = @(
    # Wave 1 - DC primaires + DNS primaire
    @{ Name = "SRV-PDC1";     Delay = 120 },
    @{ Name = "SRV-PDC2"; Delay = 90  },
    @{ Name = "SRV-DNS1";         Delay = 60  },
    # Wave 2 - DC secondaires + DNS secondaire
    @{ Name = "SRV-DC1";      Delay = 60  },
    @{ Name = "SRV-DC2";         Delay = 60  },
    @{ Name = "SRV-DNS2";         Delay = 30  },
    # Wave 3 - Services reseau
    @{ Name = "SRV-RADIUS";     Delay = 45  },
    @{ Name = "SRV-PROXY";        Delay = 30  },
    @{ Name = "SRV-SMTP";           Delay = 30  },
    @{ Name = "SRV-PASSBOLT";        Delay = 30  },
    # Wave 4 - Services applicatifs
    @{ Name = "SRV-SIEM";       Delay = 45  },
    @{ Name = "SRV-MONITORING";         Delay = 45  },
    @{ Name = "SRV-WSUS";         Delay = 30  },
    @{ Name = "SRV-PRINT";         Delay = 30  },
    @{ Name = "SRV-PKI";      Delay = 20  },
    # Wave 5 - Workstations
    @{ Name = "SRV-PXE";     Delay = 20  },
    @{ Name = "WS-01";            Delay = 20  },
    @{ Name = "WS-02";            Delay = 20  },
    @{ Name = "WS-03";          Delay = 20  }
)

# --- FONCTIONS ---------------------------------------------------------------

function Write-Log {
    param([string]$Message, [string]$Level = "INFO")
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $prefix = if ($WhatIf) { "[WHATIF] " } else { "" }
    $line = "[$timestamp] [$Level] $prefix$Message"
    Write-Host $line -ForegroundColor $(switch ($Level) {
        "INFO"   { "Cyan" }
        "OK"     { "Green" }
        "WARN"   { "Yellow" }
        "ERROR"  { "Red" }
        default  { "White" }
    })
    Add-Content -Path $LogFile -Value $line
}

function Get-FailoverRestorePoint {
    # Index 0 : restore point en etat Failover → passe a Stop-VBRReplicaFailover
    # puis a Start-VBRHvReplicaFailback
    param([string]$VmName)
    return Get-VBRRestorePoint |
        Where-Object { $_.IsReplica() -and $_.VmName -eq $VmName -and $_.State.ToString() -eq "Failover" } |
        Sort-Object CreationTime -Descending |
        Select-Object -First 1
}

function Get-CommitRestorePoint {
    # Index 1 : second RP le plus recent apres le failback
    # Start-VBRHvReplicaFailback cree un nouveau RP (index 0)
    # On committe index 1 = l'ancien RP pour eviter VM bloquee en LockedItem
    param([string]$VmName)
    return Get-VBRRestorePoint |
        Where-Object { $_.IsReplica() -and $_.VmName -eq $VmName } |
        Sort-Object CreationTime -Descending |
        Select-Object -Skip 1 -First 1
}

# --- INITIALISATION ----------------------------------------------------------

$logDir = Split-Path $LogFile
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }

Write-Log "============================================================"
Write-Log "DEBUT DE LA PROCEDURE FAILBACK TO PRODUCTION"
Write-Log "Version      : $ScriptVersion"
Write-Log "Mode         : FAILBACK $Mode"
Write-Log "Hote prod    : $ProdHost"
if ($WhatIf) { Write-Log "MODE DRY RUN - AUCUNE ACTION REELLE" "WARN" }
Write-Log "============================================================"

# Charger le module Veeam
Write-Log "Chargement du module Veeam PowerShell..."
try {
    Import-Module $VeeamModule -ErrorAction Stop -WarningAction SilentlyContinue
    Write-Log "Module Veeam charge" "OK"
} catch {
    Write-Log "Impossible de charger le module Veeam : $_" "ERROR"
    exit 1
}

# Verifier WinRM sur HYPERV1
try {
    Invoke-Command -ComputerName $ProdHost -ScriptBlock { $env:COMPUTERNAME } -ErrorAction Stop | Out-Null
    Write-Log "Connexion WinRM vers $ProdHost OK" "OK"
} catch {
    Write-Log "Impossible de se connecter a $ProdHost via WinRM : $_" "ERROR"
    exit 1
}

# --- VERIFICATION PRE-VOL ----------------------------------------------------

Write-Log "------------------------------------------------------------"
Write-Log "VERIFICATION : Restore points en etat Failover"

$missingVMs = @()
foreach ($vm in $VMStartOrder) {
    $rp = Get-FailoverRestorePoint -VmName $vm.Name
    if (-not $rp) {
        Write-Log "ATTENTION : Aucun restore point Failover pour '$($vm.Name)'" "WARN"
        $missingVMs += $vm.Name
    } else {
        Write-Log "OK : '$($vm.Name)' → restore point du $($rp.CreationTime)" "OK"
    }
}

if ($missingVMs.Count -gt 0) {
    Write-Log "$($missingVMs.Count) VM(s) sans restore point Failover : $($missingVMs -join ', ')" "WARN"
    Write-Log "Ces VMs seront ignorees pour le failback" "WARN"
}

# --- DRY RUN -----------------------------------------------------------------

if ($WhatIf) {
    Write-Log "------------------------------------------------------------"
    Write-Log "DRY RUN - Simulation de la procedure par paires :" "WARN"
    Write-Log "Continuite garantie : 1 DC par domaine + 1 DNS toujours up" "WARN"
    Write-Log "------------------------------------------------------------" "WARN"
    foreach ($vm in $VMStartOrder) {
        $rp = Get-FailoverRestorePoint -VmName $vm.Name
        if ($rp) {
            Write-Log "  → Stop-VBRReplicaFailover '$($vm.Name)' — eteint replica HYPERV2" "WARN"
            Write-Log "  → Failback '$($vm.Name)' (bloquant — resync vers HYPERV1)" "WARN"
            Write-Log "  → Commit failback '$($vm.Name)' (index 1)" "WARN"
            Write-Log "  → Attente 15s liberation VHDX" "WARN"
        } else {
            Write-Log "  → '$($vm.Name)' pas en Failover — demarrage direct si present" "WARN"
        }
        Write-Log "  → Demarrage '$($vm.Name)' sur $ProdHost — delai $($vm.Delay)s" "WARN"
        Write-Log "  ---" "WARN"
    }
    Write-Log "------------------------------------------------------------"
    Write-Log "DRY RUN TERMINE - Aucune action effectuee" "WARN"
    Write-Log "Relancer sans -WhatIf pour executer" "WARN"
    exit 0
}

# --- TRAITEMENT VM PAR VM ----------------------------------------------------

Write-Log "------------------------------------------------------------"
Write-Log "DEBUT DU FAILBACK VM PAR VM (ordre par paires)"
Write-Log "Continuite garantie : 1 DC par domaine + 1 DNS toujours up" "OK"

foreach ($vm in $VMStartOrder) {
    $vmName = $vm.Name
    $delay  = $vm.Delay

    Write-Log "============ $vmName ============"

    $rp = Get-FailoverRestorePoint -VmName $vmName

    if ($rp) {
        # ETAPE A - Undo individuel via Stop-VBRReplicaFailover
        # Eteint proprement le replica sur HYPERV2 sans toucher aux autres
        # Evite que le Failover Plan redemarre le replica apres le commit
        Write-Log "[$vmName] Undo individuel (Stop-VBRReplicaFailover)..."
        try {
            Stop-VBRReplicaFailover -RestorePoint $rp -ErrorAction Stop | Out-Null
            Write-Log "[$vmName] Replica eteint sur HYPERV2" "OK"
        } catch {
            Write-Log "[$vmName] Erreur Stop-VBRReplicaFailover : $_" "ERROR"
        }

        # ETAPE B - Failback bloquant vers HYPERV1
        Write-Log "[$vmName] Lancement failback (bloquant — resync vers HYPERV1)..."
        $rpFresh = Get-VBRRestorePoint |
            Where-Object { $_.IsReplica() -and $_.VmName -eq $vmName } |
            Sort-Object CreationTime -Descending |
            Select-Object -First 1
        if ($rpFresh) {
            try {
                Start-VBRHvReplicaFailback `
                    -RestorePoint $rpFresh `
                    -QuickRollback `
                    -PowerOn:$false `
                    -ErrorAction Stop | Out-Null
                Write-Log "[$vmName] Failback termine" "OK"
            } catch {
                Write-Log "[$vmName] Erreur failback : $_" "ERROR"
            }
        } else {
            Write-Log "[$vmName] Aucun restore point disponible pour le failback" "ERROR"
        }

        # ETAPE C - Commit sur index 1
        Write-Log "[$vmName] Commit failback (index 1)..."
        $rpCommit = Get-CommitRestorePoint -VmName $vmName
        if ($rpCommit) {
            try {
                Stop-VBRHvReplicaFailback -RestorePoint $rpCommit -ErrorAction Stop | Out-Null
                Write-Log "[$vmName] Commit OK (RP du $($rpCommit.CreationTime))" "OK"
            } catch {
                Write-Log "[$vmName] Erreur commit : $_" "WARN"
            }
        } else {
            Write-Log "[$vmName] Aucun RP index 1 trouve" "WARN"
        }

        # ETAPE D - Attente liberation VHDX
        Write-Log "[$vmName] Attente 15s liberation VHDX..."
        Start-Sleep -Seconds 15

    } else {
        Write-Log "[$vmName] Pas de restore point Failover — VM ignoree pour le failback" "WARN"
    }

    # ETAPE E - Demarrer la VM sur HYPERV1
    $vmState = Invoke-Command -ComputerName $ProdHost -ScriptBlock {
        param($name)
        $v = Get-VM -Name $name -ErrorAction SilentlyContinue
        if ($v) { $v.State.ToString() } else { "NotFound" }
    } -ArgumentList $vmName

    if ($vmState -eq "NotFound") {
        Write-Log "[$vmName] VM introuvable sur $ProdHost" "WARN"
    } elseif ($vmState -eq "Running") {
        Write-Log "[$vmName] VM deja Running sur $ProdHost" "OK"
    } else {
        try {
            Invoke-Command -ComputerName $ProdHost -ScriptBlock {
                param($name)
                Start-VM -Name $name -ErrorAction Stop
            } -ArgumentList $vmName
            Write-Log "[$vmName] VM demarree sur $ProdHost" "OK"
        } catch {
            Write-Log "[$vmName] Erreur demarrage sur $ProdHost : $_" "ERROR"
        }
    }

    # ETAPE F - Delai avant VM suivante
    Write-Log "[$vmName] Attente $delay sec avant la VM suivante..."
    Start-Sleep -Seconds $delay
}

# --- FIN ---------------------------------------------------------------------

Write-Log "============================================================"
Write-Log "FAILBACK TERMINE — PRODUCTION RESTAUREE SUR $ProdHost"
Write-Log "Verifiez les services (AD, DNS, DHCP, auth...)"
Write-Log "N'oubliez pas de reactiver l'autostart des VMs sur $ProdHost :"
Write-Log "  Get-VM | Set-VM -AutomaticStartAction StartIfRunning"
Write-Log "Log complet : $LogFile"
Write-Log "============================================================"