Automatic offline backup with Veeam and PowerShell

This post will teach you how to setup Veeam to create a Backup Copy Job to a Disk and how to automatically disconnect it after finishing the job. A scheduled task will alarm at a set frequency and remind you to do your offline backup

Table of Contents

Prerequisites

  • At least one restore point of a virtual machine or an agent backup.
  • Basic knowledge of Veeam Backup & Repository
  • A disk attached to the Veeam Backup & Recovery Server

Creating the Veeam backup copy job

The following steps are necessary to setup the disk as a repository for future use and to create the backup job we will later trigger via scheduled task and PowerShell. For best results get a ReFS partitioned disk with a 64 KB cluster size.

Creating a backup repository from the disk

  1. Navigate to Backup Infrastructure on the bottom left.
  2. Click on Add Repository and choose Direct attached storage.
  3. In the next window choose Microsoft Windows.
  4. Name the repository the way you like
  5. Choose next and browse for your attached storage
  6. Limit the concurrent tasks storage if you have a slow medium
  7. Click on Advanced and configure backed up by rotated hard drives.
  8. Leave the rest of the settings in the default state and finish the wizard. But deny the default repository change.

Creating the offline backup job

  1. Navigate to Home on the bottom left.
  2. Click on Backup Copy
  3. Choose the target you want to copy to the disk, virtual machines or windows computer backups.
  4. Name the backup job the way you like, this will be used later in the $BackupCopyJob variable.
  5. Click on Add in the Objects tab and add all the objects you need in your offline backup
  6. Choose the previously created repository as a target
  7. We will leave all the advanced settings on default values, the rest of the settings as well.
  8. Disable the job after creating it (the script will handle this)

Preparing your backup disk

To use your disk to do an offline backup, we need to be able to disconnect it via Powershell after completing the backup. We do not want our backup accessible to the network, where our worst enemy ransomware resides. This will later be used to identify the backup disk. As mentioned before, for best performance format your disk with ReFS and a 64 KB cluster size. Use Get-Disk to get the serial number of your drive.

Preparing the PowerShell script

The following code needs to be implemented on your Veeam Backup & Repository server. This will be used to identify your backup job and the disk that needs to be disconnected when the backup finishes.

$Global:NoExternalDisk = $false #This flag is used to track if a mail message has been sent or not.
$BackupCopyJob = "OfflineBackup" #The job name previously defined
$Global:DiskSerial = "WUBS20099" #Serialnumber of your backup disk

#Mail settings
$From = "sender@fistoftech.ch"
$To = "receiver@fistoftech.ch"
$SMTPServer = "123.156.13.2" #Must be accessible without authentication
$Encoding = "UTF8"

In order to load all the Veeam PowerShell modules this line is needed.

Add-PSSnapin VeeamPSSnapin

This part of the codes waits until the backup disk is present and accessible, it sents a message once.

Function Mount-OfflineBackup {
    
    $Disk = Get-Disk -SerialNumber $Global:DiskSerial  -ErrorAction Stop

        
    if ($Disk -eq $null) {
        Write-Host "$(Get-Date -Format G) The disk was not found."
        
        if (!$Global:NoExternalDisk) {
            $Body = "No disk found, please attach it."
            $Subject = "Offline backup error."
            Send-MailMessage -Subject $Subject -Body $Body -From $From  -To $To -SmtpServer $SMTPServer -Encoding $Encoding
            $Global:NoExternalDisk = $true
        }
        else {
            Write-Host "$(Get-Date -Format G) mail already has been sent, not sending it again."
        }
            
        Write-Host "Waiting for the disk."
        Start-Sleep -Seconds 300
        return $false
    }
    else {
        try {
            $Disk | Set-Disk -IsOffline $false -ErrorAction Stop #Tries to set the backup disk online.
        }
        catch {
            "Disk could not be set online"
            return $false            
        }
        return $true
    }
    return $false
}

With the following while loop, the Mount-OfflineBackup function gets repeated forever unless it returns true. This is the case if the disk is present and online.

while(!(Mount-OfflineBackup)){
    Start-Sleep -Seconds 1
}

When the script gets out of the while loop the backup disk is present, in the next step the previously created backup job gets enabled.Get-VBRJob -Name $BackupCopyJob | Enable-VBRJob

Get-VBRJob -Name $BackupCopyJob | Enable-VBRJob

The last while loop constantly checks if the backup copy job changed to IDLE. The job changing to IDLEmeans the job is waiting for new restore points and did backup all the existing restore points so far. There are two checks if the script is IDLE, to make sure the script does not interrupt too soon. After those two checks, the script steps in and stops the task until the next execution by the task scheduler.

while ($true) {
    Start-Sleep -Seconds (10) 
    $IsIdle = Get-VBRJob -Name $BackupCopyJob | Select-Object -ExpandProperty Isidle #Checking if the job is idle
    if ($IsIdle) {
        Start-Sleep -Seconds (60 * 30) #Waiting a couple of minutes and then checking again
        $IsIdle = Get-VBRJob -Name $BackupCopyJob | Select-Object -ExpandProperty Isidle  #Checking if the job is idle
        if ($IsIdle) {
            Get-VBRJob -Name $BackupCopyJob | Disable-VBRJob  #Disabling the offline backup job
            Get-Disk -SerialNumber $Global:DiskSerial  | Set-Disk -IsOffline $true #Disconnecting the disk.
            
            #Creating the mail message and sending it
            $Body = "Please remove the offline backup from the server for maximal security."
            $Subject = "Offline backup is done!"
            Send-MailMessage -Subject $Subject -Body $Body -From $From  -To $To -SmtpServer $SMTPServer -Encoding $Encoding
            break #leaving the while loop
        }
    }
}

Creating the scheduled task

Configure a task to run the script, open the task scheduler and create a task on the Veeam Backup & Recovery server.

As a trigger in my scenario, I will use every last Monday of the month. This gives me enough time to react if the backup has not been finished or other errors occure. After that switch to Action and configure the task.

In actions configure the following, -file “Path to script”. If your script file is not signed add -executionpolicy bypass to the arguments. But in general, the better option is to properly sign your scripts. In the settings change the script to “Stop the existing instance” if the script is already running.

The Complete Script

[bg_collapse view=”button-blue” color=”#FFFFFF” icon=”arrow” expand_text=”Show Script” collapse_text=”Show Less” ]

$Global:NoExternalDisk = $false #This flag is used to track if a mail message has been sent or not.
$BackupCopyJob = "OfflineBackup" #The job name previously defined
$Global:DiskSerial = "WUBS20099" #Serialnumber of your backup disk

#Mail settings
$From = "sender@fistoftech.ch"
$To = "receiver@fistoftech.ch"
$SMTPServer = "123.156.13.2" 
$Encoding = "UTF8"

Add-PSSnapin VeeamPSSnapin

Function Mount-OfflineBackup {
    
    $Disk = Get-Disk -SerialNumber $Global:DiskSerial  -ErrorAction Stop

        
    if ($Disk -eq $null) {
        Write-Host "$(Get-Date -Format G) The disk was not found."
        
        if (!$Global:NoExternalDisk) {
            $Body = "No disk found, please attach it."
            $Subject = "Offline backup error."
            Send-MailMessage -Subject $Subject -Body $Body -From $From  -To $To -SmtpServer $SMTPServer -Encoding $Encoding
            $Global:NoExternalDisk = $true
        }
        else {
            Write-Host "$(Get-Date -Format G) mail already has been sent, not sending it again."
        }
            
        Write-Host "Waiting for the disk."
        Start-Sleep -Seconds 300
        return $false
    }
    else {
        try {
            $Disk | Set-Disk -IsOffline $false -ErrorAction Stop #Tries to set the backup disk online.
        }
        catch {
            "Disk could not be set online"
            return $false            
        }
        return $true
    }
    return $false
}

while (!(Mount-OfflineBackup)) {
    Start-Sleep -Seconds 1
}

Get-VBRJob -Name $BackupCopyJob | Enable-VBRJob

while ($true) {
    Start-Sleep -Seconds (10) 
    $IsIdle = Get-VBRJob -Name $BackupCopyJob | Select-Object -ExpandProperty Isidle #Checking if the job is idle
    if ($IsIdle) {
        Start-Sleep -Seconds (60 * 30) #Waiting a couple of minutes and then checking again
        $IsIdle = Get-VBRJob -Name $BackupCopyJob | Select-Object -ExpandProperty Isidle  #Checking if the job is idle
        if ($IsIdle) {
            Get-VBRJob -Name $BackupCopyJob | Disable-VBRJob  #Disabling the offline backup job
            Get-Disk -SerialNumber $Global:DiskSerial | Set-Disk -IsOffline $true #Disconnecting the disk.
            
            #Creating the mail message and sending it
            $Body = "Please remove the offline backup from the server for maximal security."
            $Subject = "Offline backup is done!"
            Send-MailMessage -Subject $Subject -Body $Body -From $From  -To $To -SmtpServer $SMTPServer -Encoding $Encoding
            break #leaving the while loop
        }
    }
}

[/bg_collapse]

When you realize nothing is lacking, the whole world belongs to you.

Lao Tzu

Leave a Reply

Powered by WordPress.com.

Up ↑

%d bloggers like this: