Edit

Restore SQL databases in an Azure VM using Azure CLI

Azure CLI is used to create and manage Azure resources from the Command Line or through scripts. This article describes how to restore a backed-up SQL database on an Azure VM using Azure CLI. You can also perform these actions using the Azure portal.

Use Azure Cloud Shell to run CLI commands.

This article assumes you have an SQL database running on Azure VM that's backed-up using Azure Backup. If you've used Back up an SQL database in Azure using CLI to back up your SQL database, then you're using the following resources:

  • A resource group named SQLResourceGroup.
  • A vault named SQLVault.
  • Protected container named VMAppContainer;Compute;SQLResourceGroup;testSQLVM.
  • A backed-up database/item named sqldatabase;mssqlserver;testdb.
  • Resources in the westus region.

To view the backup and restore scenarios that we support today, see the support matrix. For common questions, see the frequently asked questions.

View restore points for a backed-up database

To view the list of all recovery points for a database, use the az backup recoverypoint list command as:

az backup recoverypoint list --resource-group SQLResourceGroup \
    --vault-name SQLVault \
    --container-name "VMAppContainer;Compute;SQLResourceGroup;testSQLVM" \
    --item-name "sqldatabase;mssqlserver;testdb" \
   --output table

The list of recovery points appears as:

Name                      Time                               BackupManagementType   Item Name               		RecoveryPointType
-------------------       ---------------------------------  ---------------------  ----------------------  		------------------
7660777527047692711       2019-12-10T04:00:32.346000+00:00   AzureWorkload          sqldatabase;mssqlserver;testdb  Full
7896624824685666836       2019-12-15T10:33:32.346000+00:00   AzureWorkload          sqldatabase;mssqlserver;testdb  Differential
DefaultRangeRecoveryPoint                                    AzureWorkload          sqldatabase;mssqlserver;testdb  Log

The list above contains three recovery points: each for full, differential, and log backup.

Note

You can also view the start and end points of every unbroken log backup chain, using the az backup recoverypoint show-log-chain command.

Prerequisites to restore a database

Ensure that the following prerequisites are met before restoring a database:

  • You can restore the database only to an SQL instance in the same region or to a different region if Cross Region Restore is enabled on the vault.
  • The target instance must be registered with the same vault as the source, except during Cross Region Restore and Cross Subscription Restore. .

Restore a database

Azure Backup can restore SQL databases that are running on Azure VMs as:

  • Restore to a specific date or time (to the second) by using log backups. Azure Backup automatically determines the appropriate full, differential backups and the chain of log backups that are required to restore based on the selected time.
  • Restore to a specific full or differential backup to restore to a specific recovery point.

To restore a database, use the az restore restore-azurewl command, which requires a recovery config object as one of the inputs. You can generate this object using the az backup recoveryconfig show command. The recovery config object contains all details to perform a restore. One of them is the restore mode – OriginalWorkloadRestore or AlternateWorkloadRestore.

Note

OriginalWorkloadRestore: Restores data to the same SQL instance as the original source. This option overwrites the original database. AlternateWorkloadRestore: Restores database to an alternate location and keep the original source database.

Restore to alternate location

To restore a database to an alternate location, use AlternateWorkloadRestore as the restore mode. You must then choose the restore point, which could be a previous point-in-time or any previous restore points.

Let's proceed to restore to a previous restore point. View the list of restore points for the database and choose the point you want to restore. Here, let's use the restore point with the name 7660777527047692711.

With the above restore point name and the restore mode, create the recovery config object using the az backup recoveryconfig show command. Check the remaining parameters in this command:

  • --target-item-name: The name to be used by the restored database. In this scenario, we used the name restored_database.
  • --target-server-name: The name of an SQL server that's successfully registered to a Recovery Services vault and stays the same region as per the database to be restored. Here, you're restoring the database to the same SQL server that you've protected, named testSQLVM.
  • --target-server-type: For the restore of SQL databases, you must use SQLInstance.

az backup recoveryconfig show --resource-group SQLResourceGroup \
    --vault-name SQLVault \
    --container-name "VMAppContainer;Compute;SQLResourceGroup;testSQLVM" \
    --item-name "SQLDataBase;mssqlserver;testdb" \
    --restore-mode AlternateWorkloadRestore \
    --rp-name 7660777527047692711 \
    --target-item-name restored_database \
    --target-server-name testSQLVM \
    --target-server-type SQLInstance \
    --workload-type SQLDataBase \
    --output json

The response to the above query is a recovery config object that appears as:

{
  "container_id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/SQLResourceGroup/providers/Microsoft.RecoveryServices/vaults/SQLVault/backupFabrics/Azure/protectionContainers/vmappcontainer;compute;SQLResourceGroup;testSQLVM",
  "container_uri": "VMAppContainer;compute;SQLResourceGroup;testSQLVM",
  "database_name": "MSSQLSERVER/restored_database",
  "filepath": null,
  "item_type": "SQL",
  "item_uri": "SQLDataBase;mssqlserver;testdb",
  "log_point_in_time": null,
  "recovery_mode": null,
  "recovery_point_id": "7660777527047692711",
  "restore_mode": "AlternateLocation",
  "source_resource_id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/SQLResourceGroup/providers/Microsoft.Compute/virtualMachines/testSQLVM",
  "workload_type": "SQLDataBase",
  "alternate_directory_paths": []
}

Now, to restore the database, run the az restore restore-azurewl command. To use this command, enter the above JSON output that's saved to a file named recoveryconfig.json.

az backup restore restore-azurewl --resource-group SQLResourceGroup \
    --vault-name SQLVault \
    --recovery-config recoveryconfig.json \
    --output table

The output appears as:

Name                                  Operation    Status      Item Name                          Backup Management Type    Start Time UTC                    Duration
------------------------------------  -----------  ----------  ---------------------------------  ------------------------  --------------------------------  --------------
be7ea4a4-0752-4763-8570-a306b0a0106f  Restore      InProgress  testdb [testSQLVM]  				  AzureWorkload             2022-06-21T03:51:06.898981+00:00  0:00:05.652967

The response provides you with the job name. You can use this job name to track the job status using az backup job show command.

Restore and overwrite

To restore to the original location, use OriginalWorkloadRestore as the restore mode. You must then choose the restore point, which could be a previous point-in-time or any of the previous restore points.

As an example, let's choose the previous point-in-time "28-11-2019-09:53:00" to restore to. You can provide this restore point in the following formats: dd-mm-yyyy, dd-mm-yyyy-hh:mm:ss. To choose a valid point-in-time to restore, use the az backup recoverypoint show-log-chain command, which lists the intervals of unbroken log chain backups.

az backup recoveryconfig show --resource-group SQLResourceGroup \
    --vault-name SQLVault \
    --container-name "VMAppContainer;Compute;SQLResourceGroup;testSQLVM" \
    --item-name "sqldatabase;mssqlserver;testdb" \
    --restore-mode OriginalWorkloadRestore \
    --log-point-in-time 20-06-2022-09:02:41 \
    --output json

The response to the above query is a recovery config object that appears as:

{
  "alternate_directory_paths": null,
  "container_id": null,
  "container_uri": "VMAppContainer;Compute;SQLResourceGroup;testSQLVM",
  "database_name": null,
  "filepath": null,
  "item_type": "SQL",
  "item_uri": "SQLDataBase;mssqlserver;testdb",
  "log_point_in_time": "20-06-2022-09:02:41",
  "recovery_mode": null,
  "recovery_point_id": "DefaultRangeRecoveryPoint",
  "restore_mode": "OriginalLocation",
  "source_resource_id": "/subscriptions/62b829ee-7936-40c9-a1c9-47a93f9f3965/resourceGroups/SQLResourceGroup/providers/Microsoft.Compute/virtualMachines/testSQLVM",
  "workload_type": "SQLDataBase"
}

Now, to restore the database, run the az restore restore-azurewl command. To use this command, enter the above JSON output that's saved to a file named recoveryconfig.json.

az backup restore restore-azurewl --resource-group sqlResourceGroup \
    --vault-name sqlVault \
    --recovery-config recoveryconfig.json \
    --output table

The output appears as:

Name                                  Operation    Status      Item Name                        Backup Management Type    Start Time UTC                    Duration
------------------------------------  -----------  ----------  -------------------------------  ------------------------  --------------------------------  --------------
1730ec49-166a-4bfd-99d5-93027c2d8480  Restore      InProgress  testdb [testSQLVM]  				AzureWorkload             2022-06-21T04:04:11.161411+00:00  0:00:03.118076

The response provides you with the job name. You can use this job name to track the job status using the az backup job show command.

Restore to a secondary region

To restore a database to the secondary region, specify a target vault and server located in the secondary region, in the restore configuration.

az backup recoveryconfig show --resource-group SQLResourceGroup \
    --vault-name SQLVault \
    --container-name "VMAppContainer;compute;SQLResourceGroup;testSQLVM" \
    --item-name "sqldatabase;mssqlserver;testdb" \
    --restore-mode AlternateWorkloadRestore \
    --from-full-rp-name 293170069256531 \
    --rp-name 293170069256531 \
    --target-server-name targetSQLServer \
    --target-container-name "VMAppContainer;compute;SQLResourceGroup;targetSQLServer" \
    --target-item-name testdb_restore_1 \
    --target-server-type SQLInstance \
    --workload-type SQLDataBase \
    --target-resource-group SQLResourceGroup \
    --target-vault-name targetVault \
    --backup-management-type AzureWorkload

The response is a recovery configuration object that appears as:

 {
  "container_id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/SQLResourceGroup/providers/Microsoft.RecoveryServices/vaults/targetVault/backupFabrics/Azure/protectionContainers/vmappcontainer;compute;SQLResourceGroup;targetSQLServer",
  "container_uri": "VMAppContainer;compute;SQLResourceGroup;testSQLVM",
  "database_name": "MSSQLSERVER/testdb_restore_1",
  "filepath": null,
  "item_type": "SQL",
  "item_uri": "SQLDataBase;mssqlserver;testdb",
  "log_point_in_time": null,
  "recovery_mode": null,
  "recovery_point_id": "293170069256531",
  "restore_mode": "AlternateLocation",
  "source_resource_id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/SQLResourceGroup/providers/Microsoft.Compute/virtualMachines/testSQLVM",
  "workload_type": "SQLDataBase",
  "alternate_directory_paths": []
}

Use this recovery configuration in the az restore restore-azurewl command. Select the --use-secondary-region flag to restore the database to the secondary region.

az backup restore restore-azurewl --resource-group SQLResourceGroup \
    --vault-name SQLVault \
    --recovery-config recoveryconfig.json \
    --use-secondary-region \
    --output table

The output appears as:

Name                                  Operation           Status      Item Name                  Backup Management Type    Start Time UTC                    Duration
------------------------------------  ------------------  ----------  -------------------------  ------------------------  --------------------------------  --------------
0d863259-b0fb-4935-8736-802c6667200b  CrossRegionRestore  InProgress  testdb [testSQLVM] 		 AzureWorkload             2022-06-21T08:29:24.919138+00:00  0:00:12.372421

Note

The RPO for the backup data to be available in secondary region is 12 hours. Therefore, when you turn on CRR, the RPO for the secondary region is 12 hours + log frequency duration (that can be set to a minimum of 15 minutes).

Restore as files

To restore the backup data as files instead of a database, use RestoreAsFiles as the restore mode. Then choose the restore point, which can be a previous point-in-time or any previous restore points. Once the files are dumped to a specified path, you can take these files to any SQL machine where you want to restore them as a database. Because you can move these files to any machine, you can now restore the data across subscriptions and regions.

Choose the previous point-in-time 28-11-2019-09:53:00 to restore and the location to dump backup files as C:\restoreasfiles on the same SQL server. You can provide this restore point in one of the following formats: dd-mm-yyyy or dd-mm-yyyy-hh:mm:ss. To choose a valid point-in-time to restore, use the az backup recoverypoint show-log-chain command, which lists the intervals of unbroken log chain backups.

With the above restore point name and the restore mode, create the recovery config object using the az backup recoveryconfig show command. Check each of the remaining parameters in this command:

  • --target-container-name: The name of a SQL server that's successfully registered to a Recovery Services vault and present in the same region as per the database to be restored. Let's restore the database as files to the same SQL server that you protected, named mssqlserver.
  • --rp-name: For a point-in-time restore, the restore point name is DefaultRangeRecoveryPoint.
az backup recoveryconfig show --resource-group SQLResourceGroup \
    --vault-name SQLVault \
    --container-name "VMAppContainer;Compute;SQLResourceGroup;testSQLVM" \
    --item-name "sqldatabase;mssqlserver;testdb" \
    --restore-mode RestoreAsFiles \
    --rp-name 932606668166874635 \
    --target-container-name "VMAppContainer;Compute;SQLResourceGroup;testSQLVM" \
    --filepath "C:\restoreasfiles" \
    --output json

The response to the query above js a recovery config object that appears as:

{
  "alternate_directory_paths": null,
  "container_id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/SQLResourceGroup/providers/Microsoft.RecoveryServices/vaults/SQLVault/backupFabrics/Azure/protectionContainers/VMAppContainer;Compute;SQLResourceGroup;testSQLVM",
  "container_uri": "VMAppContainer;compute;SQLResourceGroup;testSQLVM",
  "database_name": null,
  "filepath": "C:\restoreasfiles",
  "item_type": "SQL",
  "item_uri": "SQLDataBase;mssqlserver;testdb",
  "log_point_in_time": null,
  "recovery_mode": "FileRecovery",
  "recovery_point_id": "932606668166874635",
  "restore_mode": "AlternateLocation",
  "source_resource_id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/SQLResourceGroup/providers/Microsoft.Compute/virtualMachines/testSQLVM",
  "workload_type": "SQLDataBase"
}

Now, to restore the database as files run the az restore restore-azurewl command. To use this command, enter the JSON output above that's saved to a file named recoveryconfig.json.

az backup restore restore-azurewl --resource-group SQLResourceGroup \
    --vault-name SQLVault \
    --recovery-config recoveryconfig.json \
    --output json

The output appears as:

{
  "eTag": null,
  "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/SQLResourceGroup/providers/Microsoft.RecoveryServices/vaults/SQLVault/backupJobs/e9cd9e73-e3a3-425a-86a9-8dd1c500ff56",
  "location": null,
  "name": "e9cd9e73-e3a3-425a-86a9-8dd1c500ff56",
  "properties": {
    "actionsInfo": [
      "1"
    ],
    "activityId": "9e7c8ee4-f1ef-11ec-8a2c-3c52826c1a9a",
    "backupManagementType": "AzureWorkload",
    "duration": "0:00:04.304322",
    "endTime": null,
    "entityFriendlyName": "testdb [testSQLVM]",
    "errorDetails": null,
    "extendedInfo": {
      "dynamicErrorMessage": null,
      "propertyBag": {
        "Job Type": "Restore as files"
      },
      "tasksList": [
        {
          "status": "InProgress",
          "taskId": "Transfer data from vault"
        }
      ]
    },
    "isUserTriggered": true,
    "jobType": "AzureWorkloadJob",
    "operation": "Restore",
    "startTime": "2022-06-22T05:53:32.951666+00:00",
    "status": "InProgress",
    "workloadType": "SQLDataBase"
  },
  "resourceGroup": "SQLResourceGroup",
  "tags": null,
  "type": "Microsoft.RecoveryServices/vaults/backupJobs"
}

The response provides you with the job name. You can use this job name to track the job status using the az backup job show command.

Note

If you don't want to restore the entire chain but only a subset of files, follow the steps as documented here.

Cross Subscription Restore

With Cross Subscription Restore (CSR), you have the flexibility of restoring to any subscription and any vault under your tenant if restore permissions are available. By default, CSR is enabled on all Recovery Services vaults (existing and newly created vaults).

Note

  • You can trigger Cross Subscription Restore from Recovery Services vault.
  • CSR is supported only for streaming based backup and is not supported for snapshot-based backup.
  • Cross Regional Restore (CRR) with CSR is not supported.
az backup vault create

Add the parameter cross-subscription-restore-state that enables you to set the CSR state of the vault during vault creation and updating.

az backup recoveryconfig show

Add the parameter --target-subscription-id that enables you to provide the target subscription as the input while triggering Cross Subscription Restore for SQL or HANA datasources.

Example:

   az backup vault create -g {rg_name} -n {vault_name} -l {location} --cross-subscription-restore-state Enable
   az backup recoveryconfig show --restore-mode alternateworkloadrestore --backup-management-type azureworkload -r {rp} --target-container-name {target_container} --target-item-name {target_item} --target-resource-group {target_rg} --target-server-name {target_server} --target-server-type SQLInstance --target-subscription-id {target_subscription} --target-vault-name {target_vault} --workload-type SQLDataBase --ids {source_item_id}

Restore SQL instance snapshots

The following examples use PowerShell shell syntax, not Bash. Use an Azure CLI version that exposes the snapshot options shown in this article. Review the snapshot restore prerequisites and support matrix.

Replace placeholders before use. Complete the common setup and choose only the required restore scenario. The following examples are alternatives, not a script to execute in full. Restore commands can overwrite data or change disk attachments.

Set up snapshot restore variables

Use the vault's subscription for $vaultSubscription. Prepare the target resource group and user-assigned managed identity with the required permissions:

$vaultSubscription = "<vault-subscription-id>"
$vaultRg = "<vault-rg>"
$vaultName = "<vault-name>"
$sourceVm = "<source-vm-name>"
$targetVm = "<target-vm-name>"
$targetInstanceName = "<target-instance-name>"
$restoreRg = "<restore-resource-group>"
$uamiId = "/subscriptions/<subscription-id>/resourceGroups/<identity-rg>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<identity-name>"
$vaultArgs = @("--subscription", $vaultSubscription, "--resource-group", $vaultRg, "--vault-name", $vaultName)

az backup vault show --subscription $vaultSubscription --resource-group $vaultRg --name $vaultName
az backup container list @vaultArgs --backup-management-type AzureWorkload `
  --query "[].{Name:name,VM:properties.friendlyName}" --output table
$containerName = "<native-source-container-name>"
$targetContainerName = "<native-target-container-name>"
$sourceArgs = $vaultArgs + @("--container-name", $containerName, "--backup-management-type", "AzureWorkload")

az backup item list @sourceArgs --workload-type SQLInstance --output json
$instanceItemName = "<native-protected-instance-item-name>"
$instanceArgs = $sourceArgs + @("--item-name", $instanceItemName, "--workload-type", "SQLInstance")

Copy exact native container and protected item names from the service output. The source and destination setup above uses the same vault. For alternate-location restores, verify that the target instance is discoverable:

az backup protectable-item show @vaultArgs --workload-type MSSQL `
  --protectable-item-type SQLInstance --name $targetInstanceName --server-name $targetVm

The long options in these examples also support the following shorter aliases:

Long option Alias
--restore-point-query-type --rp-query-type
--get-latest-recovery-point-only --latest-rp-only
--base-snapshot-recovery-point-id --base-snapshot-rp-id
--base-recovery-point-datasource-id --base-datasource-id

Query snapshot recovery points

List all requested recovery-point types for a time window, only snapshots, or only the latest snapshot:

$startDate = (Get-Date).ToUniversalTime().AddDays(-7).ToString("dd-MM-yyyy-HH:mm:ss")
$endDate = (Get-Date).ToUniversalTime().ToString("dd-MM-yyyy-HH:mm:ss")
az backup recoverypoint list @instanceArgs --start-date $startDate --end-date $endDate `
  --restore-point-query-type FullDifferentialAndSnapshot --output json
az backup recoverypoint list @instanceArgs --restore-point-query-type Snapshot --output json
az backup recoverypoint list @instanceArgs --restore-point-query-type Snapshot `
  --get-latest-recovery-point-only true --output json

Choose a recovery point explicitly; don't assume list ordering. Retrieve its full details to inspect included databases and filesystem information:

$snapshotRpName = "<selected-snapshot-recovery-point-name>"
$snapshotJson = az backup recoverypoint show @instanceArgs --name $snapshotRpName --output json
if ($LASTEXITCODE -ne 0) { throw "Could not read the selected snapshot recovery point." }
$snapshotRp = $snapshotJson | ConvertFrom-Json

$snapshotRp.properties.extendedInfo.includedDatabases | Select-Object datasourceName, datasourceId
$snapshotRp.properties.extendedInfo.snapshotRecoveryPointInfo.snapshotResourceGroup
$snapshotRp.properties.extendedInfo.snapshotRecoveryPointInfo.sourceFilesystemInfo |
  Select-Object label, fileSystemType, accessPaths, isOnStorageSpace, storageSpaceInfo, diskInfoList

Query snapshot-based log ranges

Retrieve the instance's protectedItemDataSourceId. This ID is the base data source for log queries, not the data source ID of an individual database.

$instanceJson = az backup item show @sourceArgs --name $instanceItemName `
  --workload-type SQLInstance --output json
if ($LASTEXITCODE -ne 0) { throw "Could not read the protected instance." }
$instanceItem = $instanceJson | ConvertFrom-Json
$instanceDatasourceId = $instanceItem.properties.protectedItemDataSourceId

az backup item list @sourceArgs --workload-type MSSQL --output json
$dbItemName = "<selected-native-database-item-name>"
$databaseArgs = $sourceArgs + @("--item-name", $dbItemName, "--workload-type", "MSSQL")
az backup recoverypoint show-log-chain @databaseArgs `
  --recovery-mode SnapshotAttachAndRecover `
  --base-snapshot-recovery-point-id $snapshotRpName `
  --base-recovery-point-datasource-id $instanceDatasourceId --output json

Select a database item that corresponds to one of the snapshot's included databases. Use show-log-chain for continuous recovery time ranges, rather than treating individual recovery points as log ranges.

Instance original-location restore

Generate the original-location restore (OLR) configuration.

$configFile = ".\instance-olr.json"
az backup recoveryconfig show @instanceArgs --rp-name $snapshotRpName `
  --restore-mode OriginalWorkloadRestore --recovery-mode SnapshotAttachAndRecover `
  --snapshot-rg $restoreRg --identity-arm-id $uamiId --output json |
  Set-Content -Encoding utf8 $configFile

Before submitting, inspect the volume mappings. Original-location restore can overwrite the original databases.

Instance alternate-location restore

Use the destination container, server, and SQL instance. Set --target-item-name to the required target database name or a service-supported instance naming pattern:

$configFile = ".\instance-alr.json"
az backup recoveryconfig show @instanceArgs --rp-name $snapshotRpName `
  --restore-mode AlternateWorkloadRestore --recovery-mode SnapshotAttachAndRecover `
  --target-container-name $targetContainerName --target-server-name $targetVm `
  --target-server-type SQLInstance --target-instance-name $targetInstanceName `
  --target-item-name "<target-database-name-or-service-supported-instance-pattern>" `
  --snapshot-rg $restoreRg --identity-arm-id $uamiId --output json |
  Set-Content -Encoding utf8 $configFile

Instance point-in-time restore

Use the selected instance snapshot and its base datasource ID from the earlier queries. Resolve every included database to a unique protected item, then query its log ranges above that snapshot:

$includedDatabases = @($snapshotRp.properties.extendedInfo.includedDatabases)
if ($includedDatabases.Count -eq 0) { throw "The snapshot has no included databases." }
$itemsJson = az backup item list @sourceArgs --workload-type MSSQL --output json
if ($LASTEXITCODE -ne 0) { throw "Could not list protected databases." }
$databaseItems = @($itemsJson | ConvertFrom-Json)
$logRangesPerDb = @{}
foreach ($database in $includedDatabases) {
  $matchingItems = @($databaseItems | Where-Object {
    $_.properties.protectedItemDataSourceId -eq $database.datasourceId
  })
  if ($matchingItems.Count -eq 0) {
    $matchingItems = @($databaseItems | Where-Object {
      $_.name -eq "SQLDataBase;$($database.datasourceName)" -or
      $_.properties.friendlyName -eq $database.datasourceName
    })
  }
  if ($matchingItems.Count -ne 1) {
    throw "Could not uniquely resolve included database $($database.datasourceName)."
  }
  $rangesJson = az backup recoverypoint show-log-chain @sourceArgs `
    --item-name $matchingItems[0].name --workload-type MSSQL `
    --recovery-mode SnapshotAttachAndRecover `
    --base-snapshot-recovery-point-id $snapshotRpName `
    --base-recovery-point-datasource-id $instanceDatasourceId `
    --query "[].properties.timeRanges[]" --output json
  if ($LASTEXITCODE -ne 0) { throw "Could not query snapshot-based log ranges." }
  $logRangesPerDb[$database.datasourceName] = @(
    foreach ($range in ($rangesJson | ConvertFrom-Json)) {
      if (-not $range.startTime -or -not $range.endTime) {
        throw "The service returned a log range without startTime or endTime."
      }
      [PSCustomObject]@{
        ItemName = $matchingItems[0].name
        StartTime = ([DateTimeOffset]$range.startTime).UtcDateTime
        EndTime = ([DateTimeOffset]$range.endTime).UtcDateTime
      }
    }
  )
  if ($logRangesPerDb[$database.datasourceName].Count -eq 0) {
    throw "No snapshot-based log ranges for $($database.datasourceName)."
  }
}

Inspect a database's ranges and choose a UTC time. The following example chooses the latest end time for that database, then verifies that the time is also available for every included database. If validation fails, select a time within the common available ranges and rerun the check:

$selectedDatabase = "<instance-name>;<database-name>"
if (-not $logRangesPerDb.ContainsKey($selectedDatabase)) {
  throw "Select a database from logRangesPerDb.Keys."
}
$selectedRanges = @($logRangesPerDb[$selectedDatabase] | Sort-Object EndTime)
$selectedRanges | Format-Table ItemName, StartTime, EndTime
$rangeIndex = -1
$pointInTime = $selectedRanges[$rangeIndex].EndTime
foreach ($databaseName in $logRangesPerDb.Keys) {
  $matchingRanges = @($logRangesPerDb[$databaseName] | Where-Object {
    $_.StartTime -le $pointInTime -and $_.EndTime -ge $pointInTime
  })
  if ($matchingRanges.Count -eq 0) {
    throw "The chosen restore time is unavailable for $databaseName."
  }
}
$pit = $pointInTime.ToString("dd-MM-yyyy-HH:mm:ss")

Generate the PITR configuration using the base snapshot and the validated log replay time:

$configFile = ".\instance-pit.json"
az backup recoveryconfig show @instanceArgs --rp-name $snapshotRpName --log-point-in-time $pit `
  --restore-mode AlternateWorkloadRestore --recovery-mode SnapshotAttachAndRecover `
  --target-container-name $targetContainerName --target-server-name $targetVm `
  --target-server-type SQLInstance --target-instance-name $targetInstanceName `
  --target-item-name "<target-database-name-or-service-supported-instance-pattern>" `
  --snapshot-rg $restoreRg --identity-arm-id $uamiId --no-recovery-mode `
  --output json | Set-Content -Encoding utf8 $configFile

--no-recovery-mode requests that databases remain nonrecovered for subsequent recovery steps. Review whether that behavior is appropriate for your recovery plan.

Instance restore as disks

Use SnapshotAttach for restore as disks (RAD), rather than snapshot attachment followed by database recovery:

$configFile = ".\instance-rad.json"
az backup recoveryconfig show @instanceArgs --rp-name $snapshotRpName `
  --restore-mode AlternateWorkloadRestore --recovery-mode SnapshotAttach `
  --target-container-name $targetContainerName --target-server-name $targetVm `
  --target-server-type SQLInstance --target-instance-name $targetInstanceName `
  --snapshot-rg $restoreRg --identity-arm-id $uamiId --output json |
  Set-Content -Encoding utf8 $configFile

Restore a database protected under an instance

Select the exact native database item name from the source container. For recovery-point restores, choose a database snapshot explicitly:

az backup item list @sourceArgs --workload-type MSSQL --output json
$dbItemName = "<native-protected-database-item-name>"
$databaseArgs = $sourceArgs + @("--item-name", $dbItemName, "--workload-type", "MSSQL")
az backup recoverypoint list @databaseArgs --restore-point-query-type Snapshot --output json
$dbSnapshotRpName = "<chosen-database-snapshot-recovery-point-name>"
az backup recoverypoint show @databaseArgs --name $dbSnapshotRpName --output json

Database alternate-location restore

$configFile = ".\database-alr.json"
az backup recoveryconfig show @databaseArgs --rp-name $dbSnapshotRpName `
  --restore-mode AlternateWorkloadRestore --recovery-mode SnapshotAttachAndRecover `
  --target-container-name $targetContainerName --target-server-name $targetVm `
  --target-server-type SQLInstance --target-instance-name $targetInstanceName `
  --target-item-name "<restored-db-name>" --snapshot-rg $restoreRg `
  --identity-arm-id $uamiId --no-recovery-mode --output json |
  Set-Content -Encoding utf8 $configFile

Database point-in-time restore

Query available log ranges and choose a UTC time inside a returned range. Use the dd-MM-yyyy-HH:mm:ss format:

az backup recoverypoint show-log-chain @databaseArgs --output json
$pit = "<dd-MM-yyyy-HH:mm:ss-in-UTC-within-a-returned-range>"
$configFile = ".\database-pit.json"
az backup recoveryconfig show @databaseArgs --log-point-in-time $pit `
  --restore-mode AlternateWorkloadRestore --recovery-mode SnapshotAttachAndRecover `
  --target-container-name $targetContainerName --target-server-name $targetVm `
  --target-server-type SQLInstance --target-instance-name $targetInstanceName `
  --target-item-name "<restored-db-name>" --snapshot-rg $restoreRg `
  --identity-arm-id $uamiId --no-recovery-mode --output json |
  Set-Content -Encoding utf8 $configFile

The preceding database restore examples also use --no-recovery-mode. Review that setting before submission.

Database restore as disks

$configFile = ".\database-rad.json"
az backup recoveryconfig show @databaseArgs --rp-name $dbSnapshotRpName `
  --restore-mode AlternateWorkloadRestore --recovery-mode SnapshotAttach `
  --target-container-name $targetContainerName --target-server-name $targetVm `
  --target-server-type SQLInstance --target-instance-name $targetInstanceName `
  --snapshot-rg $restoreRg --identity-arm-id $uamiId --output json |
  Set-Content -Encoding utf8 $configFile

Database restore as disks with logs as files

Set $pit to a valid UTC time from the database log ranges, as in the database PITR example. Generate the disk-attachment configuration, then set the destination for log files:

$configFile = ".\database-rad-logs.json"
az backup recoveryconfig show @databaseArgs --log-point-in-time $pit `
  --restore-mode AlternateWorkloadRestore --recovery-mode SnapshotAttach `
  --target-container-name $targetContainerName --target-server-name $targetVm `
  --target-server-type SQLInstance --target-instance-name $targetInstanceName `
  --snapshot-rg $restoreRg --identity-arm-id $uamiId --output json |
  Set-Content -Encoding utf8 $configFile

After confirming that configuration generation succeeded, edit the generated mappings:

$cfg = Get-Content -Raw $configFile | ConvertFrom-Json
$cfg.target_directory_mapping = @{ all = 'C:\temp' }
($cfg.target_physical_path | Where-Object source_path -eq 'E:\').target_path = 'M:\'
$cfg | ConvertTo-Json -Depth 100 | Set-Content -Encoding utf8 $configFile

Review and submit a snapshot restore

For each selected scenario, confirm that az backup recoveryconfig show succeeds before using its output. Inspect the generated file and volume mappings. Remap only source volumes that require a different target path. Replace the example drive letters with actual paths:

$cfg = Get-Content -Raw $configFile | ConvertFrom-Json
$cfg.target_physical_path | Format-Table source_path, target_path
($cfg.target_physical_path | Where-Object source_path -eq 'E:\').target_path = 'X:\'
$cfg | ConvertTo-Json -Depth 100 | Set-Content -Encoding utf8 $configFile

The preceding remapping is optional. Preserve the intended mappings for your selected scenario, including any database disk-and-log-file configuration.

If disk detachment is required to resolve a filesystem clash, add only explicitly approved disk IDs to the generated configuration:

$cfg = Get-Content -Raw $configFile | ConvertFrom-Json
$cfg.disks_to_detach_on_clash = @(
  "/subscriptions/<subscription-id>/resourceGroups/<disk-rg>/providers/Microsoft.Compute/disks/<disk-name>"
)
$cfg | ConvertTo-Json -Depth 100 | Set-Content -Encoding utf8 $configFile

Warning

Detaching existing disks can disrupt destination workloads. Verify every disk ID and the full recovery configuration before submitting the restore. Don't apply the optional disk-detach example unless the operation is required and approved.

Submit only the configuration file for the chosen operation:

az backup restore restore-azurewl @vaultArgs --recovery-config $configFile

Track the returned job name with az backup job show. For stop-protection and unregister commands, see Manage SQL instance snapshot backups.

Next step