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
- You have an Azure subscription, if not sign up here: https://azure.microsoft.com/en-us/pricing/free-trial/ or use an MSDN subscription.
- Since Azure Automation is in Preview you’ll need to enabled from the Preview page: https://azure.microsoft.com/en-us/services/preview/
- An Azure Virtual Machine exists and is running.
- Familiar with PowerShell (no script writing required for this tutorial).
- 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:
If you don’t have one already, create a new Azure Automation Account by selecting CREATE at the bottom of the page:
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:
Now create a new runbook by selecting NEW at the bottom of the page and going through the menu as shown below:
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:
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:
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:
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:
Once completed you will see output similar to the following:
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:
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:
At the bottom of the SCHEDULE page select LINK and then Link to a New Schedule:
The new few images show how I configured my schedule:
Enter the requested parameters and select the check mark to complete the task of creating a schedule.
Details of the schedule is shown in the following images:
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:
Comments
- Anonymous
January 01, 2003
Error:
27-04-2015 15:39:04, Error: [knswin.cloudapp.net] Connecting to remote server knswin.cloudapp.net failed with the following error message : Access
is denied. For more information, see the about_Remote_Troubleshooting Help topic.
+ CategoryInfo : OpenError: (knswin.cloudapp.net:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : AccessDenied,PSSessionStateBroken
27-04-2015 15:39:05, Error: Stop-AzureVM : Cannot validate argument on parameter 'ServiceName'. The argument is null or empty. Provide an argument
that is not null or empty, and then try the command again.
At knsremotepscommand:93 char:93
+
+ CategoryInfo : InvalidData: (:) [Stop-AzureVM], ParameterBindingValidationException
+ FullyQualifiedErrorId :
ParameterArgumentValidationError,Microsoft.WindowsAzure.Commands.ServiceManagement.IaaS.StopAzureVMCommand
27-04-2015 15:39:06, Error: Backup-AzureVM : Cannot bind argument to parameter 'serviceName' because it is an empty string.
At knsremotepscommand:93 char:93
+
+ CategoryInfo : InvalidData: (:) [Backup-AzureVM], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Backup-AzureVM
27-04-2015 15:39:06, Error: Start-AzureVM : Cannot validate argument on parameter 'ServiceName'. The argument is null or empty. Provide an argument
that is not null or empty, and then try the command again.
At knsremotepscommand:93 char:93
+
+ CategoryInfo : InvalidData: (:) [Start-AzureVM], ParameterBindingValidationException
+ FullyQualifiedErrorId :
ParameterArgumentValidationError,Microsoft.WindowsAzure.Commands.ServiceManagement.IaaS.StartAzureVMCommand - Anonymous
January 01, 2003
workflow knsremotepscommand
{
Param
(
[parameter(Mandatory=$true)]
[String]
$AzureSubscriptionName,
[parameter(Mandatory=$true)]
[PSCredential]
$AzureOrgIdCredential,
[parameter(Mandatory=$true)]
[String]
$ServiceName,
[parameter(Mandatory=$true)]
[String]
$VMName,
[parameter(Mandatory=$true)]
[String]
$VMCredentialName,
[parameter(Mandatory=$true)]
[String]
$PSCommand,
[parameter(Mandatory=$true)]
[String]
$StorageAccountName,
[parameter(Mandatory=$true)]
[String]
$backupContainerName
)
# Get credentials to Azure VM
$Credential = Get-AutomationPSCredential -Name $VMCredentialName
if ($Credential -eq $null)
{
throw "Could not retrieve '$VMCredentialName' credential asset. Check that you created this asset in the Automation service."
}
# Set up Azure connection by calling the Connect-Azure runbook. You should call this runbook after
# every CheckPoint-WorkFlow to ensure that the management certificate is available if this runbook
# gets interrupted and starts from the last checkpoint
$Uri = Connect-AzureVM -AzureSubscriptionName $AzureSubscriptionName -AzureOrgIdCredential $AzureOrgIdCredential -ServiceName $ServiceName -VMName $VMName
# Run a command on the Azure VM
$PSCommandResult = InlineScript {
Invoke-command -ConnectionUri $Using:Uri -credential $Using:Credential -ScriptBlock {
Invoke-Expression $Args[0]
} -Args $Using:PSCommand
# 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
}
$PSCommandResult
}
AzureOrgIdCredential: venu@vnalluri2006hotmail.onmicrosoft.com
AzureSubscriptionName: BizSpark
backupContainerName: knsazurewin1
PSCommand: ipconfig/all
ServiceName: KNSWin
StorageAccountName: vnalluri2006hotmail.onmicrosoft.com
VMCredentialName:vnalluri2006hotmail.onmicrosoft.com
VMName:knsazurewin1