Experimented with a backup solution for Azure Web Apps this rainy Saturday. The backup solution I wanted to create consisted of a Resource Group with a Storage Account containing backups from any Web Apps regarding the Resource Group they were located in.
What I created was a Resource Group containing a Web App, and a Resource Group containing the Storage Account used for backup. Then added a backup configuration to the Web App, that stores the backup in the backup Resource Group. This will make more sense when seeing the code and some screenshots of the configurations.
Creating the environment
The first step, creating the environment for this experiment. For this, I used the script below. This script creates a Resource Group containing a Web App and a Resource Group containing the Storage Account for backup.
# Create Resource Group for Web App New-AzResourceGroup -Name "webapp-test-rg" -Location "West Europe" -Verbose # Using random numbers to create a unique name $Random = Get-Random -Maximum 9999 -Minimum 1000 # Create a Web App # Note if you dont specify an App Service Plan it will automagically create it for you New-AzWebApp -ResourceGroupName "webapp-test-rg" -Name ("webapp"+$Random+"-stage") -Location "West Europe" -Verbose # Create a Resource Group for Web App backup storage New-AzResourceGroup -Name "webapp-backup-rg" -Location "West Europe" -Verbose # Create a Storage Account New-AzStorageAccount -ResourceGroupName "webapp-backup-rg" -Name "webappbackpstorage" -Location "West Europe" -SkuName "Standard_LRS" -Verbose
When the Resource Groups are created, it looks like this in the Resource Group pane.
The Resource Group for the Web App contains the following:
The Resource Group for the Storage Account contains the following:
When the environment is in place, the next step is to create the backup configurations for the Web App.
Adding the Web App backup configurations
Next up, add the Web App backup configurations. For this, I created the script below. The script can seem a bit overwhelming for something trivial as adding backup configuration. The reason is, I created an advanced function with parameters, comments, and verbose messages. It makes the script more reusable, it can be piped to other cmdlets, and more, easier to troubleshoot and reuse. Let’s go through it.
Going through the script
A little go-through of the script.
Creating a function
The first part of the script is that I created an advanced function that lets you use -Verbose, -Debug, -ErrorAction and a lot more functionality than a regular function in PowerShell. It allows you to use the same features as a standard built-in cmdlet.
function Backup-AzWebApp { [CmdletBinding()] param ( ) process { } }
Creating parameters
Then created the parameters needed to perform the tasks needed. Since the script consists of different cmdlets in the process
block I needed to make different parameters to separate the Resource Groups as you can see with
and $ResourceGroupNameWebApp
. This is used to get the information needed from the different Resource Groups ****for setting up backup configurations on the Web App.$ResourceGroupNameStorageAccount
For those parameters that are mandatory is set to $true
, since this must be filled out. If you have a lot of Web Apps that you need to back to the same Storage Account, this can be added as a default value by setting the
and adding the value behind the variable for instance Mandatory = $false
. This can be adjusted after your own need if you find it useful.$StorageAccountName = "webappbackpstorage"
The
the parameter uses the name from the $WebAppName and adds “-backup” at the end. It also converts all letters from the $WebAppName lowercase letters. To keep a consistent naming standard for containers.$ContainerName
function Backup-AzWebApp { [CmdletBinding()] param ( # Name of Web App [Parameter(Mandatory = $true)] [String] $WebAppName, # Name of Resource Group the Web App is stored [Parameter(Mandatory = $true)] [String] $ResourceGroupNameWebApp, # Name of Resource Group the Web App is stored [Parameter(Mandatory = $true)] [String] $ResourceGroupNameStorageAccount, # Name of the Container Name [Parameter(Mandatory = $false)] [String] $ContainerName = (($WebAppName).ToLower() + "-backup"), # Name of the Storage Account to create Containers in. [Parameter(Mandatory = $true)] [String] $StorageAccountName, # Amount of Retentions [Parameter(Mandatory = $false)] [int] $RetentionDays = 5 ) }
The process block
This is where the action happens. It can look like it does a lot, on the other hand, it only does four basic tasks. It is the Write-Verbose
cmdlet that makes the script look bigger than it is. This is for displaying output messages when the script is running to see progress and errors during runtime.
process { # Getting Storage Account Write-Verbose "Getting and Storing Storage Account information to variable..." $Storage = Get-AzStorageAccount -ResourceGroupName $ResourceGroupNameStorageAccount -Name $StorageAccountName Write-Verbose "Finished getting Storage Account information..." # Create a storage container. Write-Verbose "Creating a new Storage Container in $($Storage.StorageAccountName)..." New-AzStorageContainer -Name $ContainerName -Context $Storage.Context Write-Verbose "Finished creating a new Storage Container in $($Storage.StorageAccountName)..." # Generates an SAS token for the storage container, valid for one month. # NOTE: You can use the same SAS token to make backups in Web Apps until -ExpiryTime Write-Verbose "Generating a SaS token for the Storage Container..." $SaSUrl = New-AzStorageContainerSASToken -Name $ContainerName ` -Permission rwdl ` -Context $Storage.Context ` -ExpiryTime (Get-Date).AddYears(1) ` -FullUri Write-Verbose "Finished generating a SaS token for the Storage Container..." $SaSUrl # Schedule a backup every day, beginning in one hour, and retain for 5 days Write-Verbose "Creating backup configurations for $WebAppName..." Edit-AzWebAppBackupConfiguration -ResourceGroupName $ResourceGroupNameWebApp ` -Name $WebAppName ` -StorageAccountUrl $SaSUrl ` -FrequencyInterval 1 ` -FrequencyUnit Day ` -KeepAtLeastOneBackup ` -StartTime (Get-Date).AddSeconds(10) ` -RetentionPeriodInDays $RetentionDays Write-Verbose "Finished creating backup configurations for $WebAppName..." Write-Verbose "Script is completed..." }
The first step
The first step is to get the information from the Storage Account by using the cmdlet Get-AzStorageAccount
and stores it to the $Storage
variable, which is used later in the script.
The second step
The second step is to use the $Storage
variable to create a new Container in the Storage Account ****by using the cmdlet New-AzStorageContainer
. After the cmdlet is run a container is added in the Storage Account and will look like this in the Azure Portal after creation.
The third step
The third step is to create a Shared Access Token, a unique URL that allows the Web App to write to the newly created Container in the Storage Account.
The fourth step
The fourth step configures the Web App with the Edit-AzWebAppBackupConfiguration
cmdlet. This configuration is set to run one time each day, backups are stored for five days, there is always a backup stored, and the first backup is created 10 seconds after the script is run. (This can be changed, but for experiment purposes. I think it’s fine).
When the script is complete, you can see the backup configuration in the Azure Portal in the Web App panel within the backup blade. Note: The configuration in the screenshots below is a different Web App then the environment above, think I have reached a limit for the number of backups with the MSDN license, it wouldn’t allow me to create more backup unless I bought a new payment model.
When you look into the configuration it looks something like below. Where you can see that the parameters within the script mirror the configuration in this blade. You can always change the location and the configuration from the Portal, but if you can script it, it is much cooler and efficient than using the portal, and most importantly more fun with scripts.
Wrap up
That’s pretty much it. Mentioned it earlier and all the Write-Verbose
cmdlets are useful to see what happens in the background when the script is running, this makes it easier to debug and troubleshoot if something causes an error. These messages can be removed to avoid noise if desired, but it is useful.
For instance, when running the script a couple of times the error Operation returned an invalid status code 'Conflict'
. This message is caused because of a backup limit of my MSDN license since when I went to the Azure Portal and performed the process manually it told me I have used the limit of backups for this day.
Running the script
Almost forgot. To run the script you can highlight all the code in either PowerShell ISE or in Visual Studio Code and hit F8 to load the script into memory or by using dot source like this . .Backup-AzWebApp.ps1
. Then run the script as a cmdlet, like below.
An example of how you can run the script based on the environment above.
Backup-AzWebApp -WebAppName "webapp1606-stage" ` -ResourceGroupNameWebApp "webapp-test-rg" ` -ResourceGroupNameStorageAccount "webapp-backup-rg" ` -StorageAccountName "webappbackpstorage" ` -Verbose
The Complete Script
function Backup-AzWebApp { [CmdletBinding()] param ( # Name of Web App [Parameter(Mandatory = $true)] [String] $WebAppName, # Name of Resource Group the Web App is stored [Parameter(Mandatory = $true)] [String] $ResourceGroupNameWebApp, # Name of Resource Group the Web App is stored [Parameter(Mandatory = $true)] [String] $ResourceGroupNameStorageAccount, # Name of the Container Name [Parameter(Mandatory = $false)] [String] $ContainerName = (($WebAppName).ToLower() + "-backup"), # Name of the Storage Account to create Containers in. [Parameter(Mandatory = $true)] [String] $StorageAccountName, # Amount of Retentions [Parameter(Mandatory = $false)] [int] $RetentionDays = 5 ) process { # Getting Storage Account Write-Verbose "Getting and Storing Storage Accountinformation to variable..." $Storage = Get-AzStorageAccount -ResourceGroupName $ResourceGroupNameStorageAccount -Name $StorageAccountName Write-Verbose "Finished getting Storage Accountinformation..." # Create a storage container. Write-Verbose "Creating a new Storage Container in ($Storage.StorageAccountName)..." New-AzStorageContainer -Name $ContainerName -Context $Storage.Context Write-Verbose "Finished creating a new StorageContainer in $($Storage.StorageAccountName)..." # Generates an SAS token for the storage container,valid for one month. # NOTE: You can use the same SAS token to make backupsin Web Apps until -ExpiryTime Write-Verbose "Generating a SaS token for the StorageContainer..." $SaSUrl = New-AzStorageContainerSASToken -Name ContainerName ` -Permission rwdl ` -Context $Storage.Context ` -ExpiryTime (Get-Date).AddYears(1) ` -FullUri Write-Verbose "Finished generating a SaS token for the Storage Container..." $SaSUrl # Schedule a backup every day, beginning in one hour,and retain for 5 days Write-Verbose "Creating backup configurations for $WebAppName..." Edit-AzWebAppBackupConfiguration -ResourceGroupName $ResourceGroupNameWebApp ` -Name $WebAppName ` -StorageAccountUrl $SaSUrl ` -FrequencyInterval 1 ` -FrequencyUnit Day ` -KeepAtLeastOneBackup ` -StartTime (Get-Date).AddSeconds(10) ` -RetentionPeriodInDays $RetentionDays Write-Verbose "Finished creating backup configurationsfor $WebAppName..." Write-Verbose "Script is complete..." } }