How to Easily Backup ESXi VM with PowerShell Scripts?

Physical server should not be backed up in the traditional way to save data on the whole machine. Try PowerShell scripts to easily backup ESXi VMs.

download-icon
Free Download
for VM, OS, DB, File, NAS, etc.
nick-zhao

Updated by Nick Zhao on 2023/08/04

Table of contents
  • ESXi VM backup vs host backup

  • How to backup ESXi VM with PowerShell scripts?

  • How to backup ESXi VM with enterprise backup software?

  • Sum Up

Are you looking for a robust ESXi backup solution? Try the VMware-verified solution Vinchin Backup & Recovery!↘ Download Free Trial

VMware vSphere is widely used in companies, providing a robust virtualization platform to fully utilize hardware resources on physical servers and its hypervisor ESXi is highly praised by users.

When virtualization technology breaks the limitation of traditional IT environment, it also brings new challenges in IT maintenance. Should IT administrators keep backing up the physical server or find a new solution to backup the ESXi VMs?

ESXi VM backup vs host backup

Before the physical servers are virtualized, IT administrators only need to backup them with traditional solutions, but now restoring the whole server to repair certain VM is not good for disaster recovery because this could influence the well-running VMs.

In addition, it’s hard to backup all the data on a virtualized host because ESXi is a type 1 hypervisor which means there is no operating system for the traditional solution to install backup agent. Some IT administrators will backup the ESXi configuration but more people think it is unnecessary.

The most effective way to protect data and business continuity in virtual environment is still backing up the VMs on the ESXi host so in the event of VM failure, IT administrators can recover a single VM while not influence the other ones.

How to backup ESXi VM with PowerShell scripts?

To manage virtual machines, you can deploy vCenter in the virtual environment and manually export the ESXi VMs as data backup.

With vCenter deployed, you can also run PowerShell scripts to backup VM as scheduled.

This section will guide you to write 3 scripts, Starter.ps1, _Configuration.ps1, and Backup-VM.ps1. After that, you need to place them in the directory structure below:

│  Backup-VM.ps1
│  Starter.ps1

└─vCenter01
       _Configuration.ps1

script tree structure

Script functionalities explantation:

Starter.ps1 - Invoked by task scheduler, in its path, it will scan all _Configuration.ps1 files, and launch Backup-VM.ps1 asynchronously.
Backup-VM.ps1 - Owns a sole parameter called "-vCenterFolder", pointing to a directory of _Configuration.ps1.
_Configuration.ps1 - Configuration file, any number of it is ok, based on the contents, do different VM backup jobs on different vCenter servers.

Script contents and description:

Starter.ps1

# Enter directory of script
Set-Location (Get-Item $MyInvocation.MyCommand.Definition).Directory

# Scan all _Configuration.ps1 files
Get-ChildItem -Filter '_Configuration.ps1' -Recurse | %{
    # Launch Backup-VM.ps1 and pass _Configuration.ps1's path in
    Start-Process -FilePath 'powershell.exe' -ArgumentList @('-File', 'Backup-VM.ps1', '-vCenterFolder', "`"$($_.DirectoryName)`"")
}

_Configuration.ps1

$Enable = $true

# Below is a sample to backup VMs
# VM - the VM name in vCenter
# Host - VMHost name in vCenter
# Datastore - Datastore name in vCenter in Host
<#
Sample:
$Entries = @(
@{VM = 'VMName01'; Host = 'TargetESXiServerName02'; Datastore = 'TargetESXiServerName02:storage1'; Reserve = 1;},
@{VM = 'VMName02'; Host = 'TargetESXiServerName01'; Datastore = 'TargetESXiServerName01:storage2'; Reserve = 1;}
)
#>

# Actual data filled in
$Entries = @(

)

# vCenter server name or IP address
$vCenter = 'vCenter01'

# Mail setting part, only there are errors needs manual work will be triggered. normal backup job will not trigger the alert.
$From =  "$($env:COMPUTERNAME)@test.com"
$To = "AlertNeedsToSend@test.com"
$Subject = "VMs backup completed with errors - $vCenter"
$SmtpServer = 'mailgateway'

Backup-VM.ps1 - Automatically judge which backup way to use, if VM uses VDS (vSphere Distributed Switch), script will choose API backup, if VM uses normal vSphere Standard Switch, script can use New-VM to clone VM. (After VDS, New-VM will fail, unless both original and target ESXi servers have the same VDS settings)

PARAM(
    [parameter(Mandatory=$true)]
    [string]$vCenterFolder
)

# Enter directory of _Configuration.ps1
Set-Location -Path $vCenterFolder

$Date = Get-Date
$strDate = $Date.ToString("yyyy-MM-dd")
$strLogFile = "${strDate}.log"

# Import _Configuration.ps1 variables
. '.\_Configuration.ps1'

# Define a logging function
function Add-Log
{
    PARAM(
        [String]$Path,
        [String]$Value,
        [String]$Type = 'Info'
    )
    $Type = $Type.ToUpper()
    $Date = Get-Date
    Write-Host "$($Date.ToString('[HH:mm:ss] '))[$Type] $Value" -ForegroundColor $(
        switch($Type)
        {
            'WARNING' {'Yellow'}
            'Error' {'Red'}
            default {'White'}
        }
    )
    if($Path){
        Add-Content -LiteralPath $Path -Value "$($Date.ToString('[HH:mm:ss] '))[$Type] $Value" -ErrorAction:SilentlyContinue
    }
}

Add-Log -Path $strLogFile -Value 'New backup started'
# Whether the configuration is enabled or not
if(!$Enable)
{
    Add-Log -Path $strLogFile -Value 'Repository disabled'
    exit
}

# $vCenter is necessary, without it, script doesn't know where to connect to
if(!$vCenter)
{
    Add-Log -Path $strLogFile -Value 'vCenter variable is null, can not continue' -Type Error
    $Alert = $true
}
else
{
    Add-Log -Path $strLogFile -Value "vCenter: [$vCenter]"
}

# Looking for necessary snapin, early version of PowerCli has no snapin for VDS, output some information to ensure the environment of the script
if(!(Get-PSSnapin -Name '*VMware.VimAutomation.Vds*' -Registered -ErrorAction:SilentlyContinue))
{
    Add-Log -Path $strLogFile -Value 'This script is built from [VMware vSphere PowerCLI 5.5], suggest to run on the version' -Type Error
    Add-Log -Path $strLogFile -Value 'PSSnapin [VMware.VimAutomation.Vds] is not found, which could cause backup failure' -Type Error
    Add-Log -Path $strLogFile -Value 'Installer path: [ServerVMwarevSphereVMware-PowerCLI-5.5.0-1295336.exe]' -Type Info
    exit
}

# Add snapin
if(!(Get-PSSnapin '*vmware*' -ErrorAction:SilentlyContinue))
{
    Add-PSSnapin *vmware*
    if(!$?)
    {
        Add-Log -Path $strLogFile -Value 'Failed to add vmware pssnapin' -Type Error
        Add-Log -Path $strLogFile -Value $Error[0] -Type Error
        exit
    }
}

# Connect to vCenter,if error, set $Alert to $true to trigger alert at last
Connect-VIServer -Server $vCenter -Force
if(!$?)
{
    Add-Log -Path $strLogFile -Value 'Failed to connect to vCenter, cause:' -Type Error
    Add-Log -Path $strLogFile -Value $Error[0] -Type Error
    $Alert = $true
}

$Tasks = @()
# Loop every VM backup item
foreach($e in $Entries)
{
    Add-Log -Path $strLogFile -Value "Start doing backup for: [$($e.VM)]"
    # Add a new VM name as "%OLDVMName%_ScriptBackup_%CurrentDate%"
    $VMNew = "$($e.VM)_ScriptBackup_$strDate"
    $e.NewVM = $VMNew
    $VM = $null
    $VM = @(Get-VM -Name $e.VM -ErrorAction:SilentlyContinue)
    if(!$VM)
    {
        Add-Log -Path $strLogFile -Value 'Capture none VM, does the VM exists?' -Type Warning
        continue
    }
    if($VM.Count -ge 2)
    {
        Add-Log -Path $strLogFile -Value "Capture [$($VM.Count)] VM, duplicated VMs?: [$(($VM | %{$_.Id}) -join '], [')]" -Type Warning
        continue
    }
    $VM = $VM[0]

    # only one VM captured, no confuse to script
    $VMHost = $null
    $VMHost = @(Get-VMHost -Name $e.Host -ErrorAction:SilentlyContinue)
    if(!$VMHost)
    {
        Add-Log -Path $strLogFile -Value "Capture none VMHost, does the VMHost exists?: [$($e.Host)]" -Type Warning
        continue
    }
    if($VMHost.Count -ge 2)
    {
        Add-Log -Path $strLogFile -Value "Capture [$($VMHost.Count)] VMHost, duplicated VMHosts?: [$(($VMHost | %{$_.Id}) -join '], [')]" -Type Warning
        continue
    }
    $VMHost = $VMHost[0]

    # only one VMHost captured, no confuse to script
    $Datastore = $null
    $Datastore = @($VMHost | Get-Datastore -Name $e.Datastore -ErrorAction:SilentlyContinue)
    if(!$Datastore)
    {
        Add-Log -Path $strLogFile -Value "Capture none Datastore, does the Datastore exists on VMHost?: [$($e.Datastore)]" -Type Warning
        continue
    }
    if($Datastore.Count -ge 2)
    {
        Add-Log -Path $strLogFile -Value "Capture [$($Datastore.Count)] Datastore, duplicated Datastores?: [$(($Datastore | %{$_.Id}) -join '], [')]" -Type Warning
        continue
    }
    $Datastore = $Datastore[0]

    # only one Datastore captured, no confuse to script
    Add-Log -Path $strLogFile -Value "INFO[OLDName][NewName][Host][Datastore]: [$($VM.Name)][$VMNew][$($VMHost.Name)][$($Datastore.Name)]"

    # Whether the VM is using VDS
    $VDS = $null
    $VDS = $VM | Get-VDSwitch -ErrorAction:SilentlyContinue
    if(!$VDS)
    {
        # if VDS is not placed, use New-VM to clone
        Add-Log -Path $strLogFile -Value 'VDSwitch not found on the VM, use commandlet [New-VM] to clone'
        $Task = $null
        $Task = New-VM -Name $VMNew -VM $VM -VMHost $VMHost -Datastore $Datastore -RunAsync -ErrorAction:SilentlyContinue
        if(!$?)
        {
            Add-Log -Path $strLogFile -Value 'New-VM failed, cause:' -Type Warning
            Add-Log -Path $strLogFile -Value $Error[0] -Type Warning
            continue
        }
        Add-Log -Path $strLogFile -Value "Task launched: [$($Task.Id)]"
        $Tasks += $Task.Id
    }
    else
    {
        # using VDS, use API to clone
        Add-Log -Path $strLogFile -Value 'VDSwitch found on the VM, need to use 2nd way to clone VM'
        Add-Log -Path $strLogFile -Value "VDS [Name][KEY]: [$(($VDS | %{$_.Name}) -join ';')][$(($VDS | %{$_.Key}) -join ';')]"
        $TargetVDS = $null
        $TargetVDS = @($VMHost | Get-VDSwitch -ErrorAction:SilentlyContinue)
        # on target VMHost search VDS, if target ESXi has no VDS, clone will fail
        if(!$TargetVDS)
        {
            Add-Log -Path $strLogFile -Value 'Target VMHost server has no VDSwitch, VM which uses a VDS is unable to clone to the VMHost' -Type Warning
            continue
        }
        $TargetVDS = $TargetVDS[-1]
        # capture VDS and pick the one owns most number of ports
        $TargetVDSGroup = @($TargetVDS | Get-VDPortgroup | Sort-Object NumPorts)[-1]
        Add-Log -Path $strLogFile -Value "Target VDS randomly picked [Name][Key]: [$($TargetVDS.Name)][$($TargetVDS.Key)]"

        # API nesessary, use default resource pool of ESXi server is fine
        $Pool = $null
        $Pool = @($VMHost | Get-ResourcePool)[0]
        if(!$Pool)
        {
            Add-Log -Path $strLogFile -Value 'No resource pool found from VMHost, please use Get-ResourcePool to find resource pool on the VMhost' -Type Warning
            continue
        }

        # Capture VM's network adapter, change it to use VDS on target ESXi
        $VMNic = $null
        $VMNic = $VM.ExtensionData.Config.Hardware.Device | ?{$_.DeviceInfo.Label -imatch 'Network adapter'}
        if($VMNic.Count -ge 2)
        {
            Add-Log -Path $strLogFile -Value 'The VM has more than 2 network adapters, not supported' -Type Warning
            continue
        }
        $VMNicBacking = $VMNic.Backing
        $VMNicBacking.Port.SwitchUuid = $TargetVDS.Key
        $VMNicBacking.Port.PortgroupKey = $TargetVDSGroup.Key
        $VMNicBacking.Port.PortKey = ''
        $VMNicBacking.Port.ConnectionCookie = ''
        
        # API necessary
        $spec = New-Object VMware.Vim.VirtualMachineCloneSpec
        $spec.Config = New-Object VMware.Vim.VirtualMachineConfigSpec
        $nicDev = New-Object VMware.Vim.VirtualDeviceConfigSpec
        $nicDev.Operation = 'edit'
        $nicDev.Device = $VMNic
        $nicDev.Device.Backing = $VMNicBacking
        $spec.Config.DeviceChange = $nicDev
        $spec.Config.DeviceChange[0].Device.Backing.Port.PortKey = ''
        $spec.Location = New-Object VMware.Vim.VirtualMachineRelocateSpec
        $spec.Location.Host = $VMHost.ExtensionData.MoRef
        $spec.Location.Datastore = $Datastore.ExtensionData.MoRef
        $spec.Location.Pool = $Pool.ExtensionData.MoRef
        $spec.PowerOn = $false
        $spec.Template = $false

        Add-Log -Path $strLogFile -Value 'Trying to get datacenter object, this could potentially cause a dead loop!'
        # to get the $Folder for API, must get datacenter of ESXi first
        $Datacenter = $VMHost.Parent
        while($Datacenter.Id -notmatch 'Datacenter')
        {
            $Datacenter = $Datacenter.Parent
        }
        # API necessary
        $Folder = $Datacenter | Get-Folder -Name 'Discovered virtual machine'
        Add-Log -Path $strLogFile -Value 'Did not fail into a dead loop!'

        # use API to launch clone task
        $Task = $null
        $Task = $VM.ExtensionData.CloneVM_Task($Folder.ExtensionData.MoRef, $VMNew, $spec)
        if(!$?)
        {
            Add-Log -Path $strLogFile -Value 'CloneVM_Task failed, cause:' -Type Warning
            Add-Log -Path $strLogFile -Value $Error[0] -Type Warning
            continue
        }
        Add-Log -Path $strLogFile -Value "Task launched: [$($Task.Type)-$($Task.Value)]"
        $Tasks += "$($Task.Type)-$($Task.Value)"
    }
}

$Tasks = @($Tasks | ?{$_})
Add-Log -Path $strLogFile -Value "Waiting for tasks to complete, count: [$($Tasks.Count)]"
while($Tasks)
{
    # clone is running asynchronously, so script needs to trace all tasks every 5 minutes
    # The sleep time better not exceed 15 minutes, due to vCenter will clean completed tasks after 15 minutes
    # when debugging, change the time to 10 seconds is fine
    Start-Sleep -Seconds 300
    $Tasks = Get-Task -Id $Tasks -ErrorAction:SilentlyContinue
    if(!$?)
    {
        Add-Log -Path $strLogFile -Value 'Failed to refresh Task states, cause:' -Type Warning
        Add-Log -Path $strLogFile -Value $Error[0] -Type Warning
    }
    $Tasks = @(
        $Tasks | %{
            if($_.State -ne 'Running')
            {
                Add-Log -Path $strLogFile -Value "Task completed [ID][State]: [$($_.Id)][$($_.State)]"
                if($_.State -eq 'Error')
                {
                    Add-Log -Path $strLogFile -Value $_.ExtensionData.Info.Error.LocalizedMessage -Type Warning
                }
            }
            else
            {
                Add-Log -Path $strLogFile -Value "Task running [ID][% Complete]: [$($_.Id)][$($_.PercentComplete)%]"
                $_.Id
            }
        }
    )
}

# script will start verification for VM backups after all Tasks done
Add-Log -Path $strLogFile -Value 'Verification start'
foreach($e in $Entries)
{
    if((Get-VM -Name $e.NewVM -ErrorAction:SilentlyContinue) -and $?)
    {
        # Backup VM found in vCenter, suggest the backup was succeed
        Add-Log -Path $strLogFile -Value "[VM][NewVM]: [$($e.VM)][$($e.NewVM)] -- New VM found in vCenter"
        if($e.Reserve)
        {
            # If Reserve's valud is set, script will find all backups for the VM, and remove addtionals
            $VMBackups = $null
            $VMBackupsRemoval = $null
            $VMBackups = Get-VM -Name "$($e.VM)_ScriptBackup_*" | ?{$_.Name -imatch '_ScriptBackup_d{4}-d{2}-d{2}$'} -ErrorAction:SilentlyContinue
            $VMBackups = @($VMBackups | Sort-Object 'Name')
            Add-Log -Path $strLogFile -Value "Old VM backups captured: [$($VMBackups.Count)][$(($VMBackups | %{$_.Name}) -join ';')]"
            $i = $VMBackups.Count - $e.Reserve
            if($i -le 0)
            {
                $i = 0
                Add-Log -Path $strLogFile -Value 'No old backups available to be removeds'
            }
            if($i -gt 0)
            {
                Add-Log -Path $strLogFile -Value "VM old backups can be removed count: [$i]"
                $VMBackupsRemoval = $VMBackups[0..(--$i)]
                Remove-VM -VM $VMBackupsRemoval -DeletePermanently -Confirm:$false
            }
        }
    }
    else
    {
        # backup VM not found in vCenter which means backup failed, better do nothing
        Add-Log -Path $strLogFile -Value "[VM][NewVM]: [$($e.VM)][$($e.NewVM)] -- New VM not found in vCenter, backup failure?" -Type Warning
        $Alert = $true
    }
}

Add-Log -Path $strLogFile -Value 'All done!'

# If there is error needs manual work, email will be triggered
if($Alert)
{
    Send-MailMessage -To $To -From $From -SmtpServer $SmtpServer -Subject $Subject -Attachments $strLogFile
    if(!$?)
    {
        Add-Log -Path $strLogFile -Value 'Failed to send email, cause:' -Type Warning
        Add-Log -Path $strLogFile -Value $Error[0] -Type Warning
    }
}

How to backup ESXi VM with enterprise backup software?

Script gives the free ESXi VM backup solution but actually it is not good for enterprise disaster recovery because it is not a comprehensive backup solution and doesn’t provide a quick VM recovery solution. Companies should have a more professional solution.

Vinchin Backup & Recovery is a VMware-verified professional enterprise backup solution supporting guest-level backup for VMware vSphere.

It just requires several minutes to deploy in virtual environment and then a user-friendly web console will help you easily create a backup job with the needed strategies.

1. Add the ESXi host with its credentials to backup VMs on it agentlessly

Add VMware Host

2. Select the ESXi host and then select the VMs on it

Select VMware VMs

3. Select the backup storage

Select Backup Storage

4. Select the backup strategies. For example, you will have incremental backup, data compression, BitDetector, etc. to reduce backup size, data encryption for data security, and LAN-free transfer to mitigate the impact on production environment.

Select Backup Strategies

5. Just review the backup job and submit

Submit the VMware backup job

To facilitate disaster recovery, Vinchin Instant Recovery can let you instantly recover a fail VM from its backup in 15 seconds and to better manage multi-hypervisor environment, you can recover ESXi VM on another host like XenServer and RHV and vice verse.

Vinchin Backup & Recovery has been selected by thousands of companies and you can also start a 60-day full-featured free trial here. Also, contact us, leave your requirements, and then you will receive your tailored solution. We have established partnerships with reputable companies all over the world so if you would like to do a local business, you can select a local partner here.

Sum Up

ESXi can virtualize a bare metal physical server to let companies better utilize the hardware resources on it. To backup data on the server, companies can backup every VM on it so when certain VM is down, recovering the failed VM will not influence the other VMs.

Except for manually exporting VM from vCenter, there is also the way to run scripts to backup ESXi VM with more options.

There is also the solution better than the scripts. Vinchin Backup & Recovery is a professional enterprise backup solution for VMware ESXi VMs and provide more well-round protection to the virtual environment with easy operation. Don’t miss the free trial.

Share on:

Categories: VM Backup