Creating Azure Web-App backup with PowerShell

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 Resource Group they where 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 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

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 like this in the Resource Group pane.

List of the resources in the Azure Portal

The Resource Group for the Web App contains the following:

List of the resources in the Web App resource group in Azure Portal

The Resource Group for the Storage Account contains the following:

List of the resources in the Storage Account resource group in Azure Portal

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, adding 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 cmdlet and more, easier to troubleshoot and reuse. Lets 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 let’s 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 $ResourceGroupNameWebApp and $ResourceGroupNameStorageAccount. This is use to get information needed from the different Resource Groups ****for setting up backup configurations on the Web App.

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 same Storage Account, this can be added as a default value by setting the Mandatory = $false and adding the value behind the variable for instance $StorageAccountName = "webappbackpstorage". This can be adjusted after your own need if you find it useful.

The $ContainerName parameter uses the name from the $WebAppName and adds “-backup” at the end. It also converts all letters from the $WebAppName to lowercase letters. To keep a consistent naming standard for containers.

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 do 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 getting 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.

List of containers in the storage account from the Azure Portal

The third step

The third step is creates a Shared Access Token, a unique URL which 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 than the environment above, think I have reached a limit for the amount of backups with the MSDN license, it wouldn’t allow me to create more backup unless I bought a new payment model.

Overview of the backup configuration blade in the Azure Portal

When you look into the configuration it looks something like below. Where you can see that the parameters within the script mirrors 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.

The backup configuration settings blade in the Azure Portal

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 troubleshooting if something causes an error. This messages can be remove to avoid noise if desired, but it is useful.

For instance, when running the script after 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..."
    }
}