Back up an Azure VM using Microsoft Azure Automation

Backing up a virtual machine in any cloud can be cumbersome. However with Microsoft Azure Automation, backing up a virtual machine (VM) can be done in just a few easy steps and be scheduled to run when you need it to.

Next I’ll walk through how to create an Azure Automation Runbook to back up a Virtual Machine running in Microsoft Azure.

ASSUMPTIONS

  1. You have an Azure subscription, if not sign up here: https://azure.microsoft.com/en-us/pricing/free-trial/ or use an MSDN subscription.
  2. Since Azure Automation is in Preview you’ll need to enabled from the Preview page: https://azure.microsoft.com/en-us/services/preview/
  3. An Azure Virtual Machine exists and is running.
  4. Familiar with PowerShell (no script writing required for this tutorial).
  5. You’ve read my previous post on Azure Automation and run through the tutorial (I use parameters and assets from that post in this one so it’s a very good idea to start there before running through this tutorial): https://blogs.technet.com/b/cbernier/archive/2014/04/08/microsoft-azure-automation.aspx

LET’S GET STARTED

Navigate to your Azure Portal and select AUTOMATION from the list on the left:

clip_image001

If you don’t have one already, create a new Azure Automation Account by selecting CREATE at the bottom of the page:

clip_image003

Note: I’ll be using my existing account called “cloudautomation”

Once your account is created, select it and then select RUNBOOKS from the top of the page:

clip_image004

Now create a new runbook by selecting NEW at the bottom of the page and going through the menu as shown below:

clip_image006

clip_image008

Now that we have a runbook created we’ll draw from the Assets in the previous post: https://blogs.technet.com/b/cbernier/archive/2014/04/08/microsoft-azure-automation.aspx - you’ll want to do run through the post.

Select the runbook and select AUTHOR from the top of the page and paste the following code in the DRAFT section:

workflow BackupAzureVM

{

Param

(

[parameter(Mandatory=$true)]

[String]

$AzureConnectionName,

[parameter(Mandatory=$true)]

[String]

$ServiceName,

[parameter(Mandatory=$true)]

[String]

$VMName,

[parameter(Mandatory=$true)]

[String]

$StorageAccountName,

[parameter(Mandatory=$true)]

[String]

$backupContainerName

)

# Set up Azure connection by calling the Connect-Azure runbook, found in my previous post.

$Uri = Connect-AzureVM -AzureConnectionName $AzureConnectionName -serviceName $ServiceName -VMName $VMName

# Stop Azure VM

Stop-AzureVM -ServiceName $ServiceName -Name $VMName –StayProvisioned

# Backup Azure VM

Backup-AzureVM -serviceName $ServiceName -VMName $VMName -backupContainerName $backupContainerName -backupStorageAccountName $StorageAccountName –includeDataDisks

# Start Azure VM

Start-AzureVM -ServiceName $ServiceName -Name $VMName

}

What the code does: when run, it will ask you for input parameters, stop the VM, back it up, and then start the VM again.

IMPORTING MODULES

To back up an Azure VM I needed to use an outside module. I used Daniele Grandini’s PowerShell module, however I needed to modify a few lines of code within the module for it to work properly in Azure.

You'll need to download Daniele’s Azure VM Backup module from here: https://gallery.technet.microsoft.com/Powershell-module-for-b46c9b62/file/110244/1/QNDAzureBackup.zip

Extract the contents of the .zip file and place both files in a folder named QNDAzureBackup. Using a text editor modify the QNDAzureBackup.psm1 file and delete all the code and add the following code in its place (the lines of code in red are my additions to track the time it takes to back up the VM):

function Copy-AzureBlob

{

    [CmdletBinding()]

    [OutputType([int])]

    Param

    (

        # Param1 help description

        [Parameter(Mandatory=$true,

                        Position=0)]

        [string] $srcBlobName,

        # Param2 help description

        [Parameter(Mandatory=$true)]

        [string] $srcContainerName,

        # Param2 help description

        [Parameter(Mandatory=$true)]

        [Microsoft.WindowsAzure.Commands.Common.Storage.AzureStorageContext] $srcContext,

        [Parameter(Mandatory=$true)]

        [string] $dstBlobName,

        [Parameter(Mandatory=$true)]

        [string] $dstContainerName,

        [Parameter(Mandatory=$true)]

        [Microsoft.WindowsAzure.Commands.Common.Storage.AzureStorageContext] $dstContext

    )

    Try

    {

        $copyContext = Start-AzureStorageBlobCopy -SrcBlob $srcBlobName -SrcContainer $srcContainerName -DestContainer $dstContainerName -DestBlob $dstBlobName -Context $srcContext -DestContext $dstContext

        $status = Get-AzureStorageBlobCopyState -Blob $dstBlobName -Container $dstContainerName -Context $dstContext -WaitForComplete

        $srcBlob = get-azurestorageblob -Container $srcContainerName -Context $srcContext -Blob $srcBlobName

        $bckBlob = get-azurestorageblob -Container $dstContainerName -Context $dstContext -Blob $dstBlobName

        if ($srcBlob.Length -ne $bckBlob.Length -or $status.Status -ne 'Success')

        {

            write-error "Error copying $srcBlobName to $dstContainerName\$dstBlobName. Copy Status: $($status.Status)"

            return 1;

        }

        else

        {

            return 0;

        }

    }

    catch

    {

        write-error "Exception copying blob $($_.Exception.Message)"

        return 2;

    }

}

function Backup-AzureVMDisk

{

    [CmdletBinding()]

    [OutputType([int])]

    Param

    (

        # Param1 help description

        [Parameter(Mandatory=$true,

                   Position=0)]

        $disk, #cannot type it since we have different types for SO and Data disks

        [string] $vmName,

        [string] $stamp,

        [Parameter(Mandatory=$true)]

        [Microsoft.WindowsAzure.Commands.Common.Storage.AzureStorageContext] $srcContext,

        [Parameter(Mandatory=$true)]

        [string] $backupContainerName,

        [Parameter(Mandatory=$true)]

        [Microsoft.WindowsAzure.Commands.Common.Storage.AzureStorageContext] $dstContext

    )

        $blobName = $disk.MediaLink.Segments[$disk.MediaLink.Segments.Count-1]

        $normalizedBlobName = $blobName.Replace('-','')

        $backupDiskName = "$vmName-$stamp-$normalizedBlobName"

        $srcContainerName = $disk.MediaLink.AbsolutePath.Replace("/$blobName",'').Substring(1)

        $copyResult = Copy-AzureBlob -srcBlobName $blobName -srcContainerName $srcContainerName -srcContext $srcContext -dstBlobName $backupDiskName -dstContainerName $backupContainerName -dstContext $dstContext

        return $copyResult;

}

<#

.Synopsis

   Creates a copy of the named virtual machines optionally including the data disks.

.DESCRIPTION

      Creates a copy of the named virtual machines optionally including the data disks. The backup disks will be time stamped following this schema

   <vm name>-<yyyyMMdd>-<HHmm>-<original blob name>. The detsination storage account name and container must be specified. The function works under the following assumptions:

   - the Azure module has been imported

   - the current subscription contains the VM to be backed up

   - the current subscription contains the target storage account 

.EXAMPLE

    Import-Module Azure

    Get-AzureAccount

    Select-AzureSubscription -SubscriptionName 'QND Subscription'

    Backup-AzureVM -serviceName 'QNDBackup' -vmName 'QNDTest1' -backupContainerName 'backup' -dstStorageAccountName 'qndvms' -includeDataDisks

    The sample creates a backup copy of the VM called QNDTest1 deployed in service QNDBackup and saves all the disks in the container named 'backup'

    in the storage acocunt 'qndvms'

#>

function Backup-AzureVM

{

    [CmdletBinding()]

    [OutputType([int])]

    Param

    (

        # Param1 help description

        [Parameter(Mandatory=$true)]

        [string] $serviceName,

        [Parameter(Mandatory=$true)]

        [string] $vmName,

        [Parameter(Mandatory=$true)]

        [string] $backupContainerName,

        [Parameter(Mandatory=$true)]

        [string] $backupStorageAccountName,

        [switch] $includeDataDisks

)

$Start = [System.DateTime]::Now

    try {

    $timeStamp = (get-date).ToString('yyyyMMdd-HHmm')

    $vm = get-Azurevm -name $vmName -ServiceName $serviceName

    if (! $vm)

    {

        write-error "Virtual machine $vmName not found in service $serviceName"

        return 1;

    }

    $osDisk = Get-AzureOSDisk -VM $vm

    $dataDisks = Get-AzureDataDisk -VM $vm

    $dstContext = new-azurestoragecontext -StorageAccountName $backupStorageAccountName -StorageAccountKey (Get-AzureStorageKey -StorageAccountName $backupStorageAccountName).Primary

    $srcStgAccountName = $osdisk.MediaLink.Host.Split('.')[0]

    $srcContext = new-azurestoragecontext -StorageAccountName $srcStgAccountName -StorageAccountKey (Get-AzureStorageKey -StorageAccountName $srcStgAccountName).Primary

    $bckContainer = Get-AzureStorageContainer -Name $backupContainerName -Context $dstContext -ErrorAction Stop

    }

    catch [Microsoft.WindowsAzure.Commands.Storage.Common.ResourceNotFoundException] {

        Write-Error "Resource doesn't exists. $($_.Exception.Message)"

        return 2;

    }

    catch {

        write-error "Generic exception getting resources info $($_.Exception.Message)"

        return 1;

    }

    try {

        if ($vm.PowerState -eq 'Started')

        {

            $vmStarted = $true

            Stop-AzureVM -VM $vm.VM -StayProvisioned -ServiceName $vm.ServiceName | Out-Null

        }

        else

        {

            $vmStarted = $false

        }

        #the backup disk name must be coded so we can have a link to the original disk

        #copy OS Disk

        $vmNormalizedName = $vm.InstanceName

        $copyResult = Backup-AzureVMDisk -disk $osDisk -vmName $vmNormalizedName -stamp $timeStamp -srcContext $srcContext -backupContainerName $backupContainerName -dstContext $dstContext

        if ($copyResult -eq 0)

        {

            Write-Output "Successfully made OS disk backup copy $($osDisk.DiskName)"

        }

        else

        {

            throw [System.Exception] "error copying OS disk"

        }

        if ($includeDataDisks)

        {

            foreach($disk in $dataDisks)

            {

                $copyResult = Backup-AzureVMDisk -disk $disk -vmName $vmNormalizedName -stamp $timeStamp -srcContext $srcContext -backupContainerName $backupContainerName -dstContext $dstContext

                if ($copyResult -eq 0)

                {

                    Write-Output "Successfully made data disk backup copy $($disk.DiskName)"

                }

                else

                {

                    throw [System.Exception] "error copying Data disk $($disk.DiskName) "

                }               

            }

        }

        return 0;

    }

    catch {

        write-error "Generic exception making backup copy $($_.Exception.Message)"

        return 1;

    }

    finally {

        if ($vmStarted)

        {

            Start-AzureVM -VM $vm.VM -ServiceName $vm.ServiceName | OUt-Null

        }

}

$Finish = [System.DateTime]::Now

$TotalUsed = $Finish.Subtract($Start).TotalSeconds               

Write-Output ("Total used {0} seconds." -f $TotalUsed)

}

Export-ModuleMember -function Backup-AzureVM

Save the .psm1 text file and right click on the folder you created that contains the two files and send to .zip. The hierarchy should be .zip -> folder -> both files (.psm1 & .psd1). This is a different hierarchy than the original .zip downloaded. There must be a folder within the .zip file that contains both files.

IMPORTING THE AZURE BACKUP MODULE

Upload Azure VM Backup module into the automation account you created by selecting ASSETS at the tops of the page and then IMPORT MODULE at the bottom of the page:

clip_image010

Note: this will take a minute or so to save and extract the contents (i.e. activities).

When the import is finished, select the QNDAzureBackup module and you will see the following:

clip_image012

Now we’re ready to test the VM backup runbook.

Select the runbook and AUTHOR, then DRAFT, and finally TEST at the bottom of the page:

clip_image014

Note: If you didn’t go through my other post and complete the steps, now would be a good time to do it. Otherwise the runbook won’t work (because I call on assets created in the previous post).

Fill out the runbook parameter values and select the check box to start the runbook:

clip_image016

Once completed you will see output similar to the following:

clip_image018

Note: If a step was missed or there are incorrect parameters you’ll see lots of red.

Here are the contents of my storage container:

clip_image020

CREATING A VM BACK UP RUNBOOK SCHEDULE

Once the runbook is functioning properly, PUBLISH it and you’ll be ready to create a schedule for it to run on.

Select the runbook and the select SCHEDULE at the top of the page:

clip_image022

At the bottom of the SCHEDULE page select LINK and then Link to a New Schedule:

clip_image023

The new few images show how I configured my schedule:

clip_image025

clip_image027

Enter the requested parameters and select the check mark to complete the task of creating a schedule.

clip_image029

Details of the schedule is shown in the following images:

clip_image031

clip_image032

Congratulations you’ve created a runbook, imported modules, backed up a VM, and created a backup schedule.

This is only scratching the surface of what Azure Automation can do. I encourage you to explore the capabilities of Azure Automation to see how you can automate your Azure infrastructure.

ADDITIONAL RESOURCES

Azure Automation Overview:

https://msdn.microsoft.com/library/azure/dn643629.aspx

Sample Azure Automation Scripts:

https://gallery.technet.microsoft.com/scriptcenter/site/search?f[0].Type=User&f[0].Value=SC%20Automation%20Product%20Team&f[0].Text=SC%20Automation%20Product%20Team&f[1].Type=RootCategory&f[1].Value=WindowsAzure&f[1].Text=Windows%20Azure