Skip to main content

Backing Up a vCenter HA (VCHA) with Veeam

  • June 19, 2026
  • 9 comments
  • 83 views

morgan.munck
Forum|alt.badge.img

Hello everyone,

This morning I had to set up a backup job for a VCHA at a client’s site.

I found that Veeam cannot natively and correctly back up a VCHA cluster due to several technical constraints imposed by VMware:

  • Taking a snapshot on a Passive or Witness node can trigger a failover or an inconsistent state, yet Veeam uses snapshots for image-based backups
  • VMware recommends backing up only the Active node and excluding the Passive and Witness nodes, yet Veeam cannot automatically distinguish the active node

To back up a VCHA, you must be able to exclude the passive node when the job starts.

To do this, I run two scripts: one that runs before the job and one that runs after the job.

 

I’m sorry, the script comments are in French.


I hope this technique will be helpful to some of you.

Morgan.

 

________________________________________________________________________


Script before the job:
 

#########################################
# PRE-JOB - Exclude vCHA Passive Node
#########################################

$vcenter  = "vcenter@domain.com"
$username = "user"
$password = "mdp"
$jobname  = "jobname"
$logFile  = "C:\Backup\Scripts\Logs\PreJob_vCHA_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
$tmpFile  = "C:\Backup\Scripts\Logs\passive_vm_name.tmp"

function Write-Log {
    param([string]$Message, [string]$Level = "INFO")
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $entry = "[$timestamp][$Level] $Message"
    Write-Output $entry
    Add-Content -Path $logFile -Value $entry
}

New-Item -ItemType Directory -Path (Split-Path $logFile) -Force | Out-Null
Write-Log "=== Début Pre-Job vCHA ==="

#########################################
# Chargement modules
try {
    Import-Module Veeam.Backup.PowerShell -ErrorAction Stop
    Write-Log "Module Veeam.Backup.PowerShell chargé."

    if (-not (Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) {
        Import-Module VMware.VimAutomation.Core -ErrorAction Stop
        Write-Log "Module VMware.VimAutomation.Core chargé."
    } else {
        Write-Log "Module VMware.VimAutomation.Core déjà chargé."
    }
} catch {
    Write-Log "ERREUR chargement modules : $_" "ERROR"
    exit 1
}

#########################################
# Connexion vCenter
try {
    Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null
    $viConnection = Connect-VIServer -Server $vcenter -User $username -Password $password -ErrorAction Stop
    Write-Log "Connecté au vCenter : $vcenter"
} catch {
    Write-Log "ERREUR connexion vCenter : $_" "ERROR"
    exit 1
}

#########################################
# Récupération du noeud passif vCHA
try {
    $si          = Get-View ServiceInstance
    $failoverMgr = Get-View -Id $si.Content.FailoverClusterManager -ErrorAction Stop
    Write-Log "FailoverClusterManager récupéré."

    $healthInfo     = $failoverMgr.GetVchaClusterHealth()
    $vcClusterState = $healthInfo.RuntimeInfo.ClusterState
    $nodeState      = $healthInfo.RuntimeInfo.NodeInfo
    Write-Log "État du cluster vCHA : $vcClusterState"

    foreach ($node in $nodeState) {
        Write-Log "Nœud détecté - Role: $($node.NodeRole) | IP: $($node.NodeIp)"
    }

    $passiveNode = $nodeState | Where-Object { $_.NodeRole -eq "passive" }

    if (-not $passiveNode) {
        Write-Log "Aucun nœud passif trouvé - sauvegarde normale." "WARN"
        Disconnect-VIServer -Server $vcenter -Confirm:$false
        exit 0
    }
    Write-Log "Nœud passif identifié - IP : $($passiveNode.NodeIp)"

} catch {
    Write-Log "ERREUR récupération info vCHA : $_" "ERROR"
    Disconnect-VIServer -Server $vcenter -Confirm:$false
    exit 1
}

#########################################
# Identification de la VM passive via IP
try {
    $passiveVM  = $null
    $retryCount = 0
    $maxRetry   = 3

    while (-not $passiveVM -and $retryCount -lt $maxRetry) {
        $passiveVM = Get-VM | Where-Object {
            $_.Guest.IPAddress -contains $passiveNode.NodeIp
        }
        if (-not $passiveVM) {
            $retryCount++
            Write-Log "VM passive non trouvée via IP (tentative $retryCount/$maxRetry), attente 10s..." "WARN"
            Start-Sleep -Seconds 10
        }
    }

    if (-not $passiveVM) {
        Write-Log "Impossible de trouver la VM avec l'IP $($passiveNode.NodeIp)" "ERROR"
        Disconnect-VIServer -Server $vcenter -Confirm:$false
        exit 1
    }
    Write-Log "VM passive identifiée : $($passiveVM.Name)"

} catch {
    Write-Log "ERREUR identification VM : $_" "ERROR"
    Disconnect-VIServer -Server $vcenter -Confirm:$false
    exit 1
}

#########################################
# Déconnexion vCenter
Disconnect-VIServer -Server $vcenter -Confirm:$false | Out-Null
Write-Log "Déconnecté du vCenter."

#########################################
# Suppression temporaire du job Veeam
try {
    $vbrJob     = Get-VBRJob -Name $jobname -ErrorAction Stop
    $jobObjects = Get-VBRJobObject -Job $vbrJob

    Write-Log "Objets présents dans le job :"
    foreach ($obj in $jobObjects) {
        Write-Log "  -> Name: $($obj.Name) | Type: $($obj.Type) | IsExcluded: $($obj.IsExcluded)"
    }

    $jobObject = $jobObjects | Where-Object { $_.Name -eq $passiveVM.Name }

    if (-not $jobObject) {
        Write-Log "VM '$($passiveVM.Name)' absente du job, rien à faire." "INFO"
        exit 0
    }

    if ($jobObject.IsExcluded -eq $true) {
        Write-Log "VM '$($passiveVM.Name)' déjà exclue, rien à faire." "INFO"
        exit 0
    }

    # Sauvegarde du nom pour le Post-Job
    $passiveVM.Name | Out-File -FilePath $tmpFile -Force -Encoding UTF8
    Write-Log "Nom VM sauvegardé dans : $tmpFile"

    # Suppression sans -Confirm
    Remove-VBRJobObject -Objects $jobObject -ErrorAction Stop | Out-Null
    Write-Log "VM '$($passiveVM.Name)' retirée du job '$jobname'." "INFO"

    # Vérification post-suppression
    $jobObjectsAfter = Get-VBRJobObject -Job $vbrJob
    Write-Log "État du job après suppression :"
    foreach ($obj in $jobObjectsAfter) {
        Write-Log "  [POST] -> Name: $($obj.Name) | Type: $($obj.Type) | IsExcluded: $($obj.IsExcluded)"
    }

} catch {
    Write-Log "ERREUR suppression VM du job : $_" "ERROR"
    exit 1
}

Write-Log "=== Fin Pre-Job vCHA - Succès ==="
exit 0
#########################################
# PRE-JOB - End
#########################################

 

 

 

Script after the job:

 

#########################################
# POST-JOB - Réintégration vCHA Passive Node
#########################################

$vcenter  = "vcenter@domain.com"
$username = "user"
$password = "mdp"
$jobname  = "jobname"
$logFile  = "C:\Backup\Scripts\Logs\PostJob_vCHA_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
$tmpFile  = "C:\Backup\Scripts\Logs\passive_vm_name.tmp"

function Write-Log {
    param([string]$Message, [string]$Level = "INFO")
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $entry = "[$timestamp][$Level] $Message"
    Write-Output $entry
    Add-Content -Path $logFile -Value $entry
}

New-Item -ItemType Directory -Path (Split-Path $logFile) -Force | Out-Null
Write-Log "=== Début Post-Job vCHA ==="

#########################################
# Vérification fichier tmp
if (-not (Test-Path $tmpFile)) {
    Write-Log "Fichier tmp introuvable ($tmpFile) - aucune VM à réintégrer." "WARN"
    Write-Log "=== Fin Post-Job vCHA ==="
    exit 0
}

$vmName = (Get-Content $tmpFile -Raw -Encoding UTF8).Trim()
if ([string]::IsNullOrEmpty($vmName)) {
    Write-Log "Fichier tmp vide - aucune VM à réintégrer." "WARN"
    exit 0
}
Write-Log "VM à réintégrer : $vmName"

#########################################
# Chargement modules
try {
    Import-Module Veeam.Backup.PowerShell -ErrorAction Stop
    Write-Log "Module Veeam.Backup.PowerShell chargé."
} catch {
    Write-Log "ERREUR chargement module Veeam : $_" "ERROR"
    exit 1
}

#########################################
# Réintégration dans le job
try {
    $vbrJob = Get-VBRJob -Name $jobname -ErrorAction Stop
    if (-not $vbrJob) {
        Write-Log "Job '$jobname' introuvable." "ERROR"
        exit 1
    }

    $existing = Get-VBRJobObject -Job $vbrJob | Where-Object { $_.Name -eq $vmName }

    Write-Log "État actuel des objets du job :"
    foreach ($obj in Get-VBRJobObject -Job $vbrJob) {
        Write-Log "  -> Name: $($obj.Name) | Type: $($obj.Type) | IsExcluded: $($obj.IsExcluded)"
    }

    if ($existing -and $existing.IsExcluded -eq $false) {
        Write-Log "VM '$vmName' déjà présente en Include. Rien à faire." "INFO"
        Remove-Item $tmpFile -Force
        exit 0
    }


    if ($existing -and $existing.IsExcluded -eq $true) {
        Write-Log "VM '$vmName' présente mais exclue - suppression de l'entrée exclue..." "INFO"
        Remove-VBRJobObject -Objects $existing -ErrorAction Stop | Out-Null
        Write-Log "Entrée exclue supprimée."
    }

    # Recherche entité Veeam
    $vbrServer = Get-VBRServer | Where-Object { $_.Name -like "*$($vcenter.Split('.')[0])*" }
    if (-not $vbrServer) {
        Write-Log "Serveur vCenter introuvable dans Veeam." "ERROR"
        exit 1
    }
    Write-Log "Serveur Veeam : $($vbrServer.Name)"

    $vbrEntity = Find-VBRViEntity -Server $vbrServer -Name $vmName -ErrorAction Stop
    if (-not $vbrEntity) {
        Write-Log "Entité Veeam introuvable pour '$vmName'." "ERROR"
        exit 1
    }
    Write-Log "Entité Veeam trouvée : $($vbrEntity.Name)"

    # Réintégration
    Add-VBRViJobObject -Job $vbrJob -Entities $vbrEntity -ErrorAction Stop | Out-Null
    Write-Log "VM '$vmName' réintégrée en Include dans le job '$jobname'." "INFO"

    # Vérification finale
    Write-Log "État du job après réintégration :"
    foreach ($obj in Get-VBRJobObject -Job $vbrJob) {
        Write-Log "  [POST] -> Name: $($obj.Name) | Type: $($obj.Type) | IsExcluded: $($obj.IsExcluded)"
    }

    Remove-Item $tmpFile -Force
    Write-Log "Fichier tmp supprimé."

} catch {
    Write-Log "ERREUR réintégration VM : $_" "ERROR"
    exit 1
}

Write-Log "=== Fin Post-Job vCHA - Succès ==="
exit 0

#########################################
# POST-JOB - End
#########################################

9 comments

Chris.Childerhose
Forum|alt.badge.img+22

Nice script for this backup.  I am still one that does not back up vCenter at all and use File backups then backup up the File Server if that is where you point or if you send to NFS share that is even better which I do to my Synology NAS.  Best practice dictates no vCenter backup and use the File Backup that is built-in.


morgan.munck
Forum|alt.badge.img
  • Author
  • VUG Leader
  • June 19, 2026

@Chris.Childerhose Yes, that's true, we actually discussed this with the client, but it's so much faster to restore the entire VM if a problem arises. It can sometimes save valuable time.


Chris.Childerhose
Forum|alt.badge.img+22

@Chris.Childerhose Yes, that's true, we actually discussed this with the client, but it's so much faster to restore the entire VM if a problem arises. It can sometimes save valuable time.

Oh, I thoroughly agree there as quick restore is more functional that file-based restore for sure. 😂

 
 
 

Tommy O'Shea
Forum|alt.badge.img+5
  • Veeam Legend
  • June 22, 2026

Great script ​@morgan.munck, I’m wondering have you tested a restore of vCenter from one of these backups, and are any scripts necessary to ensure it goes back into the HA configuration?


morgan.munck
Forum|alt.badge.img
  • Author
  • VUG Leader
  • June 22, 2026

@tommy No yet 😊 I just finished backing them up, but I have some tests planned, and I'll come back here to share the next steps. 

Logically, since there's a witness, I would have restored the witness and one of the two nodes without necessarily running a script.

I would have tested what's described starting on page 3483 of the VMware documentation  (https://docs.vmware.com/en/VMware-vSphere/8.0/vsphere-availability/GUID-4E5A0E2A-3B6D-4A6A-A604-C52F3D8C7ED2.html)


Tommy O'Shea
Forum|alt.badge.img+5
  • Veeam Legend
  • June 22, 2026

Great, I'm looking forward to seeing part two! 


kciolek
Forum|alt.badge.img+6
  • Influencer
  • June 22, 2026

great script! I don’t backup vCenter with Veeam. The VM admin takes care of it. 


Michael Melter
Forum|alt.badge.img+12

Even Veeam does no longer recommend to backup vCenter using VBR:

https://www.veeam.com/kb2328

Still, they give a lot of recommendations in the very KB on how to proceed if you still want to do so.

And we do it most of the time in our customer environments and it saved them their lower end not only once.

Most of the time we do a replication on top to prevent the chicken egg dilemma mentioned in the KB.

But I never tried with vCenter HA. So, very helpful to have the script at hand. Thanx, ​@morgan.munck.


coolsport00
Forum|alt.badge.img+23
  • Veeam Legend
  • June 23, 2026

Nice script ​@morgan.munck ! I personally have never done VC File-based backup. I always have used VBR. And thankfully..have never had a need to restore either 😊 

Thanks for sharing!