Randomize your Veeam backups via PowerShell

I’m about to write about randomly staggering your Veeam backups, which is abnormal. Most backup administrators want each and every backup to occur exactly at the same time on the same day.

There are several good reasons to have predictable backups.

  • Backups are scheduled to occur after hours when systems are less busy
  • Backups are scheduled to occur right after a database backup occurred
  • Backups are scheduled to occur when other backups are idle

My environment, however, can accept some unpredictability. I no longer wanted to micromanage my backup jobs and when they occur, so I turned to Veeam PowerShell to see if I could script some randomness into my backup schedules.

Purpose

The purpose of this Veeam PowerShell script is to configure each Veeam Backup Job to run periodically every X number of minutes, where X is a unique and random value for each backup job. Some jobs will run every 780 minutes (13 hours) while others could run every 960 minutes (16 hours).

Because most periodic backup increments won’t evenly divide into a 24-hour day, each job will likely run at a different time than the previous day. Some jobs will run more often than others, but all can remain within your service level agreement if your SLA is a range (e.g. backup at least once every 6 to 9 hours).

How Randomness is Generated

PowerShell has a nice Get-Random cmdlet with -Minimum and -Maximum parameters. My script let’s you define the minimum and maximum parameters you are willing to use, picks a random number within those parameters, and then applies that random value to one of the Veeam Backup Job’s settings.

Veeam settings that receive randomness

Since more people are familiar with the graphical interface, I’ll attempt to show you what values the script changes and introduces randomness.

Periodically Every X Minutes (FullPeriod)

Adding randomness to Schedule > Run the job automatically > Periodically Every XXX Minutes was the primary purpose for creating this script. I want my backup jobs to run every 13 to 16 hours. Since Veeam Backup and Replication version 8 does not permit odd-numbered hours, I had to use minutes instead.

My script is configured to run every 780 to 960 minutes. You may use any value you like, so long as it is between 0 and 999 as the field accepts only three characters.

Randomize in minutes how often the job runs
Randomize in minutes how often the job runs

My Veeam PowerShell script modifies this value using this line of code:

1
$jobScheduleOptions.OptionsPeriodically.FullPeriod = $RandomFullPeriod

Start Time Within an Hour (HourlyOffset)

I also wanted to randomize when (within an hour) that a job could begin. This will reduce the likelihood that two jobs that happen to fall within the same hour will begin on the same minute.

Randomize when a job can begin within an hour
Randomize when a job can begin within an hour

My Veeam PowerShell script modifies this value using this line of code:

1
$jobScheduleOptions.OptionsPeriodically.HourlyOffset = $RandomHourlyOffset

Wait before each retry attempt for X minutes (RetryTimeout)

While less important, I also wanted to randomize how long a job should wait before retying a backup should a backup fail.

Randomize how long Veeam should wait before trying again
Randomize how long Veeam should wait before trying again

My Veeam PowerShell script modifies this value using this line of code:

1
$jobScheduleOptions.RetryTimeout = $RandomRetryTimeout

Next Run and Latest Run (NextRun and LatestRun)

These two values do not appear in the Edit Backup Job GUI. The Next Run column, however, does appear on the screen that shows all of your backup jobs.

At first, I didn’t attempt to modify these values. But the problem was that after running my script, all of the jobs on the server ended up with a Next Run within the following hour. You could achieve the same result if you used the GUI to modify all backup jobs within a few minutes. This bothered me because its the exact opposite of what I’d like to accomplish. I want the jobs to rarely overlap.

Documentation on NextRun and LatestRun is sparse. Forums suggested that I only modify the LatestRun value because NextRun is automatically calculated. I didn’t find that to always be the case.

Version 1: My script randomly modifies the LatestRun value to be X minutes in the past while also modifying the NextRun value to be X minutes in the future. When you run the script, you’ll immediately see the GUI’s Next Run column get modified to the new script values. Wait a little longer, and you’ll see them change again (presumably to Veeam’s calculated values). Either way, you end up with backup jobs that are randomly staggered and not overlapping.

Version 2: My script attempts to randomly stagger the the LatestRun values, while then calculating the correct NextRun value by adding whatever the random FullPeriod value is. The only part I do not account for in my script-generated NextRun is the HourlyOffset value. Once the job runs and a backup is complete, Veeam will automatically calculate and modify the next NextRun value.

My Veeam PowerShell script calculates the LastRun and NextRun values using these lines of code:

1
2
3
$RandomlyStagger = Get-Random -Minimum 1 -Maximum 9
$jobScheduleOptions.LatestRun = (Get-Date).AddMinutes(-([math]::Round($RandomFullPeriod * $RandomlyStagger * .1)))
$jobScheduleOptions.NextRun = (Get-Date).AddMinutes([math]::Round($RandomFullPeriod * (10 - $RandomlyStagger) * .1))

Randomize Veeam Backups, a Veeam PowerShell script

Stop-Explanation. Get-Script. Ok, here it is (also Version 1 Version 2 in plain text for download).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
 # Randomize Veeam Backups, a Veeam PowerShell script (Version 2)
 # This script will add randomness to Veeam backup jobs by configuring them to use different periodic backup intervals
 # Run this on a Veeam Backup and Replication version 8 update 1 server
 # You could even copy and paste it into Veeam Backup and Replication > Menu > PowerShell
 # Written by Jason Pearce of jasonpearce.com in March 2015
 # Use at your own risk, freely share, and comment on improvements
 
 # BEGIN Random variables #####################
 
 # RandomFullPeriod: Variable for Schedule > Run the job automatically > Periodically every XX minutes. Must be a value between 0 and 999.
 # Key: 1-8 hours 1h=60min, 2h=120min, 3h=180min, 4h=240min, 5h=300min, 6h=360min, 7h=420min, 8h=480min
 # Key: 9-16 hours 9h=540min, 10h=600min, 11h=660min, 12h=720min, 13h=780min, 14h=840min, 15h=900min, 16h=960min
 $RandomFullPeriodMinimum = 540
 $RandomFullPeriodMaximum = 960
 
 # RandomHourlyOffset: Variable for Schedule > Run the job automatically > Periodically every > Schedule > Start time within an hour. Must be a value between 0 and 59.
 $RandomHourlyOffsetMinimum = 0
 $RandomHourlyOffsetMaximum = 59
 
 # RandomRetryTimeout: Variable for Schedule > Automatic Retry > Wait before each retry attempt for. Must be a value between 0 and 59.
 $RandomRetryTimeoutMinimum = 10
 $RandomRetryTimeoutMaximum = 50
 
 # END Random variables #######################
 
 # BEGIN Other variables ######################
 
 # BackupRetention: Variables for RetainDays (amount of restore points) and RetainCycles (amount of days for Deleted VMs retention period).
 $RetainDays = 14
 $RetainCycles = 14
 
 # Get All, Some, or One backup jobs.
 # You MUST uncomment and modify one of these lines to target one or more backup jobs.
 # Only one line should begin with "$jobs =". These are just four helpful examples.
 #By-All# $jobs = Get-VBRJob | Where-Object { $_.IsBackup -eq $true -and $_.IsScheduleEnabled -eq $true } | Sort Name
 #By-PREFIX# $jobs = Get-VBRJob -Name "Test-*" | Where-Object { $_.IsBackup -eq $true -and $_.IsScheduleEnabled -eq $true } | Sort Name
 #By-LIST# $jobs = Get-VBRJob -Name "Test-1","Test-2" | Where-Object { $_.IsBackup -eq $true -and $_.IsScheduleEnabled -eq $true } | Sort Name
 #By-NAME# $jobs = Get-VBRJob -Name "Test-Job" | Where-Object { $_.IsBackup -eq $true -and $_.IsScheduleEnabled -eq $true } | Sort Name
 
 # END Other variables ########################
 
 # BEGIN foreach loop #########################
 
 # Make the following changes to all backup jobs
 foreach ($job in $jobs) {
 
 # BEGIN Job Options ######################
 
 # Read current job settings
 $jobOptions = Get-VBRJobOptions -Job $job
 
 # Set more Job Advanced Storage Options 
 $jobOptions.BackupStorageOptions.RetainCycles = $RetainCycles
 $jobOptions.BackupStorageOptions.RetainDays = $RetainDays
 
 # Apply these additional Backup Mode settings
 $jobOptions = Set-VBRJobOptions -Job $job -Options $jobOptions
 
 # END Job Options ########################
 
 # BEGIN Job Schedule Options #############
 
 # Create a new job schedule 
 $jobScheduleOptions = New-VBRJobScheduleOptions
 
 # Enable Periodically Schedule
 $jobScheduleOptions.OptionsPeriodically.Enabled = $true
 
 # Configure Periodic Schedule in Minutes (0 = Hours, 1 = Minutes)
 $jobScheduleOptions.OptionsPeriodically.Kind = 1
 
 # Disable Other Schedules
 $jobScheduleOptions.OptionsDaily.Enabled = $false
 $jobScheduleOptions.OptionsMonthly.Enabled = $false
 $jobScheduleOptions.OptionsContinuous.Enabled = $false
 
 # Generate random number for FullPeriod (Minimum and Maximum variables are defined above)
 $RandomFullPeriod = Get-Random -Minimum $RandomFullPeriodMinimum -Maximum $RandomFullPeriodMaximum
 
 # Create a random FullPeriod offset
 $jobScheduleOptions.OptionsPeriodically.FullPeriod = $RandomFullPeriod
 
 # Generate random number for HourlyOffset (Minimum and Maximum variables are defined above)
 $RandomHourlyOffset = Get-Random -Minimum $RandomHourlyOffsetMinimum -Maximum $RandomHourlyOffsetMaximum
 
 # Create a random HourlyOffset
 $jobScheduleOptions.OptionsPeriodically.HourlyOffset = $RandomHourlyOffset
 
 # Generate random number for RetryTimeout (Minimum and Maximum variables are defined above)
 $RandomRetryTimeout = Get-Random -Minimum $RandomRetryTimeoutMinimum -Maximum $RandomRetryTimeoutMaximum
 
 # Create a random RetryTimeout
 $jobScheduleOptions.RetryTimeout = $RandomRetryTimeout
 
 # Do some math to randomly stagger LatestRun (past) and NextRun (future), while correctly calculating the FullPeriod difference
 $RandomlyStagger = Get-Random -Minimum 1 -Maximum 9
 $jobScheduleOptions.LatestRun = (Get-Date).AddMinutes(-([math]::Round($RandomFullPeriod * $RandomlyStagger * .1)))
 $jobScheduleOptions.NextRun = (Get-Date).AddMinutes([math]::Round($RandomFullPeriod * (10 - $RandomlyStagger) * .1))
 
 # Apply the new job schedule
 Set-VBRJobScheduleOptions -Job $job -Options $jobScheduleOptions
 
 # END Job Schedule Options ###############
 
 # Report which jobs received these changes
 Write-Host "Changed settings for" $job.Name
 }
 # END foreach loop ###########################
 # END Randomize Veeam Backups script #########

Report

I thought it would also be helpful to write a reporting script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 # BEGIN REPORT ###############################
 # Optionally Report New Job Schedule Options #
 # Safe to delete this report #################
 
 # Get all enabled backup jobs.
 $jobs = Get-VBRJob -Name "*" | Where-Object { $_.IsBackup -eq $true -and $_.IsScheduleEnabled -eq $true } | Sort Name
 
 # BEGIN foreach loop #########################
 foreach ($job in $jobs) {
 
 # Report which jobs have what schedules
 Write-Host "------------------------------------"
 Write-Host "Job Schedule settings for" $job.Name
 
 # Get job schedule
 $ReportJobScheduleOptions = Get-VBRJobScheduleOptions -Job $job
 
 # Report on a few values
 Write-Host "FullPer: " $ReportJobScheduleOptions.OptionsPeriodically.FullPeriod
 Write-Host "HourOff: " $ReportJobScheduleOptions.OptionsPeriodically.HourlyOffset
 Write-Host "LateRun: " $ReportJobScheduleOptions.LatestRun
 Write-Host "NextRun: " $ReportJobScheduleOptions.NextRun
 Write-Host ""
 
 }
 # END foreach loop ###########################
 
 # Write current date and time
 Get-Date
 
 # END REPORT #################################

Here’s what the results of the report look like.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 ------------------------------------
 Job Schedule settings for Job-1
 FullPer:  838
 HourOff:  41
 LateRun:  3/18/2015 1:43:04 PM
 NextRun:  03/19/2015 03:41:04
 
 ------------------------------------
 Job Schedule settings for Job-2
 FullPer:  700
 HourOff:  44
 LateRun:  3/18/2015 4:02:05 PM
 NextRun:  03/19/2015 03:42:05
 
 ------------------------------------
 Job Schedule settings for Job-3
 FullPer:  796
 HourOff:  24
 LateRun:  3/18/2015 6:03:06 PM
 NextRun:  03/19/2015 07:19:06
 
 ------------------------------------
 Job Schedule settings for Job-4
 FullPer:  665
 HourOff:  53
 LateRun:  3/18/2015 12:56:07 PM
 NextRun:  03/19/2015 00:02:07
 
 ------------------------------------
 Job Schedule settings for Job-5
 FullPer:  860
 HourOff:  48
 LateRun:  3/18/2015 2:58:08 PM
 NextRun:  03/19/2015 05:18:08
 
 PS C:\>  # Write current date and time
 PS C:\>  Get-Date
 
 Wednesday, March 18, 2015 8:59:33 PM
 
 PS C:\>  # END REPORT #################################

The report is not much to look at, but helps you quickly determine if the script made the changes you were expecting. I hope at least one random person finds this script to be helpful.