Using PowerShell to copy an Azure virtual machine to a different subscription…

Recently, one of my customers faced a challenge that requires moving a bunch of Azure virtual machines to a different subscription.

I was happy to investigate the issue because it is something I can always use myself or in the future. We all know this is something the Azure portal simply doesn’t facilitate, so a PowerShell script is required. One of the first things I did was checking out if someone has done this in the past which allows me to reuse it. Fortunately, I found the following article:

http://blogs.msdn.com/b/microsoft_press/archive/2014/01/29/from-the-mvps-copying-a-virtual-machine-from-one-windows-azure-subscription-to-another-with-powershell.aspx

As this article provides a ‘ready-to-use’ script, I decided to analyze what the script is actually doing so that I understand what I’m doing as well (something I consider very important).

The following workflow describes what the script actually does:

  1. It shuts down the machine
  2. It gets the VM information specified and creates an export file of the VM configuration
  3. The attached .vhd file(s) is/are copied to the blob storage of the destination subscription
  4. A new VM is created using the export file created before

My first thought was: great, exactly what I’m looking for since it will meet the requirement of moving the VM. Since the VM is not deleted at the source subscription I always have the existing scenario.

Unfortunately, the script has three flaws that will make copying the VM fail:

  1. Line 80 contains a mistake. The line I’m talking about is: Get-AzureSubscription -Current | Set-AzureSubscription -CurrentStorageAccountName $destStorageName
  2. The script assumes that the source and destination Virtual Network (VNet) names are equal
  3. The script assumes it is run from the Azure PowerShell module, but what if you want to run it in the ISE?

The first flaw is easy to fix with something like this: Set-AzureSubscription –SubscriptionName $destSubscription -CurrentStorageAccountName $destStorageName

No need to get the current subscription and pipe it the Set-AzureSubscription cmdlet…

To fix the third flaw, all that needs to be done is to use an Import-Module cmdlet to load the Azure PowerShell module. It also allows to use the Add-AzureAccount cmdlet that is at least Co-Administrator of both subscriptions used. I oppose adding credentials in the script because I consider it a serious security risk. Therefore, I prefer to use the ISE have some more control of the lines I want to run separately.

If the source and destination Virtual Networks have different names, then a little bit more work is required. This means the workflow will look like this:

  1. Import the Azure PowerShell module and log on to Azure
  2. Specify source and destination subcriptions, VNets and the VM info that needs to be copied
  3. Shutdown the machine specified and create an export file of the VM configuration
  4. Copy the attached .vhd files to the destination blob storage of the destination subscription
  5. Rename the VNet name in the export .xml file and create a second .xml that contains the destination VNet name
  6. Create a VM with the secondly created .xml file

In the original script the VM creation part looks like this:

$vmConfig = Import-AzureVM -Path $vmConfigurationPath

New-AzureVM -ServiceName $destServiceName -Location $location -VMs $vmConfig -WaitForBoot

 

I modified the script by changing that part to this:

get-content $vmConfigurationPath | % { $_ -replace $sourceVNet, $destVNet } | set-content $vmConfigurationPathnew

$vmConfig = Import-AzureVM -Path $vmConfigurationPathnew

New-AzureVM -ServiceName $destServiceName -VMs $vmConfig –WaitForBoot

 

After modifying the script I was able to successfully copy an Azure VM to a new subscription.

While the script was running I used the opportunity to look a bit deeper on how to approach this scenario. I was considering to automate it a little further by running the script in a loop to allow multiple VM’s to be copied. I must admit that an action such a this is something that must be carefully planned with the tenant who uses the virtual machines that need to be copied. Since big changes always involve a lot of work I recommend testing things like these first and copy the virtual machines in a more controlled fashion.

Last but not least, here’s my full sample script. You can discover the differences between the original one and mine (you may need to check for unexpected ‘enters’):

Import-Module “C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Azure.psd1″

Add-AzureAccount

 

$sourceSubscription = “EXISTING SUBSCRIPTION NAME”

$destSubscription = “NEW SUBSCRIPTION NAME”

 

Select-AzureSubscription -SubscriptionName $sourceSubscription

 

#Get-AzureVM

 

$vmName = “YOUR_VM_NAME”

$serviceName = “CLOUD_SERVICE_NAME”

$destServiceName = “NEW_CLOUD_SERVICE_NAME”

$workingDir = (Get-Location).Path

 

$sourceVm = Get-AzureVM –ServiceName $serviceName –Name $vmName

$vmConfigurationPath = $workingDir + “\exportedVM.xml”

$vmConfigurationPathnew = $workingDir + “\exportedVM_new.xml”

$sourceVm | Export-AzureVM -Path $vmConfigurationPath

 

$sourceVNet = “SOURCE_SUBNET_NAME”

$destVNet = “DESTINATION_SUBNET_NAME”

 

$sourceOSDisk = $sourceVm.VM.OSVirtualHardDisk

$sourceDataDisks = $sourceVm.VM. DataVirtualHardDisks

 

$sourceStorageName = $sourceOSDisk.MediaLink.Host -split “\.” | select -First 1

$sourceStorageAccount = Get-AzureStorageAccount –StorageAccountName $sourceStorageName

$sourceStorageKey = (Get-AzureStorageKey -StorageAccountName $sourceStorageName).Primary

 

 

 

Stop-AzureVM –ServiceName $serviceName –Name $vmName -Force

 

Select-AzureSubscription -SubscriptionName $destSubscription

 

$location = $sourceStorageAccount.Location

 

$destStorageAccount = Get-AzureStorageAccount | ? {$_.Location -eq $location} | select -first 1

if ($destStorageAccount -eq $null)

{

$destStorageName = “TARGET_STORAGE_ACCOUNT”

New-AzureStorageAccount -StorageAccountName $destStorageName -Location $location

$ destStorageAccount = Get-AzureStorageAccount -StorageAccountName $destStorageName

}

$destStorageName = $destStorageAccount.StorageAccountName

$destStorageKey = (Get-AzureStorageKey -StorageAccountName $destStorageName).Primary

 

$sourceContext = New-AzureStorageContext –StorageAccountName $sourceStorageName `

-StorageAccountKey $sourceStorageKey

$destContext = New-AzureStorageContext –StorageAccountName $destStorageName `

-StorageAccountKey $destStorageKey

 

if ((Get-AzureStorageContainer -Context $destContext -Name vhds -ErrorAction SilentlyContinue) -eq $null)

{

New-AzureStorageContainer -Context $destContext -Name vhds

}

 

$allDisks = @($sourceOSDisk) + $sourceDataDisks

$destDataDisks = @()

foreach($disk in $allDisks)

{

$blobName = $disk.MediaLink.Segments[2]

$targetBlob = Start-CopyAzureStorageBlob -SrcContainer vhds -SrcBlob $blobName `

-DestContainer vhds -DestBlob $blobName `

-Context $sourceContext -DestContext $destContext -Force

Write-Host “Copying blob $blobName”

$copyState = $targetBlob | Get-AzureStorageBlobCopyState

while ($copyState.Status -ne “Success”)

{

$percent = ($copyState.BytesCopied / $copyState.TotalBytes) * 100

Write-Host “Completed $(‘{0:N2}’ -f $percent)%”

sleep -Seconds 5

$copyState = $targetBlob | Get-AzureStorageBlobCopyState

}

If ($disk –eq $sourceOSDisk)

{

$destOSDisk = $targetBlob

}

Else

{

$destDataDisks += $targetBlob

}

}

 

Add-AzureDisk -OS $sourceOSDisk.OS -DiskName $sourceOSDisk.DiskName -MediaLocation $destOSDisk.ICloudBlob.Uri

foreach($currenDataDisk in $destDataDisks)

{

$diskName = ($sourceDataDisks | ? {$_.MediaLink.Segments[2] -eq $currenDataDisk.Name}).DiskName

Add-AzureDisk -DiskName $diskName -MediaLocation $currenDataDisk.ICloudBlob.Uri

}

 

Set-AzureSubscription –SubscriptionName $destSubscription -CurrentStorageAccountName $destStorageName

 

get-content $vmConfigurationPath | % { $_ -replace $sourceVNet, $destVNet } | set-content $vmConfigurationPathnew

 

$vmConfig = Import-AzureVM -Path $vmConfigurationPathnew

New-AzureVM -ServiceName $destServiceName -VMs $vmConfig -WaitForBoot


Using PowerShell to copy an Azure virtual machine to a different subscription…