-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathclone-and-delete-vms.ps1
335 lines (296 loc) · 10 KB
/
clone-and-delete-vms.ps1
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
<#
.SYNOPSIS
Backups VMware VMs by cloning them
.DESCRIPTION
Script to clone VMs to new VM with suffix -backup-YYYY-MM-DDTHH:MM.
Backups older than $retention are deleted - after the backup is taken.
Backups created during this scripts execution are not deleted during the same run of the script.
.NOTES
The configuration file is an XML file with the following structure:
<clone-and-delete-vms>
<server>my-vc-server</server>
<username>vmbackup</username>
<password>sekret</password>
<retention>6</retention>
<targetFolder>BACKUP</targetFolder>
<targetBackups>
<target>
<name>host1</name>
<datastore>DATASTORE2</datastore>
<cluster>Cluster 2</cluster>
</target>
<target>
<name>host2</name>
<datastore>DATASTORE1</datastore>
<cluster>Cluster 1</cluster>
</target>
</targetBackups>
</clone-and-delete-vms>
The author is Jonathan Barber <[email protected]>.
.PARAMETER server
VMware Virtual Center machine to connect to. Defaults to 127.0.0.1
.PARAMETER username
Username to connect to Virtual Center with. Defaults to vmbackup
.PARAMETER password
Password to use to connect to Virtual Center. No default value.
.PARAMETER retention
Minimum number of days between which VM should be backed up. Defaults to 6.
.PARAMETER targetFolder
Folder that VMs are cloned to. Defaults to BACKUP
.PARAMETER config
Path to configuration XML file. Defaults to "clone-and-delete-vms.xml"
.PARAMETER verbose
Switch for more logging
.PARAMETER dryrun
Switch to disable taking backups and deleting old backups
.PARAMETER nobackup
Disable taking backups
.PARAMETER nodelete
Disable deleting old backups
#>
Param(
[string]$server = "127.0.0.1",
[string]$username = "vmbackup",
[string]$password,
[int]$retention = 6,
[string]$targetFolder = "BACKUP",
[string]$config = "clone-and-delete-vms.xml",
[switch]$verbose = $False,
[switch]$dryrun = $False,
[switch]$nobackup = $False,
[switch]$nodelete = $False
)
# Read the config
if (Test-path $config) {
$doc = [xml](get-content $config)
$xml = $doc.FirstChild
if ($xml.server -ne $null) { $server = $xml.server }
if ($xml.username -ne $null) { $username = $xml.username }
if ($xml.password -ne $null) { $password = $xml.password }
if ($xml.retention -ne $null) { $retention = $xml.retention }
if ($xml.targetFolder -ne $null) { $targetFolder = $xml.targetFolder }
$new_retention = $doc.CreateElement("retention")
[void]$new_retention.set_InnerXML($retention)
$targetBackups = @{}
$xml.targetBackups.target | %{
if (-not $_.retention) {
[void]$_.AppendChild($new_retention)
}
[void]$targetBackups.Add( $_.name, $_ )
}
}
# check options are set after reading config
if (! $server) {
write-error "server option not set, required"
exit 1
}
if (! $username) {
write-error "username option not set, required"
exit 1
}
if (! $password) {
write-error "password option not set, required"
exit 1
}
if ($verbose) {
$VerbosePreference = "Continue"
}
# Only load a Snappin if it's not already registered
function addSnappin (
[parameter(Mandatory = $true)][string]$snappin
) {
if (-not (get-pssnapin | ?{ $_.name -eq $snappin })) {
Add-PSSnapin $snappin
}
}
$evt = New-Object System.Diagnostics.EventLog("Application")
$evt.Source = $MyInvocation.MyCommand.Name
function evtLog {
try {
$evt.WriteEntry( $args )
}
catch {
}
}
function Clone-VM (
[parameter(Mandatory = $true)][string]$sourceVM,
[parameter(Mandatory = $true)][string]$targetVM,
[string]$targetFolderName,
[string]$targetDatastore,
[switch]$sparse,
[string]$targetCluster) {
<#
.SYNOPSIS
This function will clone a Virtual Machine from an existing VM.
.PARAMETER -sourceVM
The name of the VM you want to clone.
.PARAMETER -targetVM
The name of the VM you want to create. Defaults to "root folder".
.PARAMETER -targetFolderName
The name of the folder the new VM should be created under.
.PARAMETER -targetDatastore
The name of the datastore the new VM should be created on.
.PARAMETER -targetCluster
The name of the cluster the new VM should be created on.
#>
if ( $targetFolderName ) {
$folder = Get-Folder -Name $targetFolderName -erroraction stop
$targetFolder = Get-View $folder.ID
}
else {
$targetFolder = Get-View ( Get-Folder -Name vm ).ID
$targetFolderName = "root folder"
}
$VMCloneSpec = New-Object VMware.Vim.VirtualMachineCloneSpec
$VMCloneSpec.Location = New-Object VMware.Vim.VirtualMachineRelocateSpec
$VMCloneSpec.powerOn = $false
if ( $targetDatastore ) {
$VMCloneSpec.Location.Datastore = (Get-View (Get-Datastore -Name $targetDatastore -erroraction stop).ID ).MoRef
}
if ( $targetCluster ) {
$cluster = (Get-View -viewtype ClusterComputeResource -Filter @{ Name = $targetCluster })
$VMCloneSpec.Location.Host = $cluster.Host[0]
$VMCloneSpec.Location.Pool = $cluster.ResourcePool
}
if ( $sparse ) {
$VMCloneSpec.Location.Transform = [VMware.Vim.VirtualMachineRelocateTransformation]::sparse
}
write-verbose "Cloning $sourceVM to $targetVM in folder $targetFolderName"
return (Get-View (Get-VM -Name $sourceVM).ID).CloneVM_Task($targetFolder.MoRef, $targetVM, $VMCloneSpec)
}
# Match $VMname with $regex and return how long ago the backup was made
function backupAge (
[string]$VMname,
[string]$regex
) {
if ($VMname -match $regex) {
return ((get-date) - (get-date -date $matches[2]))
}
else {
throw "VMname ($VMname) isn't matched by regex ($regex)"
}
}
# Return whether the VM was backed up more than $age days ago
function backupOld (
[string]$VMname,
[string]$regex,
[int]$age
) {
return (backupAge -VMname $VMname -regex $regex).Days -ge $age
}
function backupVMs (
[array]$vms,
[string]$backupMatch,
[int]$retention,
[string]$dateFormat,
$targetBackups
) {
$targets = $vms | %{ $_.name } | ?{ -not ($_ -match $backupMatch) } | ?{ $targetBackups.containsKey( $_ ) }
foreach ($src in $targets) {
$date = get-date -uformat $dateFormat
$target = ($src, "backup", $date) -join "-"
$targetStore = $targetBackups[$src].datastore
$targetCluster = $targetBackups[$src].cluster
# Check if the $src has a backup
$backup = $false
# if ( $backups = ($vms | %{ $_.name } | ?{ ($_ -like "$src-*") -and ($_ -match $backupMatch) }) ) {
# write-verbose "$src has backups"
## $evt.WriteEntry( "$src has backups" )
# # See if any of the backups are old
# if ( $backups | ?{ backupOld $_ $backupMatch $retention } ) {
# $backup = $true
# write-verbose "$src backups are stale"
## $evt.WriteEntry( "$src backups are stale" )
# }
# else {
# write-verbose "$src backups are fresh"
## $evt.WriteEntry( "$src backups are fresh" )
# }
# }
# else {
# $backup = $true
# write-verbose "$src doesn't have backups"
## $evt.WriteEntry( "$src doesn't have backups" )
# }
$backup = $true
if ($backup) {
write-verbose "Taking backup of $src"
evtLog "Taking backup of $src"
if ($dryrun) {
write-verbose "In dryrun mode, not cloning"
}
else {
try {
wait-task (get-viobjectbyviview (Clone-VM -sourceVM $src -targetVM $target -targetDatastore $targetStore -targetFolderName $targetFolder -sparse -targetCluster $targetCluster))
}
catch {
evtLog ("Cloning $vm failed: " + $_.Exception.Message)
write-verbose ("Cloning $vm failed: " + $_.Exception.Message)
}
}
}
}
}
# Look for old backups and delete them
function deleteOldBackups (
[array]$vms,
[string]$backupMatch,
[int]$retention,
$targetBackups
) {
write-verbose "Looking for old backups:"
$candidates = $vms | ?{ $_.name -match $backupMatch } | %{ $_.name }
foreach ($vm in $candidates) {
write-verbose " VM found: $vm"
$targets = $targetBackups.getEnumerator() | ?{ $vm -like ($_.name + "-*") }
if (-not $targets) {
write-verbose " - not found in targetBackups, ignoring"
continue
}
if ($targets.count -gt 1) {
write-verbose " - name matches more than one target!"
}
# TODO: Select one element from $targets and use the retention time from
# that element
#$target = $targets[0].value
#write-verbose (" - VM age is " + (backupAge -VMname $vm -regex $backupMatch))
if (backupOld $vm $backupMatch $retention) {
write-verbose " - older than $retention, deleting"
evtLog "Deleting $vm"
if ($dryrun) {
write-verbose " - In dryrun mode, not deleting"
}
else {
try {
remove-vm -deletefromdisk -runasync -confirm:$false -vm $vm
}
catch {
evtLog ("Delting $vm failed: " + $_.Exception.Message)
write-verbose (" - deletion failed: " + $_.Exception.Message)
}
}
}
else {
write-verbose " - younger than $retention, keeping"
}
}
}
# Regex to find backup clones and extract their base VM name and when they were created
$backupMatch = '(.*)-backup-(\d{4}-\d{2}-\d{2}T\d{2}:\d{2})$'
# This date format is used to name the backups, and it *must* match
# $backupMatch - otherwise you'll never delete old backups
$dateFormat = '%Y-%m-%dT%R'
$ErrorActionPreference = "Stop"
evtLog "Starting script"
addSnappin 'VMware.VimAutomation.Core'
Connect-VIserver -server $server -username $username -password $password
# Clone VMs that aren't a backup in their name and that have backups older than $retention
$vms = get-vm
if (-not $nobackup) {
backupVMs $vms $backupMatch $retention $dateFormat $targetBackups
}
if (-not $nodelete) {
deleteOldBackups $vms $backupMatch $retention $targetBackups
}
evtLog "Stopping script"
disconnect-viserver -force -server * -confirm:$false