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.


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:

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.


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:


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:

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 NAME with 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.


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 NAME with 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




        $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:

For more Windows Azure automation tips, check my blog at

Comments (24)
  1. Mark Harby says:

    Thanks Ido

    Great post, this is the only article I have found that actually worked for us.

    Successfully moved two VMs across subscriptions and locations.

    Thanks again

    Mark Harby

    Nottingham. UK

  2. Nony Moose says:

    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.

  3. Nick Beaugié says:

    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.

  4. Nick Beaugié says:

    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… 🙁

  5. Subodh patil says:

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

    Gets following error

    WARNING: No deployment found in service: ' '

    Powershell version

    Major  Minor  Build  Revision

    —–  —–  —–  ——–

    0      8      3      -1

  6. Nelson says:

    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?

  7. Z says:

    PS C:> $sourceContext = New-AzureStorageContext -StorageAccountName $sourceStorageName -StorageAccountKey $sourceStorag


    New-AzureStorageContext : Cannot validate argument on parameter 'StorageAccountKey'. The argument is null or empty.

    Supply an argument that is not null or empty and then try the command again.

    At line:1 char:100

    + … rageAccountKey $sourceStorageKey

    +                    ~~~~~~~~~~~~~~~~~

       + CategoryInfo          : InvalidData: (:) [New-AzureStorageContext], ParameterBindingValidationException

       + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.WindowsAzure.Commands.Storage.Common.Cmdlet.N


  8. Ricker Silva says:

    I think this is it. Thanks for the walkthrough, so far I´m good. Just a weird detail during blob copy, counter reaches only 23% but checking the coptState it shows copied and total bytes the same, so no problem.

    However, Igot this error in step 14

    PS C:exportAzureVm> Get-AzureSubscription -Current | Set-AzureSubscription -CurrentStorageAccountName $desStorageName

    Set-AzureSubscription : The input object cannot be bound because it did not contain the information required to bind

    all mandatory parameters:  Certificate

    At line:1 char:34

    + Get-AzureSubscription -Current | Set-AzureSubscription -CurrentStorageAccountNam …

    +                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

       + CategoryInfo          : InvalidArgument: (Microsoft.Windo…ureSubscription:PSObject) [Set-AzureSubscription], P


       + FullyQualifiedErrorId : InputObjectMissingMandatory,Microsoft.WindowsAzure.Commands.Profile.SetAzureSubscription


    Any clue. I´m really n00b in Azure powershell and have no idea what ttit can be.

    I realized also that my new subscriptio, yet active, is not shown in my available bscription when creating a new VM on the management portal. But On Azure Power Shell, I see both subscription Ids. (free trial and thenew paay-as-you-go)


  9. Alan Ross says:

    Many thanks for this guide!  Here's my notes after having just worked through it.…/move-an-azure-virtual-machine-between-subscriptions-or-data-centers-geos.aspx

  10. Dirk Smith says:

    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…/persistent-systems-releases-azure-data-center-migration-solution

  11. Don Smith says:

    I ran into the same error Ricker Silva did. I found it works if you removing "Get-AzureSubscription -Current |" from the beginning of that line and add the -SubscriptionName parameter so the line becomes "Set-AzureSubscription -SubscriptionName "DESTINATION_SUBSCRIPTION_NAME" -CurrentStorageAccountName $destStorageName".

  12. Severn Dickinson says:

    I've ran through the post,  entered this command:  

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

    outputs this result:

    Set-AzureSubscription : The input object cannot be bound because it did not contain the information required to bind

    all mandatory parameters:  Certificate

    At line:1 char:34

    + …  -Current | Set-AzureSubscription -CurrentStorageAccountName $destSto …

    +                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

       + CategoryInfo          : InvalidArgument: (Microsoft.Windo…ureSubscription:PSObject) [Set-AzureSubscription], ParameterBindingException

       + FullyQualifiedErrorId : InputObjectMissingMandatory,Microsoft.WindowsAzure.Commands.Profile.SetAzureSubscription


    any thoughts?

  13. Tom Wu says:

    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.…/migrate-vms-between-azure-subscriptions

  14. Josh says:

    I'm getting the certificate error on step 14 as well

  15. Brian says:

    Don smith did provide a solution to fix step 14 error . See below command he suggested

    Set-AzureSubscription -SubscriptionName "DESTINATION_SUBSCRIPTION_NAME" -CurrentStorageAccountName $destStorageName

  16. Nk says:

    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?

  17. Mohamad says:

    There is a typo on line 31:

    $ destStorageAccount = Get-AzureStorageAccount -StorageAccountName $destStorageName

    The space between $ and destStorageAccount is not recognized. It tries to take the $ as a Cmdlet and fails with an error. Just FYI..

  18. Vadym Kudley says:

    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

  19. Daniel says:


    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.

  20. Aaron K says:

    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!

  21. Martin says:

    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?

  22. Martin says:


    But how to do this with new Resource Manager VMs?

  23. Amit Pachahara says:

    Hi Devon,

    Thank you for writing this blog.

    I am stuck in middle of this script as I am getting error msg on line
    Get-AzureSubscription -Current | Set-AzureSubscription -CurrentStorageAccountName $destStorageName

    Error Msg:
    “Set-AzureSubscription : The input object cannot be bound because it did not contain the information required
    to bind all mandatory parameters: Certificate”

    Amit Pachahara

  24. Kalpesh says:

    Link for “” is not working now anymore. Can you post updated link please?

Comments are closed.

Skip to main content