-
Notifications
You must be signed in to change notification settings - Fork 1
/
vmcopy.ps1
515 lines (406 loc) · 18.6 KB
/
vmcopy.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
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
<#
Copyright (c) Opsgility. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
#>
<#
.SYNOPSIS
This cmdlet takes an existing virtual machine, makes a copy of its disks in the local storage account and then copies the virtual machine to a seperate subscription.
Running this script can take several minutes and up to hours if copying a virtual machine to a remote storage account.
Ensure the source subscription and the destination subscription are configured for PowerShell access.
Verify by running the following command to ensure that both subscriptions are listed:
Get-AzureSubscription | select SubscriptionName
.DESCRIPTION
This script works by creating a copy of all of the disks of the virtual machine to a container in the same storage account.
You may optionally specify -ShutdownVM to shut the virtual machine down to ensure the backup is clean first.
Once the disks are backed up locally the script validates that the remote cloud service name, storage account, virtual network and subnet are all accessible from the current configuration.
Once validation is complete the script will copy the disks from the backup in the source storage account to the destination storage account.
Once the copy is complete the script will register the disks in the subscription with the same disk names to match the virtual machine configuration.
The script next will export the virtual machine settings to a local file on the file system and import them into the target subscription and create the virtual machine.
.EXAMPLE
# Copy a virtual machine to a different subscription
.\vmcopy.ps1 -SourceSubscription "source subscription" `
-DestinationSubscription "destination subscription" `
-VirtualMachineName "existingvmname" `
-SourceServiceName "sourcecloudservice" `
-DestinationServiceName "destinationcloudservice" `
-DestinationStorageAccount "destinationstorageaccount" `
-Location "West US"
# Copy a virtual machine to a different subscription and specify an existing virtual network and subnet.
.\vmcopy.ps1 -SourceSubscription "source subscription" `
-DestinationSubscription "destination subscription" `
-VirtualMachineName "existingvmname" `
-SourceServiceName "sourcecloudservice" `
-DestinationServiceName "destinationcloudservice" `
-DestinationStorageAccount "destinationstorageaccount" `
-VNETName "DestinationVNET" `
-SubnetName "DestinationSubnet"
#>
[CmdletBinding(DefaultParameterSetName="Default")]
param(
[parameter(Mandatory, ParameterSetName="Default")]
[parameter(Mandatory, ParameterSetName="VNET")]
[string]$SourceSubscription,
[parameter(Mandatory, ParameterSetName="Default")]
[parameter(Mandatory, ParameterSetName="VNET")]
[string]$DestinationSubscription,
[parameter(Mandatory, ParameterSetName="Default")]
[parameter(Mandatory, ParameterSetName="VNET")]
[string]$VirtualMachineName,
[parameter(Mandatory, ParameterSetName="Default")]
[parameter(Mandatory, ParameterSetName="VNET")]
[string]$SourceServiceName,
[parameter(Mandatory, ParameterSetName="Default")]
[parameter(Mandatory, ParameterSetName="VNET")]
[string]$DestinationServiceName,
[parameter(Mandatory, ParameterSetName="Default")]
[parameter(Mandatory, ParameterSetName="VNET")]
[string]$DestinationStorageAccount,
[parameter(Mandatory, ParameterSetName="Default")]
[string]$Location,
[parameter(Mandatory, ParameterSetName="VNET")]
[string]$VNETName,
[parameter(Mandatory, ParameterSetName="VNET")]
[string]$SubnetName,
[parameter()]
[string]$DestinationContainer="vhds",
[parameter()]
[switch]$ShutdownVM
)
## Validation ##
# Validate source virtual machine
Select-AzureSubscription $SourceSubscription
$sourceVM = Get-AzureVM -ServiceName $SourceServiceName -Name $VirtualMachineName
if($sourceVM -eq $null)
{
Write-Error "Virtual Machine $VirtualMachineName in cloud service $SourceServiceName cannot be accessed using the subscription $SourceSubscription"
return
}
# Validate destination cloud service and storage account
Select-AzureSubscription $DestinationSubscription
# Required to create storage account if it does not exist
$g_DestinationStorageAccountLocation = ""
# Used to track disk copy status
$g_diskCopyStates = @()
# If part of a vnet save the affinity group for later reference
$g_vnetAffinityGroup = ""
# The name of the container to locally backup the virtual machine disks to.
$g_BackupContainer = "vmbackup"
if($PSCmdlet.ParameterSetName -eq "VNET"){
# Validate VNET and Subnet Settings
$vnetXMLPath = Join-Path $env:TEMP "NetworkConfig.xml"
Get-AzureVNetConfig -ExportToFile $vnetXMLPath
[xml] $vnetXML = Get-Content $vnetXMLPath
$vnetExists = $false
$subnetExists = $false
foreach($tmpVNET in $vnetXML.NetworkConfiguration.VirtualNetworkConfiguration.VirtualNetworkSites.VirtualNetworkSite)
{
if($tmpVNET.name -eq $VNETName)
{
$vnetExists = $true
$g_vnetAffinityGroup = $tmpVNET.AffinityGroup
$DestinationAG = Get-AzureAffinityGroup -Name $g_vnetAffinityGroup
$g_DestinationStorageAccountLocation = $DestinationAG.Location
foreach($tmpSubnet in $tmpVNET.Subnets.Subnet)
{
if($tmpSubnet.name -eq $SubnetName)
{
$subnetExists = $true
break
}
}
break
}
}
if($vnetExists -eq $false)
{
Write-Error "VNET $VNETName does not exist in $DesinationSubscription"
return
}
if($subnetExists -eq $false)
{
Write-Error "Subnet $subnet does not exist in $DesinationSubscription"
return
}
}
else
{
$g_DestinationStorageAccountLocation = $Location
$existingSubnet = $sourceVM | Get-AzureSubnet
if($existingSubnet -ne $null)
{
Write-Host "Not deploying to a virtual network. Existing Subnet will be removed from Virtual Machine configuration." -ForegroundColor Yellow
}
}
if((Get-AzureService -ServiceName $DestinationServiceName -ErrorAction SilentlyContinue ) -eq $null)
{
# doesn't exist in destination subscripton
# check if can create (created later)
if((Test-AzureName -Service $DestinationServiceName) -eq $true)
{
Write-Error "Destination Cloud Service $DestinationServiceName already exists in another subscription. Choose another name to continue."
return
}
}
else
{
$tmpDestService = Get-AzureService -ServiceName $DestinationServiceName
if($PSCmdlet.ParameterSetName -eq "VNET")
{
if($tmpDestService.AffinityGroup -ne $g_vnetAffinityGroup)
{
Write-Error "Existing Destination Cloud Service is not in virtual network affinity group location."
return
}
}
else
{
if($tmpDestService.Location -ne $g_DestinationStorageAccountLocation)
{
Write-Error "Existing Destination Cloud Service Location does not match specified location or virtual network affinity group location."
return
}
}
}
if((Get-AzureStorageAccount -StorageAccountName $DestinationStorageAccount -ErrorAction SilentlyContinue) -eq $null)
{
# doesn't exist in destination subscripton
# check if can create
if((Test-AzureName -Storage $DestinationStorageAccount) -eq $true)
{
Write-Error "Destination Storage Account $DestinationStorageAccount already exists in another subscription. Choose another name to continue"
return
}
else
{
New-AzureStorageAccount -StorageAccountName $DestinationStorageAccount -Location $g_DestinationStorageAccountLocation
}
}
else
{
# Destination storage account exists
# Validate storage account location matches destination location
$tmpDestStorage = Get-AzureStorageAccount -StorageAccountName $DestinationStorageAccount
if($tmpDestStorage.Location -ne $g_DestinationStorageAccountLocation)
{
Write-Error "Destination Storage Account Location does not match specified location or virtual network affinity group location."
return
}
}
# Shutdown if specified
if($ShutdownVM.IsPresent)
{
Select-AzureSubscription $SourceSubscription
Write-Output "Stopping Virtual Machine $VirtualMachineName"
Stop-AzureVM -ServiceName $SourceServiceName -Name $sourceVM.Name -Force
}
$disk_configs = @{}
# Copies to local storage account
# Returns URI to backed up disk
function BackupDisk($diskUri)
{
Select-AzureSubscription $SourceSubscription
$vhdName = $diskUri.Segments[$diskUri.Segments.Length - 1].Replace("%20"," ") # fix encoding for space in data disk name
$sourceContainer = $diskUri.Segments[$diskUri.Segments.Length - 2].Replace("/", "")
$storageAccount = $diskUri.Host.Replace(".blob.core.windows.net", "")
$storageAccountKey = (Get-AzureStorageKey -StorageAccountName $storageAccount).Primary
$context = New-AzureStorageContext -StorageAccountName $storageAccount -StorageAccountKey $storageAccountKey
if((Get-AzureStorageContainer -Name $g_BackupContainer -Context $context -ErrorAction SilentlyContinue) -eq $null)
{
New-AzureStorageContainer -Name $g_BackupContainer -Context $context | Out-Null
while((Get-AzureStorageContainer -Name $g_BackupContainer -Context $context -ErrorAction SilentlyContinue) -eq $null)
{
Write-Host "Pausing to ensure container $g_BackupContainer is created.." -ForegroundColor Green
Start-Sleep 10
}
}
$backupUri = "https://$storageAccount.blob.core.windows.net/$g_BackupContainer/$vhdName"
Write-Host "Backing up disk $vhdName to local storage account" -ForegroundColor Green
Start-AzureStorageBlobCopy -SrcContainer $sourceContainer -SrcBlob $vhdName -DestContainer $g_BackupContainer -DestBlob $vhdName -Context $context -Force | Out-Null
$backupUri = [System.Uri] $backupUri
return $backupUri
}
# Copies to remote storage account
# Returns blob copy state to poll against
function StartCopyDisk($sourceDiskUri, $diskName, $OS, $destStorageAccount, $destContainer)
{
Select-AzureSubscription $SourceSubscription
$sourceStorageAccount = $sourceDiskUri.Host.Replace(".blob.core.windows.net", "")
Set-AzureSubscription -SubscriptionName $SourceSubscription -CurrentStorageAccountName $sourceStorageAccount
$vhdName = $sourceDiskUri.Segments[$sourceDiskUri.Segments.Length - 1].Replace("%20"," ") # fix encoding for space in data disk name
$sourceContainer = $sourceDiskUri.Segments[$sourceDiskUri.Segments.Length - 2].Replace("/", "")
$sourceStorageAccountKey = (Get-AzureStorageKey -StorageAccountName $sourceStorageAccount).Primary
$sourceContext = New-AzureStorageContext -StorageAccountName $sourceStorageAccount -StorageAccountKey $sourceStorageAccountKey
Select-AzureSubscription $DestinationSubscription
$destStorageAccountKey = (Get-AzureStorageKey -StorageAccountName $destStorageAccount).Primary
$destContext = New-AzureStorageContext -StorageAccountName $destStorageAccount -StorageAccountKey $destStorageAccountKey
if((Get-AzureStorageContainer -Name $destContainer -Context $destContext -ErrorAction SilentlyContinue) -eq $null)
{
New-AzureStorageContainer -Name $destContainer -Context $destContext | Out-Null
while((Get-AzureStorageContainer -Name $destContainer -Context $destContext -ErrorAction SilentlyContinue) -eq $null)
{
Write-Host "Pausing to ensure container $destContainer is created.." -ForegroundColor Green
Start-Sleep 10
}
}
# Save for later disk registration
$destinationUri = "https://$destStorageAccount.blob.core.windows.net/$destContainer/$vhdName"
if($OS -eq $null)
{
$disk_configs.Add($diskName, "$destinationUri")
}
else
{
$disk_configs.Add($diskName, "$destinationUri;$OS")
}
$copyState = Start-AzureStorageBlobCopy -SrcBlob $vhdName -SrcContainer $sourceContainer -SrcContext $sourceContext -DestContainer $destContainer -DestBlob $vhdName -DestContext $destContext -Force
return $copyState
}
# Backup OS Disk
$osdisk = $sourceVM | Get-AzureOSDisk
$osBackupUri = BackupDisk $osdisk.MediaLink
$ddBackupUris = @()
# Backup any data disks
foreach($dduri in ($sourceVM | Get-AzureDataDisk))
{
$ddBackupUris += BackupDisk $dduri.MediaLink
}
# Copy disks using the async API from the backup URL to the destination storage account
$g_diskCopyStates += StartCopyDisk -sourceDiskUri $osBackupUri -destStorageAccount $DestinationStorageAccount -destContainer $DestinationContainer -diskName $osdisk.DiskName -OS $osdisk.OS
$sourceVM | Get-AzureDataDisk | foreach {
$g_diskCopyStates += StartCopyDisk -sourceDiskUri $_.MediaLink -destStorageAccount $DestinationStorageAccount -destContainer $DestinationContainer -diskName $_.DiskName
}
function CheckBlobCopyStatus()
{
param($diskCopyStates)
do
{
$backupComplete = $true
Write-Host "Checking Disk Copy Status for VM Copy" -ForegroundColor Green
foreach($diskCopy in $diskCopyStates)
{
$state = $diskCopy | Get-AzureStorageBlobCopyState | Format-Table -AutoSize -Property Status,BytesCopied,TotalBytes,Source
if($state -ne "Success")
{
$backupComplete = $true
Write-Host "Current Status" -ForegroundColor Green
$hideHeader = $false
$inprogress = 0
$complete = 0
foreach($diskCopyTmp in $diskCopyStates)
{
$stateTmp = $diskCopyTmp | Get-AzureStorageBlobCopyState
$source = $stateTmp.Source
if($stateTmp.Status -eq "Success")
{
Write-Host (($stateTmp | Format-Table -HideTableHeaders:$hideHeader -AutoSize -Property Status,BytesCopied,TotalBytes,Source | Out-String)) -ForegroundColor Green
$complete++
}
elseif(($stateTmp.Status -like "*failed*") -or ($stateTmp.Status -like "*aborted*"))
{
Write-Error ($stateTmp | Format-Table -HideTableHeaders:$hideHeader -AutoSize -Property Status,BytesCopied,TotalBytes,Source | Out-String)
return $false
}
else
{
Write-Host (($stateTmp | Format-Table -HideTableHeaders:$hideHeader -AutoSize -Property Status,BytesCopied,TotalBytes,Source | Out-String)) -ForegroundColor DarkYellow
$backupComplete = $false
$inprogress++
}
$hideHeader = $true
}
if($backupComplete -eq $false)
{
Write-Host "$complete Blob Copies are completed with $inprogress that are still in progress." -ForegroundColor Magenta
Write-Host "Pausing 30 seconds before next status check." -ForegroundColor Green
Start-Sleep 30
}
else
{
Write-Host "Disk Copy Complete" -ForegroundColor Green
break
}
}
}
} while($backupComplete -ne $true)
Write-Host "Successfully Copied up all Disks" -ForegroundColor Green
}
# Wait for disks to complete copying
CheckBlobCopyStatus -diskCopyStates $g_diskCopyStates
# Register Disks
Write-Host "Registering Copied Disk in Destination Subscription" -ForegroundColor Green
Select-AzureSubscription $DestinationSubscription
foreach($diskName in $disk_configs.Keys)
{
$diskConfig = $disk_configs[$diskName].Split(";")
if($diskConfig.Length -gt 1)
{
Add-AzureDisk -DiskName $diskName -OS $diskConfig[1] -MediaLocation $diskConfig[0]
}
else
{
Add-AzureDisk -DiskName $diskName -MediaLocation $diskConfig[0]
}
}
# Export source virtual machine configuration
Select-AzureSubscription $SourceSubscription
$configFile = $sourceVM.Name + ".xml"
$configPath = Join-Path $env:TEMP $configFile
Write-Host "Saving Virtual Machine Configuration to $configPath" -ForegroundColor Green
Export-AzureVM -ServiceName $SourceServiceName -Name $VirtualMachineName -Path $configPath
# Import and create virtual machine in destination subscription
Select-AzureSubscription $DestinationSubscription
$serviceExists = $false
$deploymentExists = $false
if((Get-AzureService -ServiceName $DestinationServiceName -ErrorAction SilentlyContinue) -ne $null)
{
$serviceExists = $true
}
if((Get-AzureDeployment -ServiceName $DestinationServiceName -Slot Production -ErrorAction SilentlyContinue) -ne $null)
{
$deploymentExists = $true
}
$vmConfig = Import-AzureVM -Path $configPath
Set-AzureSubscription -SubscriptionName $DestinationSubscription -CurrentStorageAccountName $DestinationStorageAccount
Write-Host "Creating virtual machine in destination subscription" -ForegroundColor Green
if($PSCmdlet.ParameterSetName -eq "VNET")
{
$vmConfig | Set-AzureSubnet -SubnetNames $SubnetName
if($serviceExists -eq $false -and $deploymentExists -eq $false)
{
$vmConfig | New-AzureVM -ServiceName $DestinationServiceName -VNetName $VNETName -AffinityGroup $g_vnetAffinityGroup
}
elseif($serviceExists -eq $true -and $deploymentExists -eq $false)
{
$vmConfig | New-AzureVM -ServiceName $DestinationServiceName -VNetName $VNETName
}
else
{
$vmConfig | New-AzureVM -ServiceName $DestinationServiceName
}
}
else
{
$existingSubnet = $vmConfig | Get-AzureSubnet
# Remove existing subnet since we are not deploying to a virtual network.
if($existingSubnet -ne $null)
{
$vmConfig.ConfigurationSets[0].SubnetNames = $null
}
if($serviceExists -eq $false)
{
$vmConfig | New-AzureVM -ServiceName $DestinationServiceName -Location $Location
}
else
{
$vmConfig | New-AzureVM -ServiceName $DestinationServiceName
}
}
Write-Host "Virtual Machine Copy is Complete. Check script execution for any errors." -ForegroundColor Green