From the MVPs: Copying a virtual machine from one Windows Azure subscription to another with PowerShell

This is the 36th in our series of guest posts by Microsoft Most Valued Professionals (MVPs). You can click the “MVPs” tag in the right column of our blog to see all the articles.

Since the early 1990s, Microsoft has recognized technology champions around the world with the MVP Award . MVPs freely share their knowledge, real-world experience, and impartial and objective feedback to help people enhance the way they use technology. Of the millions of individuals who participate in technology communities, around 4,000 are recognized as Microsoft MVPs. You can read more original MVP-authored content on the Microsoft MVP Award Program Blog .

This post is by ASP.Net MVP Ido Flatow Thanks, Ido!

And don’t forget: a week of free online Windows Azure training is in progress. Information and registration links are here. It’s not too late to attend the Wednesday, Thursday, and/or Friday sessions.

Why?

During the early stages of development and proof-of-concept (POC) steps, it is common to find developers and IT Pros using their own Windows Azure trial subscription to “experiment” with Windows Azure Virtual Machines (VMs). These experiments include creating a VM, installing your environment on it, and testing it out to conclude how easy it will be to start off a new project or migrate your existing application to Windows Azure.

Note: You can use the Windows Azure Cmdlets to automate your VM creation. Check here to see how: https://msdn.microsoft.com/en-us/library/windowsazure/jj835085.aspx.

After the initial POC succeeds (assuming it succeeded, there’s no reason to think otherwiseJ), your company creates a Windows Azure subscription, and you are asked to create your VM in the new subscription, so the bill can be charged to the company.

Now comes the part of migrating your VM to the new subscription. You actually don’t need to re-create the VM in the new subscription. You can simply move your current VM to the new subscription by using PowerShell and the Windows Azure PowerShell Cmdlets.

What?

So what do you need before starting this task?

1. Access to the existing and new Windows Azure subscriptions, either as admin or co-admin.

2. The Windows Azure PowerShell module, which you can install from here: https://go.microsoft.com/fwlink/p/?linkid=320376&clcid=0x409

How?

Connect your PowerShell environment to your Windows Azure subscription

You cannot use the Windows Azure Cmdlets until you configure PowerShell to use your subscription. To do so, follow the steps detailed in this article: https://www.windowsazure.com/en-us/documentation/articles/install-configure-powershell/#Connect

Now we can open a Windows PowerShell window and start typing.

Get the required information from the source VM

To copy the VM from the original subscription, we need to know where the VHDs (virtual hard disks) of the VM are stored. If the VM has both an OS disk and data disks, we will need to get the location of each of the disks.

1. Type the following command to use your original subscription, where you have the existing VM. Replace EXISTING SUBSCRIPTION NAMEwith the name of your original subscription.

Select-AzureSubscription -SubscriptionName "EXISTING SUBSCRIPTION NAME"

2. Type the following command to get the list of VMs and verify you see your VM in the list.

Get-AzureVM

3. Locate your VM in the shown table, and type the following commands to store the VM name and the Cloud Service where the VM is hosted. Replace YOUR_VM_NAME with the value from the Name column and CLOUD_SERVICE_NAME with the value from the ServiceName column. Finally, replace NEW_CLOUD_SERVICE_NAME with the unique name you want to give to the new VM in the target subscription.

$vmName = "YOUR_VM_NAME"

$serviceName = "CLOUD_SERVICE_NAME"

$destServiceName = "NEW_CLOUD_SERVICE_NAME"

$workingDir = (Get-Location).Path

4. Type the following commands to get the information of your existing VM. The commands will retrieve information about the VM disks and export the VM configuration to an XML file.

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

$vmConfigurationPath = $workingDir + "\exportedVM.xml"

$sourceVm | Export-AzureVM -Path $vmConfigurationPath

$sourceOSDisk = $sourceVm.VM.OSVirtualHardDisk

$sourceDataDisks = $sourceVm.VM. DataVirtualHardDisks

5. Type the following commands to get the Windows Azure storage account name containing the original VM VHDs, and its access key.

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

$sourceStorageAccount = Get-AzureStorageAccount –StorageAccountName $sourceStorageName

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

6. If you have not done so yet, turn off the original VM. You can either do it manually through the Windows Azure Management Portal, or by typing the following command:

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

Now we have all the information we need from the original VM, it’s time to move to the new subscription and create the VM there. Keep the PowerShell window opened and continue to the next part.

Verify you have everything you need to create the VM in the new subscription

7. First, we need to switch the current subscription to the new subscription. Type the following command to do so, and replace NEW SUBSCRIPTION NAMEwith the name of your original subscription.

Select-AzureSubscription -SubscriptionName "NEW SUBSCRIPTION NAME"

8. Next, we need to set where we want to create the new VM – which region – by type the next command.

$location = $sourceStorageAccount.Location

Note: We’re assuming the new VM is going to be created in the same region as the original VM. If you are planning on creating the new VM in a different region, or in an affinity group, set the $location variable accordingly.

9. Type the following commands to verify you have a storage account in the selected region. If the storage account is not found, one will be created, which may take a couple of minutes. Make sure you replace NEW_STORAGE_NAME with a unique name for the new storage.

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

if ($destStorageAccount -eq $null)

{

    $destStorageName = "NEW_STORAGE_NAME"

    New-AzureStorageAccount -StorageAccountName $destStorageName -Location $location

    $destStorageAccount = Get-AzureStorageAccount -StorageAccountName $destStorageName

}

$destStorageName = $destStorageAccount.StorageAccountName

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

Note: Make sure the name is you use for the new storage account unique and only contains lowercase letters, otherwise the storage account creation may fail.

10. Type the following commands to create the required storage context variables.

$sourceContext = New-AzureStorageContext –StorageAccountName $sourceStorageName `

        -StorageAccountKey $sourceStorageKey

$destContext = New-AzureStorageContext –StorageAccountName $destStorageName `

        -StorageAccountKey $destStorageKey

11. Type the following commands to verify the target storage account has a container for the VHDs.

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

{

    New-AzureStorageContainer -Context $destContext -Name vhds

}

Copy the VHDs from the source storage to the destination storage

Now that we have the information on both the source and destination storage accounts, it’s time to copy file blobs.

12. Type the following commands to copy the blobs from the original storage account to the destination.

$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

    }

}

Note: If you changed the $location variable to point to a different data center, the copy process can take several minutes or even hours. Copying blobs within the same data center should several seconds to minutes.

13. Type the following commands to register the new disks as data/OS disks.

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

}

Create the new VM in the new subscription

We now have the VM disks in our new subscription, in the destination storage account. Now all we need to do is create the new VM with the existing disks.

14. Type the following commands to create a new VM configuration, based on the original VM configuration.

Get-AzureSubscription -Current | Set-AzureSubscription -CurrentStorageAccountName $destStorageName

$vmConfig = Import-AzureVM -Path $vmConfigurationPath

Note: The above command will copy all the settings of your original VM. If that VM was deployed to a virtual network, make sure your new subscription has a virtual network with the same subnet name. You will also need to add the –VNetName parameter to the next command, and set it to the name of the new virtual network.

15. Lastly, type the following commands to create the new VM according to the configuration.

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

Note: if you want to create the new VM in an affinity group, and you have already set the $location variable to the group’s name, change the –Location parameter to –AffinityGroup.

And that is it. Since the new VM is based on the same disk as the original VM, we do not need to add any information regarding the admin username and password – you can connect to the new VM with your original username and password.

If you want to download an RDP file for the new VM, just type the following command:

Get-AzureRemoteDesktopFile -ServiceName $destServiceName -Name $vmConfig.RoleName -LocalPath ($workingDir+"\newVM.rdp")

You can find the complete script file here: https://sdrv.ms/1aSXfXD

For more Windows Azure automation tips, check my blog at https://blogs.microsoft.co.il/blogs/idof

Comments

  • Anonymous
    May 29, 2014
    The comment has been removed

  • Anonymous
    June 11, 2014
    Look, Azure is pretty cool.  And Microsoft gets a lot of things right.  But this procedure is supposed to be simpler than just re-creating the vm?  Really?  What genius decided adding a 'publish' button to wrap this logic was a bad idea?  It's always been like this with Microsoft, they have technologies that get you 95% there, but the last 5% borders on incomprehensible, and when you finally get that last 5%, they deprecate the technology in favor of their new 'best' way of doing things.  Oye Veh.

  • Anonymous
    July 17, 2014
    I created a VM on my Free Trial subscription that has now expired. I'm trying to move it to my new Pay-As-You-Go subscription using your script. I note that the VM shows on the Azure Portal as being in state "Stopped (Deallocated)". When I run the command: $sourceVm | Export-AzureVM -Path $vmConfigurationPath I get this error: Export-AzureVM : Could not read properties for virtual machine: MyVM. It may still be provisioning. Is this error because the VM is deallocated? I really don't want to have to create my VM again from scratch.

  • Anonymous
    July 18, 2014
    Further to my last comment, it turned out that it was because the Azure Powershell console had the root of C: as its working directory. There was a permissions issue in writing the file to there. Changing the working directory solved the problem! The error message was misleading... :-(

  • Anonymous
    July 23, 2014
    $sourceVm = Get-AzureVM –ServiceName $serviceName –Name $vmName Gets following error WARNING: No deployment found in service: 'http://xyspqr.cloudapp.net ' Powershell version Major  Minor  Build  Revision -----  -----  -----  -------- 0      8      3      -1

  • Anonymous
    October 06, 2014
    On step 7, is NEW SUBSCRIPTION NAME the original subscription or the new subscription where I am going to create the copy? Otherwise, how will this process find the subscription to create the new VM?

  • Anonymous
    November 23, 2014
    The comment has been removed

  • Anonymous
    December 17, 2014
    The comment has been removed

  • Anonymous
    December 19, 2014
    Many thanks for this guide!  Here's my notes after having just worked through it. blogs.msdn.com/.../move-an-azure-virtual-machine-between-subscriptions-or-data-centers-geos.aspx

  • Anonymous
    January 07, 2015
    You can use DC Migration Tool, an open source tool to copy azure resources from one subscription to other subscription across data centers as well. Azure Data Center Migration Tool Blog blog.persistentsys.com/.../persistent-systems-releases-azure-data-center-migration-solution

  • Anonymous
    March 24, 2015
    The comment has been removed

  • Anonymous
    June 25, 2015
    The comment has been removed

  • Anonymous
    July 09, 2015
    Very good guide. A little issue is that the script can't work with a source VM with VNet configuration. I revised and generate a script file. tombwu.wordpress.com/.../migrate-vms-between-azure-subscriptions

  • Anonymous
    August 04, 2015
    I'm getting the certificate error on step 14 as well

  • Anonymous
    August 16, 2015
    Don smith did provide a solution to fix step 14 error . See below command he suggested Set-AzureSubscription -SubscriptionName "DESTINATION_SUBSCRIPTION_NAME" -CurrentStorageAccountName $destStorageName

  • Anonymous
    August 28, 2015
    Great post! It worked fine for me. But I had this issue, I changed the VM size on the destination subscription and now I can not login in anymore on the machine (I am getting Access Denied by SSL). Do you know why?

  • Anonymous
    September 21, 2015
    The comment has been removed

  • Anonymous
    November 18, 2015
    Problem with #14 will be resolved if you follow these steps.

  1. Download publishsettings file (login, then save to "%temp%AllMySubscriptions.publishsettings"): > Get-AzurePublishSettingsFile
  2. Import all certs to your private cert store: > Import-AzurePublishSettingsFile "%temp%AllMySubscriptions.publishsettings"
  3. Load store (run certmgr.msc, look under Personal) and find your cert by subscription's name in Friendly Name column.
  4. Open cert and copy its Thumbprint into variable: > $thumb = "00 55 ff ...."
  5. Create Cert object: > $cert = Get-Item cert:\CurrentUserMy$thumb
  6. Now just add -Certificate $cert to the end of command, like this: > Get-AzureSubscription -Current | Set-AzureSubscription -CurrentStorageAccountName $destStorageName -Certificate $cert
  • Anonymous
    November 28, 2015
    Hi, I wish to move my VM from a existing subscription1-RegionA to a existing Subscription2-RegionB,  but retaining its original name. This script seems to be unaware of what I need, and gives me the error "Could not read properties for virtual machine: (myMachineName). It may still be provisioning.", Is there a way to sort this?.. please help.

  • Anonymous
    January 06, 2016
    Killer post. I used this as a foundation for creating a module specific to our organization with validations and checkpoints along the way. Also, the script can easily handle VMs with VNets, you just need to modify the exported XML file as appropriate for your situation. Many thanks!

  • Anonymous
    February 16, 2016
    Thanks, this was useful. I wonder, now when the new Azure portal is running, are there any better ways to move a VM between subscriptions?

  • Anonymous
    February 17, 2016
    Thanks. But how to do this with new Resource Manager VMs?

  • Anonymous
    March 14, 2016
    The comment has been removed

  • Anonymous
    June 01, 2017
    Link for "http://sdrv.ms/1aSXfXD" is not working now anymore. Can you post updated link please?