Remove spaces from column names when using PowerShell’s Import-Csv

When using PowerShell’s Import-Csv cmdlet, it is ideal for the column headings in the source file to not have any spaces. While the import will work, later referencing values in each column heading is messy. Let me demonstrate.

Messy Column Headers with spaces

Let’s say your source file looks like this. Note that there are 10 column headings, but seven of them contain spaces (highlighted in yellow):

column-headers-with-spaces

If you used Import-Csv to import this file, your imported data would like something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Source Folder
$SourceFolder = "C:\Temp"
 
# Source File
$SourceFile = "Sample-Addresses.csv"
 
# Source Path
$SourcePath = $SourceFolder + "\" + $SourceFile
 
# Import-CSV
$SourceData = Import-CSV -Path $SourcePath
 
# Write Host
$SourceData | Format-Table -AutoSize
 
ID    Last Name First Name Middle Initial Address 1        Address 2 City         State Zip Code Phone Work
--    --------- ---------- -------------- ---------        --------- ----         ----- -------- ----------
00001 Beaver    Bobby      B              123 Valley Rd    Apt A     Indianapolis IN    46260    (317)555-0001
00002 Stout     Stewart    S              234 Plains Ave   Ste B     Indianapolis IN    46260    (317)555-0002
00003 Connor    Chris      C              345 Foothill Cir Fl C      Indianapolis IN    46260    (317)555-0003
00004 Davis     Donald     D              456 Hill St      Bld D     Indianapolis IN    46260    (317)555-0004
00005 Pierce    Peter      P              567 Mountain Pl  Unit E    Indianapolis IN    46260    (317)555-0005

Not bad, right? Well, retrieving all IDs would be easy; but doing the same with Last Names would be a bit messier:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# List all IDs
$SourceData.ID
00001
00002
00003
00004
00005
 
# List all Last Names
$SourceData."Last Name"
Beaver
Stout
Connor
Davis
Pierce

Both work and retrieve the correct results, but the dot-notation for a column heading that contains a space requires double-quotes to reference.

Clean Column Headers Without Spaces

The solution I propose involves the following steps:

  • Import just the first two rows using Get-Content and ConvertFrom-Csv
  • Use Trim() and -Replace to remove unwanted spaces
  • Use Import-CSV with the new -Header to now use the cleaned headers without spaces

Here’s some detail regarding the dot-notation and how Trim() and -Replace cleaned up the column Name values.

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
$SourceHeadersDirty | Format-Table -AutoSize
 
ID    Last Name First Name Middle Initial Address 1     Address 2 City         State Zip Code Phone Work
--    --------- ---------- -------------- ---------     --------- ----         ----- -------- ----------
00001 Beaver    Bobby      B              123 Valley Rd Apt A     Indianapolis IN    46260    (317)555-0001
 
$SourceHeadersDirty.PSObject
 
Members             : {string ID=00001, string Last Name=Beaver, string First Name=Bobby, string Middle Initial=B...}
Properties          : {string ID=00001, string Last Name=Beaver, string First Name=Bobby, string Middle Initial=B...}
Methods             : {string ToString(), bool Equals(System.Object obj), int GetHashCode(), type GetType()}
ImmediateBaseObject :
BaseObject          :
TypeNames           : {System.Management.Automation.PSCustomObject, System.Object}
 
$SourceHeadersDirty.PSObject.Properties | Format-Table -AutoSize
 
  MemberType IsSettable IsGettable Value         TypeNameOfValue Name           IsInstance
  ---------- ---------- ---------- -----         --------------- ----           ----------
NoteProperty       True       True 00001         System.String   ID                   True
NoteProperty       True       True Beaver        System.String   Last Name            True
NoteProperty       True       True Bobby         System.String   First Name           True
NoteProperty       True       True B             System.String   Middle Initial       True
NoteProperty       True       True 123 Valley Rd System.String   Address 1            True
NoteProperty       True       True Apt A         System.String   Address 2            True
NoteProperty       True       True Indianapolis  System.String   City                 True
NoteProperty       True       True IN            System.String   State                True
NoteProperty       True       True 46260         System.String   Zip Code             True
NoteProperty       True       True (317)555-0001 System.String   Phone Work           True
 
$SourceHeadersDirty.PSObject.Properties.Name | Format-Table -AutoSize
 
ID
Last Name
First Name
Middle Initial
Address 1
Address 2
City
State
Zip Code
Phone Work
 
$SourceHeadersDirty.PSObject.Properties.Name.Trim(' ') -Replace '\s','' | Format-Table -AutoSize
 
ID
LastName
FirstName
MiddleInitial
Address1
Address2
City
State
ZipCode
PhoneWork

End Result

Put it all together and you have a nice and simple way to clean your CSV column names upon import.

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
# Import-Csv without spaces in column header (by jasonpearce.com)
 
# Source Folder
$SourceFolder = "C:\Temp"
 
# Source File
$SourceFile = "Sample-Addresses.csv"
 
# Source Path
$SourcePath = $SourceFolder + "\" + $SourceFile
 
# Source Headers Dirty (source CSV has unwanted spaces)
$SourceHeadersDirty = Get-Content -Path $SourcePath -First 2 | ConvertFrom-Csv
 
# Source Headers Cleaned (removed spaces)
$SourceHeadersCleaned = $SourceHeadersDirty.PSObject.Properties.Name.Trim(' ') -Replace '\s',''
 
# Import-CSV
$SourceData = Import-CSV -Path $SourcePath -Header $SourceHeadersCleaned | Select-Object -Skip 1
 
# Write Host
$SourceData | Format-Table -AutoSize
 
ID    LastName FirstName MiddleInitial Address1         Address2 City         State ZipCode PhoneWork
--    -------- --------- ------------- --------         -------- ----         ----- ------- ---------
00001 Beaver   Bobby     B             123 Valley Rd    Apt A    Indianapolis IN    46260   (317)555-0001
00002 Stout    Stewart   S             234 Plains Ave   Ste B    Indianapolis IN    46260   (317)555-0002
00003 Connor   Chris     C             345 Foothill Cir Fl C     Indianapolis IN    46260   (317)555-0003
00004 Davis    Donald    D             456 Hill St      Bld D    Indianapolis IN    46260   (317)555-0004
00005 Pierce   Peter     P             567 Mountain Pl  Unit E   Indianapolis IN    46260   (317)555-0005

Using PowerShell to clean up DHCP and DNS for VDI

When you run a virtual desktop infrastructure that builds and deletes hundreds of virtual machines every day, DHCP and DNS might eventually get out of sync and need some cleanup.

When writing this PowerShell cleanup script, I decided to make vCenter my source of truth because has the virtual machine host names, IP addresses, power state (on/off), and even MAC address. With vCenter as my source of truth, my objectives were as follows:

  • In vCenter, note the virtual machine names, IPs, MACs, and power state
  • For powered off VMs, delete their DHCP, DNS Foward, and DNS Reverse records
  • For powered on VMs, if the hostname does not match the IP address in vCenter, delete their DHCP, DNS Foward, and DNS Reverse records
  • Have all powered on VMs renew their DHCP address (e.g. ipconfig /renew)
  • Provide some basic before and after reporting

I comment my script well enough for many to follow it. Some of the Select-Object makes use of some custom column headings, which you can learn about in this PowerShell tip of the week.

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# VDI/DHCP/DNS: Clean up DHCP and DNS, removing unused or incorrect records
 
#########################################
# Import Modules
#########################################
 
# Import Required Modules
Import-Module VMware.VimAutomation.Core
Import-Module ActiveDirectory
Import-Module DhcpServer
Import-Module DnsServer
 
#########################################
# Define user-configured variables (admin should make changes here)
#########################################
 
# Scope: What machine names would you like to target?
# $VMNameLike = "VDI-Example-1"
# $VMNameLike = "VDI-Example*"
$VMNameLike = "VDI-*"
 
# Servers
$ServerVCenter = "vcenterservername"
$ServerDHCP = "dhcpservername"
$ServerDNS = "dnsservername"
 
# DHCP Scope and DNS Zones
$DHCPScopeID = "10.10.10.1"
$DNSForwardZone = "example.local"
$DNSReverseZone = "10.10.in-addr.arpa"
 
#########################################
# Begin Script (no need for admin to make changes below this point)
#########################################
 
# Connect to Desktop vCenter
# Connect-VIServer $ServerVCenter
Connect-VIServer -Server $global:DefaultVIServer -Session $global:DefaultVIServer.SessionId
 
# Get VMware virtual machine names that begin with the $VMNameLike prefix
$GetVSphereAll = Get-VM | Where-Object {$_.Name -like $VMNameLike}
 
# Get VMware virtual machine names that begin with the $VMNameLike prefix and are powered On
$GetVSphereOn = Get-VM | Where-Object {$_.Name -like $VMNameLike -AND $_.PowerState -eq "PoweredOn"} | Select-Object Name, @{N="FQDN";E={@($_.Name+".riverview.local")}}, @{N="IPAddress";E={@($_.Guest.IPAddress)}}, @{N="MACColon";E={@($_.Guest.Nics.MacAddress)}}, @{N="MACHyphen";E={@($_.Guest.Nics.MacAddress -Replace ':','-')}}
 
# Get VMware virtual machine names that begin with the $VMNameLike prefix and are powered Off
$GetVSphereOff = Get-VM | Where-Object {$_.Name -like $VMNameLike -AND $_.PowerState -eq "PoweredOff"} | Select-Object Name, @{N="FQDN";E={@($_.Name+".riverview.local")}}
 
# Get AD machines that begin with the $VMNameLike prefix
$GetAD = Get-ADComputer -Filter {Name -like $VMNameLike}
 
# Get DHCP machines that begin with the $VMNameLike prefix
$GetDHCP = Get-DhcpServerv4Lease -ComputerName $ServerDHCP -ScopeId $DHCPScopeID | Where-Object {$_.HostName -like $VMNameLike} 
 
# Get DNS A records for machines that begin with the $VMNameLike prefix
$GetDNSA = Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSForwardZone -RRType A | Where-Object {$_.HostName -like $VMNameLike} | Select-Object RecordType, @{N="Name";E={@($_.Hostname)}}, @{N="IPv4Address";E={@($_.RecordData.IPv4Address)}}, HostName, RecordData
 
# Get DNS PTR records for machines that begin with the $VMNameLike prefix
$GetDNSPTR = Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSReverseZone -RRType PTR | Where-Object {$_.RecordData.PtrDomainName -like $VMNameLike} | Select-Object RecordType, @{N="Name";E={@($_.RecordData.PtrDomainName -Replace '.riverview.local.','')}}, @{N="IPv4Address";E={@('172.22.'+$_.HostName)}}, HostName, RecordData
 
# Count Results to quickly compare of vCenter, DHCP, and DNS have the same quantity of records
$CountVSphereOnBefore = $GetVSphereOn | Measure-Object | Select-Object -ExpandProperty Count
$CountVSphereOffBefore = $GetVSphereOff | Measure-Object | Select-Object -ExpandProperty Count
$CountVSphereAllBefore = $GetVSphereAll | Measure-Object | Select-Object -ExpandProperty Count
$CountDNSPTRBefore = $GetDNSPTR | Measure-Object | Select-Object -ExpandProperty Count
$CountDNSABefore = ($GetDNSA | Measure-Object | Select-Object -ExpandProperty Count)/2
$CountDHCPBefore = $GetDHCP | Measure-Object | Select-Object -ExpandProperty Count
$CountADBefore = $GetAD | Measure-Object | Select-Object -ExpandProperty Count
 
# Show Before Count Results
# #################################################################################
Write-Host "----------------------------------------------------------------------"
Write-Host "BEFORE making changes, here's some data for you to review"
Write-Host "----------------------------------------------------------------------"
Write-Host $CountVSphereOnBefore "powered on machines in vCenter named" $VMNameLike "BEFORE making changes"
Write-Host $CountVSphereOffBefore "powered off machines in vCenter named" $VMNameLike "BEFORE making changes"
Write-Host $CountVSphereAllBefore "all machines in vCenter named" $VMNameLike "BEFORE making changes"
Write-Host $CountDNSPTRBefore "machines in DNS Reverse lookup zone named" $VMNameLike "BEFORE making changes"
Write-Host $CountDNSABefore "machines in DNS Forward lookup zone named" $VMNameLike "BEFORE making changes"
Write-Host $CountDHCPBefore "machines in DHCP named" $VMNameLike "BEFORE making changes"
Write-Host $CountADBefore "machines in Active Directory named" $VMNameLike "BEFORE making changes"
# #################################################################################
 
 
#########################################
# MAKE CHANGES for Powered OFF VMs
#########################################
 
# Delete DHCP Leases for Powered Off VMs
#########################################
 
# CHANGE: Remove DHCP Lease for each PoweredOff VMs
foreach ($HostName in $GetVSphereOff) {
	Get-DhcpServerv4Lease -ComputerName $ServerDHCP -ScopeId $DHCPScopeID | Where-Object {$_.HostName -eq $HostName.FQDN} | Remove-DhcpServerv4Lease -ComputerName $ServerDHCP
}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
# Delete DNS A records for Powered Off VMs
#########################################
 
# CHANGE: Remove DNA A record for each PoweredOff VM
foreach ($HostName in $GetVSphereOff) {
	Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSForwardZone -RRType A | Where-Object {$_.HostName -eq $HostName.Name} | Remove-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSForwardZone -Force
}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
# Delete DNS PTR records for Powered Off VMs
#########################################
 
# CHANGE: Remove DNA PTR record for each PoweredOff VM
foreach ($HostName in $GetVSphereOff) {
	Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSReverseZone -RRType PTR | Where-Object {$_.RecordData.PtrDomainName -like ($HostName.Name + "*")} | Remove-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSReverseZone -Force
}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
#########################################
# MAKE CHANGES for Powered ON VMs
#########################################
 
# Delete DHCP Leases for Powered On VMs that have IPs that don't match vCenter
#########################################
 
# CHANGE: Remove DHCP Lease for mismatched PoweredOn VMs
foreach ($HostName in $GetVSphereOn) {
 
	# If the VM name from vCenter matches the HostName in DHCP, but the IP address does not, delete the DHCP lease
	Get-DhcpServerv4Lease -ComputerName $ServerDHCP -ScopeId $DHCPScopeID | Where-Object {$_.HostName -eq $HostName.FQDN -AND $_.IPADDRESS -ne $HostName.IPAddress} | Remove-DhcpServerv4Lease -ComputerName $ServerDHCP
 
	# If the VM MAC from vCenter matches the HostName in DHCP, but the IP address does not, delete the DHCP lease
	Get-DhcpServerv4Lease -ComputerName $ServerDHCP -ScopeId $DHCPScopeID | Where-Object {$_.ClientID -eq $HostName.MACHyphen -AND $_.IPADDRESS -ne $HostName.IPAddress} | Remove-DhcpServerv4Lease -ComputerName $ServerDHCP
 
}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
# Delete DNS A records for Powered On VMs that have IPs that don't match vCenter
#########################################
 
# CHANGE: Remove DNA A record for mismatched PoweredOn VMs
foreach ($HostName in $GetVSphereOn) {
	Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSForwardZone -RRType A | Where-Object {$_.HostName -eq $HostName.Name -AND $_.RecordData.IPv4Address -ne $HostName.IPAddress} | Remove-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSForwardZone -Force
}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
# Delete DNS PTR records for for Powered On VMs that have IPs that don't match vCenter
#########################################
 
# CHANGE: Remove DNA PTR record for mismatched PoweredOn VMs
foreach ($HostName in $GetVSphereOn) {
 
	# Spilt VM IP address into the last two octets to match DNS PTR HostName
	$IPAddress = $HostName.IPAddress
	$IPAddressOctets = $ipAddress.Split('.')
	$IPAddressLastTwoOctets = $IPAddressOctets[3] + "." + $IPAddressOctets[2]
 
	# Find and delete mismatched records
	Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSReverseZone -RRType PTR | Where-Object {$_.RecordData.PtrDomainName -like ($HostName.Name + "*") -AND $_.HostName -notlike $IPAddressLastTwoOctets} | Remove-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSReverseZone -Force
}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
#########################################
# RENEW IP Addresses (e.g. attempt to get DHCP and DNS in sync)
#########################################
 
# REFRESH: Have each powered on vCenter VM ask DHCP to renew its IP address. (this will take 5 to 10 seconds per VM)
foreach ($Computer in $GetVSphereOn) {Invoke-Command -ComputerName $Computer.Name { ipconfig /renew }}
 
# Pause: Pause 5 seconds to replicate changes
Start-Sleep -s 5
 
 
#########################################
# REPORT on what changed by getting new counts
#########################################
 
# Get VMware virtual machine names that begin with the $VMNameLike prefix
$GetVSphereAll = Get-VM | Where-Object {$_.Name -like $VMNameLike}
 
# Get VMware virtual machine names that begin with the $VMNameLike prefix and are powered On
$GetVSphereOn = Get-VM | Where-Object {$_.Name -like $VMNameLike -AND $_.PowerState -eq "PoweredOn"} | Select-Object Name, @{N="FQDN";E={@($_.Name+".riverview.local")}}, @{N="IPAddress";E={@($_.Guest.IPAddress)}}, @{N="MACColon";E={@($_.Guest.Nics.MacAddress)}}, @{N="MACHyphen";E={@($_.Guest.Nics.MacAddress -Replace ':','-')}}
 
# Get VMware virtual machine names that begin with the $VMNameLike prefix and are powered Off
$GetVSphereOff = Get-VM | Where-Object {$_.Name -like $VMNameLike -AND $_.PowerState -eq "PoweredOff"} | Select-Object Name, @{N="FQDN";E={@($_.Name+".riverview.local")}}
 
# Get AD machines that begin with the $VMNameLike prefix
$GetAD = Get-ADComputer -Filter {Name -like $VMNameLike}
 
# Get DHCP machines that begin with the $VMNameLike prefix
$GetDHCP = Get-DhcpServerv4Lease -ComputerName $ServerDHCP -ScopeId $DHCPScopeID | Where-Object {$_.HostName -like $VMNameLike} 
 
# Get DNS A records for machines that begin with the $VMNameLike prefix
$GetDNSA = Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSForwardZone -RRType A | Where-Object {$_.HostName -like $VMNameLike} | Select-Object RecordType, @{N="Name";E={@($_.Hostname)}}, @{N="IPv4Address";E={@($_.RecordData.IPv4Address)}}, HostName, RecordData
 
# Get DNS PTR records for machines that begin with the $VMNameLike prefix
$GetDNSPTR = Get-DnsServerResourceRecord -ComputerName $ServerDNS -ZoneName $DNSReverseZone -RRType PTR | Where-Object {$_.RecordData.PtrDomainName -like $VMNameLike} | Select-Object RecordType, @{N="Name";E={@($_.RecordData.PtrDomainName -Replace '.riverview.local.','')}}, @{N="IPv4Address";E={@('172.22.'+$_.HostName)}}, HostName, RecordData
 
# Count Results to quickly compare of vCenter, DHCP, and DNS have the same quantity of records
$CountVSphereOnAfter = $GetVSphereOn | Measure-Object | Select-Object -ExpandProperty Count
$CountVSphereOffAfter = $GetVSphereOff | Measure-Object | Select-Object -ExpandProperty Count
$CountVSphereAllAfter = $GetVSphereAll | Measure-Object | Select-Object -ExpandProperty Count
$CountDNSPTRAfter = $GetDNSPTR | Measure-Object | Select-Object -ExpandProperty Count
$CountDNSAAfter = ($GetDNSA | Measure-Object | Select-Object -ExpandProperty Count)/2
$CountDHCPAfter = $GetDHCP | Measure-Object | Select-Object -ExpandProperty Count
$CountADAfter = $GetAD | Measure-Object | Select-Object -ExpandProperty Count
 
# Compare Count Results
# #################################################################################
Write-Host "----------------------------------------------------------------------"
Write-Host "COMPARE before and after counts"
Write-Host "----------------------------------------------------------------------"
Write-Host $CountVSphereOnBefore "powered on machines in vCenter named" $VMNameLike "BEFORE making changes"
Write-Host $CountVSphereOnAfter "powered on machines in vCenter named" $VMNameLike "AFTER making changes"
Write-Host $CountVSphereOffBefore "powered off machines in vCenter named" $VMNameLike "BEFORE making changes"
Write-Host $CountVSphereOffAfter "powered off machines in vCenter named" $VMNameLike "AFTER making changes"
Write-Host $CountVSphereAllBefore "machines in vCenter named" $VMNameLike "BEFORE making changes"
Write-Host $CountVSphereAllAfter "machines in vCenter named" $VMNameLike "AFTER making changes"
Write-Host $CountDNSPTRBefore "machines in DNS Reverse lookup zone named" $VMNameLike "BEFORE making changes"
Write-Host $CountDNSPTRAfter "machines in DNS Reverse lookup zone named" $VMNameLike "AFTER making changes"
Write-Host $CountDNSABefore "machines in DNS Forward lookup zone named" $VMNameLike "BEFORE making changes"
Write-Host $CountDNSAAfter "machines in DNS Forward lookup zone named" $VMNameLike "AFTER making changes"
Write-Host $CountDHCPBefore "machines in DHCP named" $VMNameLike "BEFORE making changes"
Write-Host $CountDHCPAfter "machines in DHCP named" $VMNameLike "AFTER making changes"
Write-Host $CountADBefore "machines in Active Directory named" $VMNameLike "BEFORE making changes"
Write-Host $CountADAfter "machines in Active Directory named" $VMNameLike "AFTER making changes"
# #################################################################################

Use PowerShell to randomly stagger Veeam backup schedules

In previous posts “Randomize your Veeam backups via PowerShell” and “Fixing Veeam Backup v8 Periodic Scheduling” I discussed the unwanted and unexpected changes Veeam Backup and Replication 8 made to is Periodic Scheduling.

In summary, Periodic Schedules could no longer exceed a 24-hour period (e.g. you could no longer configure jobs to run every 30 hours) and that all Periodic Schedules were reset at midnight (causing all periodically scheduled backup jobs to run at the same time, right after midnight).

My desire is to have backup jobs evenly distributed throughout each day, with only a few running at a time. I also don’t want to manually tweak and manage all of my backup job schedules to avoid undesired concurrent operations. When a new backup job gets created, I don’t want my team to waste time trying to figure out when it should be scheduled to run.

My solution was to create a PowerShell script that will use Get-Random to randomly stagger all of the scheduled features of each backup job — leaving it to random chance to automatically schedule my backup jobs to avoid running all at the same time.

Veeam 9 made a some changes and additions to its PowerShell cmdlets to warrant a re-write of the scripts that I wrote to randomize the schedules of my backup jobs. My script modifies backup job schedules, but does not enable or disable if those schedules are used (meaning, you’ll see some settings below that are grayed out, but have settings that the script will change).

Scheduled Settings Before running Script

There are many settings in a backup job that are scheduled, and Veeam 9 introduced a few new ones. These screen shots are of a test/sample backup job and graphically illustrate the settings that my script could randomize for you.

Backup Job > Storage > Advanced > Backup

My script could randomize the following for you:

  • Create synthetic full backups periodically (currently set for Saturday)
  • Create active full backups periodically
  • Monthly on (currently set for First Friday)
  • Weekly on selected days (currently set for Saturday)

veeam-a-storage-backup

Backup Job > Storage > Advanced > Maintenance

These settings are new to Veeam 9. Depending on your architecture/storage, they likely require a lot of IOPS. Having these maintenance tasks staggered among jobs is ideal.

  • Perform backup files health check
  • Monthly on (currently set for Second Sunday)
  • Weekly on selected days (currently set for Tuesday)
  • Defragment and compact full backup file
  • Monthly on (currently set for Third Tuesday)
  • Weekly on selected days (currently set for Saturday)

veeam-a-storage-maintenance

Backup Job > Schedule

Modifying the periodic schedule is the primary purpose of the script. Simply state how many backups you want performed each day and the script will randomly stagger in minutes how often the job should run.

  • Periodically every (currently set for 354 Minutes)
  • Wait before each retry attempt for (currently set for 32 minutes)

veeam-a-schedule

Backup Job > Schedule > Time Periods

Preventing all backup jobs from running at midnight is the secondary purpose of the script. By using Permitted and Denied options, the script will automatically Deny jobs from running up to 4 hours after midnight (again, a offset that is randomly selected per day per job across all jobs).

  • Denied (note the pattern and that some days there is no delay, while other days have a 4-hour delay)
  • Start time within an hour (currently set for 42 minutes)

veeam-a-schedule-timeperiods

Scheduled Settings After running Script

After running my script, all of those settings have been semi-randomly modified.

Backup Job > Storage > Advanced > Backup

  • Create synthetic full backups periodically (currently set for Wednesday)
  • Create active full backups periodically
  • Monthly on (currently set for First Saturday)
  • Weekly on selected days (currently set for Wednesday)

veeam-b-storage-backup

Backup Job > Storage > Advanced > Maintenance

  • Perform backup files health check
  • Monthly on (now set for Last Friday)
  • Weekly on selected days (now set for Sunday)
  • Defragment and compact full backup file
  • Monthly on (now set for Last Tuesday)
  • Weekly on selected days (now set for Saturday)

veeam-b-storage-maintenance

Backup Job > Schedule

  • Periodically every (now set for 451 Minutes)
  • Wait before each retry attempt for (now set for 34 minutes)

veeam-b-schedule

Backup Job > Schedule > Time Periods

  • Denied (note the pattern and that some days there is no delay, while other days have a 4-hour delay)
  • Start time within an hour (now set for 20 minutes)

veeam-b-schedule-timeperiods

Veeam PowerShell Script to randomize backup schedules

I’ve added enough comments to my script that I think it sufficiently explains itself. I hope you find it useful.

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# This script will randomly stagger Veeam 9 backup schedules for one or more jobs 
# Only schedule-related settings are modified. The script does not enable or disable job options.
# Run this on a Veeam Backup and Replication version 9 server
# You could even copy and paste it into Veeam Backup and Replication > Menu > PowerShell
# Written by Jason Pearce of jasonpearce.com in September 2016
# Use at your own risk, freely share, and comment on improvements
 
##############################################
# ENABLE Veeam PowerShell Snap-In ############
##############################################
 
# Add Veeam snap-in if required
if ((Get-PSSnapin -Name VeeamPSSnapin -ErrorAction SilentlyContinue) -eq $null) {add-pssnapin VeeamPSSnapin}
 
 
##############################################
# DEFINE User Preferences ####################
##############################################
 
# Target: Using the names of backup jobs (wildcards, prefix, suffix), define which jobs this script should target
#$Jobs = Get-VBRJob -Name "ExampleJob" #(one specific job)
#$Jobs = Get-VBRJob -Name "Prefix-*" #(one or more jobs)
#$Jobs = Get-VBRJob -Name "*-Suffix" #(one or more jobs)
$Jobs = Get-VBRJob -Name "TestJob"
#$Jobs = Get-VBRJob -Name "*" | Where-Object { $_.IsBackup -eq $true -and $_.IsScheduleEnabled -eq $true } | Sort Name #(all enabled backup jobs)
#$Jobs = Get-VBRJob -Name "*-by-folder" | Where-Object { $_.IsBackup -eq $true -and $_.IsScheduleEnabled -eq $true } | Sort Name
 
# Frequency: How many times per day should each job run? (e.g. minum frequency of backups)
$BackupFrequencyPerDay = 4
 
# Retention: How many days of restore points should be retained? (e.g. the oldest restore point). Leave black if you do not want it modified.
$BackupRetentionInDays = 7
 
# --------------------------------------------
# Decide what to Randomize (set $True or $False). 
# The script will randomize each schedule, but does not Enable or Disable the backup job option.
# --------------------------------------------
 
# Randomize Synthetic Full Backups Periodically schedule?
$RandomizeSyntheticFull = $True
 
# Randomize Active Full Backup schedule (both Monthly On and Weekly On)?
$RandomizeActiveFull = $True
 
# Randomize Storage-Level Corruption Guard schedule (both Monthly On and Weekly On)?
$RandomizeStorageLevelCorruptionGard = $True
 
# Randomize Full Backup File Maintenance schedule (both Monthly On and Weekly On)?
$RandomizeFullBackupFileMaintenance = $True
 
# Randomize Periodically Every X Minutes schedule? (this is the main purpose of the script)
$RandomizePeriodicallyEvery = $True
 
# Randomize Time Periods schedule? (this adds a random Denied offset to each day to prevent all Periodic jobs from running at midnight)
$RandomizeTimePeriods = $True
 
# Randomize Start Time Within An Hour schedule? (instead of starting at :00, jobs will stagger start at :09, :17, :41, etc. minutes after the hour)
$RandomizeStartTimeWithinHour = $True
 
# Randomize Automatic Retry Wait in minutes (wait before each retry attempt for X minutes)
$RandomizeAutomaticRetryWait = $True
 
##############################################
# CALCULATE User Preferences #################
##############################################
 
# Frequency Quantity Calculation: Create a range of how many times per day each job should run (e.g. +-0.9)
$MinNumBackupsPerDay = $BackupFrequencyPerDay - 0.9
$MaxNumBackupsPerDay = $BackupFrequencyPerDay + 0.9
 
# Frequency Minutes Calculation: Converting the quantity range into minutes (e.g. backup every 200 to 300 minutes)
$MinutesInADay = 1440
$MinBackupRangeInMinutes = [math]::Round($MinutesInADay/$MaxNumBackupsPerDay)
$MaxBackupRangeInMinutes = [math]::Round($MinutesInADay/$MinNumBackupsPerDay)
 
# Retention Calculation: Should be ($BackupFrequencyPerDay x $BackupRetentioninDays)
# GUI > Edit Backup Job > Storage > Retention Policy > Restore points to keep on disk
$RetainCycles = $BackupFrequencyPerDay * $BackupRetentioninDays
 
 
##############################################
# BEGIN Script ###############################
##############################################
 
 
##############################################
# BEGIN Random variables                     #
##############################################
 
# RandomFullPeriod: Variable for Schedule > Run the job automatically > Periodically every XX minutes. Must be a value between 0 and 1440.
$RandomFullPeriodMinimum = $MinBackupRangeInMinutes
$RandomFullPeriodMaximum = $MaxBackupRangeInMinutes
 
# 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 = 30
$RandomRetryTimeoutMaximum = 59
 
# RandomMidnightDelay: Variable to prevent all periodic backup jobs from running at midnight (Schedule Denied).
$RandomMidnightDelay0Hours = "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
$RandomMidnightDelay1Hours = "1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
$RandomMidnightDelay2Hours = "1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
$RandomMidnightDelay3Hours = "1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
$RandomMidnightDelay4Hours = "1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
$RandomMidnightDelay5Hours = "1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
$RandomMidnightDelay6Hours = "1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
 
# RandomPermitDeny: Default schedule settings for documentation purposes.
#$RandomPermitDenyDefault = "<scheduler><Sunday>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0</Sunday><Monday>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0</Monday><Tuesday>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0</Tuesday><Wednesday>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0</Wednesday><Thursday>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0</Thursday><Friday>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0</Friday><Saturday>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0</Saturday></scheduler>"
 
# Every Day Number in the Month
$EveryDayNumberInMonth = "First","Second","Third","Fourth","Last"
 
# Every Day of the Week
$EveryDayOfWeek = "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"
 
# Every Month of the Year
$EveryMonthOfYear = "January","February","March","April","May","June","July","August","September","October","November","December"
 
# Random Day Number in the Month (mostly here for copy/paste reference in the foreach loop, because we'll want uniquely random results each time)
$RandomDayNumberInMonth = Get-Random -Input "First","Second","Third","Fourth","Last"
 
# Random Day of the Week (mostly here for copy/paste reference in the foreach loop, because we'll want uniquely random results each time)
$RandomDayOfWeek = Get-Random -Input "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"
 
# Random Month of the Year (mostly here for copy/paste reference in the foreach loop, because we'll want uniquely random results each time)
$RandomMonthOfYear = Get-Random -Input "January","February","March","April","May","June","July","August","September","October","November","December"
 
# Script Name
$scriptName = "RandomizeBackupJobSchedules"
 
# Start Time
$starttime = Get-Date -uformat "%m-%d-%Y %I:%M:%S"
 
# Report Header
Clear-Host
Write-Host "********************************************************************************"
Write-Host "$scriptName`t`tStart Time:`t$starttime"
Write-Host "********************************************************************************`n"
 
 
##############################################
# BEGIN foreach loop                         #
##############################################
 
# Make the following changes to all backup jobs
foreach ($job in $jobs) {
 
	# Notice: State which job is being updated
	Write-Host "Setting job options on "$job.Name -ForegroundColor Yellow
 
	#--------------------------------------------------------------------
	# Retention Policy
	if ($BackupRetentionInDays -ne $null) {
 
	# BEGIN Job Options ######################
 
	# Read current job settings
	$jobOptions = Get-VBRJobOptions -Job $job
 
	# Set Job Advanced Backup Storage Options 
	$jobOptions.BackupStorageOptions.RetainCycles = $RetainCycles
 
	# Apply these additional Backup Mode settings
	$jobOptions = Set-VBRJobOptions -Job $job -Options $jobOptions
 
	}
 
	#--------------------------------------------------------------------
	# Randomize Synthetic Full Backups Periodically
	if ($RandomizeSyntheticFull -eq $True) {
 
		# Set-VBRJobAdvancedBackupOptions: Customizes advanced job backup settings.
		Set-VBRJobAdvancedBackupOptions -Job $job -TransformToSyntethicDays (Get-Random -Input "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday") | Out-Null
 
	}
 
	#--------------------------------------------------------------------
	# Randomize Active Full Backup
	if ($RandomizeActiveFull -eq $True) {
 
		# Set-VBRJobAdvancedBackupOptions: Customizes advanced job backup settings.
		Set-VBRJobAdvancedBackupOptions -Job $job -FullBackupDays (Get-Random -Input "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday") -Months $EveryMonthOfYear -DayNumberInMonth (Get-Random -Input "First","Second","Third","Fourth","Last") -DayOfWeek (Get-Random -Input "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday") | Out-Null
 
	}
 
	#--------------------------------------------------------------------
	# Randomize Storage-Level Corruption Guard
	if ($RandomizeStorageLevelCorruptionGard -eq $True) {
 
		# Set-VBRJobOptions: Applies custom job settings. Acts like a catch all for settings not in other Veeam 9 PowerShell Cmdlets.
 
		# Get current job settings
		$jobOptions = Get-VBRJobOptions $job
 
		# Define new randomized job settings
		$jobOptions.GenerationPolicy.RecheckDays = Get-Random -Input "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"
		$jobOptions.GenerationPolicy.RecheckBackupMonthlyScheduleOptions.DayNumberInMonth = Get-Random -Input "First","Second","Third","Fourth","Last"
		$jobOptions.GenerationPolicy.RecheckBackupMonthlyScheduleOptions.DayOfWeek = Get-Random -Input "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"
		$jobOptions.GenerationPolicy.RecheckBackupMonthlyScheduleOptions.Months = $EveryMonthOfYear
 
		# Apply these additional Backup Mode settings
		Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null
 
	}
 
	#--------------------------------------------------------------------
	# Randomize Full Backup File Maintenance
	if ($RandomizeFullBackupFileMaintenance -eq $True) {
 
		# Set-VBRJobOptions: Applies custom job settings. Acts like a catch all for settings not in other Veeam 9 PowerShell Cmdlets.
 
		# Get current job settings
		$jobOptions = Get-VBRJobOptions $job
 
		# Define new randomized job settings
		$jobOptions.GenerationPolicy.CompactFullBackupDays = Get-Random -Input "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"
		$jobOptions.GenerationPolicy.CompactFullBackupMonthlyScheduleOptions.DayNumberInMonth = Get-Random -Input "First","Second","Third","Fourth","Last"
		$jobOptions.GenerationPolicy.CompactFullBackupMonthlyScheduleOptions.DayOfWeek = Get-Random -Input "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"
		$jobOptions.GenerationPolicy.CompactFullBackupMonthlyScheduleOptions.Months = $EveryMonthOfYear
 
		# Apply these additional Backup Mode settings
		Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null
 
	}
 
	#--------------------------------------------------------------------
	# Randomize Periodically Every X Minutes
	if ($RandomizePeriodicallyEvery -eq $True) {
 
		# Create a new job schedule
		$jobScheduleOptions = Get-VBRJobScheduleOptions -Job $job
 
		# Disable Other Schedules
		$jobScheduleOptions.OptionsDaily.Enabled = $false
		$jobScheduleOptions.OptionsMonthly.Enabled = $false
		$jobScheduleOptions.OptionsContinuous.Enabled = $false
 
		# Enable Periodically Schedule
		$jobScheduleOptions.OptionsPeriodically.Enabled = $true
 
		# Configure Periodic Schedule in Minutes (0 = Hours, 1 = Minutes)
		$jobScheduleOptions.OptionsPeriodically.Kind = 1
 
		# 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
 
		# Do some math to randomly stagger the NextRun (future), while correctly calculating the FullPeriod difference
		$RandomlyStagger			= Get-Random -Minimum 1 -Maximum $RandomFullPeriod
		$jobScheduleOptions.NextRun	= ($jobScheduleOptions.LatestRunLocal).AddMinutes($RandomlyStagger)
 
		# Apply the new job schedule
		Set-VBRJobScheduleOptions -Job $job -Options $jobScheduleOptions | Out-Null
 
	}
 
	#--------------------------------------------------------------------
	# Randomize Time Periods schedule (e.g. midnight "run all jobs" avoidance)
	if ($RandomizeTimePeriods -eq $True) {
 
		# Create a new job schedule
		$jobScheduleOptions = Get-VBRJobScheduleOptions -Job $job
 
		# Generate random Midnight Delay (Schedule Denied) for each day. Variables up to $RandomMidnightDelay6Hours are already created above.
		$RandomMidnightDelaySun = Get-Random $RandomMidnightDelay0Hours,$RandomMidnightDelay1Hours,$RandomMidnightDelay2Hours,$RandomMidnightDelay3Hours,$RandomMidnightDelay4Hours
		$RandomMidnightDelayMon = Get-Random $RandomMidnightDelay0Hours,$RandomMidnightDelay1Hours,$RandomMidnightDelay2Hours,$RandomMidnightDelay3Hours,$RandomMidnightDelay4Hours
		$RandomMidnightDelayTue = Get-Random $RandomMidnightDelay0Hours,$RandomMidnightDelay1Hours,$RandomMidnightDelay2Hours,$RandomMidnightDelay3Hours,$RandomMidnightDelay4Hours
		$RandomMidnightDelayWed = Get-Random $RandomMidnightDelay0Hours,$RandomMidnightDelay1Hours,$RandomMidnightDelay2Hours,$RandomMidnightDelay3Hours,$RandomMidnightDelay4Hours
		$RandomMidnightDelayThu = Get-Random $RandomMidnightDelay0Hours,$RandomMidnightDelay1Hours,$RandomMidnightDelay2Hours,$RandomMidnightDelay3Hours,$RandomMidnightDelay4Hours
		$RandomMidnightDelayFri = Get-Random $RandomMidnightDelay0Hours,$RandomMidnightDelay1Hours,$RandomMidnightDelay2Hours,$RandomMidnightDelay3Hours,$RandomMidnightDelay4Hours
		$RandomMidnightDelaySat = Get-Random $RandomMidnightDelay0Hours,$RandomMidnightDelay1Hours,$RandomMidnightDelay2Hours,$RandomMidnightDelay3Hours,$RandomMidnightDelay4Hours
 
		# Create a random Midnight Delay (Schedule Denied) for the full week
		$RandomMidnightDelay = "<scheduler><Sunday>" + $RandomMidnightDelaySun + "</Sunday><Monday>" + $RandomMidnightDelayMon + "</Monday><Tuesday>" + $RandomMidnightDelayTue + "</Tuesday><Wednesday>" + $RandomMidnightDelayWed + "</Wednesday><Thursday>" + $RandomMidnightDelayThu + "</Thursday><Friday>" + $RandomMidnightDelayFri + "</Friday><Saturday>" + $RandomMidnightDelaySat + "</Saturday></scheduler>"
 
		# Apply the random Midnight Delay (Schedule Denied)
		$jobScheduleOptions.OptionsPeriodically.Schedule = $RandomMidnightDelay
 
		# Apply the new job schedule
		Set-VBRJobScheduleOptions -Job $job -Options $jobScheduleOptions | Out-Null
 
	}
 
	#--------------------------------------------------------------------
	# Randomize Start Time Within An Hour schedule
	if ($RandomizeStartTimeWithinHour -eq $True) {
 
		# Create a new job schedule
		$jobScheduleOptions = Get-VBRJobScheduleOptions -Job $job
 
		# 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
 
		# Apply the new job schedule
		Set-VBRJobScheduleOptions -Job $job -Options $jobScheduleOptions | Out-Null
 
	}
 
	#--------------------------------------------------------------------
	# Randomize Automatic Retry Wait in minutes
	if ($RandomizeAutomaticRetryWait -eq $True) {
 
		# Create a new job schedule
		$jobScheduleOptions = Get-VBRJobScheduleOptions -Job $job
 
		# 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
 
		# Apply the new job schedule
		Set-VBRJobScheduleOptions -Job $job -Options $jobScheduleOptions | Out-Null
 
	}
 
	# Report which jobs received these changes
	Write-Host "Changed settings for" $job.Name
 
}
# END foreach loop ###########################
# END Backup Settings Script #################
 
 
#--------------------------------------------------------------------
# Outputs
 
Write-Host "`nProcessing Complete" -ForegroundColor Yellow
 
 
$finishtime = Get-Date -uformat "%m-%d-%Y %I:%M:%S"
Write-Host "`n`n"
Write-Host "********************************************************************************"
Write-Host "$scriptName`t`tFinish Time:`t$finishtime"
Write-Host "********************************************************************************"
 
# Prompt to exit script - This leaves PS window open when run via right-click
Write-Host "`n`n"
Write-Host "Press any key to continue ..." -foregroundcolor Gray
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

Lastly, if you want to further stagger your jobs, you could create a Windows Schedule Task to run this script once a month or quarter to further keep your jobs and their settings randomly staggered.

Carmel’s Artomobilia as 360 degree panoromas

I took my Theta S camera and a selfie stick to the 2016 Carmel Artomobilia car show yesterday. I have fun capturing these 360 degree panoramas.

Several of the car owners were curious about my camera, which I had just as much fun talking about as they did their cars. It was a good show with lots of interesting cars and people.

Carmel Artomobilia 360 Panoramas via Flickr

Artomobilia 360, Carmel (2016 Aug)

If you prefer Google Photos, I have the same photos posted here.

Beulah Bradley Cameron (1917 to 2016)

My last living grandparent died last week at the age of 98 (plus 11 months 16 days).

Beulah Arena Bradley was born on 30 Jun 1917 to parents Reverend James William Bradley Sr (1871 to 1961) and Bettie Cutchin Lentz (1881 to 1960). She married John Lansing Cameron (1916 to 1997) on 7 Sep 1940. She died on 15 Jun 2016.

Following is her obituary.

Beulah Bradley Cameron

June 30, 1917 – June 15, 2016

Beulah Cameron passed away at Springmoor Retirement Community four days after suffering a stroke. She was born in Hookerton, NC to Rev. James William Bradley and Bettie Lentz Bradley, both deceased. She is predeceased by her husband, John Lansing Cameron, originally from Jonesboro, NC. Their loving marriage was 57 years strong upon his death on February 20, 1997. She also is predeceased by her older brother, Julian Willis Bradley, who passed away on April 12, 2002, and her first son, William John Cameron, who passed away on May 17, 2015.

She is survived by her children, Ann Cameron Pearce (Irvin) of Raleigh, NC, David Bradley Cameron (Martha) of Union Mills, NC, and her daughter-in-law, Joan Mabes Cameron of Eden, NC. She was so proud of her grandchildren, Will (Karen) of Stafford, VA, Chad of Orlando, FL, Lans (Marte) of Kristiansand, Norway, Jason (Jenny) of Carmel, IN, and Cameron (Arianne) of Pensacola, FL. The added joys of her life were her great grandchildren, Josh, Jacob, Eva, Liv, and Jenson, in addition to her step-grandchildren, Rob, George, and Molly.

She began married life with John in Louisburg, NC where he was the coach at Louisburg College. Then, after John’s US Navy service, they settled in Raleigh for 12 years, after which they lived in Falls Church, VA for 22 years. Raleigh became her home again in 1982 and they moved to Springmoor in 1988.

Her major love beyond her family and church was music. She brought joy and inspiration to countless persons through teaching piano and organ, accompanying solos and hymns on the piano, and playing organ for over 200 churches in Raleigh and the Northern Virginia area. She was honored to have played for most of the Vespers and memorial services at Springmoor for 28 years.

Her formal training was a piano degree from Greensboro College in 1938 (“magna cum laude” and president of the centennial class) and an organ degree from Meredith College twenty years later. Her involvements in Hayes Barton United Methodist Church, PEO, the Raleigh Music Club, UMW, and the American Guild of Organists, afforded her opportunities to be a positive influence on many people throughout her life.

This gentle lady will be missed by those who will always love her. We rest in the knowledge that her spirit is with those who have gone on before and she is encompassed by the hands of her Savior.

A Memorial Service will be held at a later date. Memorial donations may be made to the Music Department of Hayes Barton United Methodist Church.