Azure.ToolKit.IAAS.Copy.psm1

<#
.SYNOPSIS
Copy a VM. It is possible to move vms across regions, subscriptions and vnets using this command
 
.DESCRIPTION
Copy a VM. It is possible to move vms across regions, subscriptions and vnets using this command
VM must be stopped before running this script and any static ip on the source vm must be removed i.e. it must be set to a dynamic ip
 
.PARAM SourceSubscription
The source subscription
 
.PARAM SourceServiceName
The source cloud service
 
.PARAM SourceVMName
The source vm name
 
.PARAM DestSubscription
The destination subscription
 
.PARAM DestServiceName
The destination cloud service
 
.PARAM DestVMName
The destination vm name
 
.PARAM DestLocation
The dedstination Azure region
 
.PARAM DestStorageAccount
Destintaion storage account. Copys of the vm disks will be made in this account
 
.PARAM DestContainer
Destination container - created if not already present
 
.PARAM DestVnetName
The destination vnet. If blank the dest vm will not be placed in a vnet
 
.PARAM DestSubnetName
The destination subnet, must be specified if destvnet is specified
 
.PARAM PerformVhdBlobCopy
You can use $PerformVhdBlobCopy to control if the vhd copy is performed - e.g. if you previously copied the vhds successfully but the vm creation failed for another reason
 
.PARAM OverwriteDestDisks
You can use $OverwriteDestDisks to control whether you create the dest disks - e.g. if you previously suceedded in creating the dest disks but the vm creation failed for another reason
 
.EXAMPLE
# Copy vm to a new vnet in a different region and subscription
 
Copy-AzureVM -SourceSubscription 'sourceSubscription' `
    -SourceServiceName 'sourceServiceName' `
    -SourceVMName 'sourceVMNam' `
    -DestSubscription 'destSubscription' `
    -DestServiceName 'destServiceName' `
    -DestVMName 'destVMName' `
    -DestLocation 'North Europe' `
    -DestStorageAccount 'NorthEuropeStorage' `
    -DestContainer 'vhds' `
    -DestVnetName 'NorthEuropeVNet' `
    -DestSubnetName 'Subnet-1' `
    -PerformVhdBlobCopy $true `
    -OverwriteDestDisks $true
#>

function Copy-AzureVM
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)][string]$SourceSubscription,
        [Parameter(Mandatory=$true)][string]$SourceServiceName,
        [Parameter(Mandatory=$true)][string]$SourceVMName,

        [Parameter(Mandatory=$true)][string]$DestSubscription,
        [Parameter(Mandatory=$true)][string]$DestServiceName,
        [Parameter(Mandatory=$true)][string]$DestVMName,
        [Parameter(Mandatory=$true)][string]$DestLocation,
        [Parameter(Mandatory=$true)][string]$DestStorageAccount,
        [Parameter(Mandatory=$true)][string]$DestContainer,
        [string]$DestVnetName,
        [string]$DestSubnetName,        

        [boolean]$PerformVhdBlobCopy = $true,
        [boolean]$OverwriteDestDisks = $true
    )

    # This is a 3 stage operation
    # 1) copy the vhds to a new storage account
    # 2) create new disks from the copied vhds
    # 3) create the vm from these new disks
    
    # Pre conditions
    if(![string]::IsNullOrEmpty($DestVMName) -and [string]::IsNullOrEmpty($DestSubnetName))
    {
        throw "You must set the dest subnet name if you set the dest vm name"
    }
    $sourceVM = Get-AzureVM -ServiceName $SourceServiceName -Name $SourceVMName
    $staticIP = Get-AzureStaticVNetIP -VM $sourceVM
    if($null -ne $staticIP)
    {
        throw "Can not copy a vm with a static ip. Please set the source vm to a dynamic ip"
    }

    # Export the source vm config
    Select-AzureSubscription -SubscriptionName $SourceSubscription 
    $tempPath = [System.IO.Path]::GetTempPath()
    $exportPath = "{0}\{1}-{2}-State.xml" -f $tempPath, $SourceServiceName, $SourceVMName 
    Write-Output "Exporting the vm config to $exportPath"
    Export-AzureVM -ServiceName $SourceServiceName -Name $SourceVMName -Path $exportPath
        
    $disks = Get-AzureDisk | Where-Object { 
        ![string]::IsNullOrEmpty($_.AttachedTo) -and $_.AttachedTo.RoleName -eq $SourceVMName 
    }

    # Copy the disks
    if($PerformVhdBlobCopy)
    {
        # vm must be stopped before we move its disks
        
        if($sourceVM.InstanceStatus -ne "StoppedDeallocated")
        {
            Write-Output "Stopping vm $SourceVMName"
            Stop-AzureVM -ServiceName $SourceServiceName -Name $SourceVMName -Force
        }

        $copyTasks = @()
        foreach($disk in $disks)
        {
            $srcContainer = ($disk.MediaLink.Segments[1]).Replace("/","")
            $blobName = $disk.MediaLink.Segments | Where-Object { $_ -like "*.vhd" } 
            $destBlobName = "$DestServiceName-$DestVMName-$blobName"
            $srcStorageAccount = $disk.MediaLink.Host.Replace(".blob.core.windows.net", "")
            Write-Output "Copying disk $blobName from $srcStorageAccount to $DestStorageAccount"
            $copyTask = Copy-BlobToStorageAccountASync `
                -SourceBlobName  $blobName `
                -SourceStorageAccount $srcStorageAccount `
                -SourceContainer $srcContainer `
                -SourceSubscription $SourceSubscription `
                -DestBlobName $destBlobName `
                -DestStorageAccount $DestStorageAccount `
                -DestContainer $DestContainer `
                -DestSubscription $DestSubscription `
                -Force

            $copyTasks += $copyTask
        }

        # Wait for all copy tasks to end
        $complete = $false
        while(!$complete)
        {
            $complete =  $true
            $copyTasks | ForEach-Object {
                $_ | Get-AzureStorageBlobCopyState
                if(($_ | Get-AzureStorageBlobCopyState).Status -eq "Pending")
                {
                    $complete =  $false
                }
            }    
            Start-Sleep -s 60
        }
    }

    # Set destination subscription context
    Select-AzureSubscription -SubscriptionName $DestSubscription
    Set-AzureSubscription -SubscriptionName $DestSubscription -CurrentStorageAccountName $DestStorageAccount

    # Create the dest cloud service if it doesnt already exists
    $service = Get-AzureService -ServiceName $DestServiceName -ErrorAction SilentlyContinue       
    if ($null -eq $service) 
    {
        Write-Output "Creating Azure cloud service: $DestServiceName in region: $DestLocation"
        New-AzureService -ServiceName $DestServiceName -Location $DestLocation -ErrorAction Stop
    }

    # Load VM config
    $vmConfig = Import-AzureVM -Path $exportPath
 
    # Loop through each disk again and create the destination disks
    $diskNum = 0
    foreach($disk in $disks)
    {
        # Construct new Azure disk name as [DestServiceName]-[DestVMName]-[Index]
        $destDiskName = "{0}-{1}-{2}" -f $DestServiceName, $DestVMName, $diskNum  
 
        # Check if an Azure Disk already exists in the destination subscription
        $azureDisk = Get-AzureDisk -DiskName $destDiskName `
                                  -ErrorAction SilentlyContinue `
                                  -ErrorVariable LastError

        if ($null-ne $azureDisk )
        {
            Write-Output "Disk: $destDiskName already exists"

            if ($OverwriteDestDisks -eq $true)
            {
                Write-Output "Deleting disk: $destDiskName"
                Remove-AzureDisk -DiskName $destDiskName            
                $azureDisk = $null
            }
        }
 
        # Determine media location
        $blobName = $disk.MediaLink.Segments | Where-Object { $_ -like "*.vhd" }
        $destBlobName = "$DestServiceName-$DestVMName-$blobName"
        $destMediaLocation = "http://{0}.blob.core.windows.net/{1}/{2}" -f $DestStorageAccount,$DestContainer,$destBlobName
 
        # Attempt to add the azure OS or data disk
        if ($null -ne $disk.OS -and $disk.OS.Length -ne 0)
        {
            if ($null-eq $azureDisk )
            {
                Write-Output "Creating OS disk $destDiskName from vhd $destMediaLocation"
                $azureDisk = Add-AzureDisk -DiskName $destDiskName `
                                            -MediaLocation $destMediaLocation `
                                            -Label $destDiskName `
                                            -OS $disk.OS `
                                            -ErrorAction SilentlyContinue `
                                            -ErrorVariable LastError
            }
        
 
            # Update VM config
            $vmConfig.OSVirtualHardDisk.DiskName = $azureDisk.DiskName
        }
        else
        {
            # Data disk
            if ($null -eq $azureDisk )
            {
                Write-Output "Creating data disk $destDiskName from vhd $destMediaLocation"
                $azureDisk = Add-AzureDisk -DiskName $destDiskName `
                                            -MediaLocation $destMediaLocation `
                                            -Label $destDiskName `
                                            -ErrorAction SilentlyContinue `
                                            -ErrorVariable LastError
            }
         
            # Update VM config
            # Match on source disk name and update with dest disk name
            $vmConfig.DataVirtualHardDisks | ForEach-Object { 
                if($_.DiskName -eq $disk.DiskName)
                {
                    $_.DiskName = $azureDisk.DiskName
                }             
            }
        }              
 
        # Next disk number
        $diskNum = $diskNum + 1
    }

    # Create destination VM
    $vmConfig.RoleName = $DestVMName

    if([string]::IsNullOrEmpty($DestSubnetName))
    {
        $vmConfig.ConfigurationSets[0].SubNetNames = $null
    }
    else
    {
        $vmConfig | Set-AzureSubnet $DestSubnetName
    }

    Write-Output "Creating new VM $DestVMName in cloud service $DestServiceName"

    if([string]::IsNullOrEmpty($DestVnetName))
    {
        $vmConfig | New-AzureVM -ServiceName $DestServiceName -WaitForBoot
    }
    else
    {
        $vmConfig | New-AzureVM -ServiceName $DestServiceName -VNetName $DestVnetName -WaitForBoot
    }
}

Export-ModuleMember 'Copy-AzureVM'