Recover an Azure Blob Version

Recover an overwritten Azure Blob and verify the restored file with SHA256.

Introduction

30 Second Summary

A familiar filename can hide a serious problem when the file behind it has been overwritten with damaged data. Without a trustworthy earlier copy, a successful download can still deliver the wrong content.

In this project, you will deploy an Azure Blob Storage lab with Bicep that reproduces an overwritten report. You will restore the approved revision with blob versioning. A SHA256 hash comparison proves byte-for-byte recovery.

What You'll Build

You'll upload an approved report with AzCopy, overwrite the same blob with corrupted content, select the correct earlier version, promote it to current, and watch the recovered file produce the same hash as the original.

By the end of this project, you'll have:

  • A repeatable recovery drill that you can deploy again from infra/main.bicep to rehearse an overwrite incident safely.
  • A private transfer workflow where you can upload and download report.txt in the workfiles container using Microsoft Entra authentication.
  • A recovery evidence package that lets you show the approved baseline, corrupted overwrite, selected version, recovered file, and matching hashes.
  • Secret Mission: Add a draft revision, then identify and restore the original approved report from a three-version history.

Are there any prerequisites?

You'll need a Windows device plus an Azure subscription that can deploy resources and create role assignments. Azure applies pay-as-you-go storage and transaction charges while the lab resources exist.

The project provides the files you need. Step 1 verifies Azure CLI, Bicep, and Visual Studio Code before setting up AzCopy.

Before We Start

Before any hands-on work, lock in why a valid report.txt blob can still point to corrupted content after an overwrite. You will recover the approved report from the private workfiles container, with matching SHA256 hashes proving that the recovered download matches samples/report-good.txt.

Set Up AzCopy and Deploy the Lab

The recovery drill depends on AzCopy reaching a private container through your Microsoft Entra ID credentials. The transfer tool must work before you introduce the approved report.

Your Azure Blob Storage lab also needs version protection. It needs container-scoped data access for your signed-in user.

In this step, get ready to:
  • Verify the pinned Windows toolchain.
  • Confirm the Azure identity that receives container access.
  • Deploy the protected storage lab.
Verify the Windows toolchain

Azure CLI manages your Azure session. Bicep turns the supplied infrastructure files into Azure resources.

This lab targets Azure CLI 2.92.0 plus Bicep CLI 0.48.1. Check both from PowerShell.

  • Check the installed Azure CLI plus Bicep CLI versions by running these commands:
az --version
az bicep version

What Do These Commands Check?

  • The first command displays the installed Azure CLI version.
  • The second command displays the Bicep CLI version managed through Azure CLI.

✔️ I see Azure CLI 2.92.0 and Bicep CLI 0.48.1

Both deployment tools match the project targets. Continue to the AzCopy check.

ⓧ I see an older version

The installed toolchain needs an update before it processes the supplied parameter file.

  • Upgrade Azure CLI plus its managed Bicep CLI by running these commands:
az upgrade
az bicep upgrade

What Do These Commands Do?

  • The first command updates Azure CLI on Windows.
  • The second command updates the Bicep CLI managed through Azure CLI.
  • Rerun the two version checks shown above.
  • Confirm that Azure CLI reports 2.92.0.
  • Confirm that Bicep CLI reports 0.48.1.

ⓧ A command is unavailable

Azure CLI must be installed before its managed Bicep CLI can run.

  • Visit the official Azure CLI installation page.
  • Install Azure CLI for Windows.
  • Start a fresh PowerShell session.
  • Rerun the two version checks shown above.
  • Run the upgrade commands from the older-version tab if either tool is behind its target.

AzCopy v10.34.1 handles every local upload plus download in the recovery drill. Its version output identifies the executable that PowerShell finds on your PATH.

  • Check the AzCopy version plus command help by running these commands:
azcopy --version
azcopy -h

What Do These Commands Check?

  • The version command identifies the available AzCopy release.
  • The help command prints the supported AzCopy commands.

✔️ I see AzCopy 10.34.1

PowerShell can already find the target AzCopy release on PATH. Save its full executable location for the remaining steps.

  • Store the detected executable in $AzCopy by running these commands:
$AzCopy = (Get-Command azcopy).Source
& $AzCopy --version
& $AzCopy -h

What Do These Commands Do?

  • The first command stores the detected executable path in $AzCopy.
  • The second command confirms that the saved path runs AzCopy 10.34.1.
  • The final command prints help through the saved path.

ⓧ I see an older version

Replace the older executable with the Windows 64-bit release used by this project.

  • Visit the AzCopy v10.34.1 release page.
  • Download azcopy_windows_amd64_10.34.1.zip.
  • Extract azcopy.exe directly into C:\Tools\AzCopy.
  • Store the extracted executable path by running these commands:
$AzCopy = 'C:\Tools\AzCopy\azcopy.exe'
& $AzCopy --version
& $AzCopy -h

What Do These Commands Do?

  • The first command stores the extracted executable path in $AzCopy.
  • The second command confirms the extracted AzCopy version.
  • The final command prints help from the extracted executable.

PowerShell should report AzCopy 10.34.1. The help output should follow.

ⓧ Command not found

Finding the executable after ZIP extraction can be fiddly. A fixed folder gives every later command one predictable path.

  • Visit the AzCopy v10.34.1 release page.
  • Download azcopy_windows_amd64_10.34.1.zip.
  • Extract azcopy.exe directly into C:\Tools\AzCopy.
  • Store the extracted executable path by running these commands:
$AzCopy = 'C:\Tools\AzCopy\azcopy.exe'
& $AzCopy --version
& $AzCopy -h

What Do These Commands Do?

  • The first command stores the portable executable path in $AzCopy.
  • The second command confirms the extracted AzCopy version.
  • The final command proves that PowerShell can invoke the executable.

PowerShell should report AzCopy 10.34.1. The help output should follow.

A Tool Is Still Unavailable?

Start a fresh PowerShell session after installing or upgrading Azure CLI. A new session reloads executable locations.

Confirm that azcopy.exe sits directly inside C:\Tools\AzCopy when you use the portable download.

Ask for targeted help with diagnosing the Windows toolchain.

Confirm the identity and create the resource group

The deployment uses your active Azure subscription for resource management. Your signed-in user object ID becomes the principal in a container-scoped Azure RBAC assignment.

Azure CLI starts an interactive browser sign-in. Use the personal account that owns the intended subscription.

  • Start the Azure CLI sign-in flow by running this command:
az login

What Does This Command Do?

The command authenticates Azure CLI through an interactive browser flow. The resulting session can manage resources available to your account.

  • Complete the Microsoft sign-in prompt in your browser.
  • Return to PowerShell.
  • Display the active subscription by running this command:
az account show

What Does This Command Show?

The command displays the subscription currently selected by Azure CLI. This subscription receives the recovery lab.

  • Confirm that the output names your intended personal subscription.
  • Retrieve the signed-in user record by running this command:
az ad signed-in-user show

Why Retrieve the User Record?

The returned id identifies your Microsoft Entra user object. Bicep uses this value for the container-scoped role assignment.

  • Copy the returned id into <your-user-object-id>.
  • Set the Bicep parameter environment variable by running this command:
$env:AZURE_PRINCIPAL_ID = '[[AZURE_PRINCIPAL_ID="<your-user-object-id>"]]'

What Does This Variable Control?

The environment variable supplies your user object ID to infra/main.bicepparam. The deployment assigns the container data role to that object.

  • Create rg-blob-recovery in eastus by running this command:
az group create --location eastus --name rg-blob-recovery

What Does This Command Create?

The command creates rg-blob-recovery as the lab boundary in eastus.

The deployment places the storage resources inside this boundary. Cleanup can remove the complete lab through the resource group.

That is the first Azure resource in place. The lab now has one boundary for deployment plus cleanup.

Azure Sign-In or Group Creation Failed?

Confirm that your active subscription is the intended personal subscription. Your account needs permission to create resources plus role assignments.

Ask for help with diagnosing Azure CLI authentication or permissions.

Deploy and authenticate

The supplied Bicep files define the storage account protection controls. They also define the private workfiles container plus its container-scoped data role.

  • Return to the supplied project in Visual Studio Code.
  • Confirm that the Explorer sidebar contains infra/main.bicep.
  • Confirm that the Explorer sidebar contains infra/main.bicepparam.
  • Save both files.

✔️ Awesome, I've got everything!

Both infrastructure files are saved under infra. Continue to the deployment warning.

ⓧ I'd like to double check the full code

Compare infra/main.bicep with this complete reference file.

targetScope = 'resourceGroup'

@description('Azure region for the storage account.')
param location string = resourceGroup().location

@description('Microsoft Entra object ID of the user who will perform the recovery drill.')
param principalId string

@description('Globally unique storage account name.')
param storageAccountName string = 'st${uniqueString(subscription().id, resourceGroup().id)}'

var storageBlobDataContributorRoleDefinitionId = subscriptionResourceId(
  'Microsoft.Authorization/roleDefinitions'
  'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
)

resource storageAccount 'Microsoft.Storage/storageAccounts@2026-09-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    accessTier: 'Hot'
    allowBlobPublicAccess: false
    allowSharedKeyAccess: false
    defaultToOAuthAuthentication: true
    isHnsEnabled: false
    minimumTlsVersion: 'TLS1_2'
    publicNetworkAccess: 'Enabled'
    supportsHttpsTrafficOnly: true
  }
}

resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2026-09-01' = {
  name: 'default'
  parent: storageAccount
  properties: {
    deleteRetentionPolicy: {
      enabled: true
      days: 7
    }
    isVersioningEnabled: true
  }
}

resource workfilesContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2026-09-01' = {
  name: 'workfiles'
  parent: blobService
  properties: {
    publicAccess: 'None'
  }
}

resource previousVersionLifecycle 'Microsoft.Storage/storageAccounts/managementPolicies@2026-09-01' = {
  name: 'default'
  parent: storageAccount
  properties: {
    policy: {
      rules: [
        {
          enabled: true
          name: 'delete-previous-versions-after-7-days'
          type: 'Lifecycle'
          definition: {
            actions: {
              version: {
                delete: {
                  daysAfterCreationGreaterThan: 7
                }
              }
            }
            filters: {
              blobTypes: [
                'blockBlob'
              ]
              prefixMatch: [
                'workfiles/'
              ]
            }
          }
        }
      ]
    }
  }
}

resource blobDataContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(workfilesContainer.id, principalId, storageBlobDataContributorRoleDefinitionId)
  scope: workfilesContainer
  properties: {
    principalId: principalId
    principalType: 'User'
    roleDefinitionId: storageBlobDataContributorRoleDefinitionId
  }
}

output storageAccountName string = storageAccount.name
output containerUrl string = '${storageAccount.properties.primaryEndpoints.blob}workfiles'

What Does This File Define?

  • The storage account uses the Standard_LRS SKU with the Hot access tier.
  • The account requires HTTPS traffic with a TLS1_2 minimum.
  • The account disables public blob access plus Shared Key authorization.
  • The blob service enables Blob versioning plus seven-day blob soft delete.
  • The lifecycle management rule targets previous block blob versions under workfiles/ after seven days.
  • The role assignment scopes Storage Blob Data Contributor access to the private container.

Compare infra/main.bicepparam with this complete reference file.

using './main.bicep'

param principalId = readEnvironmentVariable('AZURE_PRINCIPAL_ID')

What Does This Parameter File Do?

The parameter file reads AZURE_PRINCIPAL_ID from the current PowerShell environment. It passes that value to the principalId parameter.

  • Correct any differences in infra/main.bicep.
  • Correct any differences in infra/main.bicepparam.
  • Save both files.

Before You Deploy

This deployment creates Blob Storage resources that can incur storage plus transaction charges. The cleanup section removes the resource group when you finish.

  • Deploy the protected storage lab from the folder containing infra by running this command:
az deployment group create --name blob-recovery --resource-group rg-blob-recovery --parameters .\infra\main.bicepparam

What Does This Deployment Create?

  • The deployment creates a StorageV2 account with the Standard_LRS SKU.
  • The account uses the Hot access tier.
  • The private workfiles container receives a container-scoped Storage Blob Data Contributor assignment.
  • Versioning preserves previous revisions after an overwrite.
  • Soft delete retains deleted blobs for seven days.
  • The lifecycle rule targets previous versions more than seven days old.

The deployment may take a few minutes while Azure creates the resources. A successful result identifies the blob-recovery deployment.

The Deployment Did Not Succeed?

Confirm that AZURE_PRINCIPAL_ID is set in the current PowerShell session. A fresh session does not inherit a value from an earlier session.

Confirm that your Azure account can create role assignments. The deployment needs that permission for the container-scoped data role.

Ask for targeted help with diagnosing the Bicep deployment.

  • Display the completed deployment plus its outputs by running this command:
az deployment group show --name blob-recovery --resource-group rg-blob-recovery

Which Outputs Matter?

The storageAccountName output identifies the deployed account. The containerUrl output identifies the private container endpoint.

  • Copy the storageAccountName output into <storageAccountName-output>.
  • Copy the containerUrl output into <containerUrl-output>.
  • Store the deployment outputs in PowerShell by running these commands:
$storageAccount = '[[STORAGE_ACCOUNT_NAME="<storageAccountName-output>"]]'
$containerUrl = '[[CONTAINER_URL="<containerUrl-output>"]]'

Why Save These Outputs?

  • The $storageAccount variable identifies the deployed storage account.
  • The $containerUrl variable identifies the private workfiles container.

AzCopy uses its own Microsoft Entra session for Blob data requests. The login command starts an interactive browser flow.

  • Start the AzCopy sign-in flow by running these commands:
& $AzCopy login
& $AzCopy login status

What Do These Commands Confirm?

  • The first command authenticates AzCopy with your Microsoft Entra identity.
  • The second command reports the current AzCopy authentication status.
  • Complete the Microsoft sign-in prompt in your browser.
  • Return to PowerShell.
  • Confirm that the status shows an authenticated session.

Role assignments can take up to ten minutes to propagate. A temporary authorization denial can clear after Azure finishes applying the container-scoped role.

Before you run the final check, do you expect the empty private container to accept your Microsoft Entra identity?

  • Verify data access to the empty container by running this command:
& $AzCopy list $containerUrl

What Does This Check Prove?

The command sends a Blob data request to the private container. Success proves that AzCopy can use your container-scoped data role without Shared Key authorization.

The command should complete without an authorization error. You should see no blob names because workfiles is empty.

AzCopy Cannot List the Container?

Wait for the role assignment to propagate before rerunning the same list command. Azure RBAC changes can take up to ten minutes.

Confirm that $containerUrl contains the complete deployment output. Confirm that the AzCopy login status shows your intended identity.

Ask for targeted help with diagnosing AzCopy container access.

The setup loop is complete. Your private versioned container is ready for the approved baseline report.

Upload and Prove the Good Baseline

Your protected Azure Blob Storage lab is ready to hold the report. This step establishes the approved content before any overwrite occurs.

A visible SHA256 hash defines exactly what good means. AzCopy then proves that the approved bytes survive an upload and download through the private container.

In this step, get ready to:
  • Create the approved report plus the supporting incident files.
  • Record the approved report's SHA256 hash.
  • Prove the approved report survives a Blob Storage round trip unchanged.
Create and hash the evidence files

The approved report becomes your integrity reference for the entire recovery drill. Its content and hash give you two independent ways to identify the correct revision.

  • Switch back to Visual Studio Code from the deployment.
  • Create samples/report-good.txt beside the existing infra folder.
  • Add the approved report by copying this content into samples/report-good.txt:
Quarterly status: APPROVED
Amount: 1250
Owner: Operations

What Does This File Establish?

The status, amount, and owner form the known-good business record. Every later recovery check traces back to these exact bytes.

  • Save samples/report-good.txt.
  • Return to PowerShell in the lab directory.
  • Record the approved file's SHA256 hash by running these commands:
$goodHash = (Get-FileHash .\samples\report-good.txt -Algorithm SHA256).Hash
$goodHash

What Do These Commands Do?

  • Get-FileHash reads the approved file and calculates its SHA256 digest.
  • $goodHash keeps that digest available for later integrity checks.
  • The second line prints the recorded digest so you can see the baseline value.

PowerShell prints a long hexadecimal hash. That value now represents the exact approved file on your device.

Cannot Calculate the Hash?

Confirm that your PowerShell session is in the lab directory containing samples. Check that samples/report-good.txt is saved with the exact filename.

Ask for targeted help with calculating the approved report hash.

The corrupted sample provides a controlled incident input. Keeping it separate from the approved file prevents the source evidence from changing during the drill.

  • Create samples/report-bad.txt beside samples/report-good.txt.
  • Add the corrupted revision by copying this content into samples/report-bad.txt:
Quarterly status: CORRUPTED
Amount: 999999
Owner: Unknown

What Does This File Represent?

This revision changes every business field in a visible way. Those differences make the later overwrite incident easy to prove.

  • Save samples/report-bad.txt.

The editor should show CORRUPTED, 999999, and Unknown in the saved file.

Does the Corrupted Sample Look Different?

Compare each line with the snippet above. A copied value from the approved file would make the incident evidence ambiguous.

Ask for help with checking the two sample files.

An incident record connects the technical recovery to the business event. You will fill its placeholders with the selected version and integrity results after recovery.

  • Create evidence/recovery-notes.md beside the samples folder.
  • Add the incident record by copying this content into evidence/recovery-notes.md:
# Blob Recovery Evidence

Storage account: replace-with-storage-account-name
Container: workfiles
Blob: report.txt

Incident: The approved report was overwritten by a corrupted revision.
Selected good version ID: replace-with-version-id
Original SHA256: replace-with-original-hash
Recovered SHA256: replace-with-recovered-hash
Hash comparison result: replace-with-True-or-False

Recovery method: Promote the selected previous version over the base blob with Azure CLI Copy Blob, then download the new current blob with AzCopy.
Lifecycle observation: The policy is configured to target previous versions older than seven days. Execution was not awaited during this lab.

What Will This Record Capture?

The note ties the storage account and blob to the selected recovery point. It also records both hashes plus the final comparison result.

The lifecycle observation distinguishes configured protection from asynchronous policy execution. Your evidence stays accurate without claiming that the policy already ran.

  • Save evidence/recovery-notes.md.

The file sidebar should now show the samples folder plus the evidence folder. The recovery note should contain every placeholder shown above.

Missing a Sample or Evidence File?

Confirm that samples and evidence sit beside infra. Check each filename for an extra extension added by Windows.

Ask for help with checking the lab file layout.

Upload the approved baseline

Uploading the approved file creates the base report.txt blob. Blob versioning records this write as the initial current version.

  • Return to the PowerShell session that retains $AzCopy and $containerUrl.
  • Upload the approved report to the private container by running this command:
& $AzCopy copy '.\samples\report-good.txt' "$containerUrl/report.txt"

What Does This Command Do?

  • $AzCopy runs the verified transfer executable from your existing PowerShell session.
  • copy transfers the approved local file to the private container through your signed-in identity.
  • $containerUrl/report.txt gives the uploaded blob its stable recovery-drill name.

The AzCopy job summary should report a completed transfer. The private container now has an initial current report.txt version.

Did the Upload Fail?

Confirm that you returned to the PowerShell session containing $AzCopy and $containerUrl. Retry after signing back into AzCopy if the session no longer has your Microsoft Entra credentials.

Check that samples/report-good.txt is saved before rerunning the transfer.

Ask for targeted help with diagnosing the approved report upload.

Download and verify the current blob

A completed upload proves that bytes reached Blob Storage. Downloading the current blob lets you verify that those bytes still represent the approved report.

  • Download the current report.txt blob into the evidence folder by running this command:
& $AzCopy copy "$containerUrl/report.txt" '.\evidence\report-baseline.txt'

What Does This Command Do?

AzCopy reads the current blob through your Microsoft Entra identity. It saves the downloaded bytes as evidence/report-baseline.txt so the cloud copy can be checked independently.

The job summary should report another completed transfer. Your evidence folder now contains the downloaded current blob.

Was the Baseline Downloaded?

Confirm that evidence exists beside samples. Check that the source ends with report.txt and the destination ends with report-baseline.txt.

Ask for help with diagnosing the baseline download.

Before you inspect the download, do you expect every business field to match the approved local source?

  • Print the downloaded baseline by running this command:
Get-Content .\evidence\report-baseline.txt

What Does This Command Check?

Get-Content prints the downloaded file directly in PowerShell. This exposes the current blob's business values before the hash check.

You should see Quarterly status: APPROVED, Amount: 1250, and Owner: Operations.

  • Compare the downloaded baseline's SHA256 hash with $goodHash by running this command:
$goodHash -eq (Get-FileHash .\evidence\report-baseline.txt -Algorithm SHA256).Hash

What Does This Comparison Prove?

PowerShell calculates a SHA256 hash from the downloaded evidence file. The equality check compares that value with the original hash stored in $goodHash.

Matching hashes prove that the local source and downloaded current blob contain the same bytes.

PowerShell should return True. You now have a visible approved baseline plus byte-for-byte proof that its Blob Storage copy is intact.

Did PowerShell Return False?

Confirm that the upload source was samples/report-good.txt. Check that the hash comparison targets evidence/report-baseline.txt.

Recalculate $goodHash from the saved approved file if that file changed after the first hash calculation.

Ask for targeted help with diagnosing the hash mismatch.

✔️ Awesome, I've got everything!

Your source files and evidence note are saved. The downloaded baseline matches the approved report.

ⓧ I'd like to double check the full code

targetScope = 'resourceGroup'

@description('Azure region for the storage account.')
param location string = resourceGroup().location

@description('Microsoft Entra object ID of the user who will perform the recovery drill.')
param principalId string

@description('Globally unique storage account name.')
param storageAccountName string = 'st${uniqueString(subscription().id, resourceGroup().id)}'

var storageBlobDataContributorRoleDefinitionId = subscriptionResourceId(
  'Microsoft.Authorization/roleDefinitions'
  'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
)

resource storageAccount 'Microsoft.Storage/storageAccounts@2026-09-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    accessTier: 'Hot'
    allowBlobPublicAccess: false
    allowSharedKeyAccess: false
    defaultToOAuthAuthentication: true
    isHnsEnabled: false
    minimumTlsVersion: 'TLS1_2'
    publicNetworkAccess: 'Enabled'
    supportsHttpsTrafficOnly: true
  }
}

resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2026-09-01' = {
  name: 'default'
  parent: storageAccount
  properties: {
    deleteRetentionPolicy: {
      enabled: true
      days: 7
    }
    isVersioningEnabled: true
  }
}

resource workfilesContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2026-09-01' = {
  name: 'workfiles'
  parent: blobService
  properties: {
    publicAccess: 'None'
  }
}

resource previousVersionLifecycle 'Microsoft.Storage/storageAccounts/managementPolicies@2026-09-01' = {
  name: 'default'
  parent: storageAccount
  properties: {
    policy: {
      rules: [
        {
          enabled: true
          name: 'delete-previous-versions-after-7-days'
          type: 'Lifecycle'
          definition: {
            actions: {
              version: {
                delete: {
                  daysAfterCreationGreaterThan: 7
                }
              }
            }
            filters: {
              blobTypes: [
                'blockBlob'
              ]
              prefixMatch: [
                'workfiles/'
              ]
            }
          }
        }
      ]
    }
  }
}

resource blobDataContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(workfilesContainer.id, principalId, storageBlobDataContributorRoleDefinitionId)
  scope: workfilesContainer
  properties: {
    principalId: principalId
    principalType: 'User'
    roleDefinitionId: storageBlobDataContributorRoleDefinitionId
  }
}

output storageAccountName string = storageAccount.name
output containerUrl string = '${storageAccount.properties.primaryEndpoints.blob}workfiles'
using './main.bicep'

param principalId = readEnvironmentVariable('AZURE_PRINCIPAL_ID')
Quarterly status: APPROVED
Amount: 1250
Owner: Operations
Quarterly status: CORRUPTED
Amount: 999999
Owner: Unknown
# Blob Recovery Evidence

Storage account: replace-with-storage-account-name
Container: workfiles
Blob: report.txt

Incident: The approved report was overwritten by a corrupted revision.
Selected good version ID: replace-with-version-id
Original SHA256: replace-with-original-hash
Recovered SHA256: replace-with-recovered-hash
Hash comparison result: replace-with-True-or-False

Recovery method: Promote the selected previous version over the base blob with Azure CLI Copy Blob, then download the new current blob with AzCopy.
Lifecycle observation: The policy is configured to target previous versions older than seven days. Execution was not awaited during this lab.
Quarterly status: APPROVED
Amount: 1250
Owner: Operations

Your known-good recovery point is proven. Next, you will overwrite the same blob name and witness how a successful transfer can leave the current business data corrupted.

Cause and Witness the Overwrite

Your approved report is now the current blob in Azure Blob Storage. Its downloaded baseline matches the original hash.

AzCopy can complete a transfer even when the replacement content is wrong. This step turns that gap into a visible overwrite incident.

In this step, get ready to:
  • Overwrite the current report.txt blob with the corrupted sample.
  • Download the current blob to expose its corrupted content.
  • Inspect the version history to locate the approved baseline version.
Overwrite the current blob

A blob write targets a destination URL. Reusing the existing URL updates the base blob associated with that name.

Before you run this, do you expect AzCopy to reject the replacement because its business content is wrong?

  • Target the existing report.txt destination with the corrupted sample by running this command:
& $AzCopy copy '.\samples\report-bad.txt' "$containerUrl/report.txt"

What Does This Transfer Prove?

The command sends samples/report-bad.txt to the same report.txt destination used by the approved baseline.

AzCopy verifies that the transfer completes. It cannot judge whether the report contains valid business data.

AzCopy should report a completed transfer. That result proves delivery alone.

Transfer Did Not Complete?

Confirm that $AzCopy still points to the AzCopy executable. Check that $containerUrl still contains the deployed container URL.

Ask for targeted help with diagnosing the failed overwrite transfer.

Prove the current content is wrong

Downloading the base blob reveals what the current report.txt name serves after the successful transfer. The evidence copy preserves that result for the incident record.

  • Download the current blob into evidence/report-overwritten.txt by running this command:
& $AzCopy copy "$containerUrl/report.txt" '.\evidence\report-overwritten.txt'

What Does This Download Capture?

The command reads the base report.txt blob without selecting a version ID. It saves the current bytes as evidence/report-overwritten.txt.

AzCopy should report another completed transfer. The new evidence file now captures the current blob state.

Before you print the download, which status do you think the current blob now carries?

  • Reveal the current report content by running this command:
Get-Content .\evidence\report-overwritten.txt

What Does This Check Reveal?

The command prints the downloaded evidence file. Its lines show the business content currently stored behind the base blob name.

You should see Quarterly status: CORRUPTED followed by Amount: 999999.

  • Compare the printed status with Quarterly status: APPROVED from the baseline.
  • Compare the printed amount with Amount: 1250 from the baseline.

You have reproduced the incident: a successful transfer left the current business data corrupted.

Downloaded Content Looks Unexpected?

Confirm that the source upload used samples/report-bad.txt. Check that the download destination is evidence/report-overwritten.txt.

Ask for help with checking why the downloaded blob does not show the corrupted report.

Inspect the version history

Blob versioning records each write as a distinct revision. The version table now shows whether the approved baseline survived after it stopped being current.

Before you list the history, how many revisions do you expect the single blob name to show?

  • List every version of report.txt through your signed-in Azure identity by running this command:
az storage blob list --account-name $storageAccount --container-name workfiles --prefix report.txt --include v --auth-mode login --query "[].{Name:name,VersionId:versionId,Current:isCurrentVersion}" --output table

What Does This Version Query Show?

The --include v option includes blob versions. The --auth-mode login option uses your signed-in Azure identity.

The query reduces the table to the blob name, version ID, and current-version state. This creates a compact incident timeline.

You should see multiple rows for report.txt. One row represents the corrupted current blob. A noncurrent row represents the approved baseline.

  • Identify the noncurrent row as the approved baseline recovery candidate.
  • Leave every current and previous version in place.

Version History Is Missing?

Confirm that the command includes --include v. Check that $storageAccount still contains the deployed storage account name.

Ask for targeted help with finding the missing blob versions.

The overwrite is confirmed. Next, you will promote the approved previous version while preserving the incident history.

Promote the Good Version

The corrupted report.txt is now the current blob. Blob versioning preserved the approved baseline as a recovery point.

The supported Copy Blob pattern promotes that previous version over the current blob. This creates a fresh current version while preserving the incident history.

In this step, get ready to:
  • Select the approved noncurrent version as the recovery source.
  • Promote the selected version over workfiles/report.txt.
  • Prove the recovered current blob matches the approved baseline.
Select the recovery point

A version ID identifies one immutable revision of the blob. Your existing PowerShell variables let you turn the approved version ID into a precise recovery source.

  • Return to the PowerShell session from the previous step.
  • Display the version history again by running this command:
az storage blob list --account-name $storageAccount --container-name workfiles --prefix report.txt --include v --auth-mode login --query "[].{Name:name,VersionId:versionId,Current:isCurrentVersion}" --output table

What Does This Command Do?

  • The version flag includes the previous revisions of report.txt.
  • Microsoft Entra credentials authorize the request through your signed-in identity.
  • The query reduces the output to the blob name, version ID, and current-version status.

You should see the corrupted revision marked as current. The approved baseline should appear as a noncurrent revision with its own VersionId.

  • Copy the VersionId from the approved noncurrent revision.

The next command pauses for the copied value. PowerShell stores it in $goodVersionId before building the version-specific source URI.

  • Capture the approved version ID as the recovery source by running these commands:
$goodVersionId = Read-Host 'Paste the known-good VersionId'
$sourceUri = "$containerUrl/report.txt?versionId=$goodVersionId"

How Is the Recovery Source Built?

  • Read-Host stores the pasted version ID in $goodVersionId.
  • The source URI combines the container URL, blob name, and selected version ID.
  • The resulting $sourceUri points to the approved immutable revision.

Cannot Identify the Approved Version?

Use the current-version status to exclude the corrupted current revision. The approved recovery point is the noncurrent version created before the overwrite.

Ask for help with selecting the correct recovery version.

Promote the selected version

Promoting over the current blob can feel risky. Blob versioning preserves the existing revisions during this recovery copy.

  • Promote the selected previous version by running this command:
az storage blob copy start --account-name $storageAccount --destination-blob report.txt --destination-container workfiles --source-uri $sourceUri --auth-mode login --requires-sync true

What Does This Command Do?

  • Azure CLI copies the version-specific source over the base report.txt blob.
  • The destination remains inside the private workfiles container.
  • Synchronous completion keeps PowerShell waiting until the recovery copy finishes.
  • Login authentication uses your existing container-scoped data access.

The copy result should report a completed operation. The approved content is now the base blob's latest revision.

Before you rerun the version table, how many revisions do you expect to remain visible?

  • Inspect the post-recovery history by running this command:
az storage blob list --account-name $storageAccount --container-name workfiles --prefix report.txt --include v --auth-mode login --query "[].{Name:name,VersionId:versionId,Current:isCurrentVersion}" --output table

What Does the New History Prove?

  • The new recovery copy appears as the current version.
  • The original approved baseline remains in the history.
  • The corrupted overwrite remains available as incident evidence.

You should see a new current version alongside the earlier approved and corrupted revisions. Recovery has preserved the full sequence of events.

Recovery Copy Did Not Complete?

Confirm that $sourceUri contains the approved VersionId. Check that $storageAccount still contains the deployed storage account name.

Role assignments can take time to propagate. Retry the copy after confirming that your Azure CLI session still uses the intended subscription.

Ask for targeted help with diagnosing the version promotion.

Download and prove recovery

A successful copy operation proves that Azure accepted the request. A fresh AzCopy download lets you inspect the current blob that clients receive.

  • Download the recovered current blob by running this command:
& $AzCopy copy "$containerUrl/report.txt" '.\evidence\report-recovered.txt'

What Does This Command Do?

  • AzCopy reads the base report.txt blob without specifying a version ID.
  • The downloaded bytes therefore come from the new current version.
  • The destination creates evidence/report-recovered.txt for the recovery proof package.

The AzCopy job summary should report a completed transfer. The recovered file now exists beside the baseline and overwritten evidence files.

  • Print the recovered file by running this command:
Get-Content .\evidence\report-recovered.txt

What Does This Command Show?

  • PowerShell reads the downloaded evidence file.
  • The terminal displays the content stored in the recovered current blob.

You should see Quarterly status: APPROVED, Amount: 1250, and Owner: Operations.

The content looks correct. A SHA256 comparison now tests whether every byte matches the original file.

Before you run the final check, do you expect PowerShell to return True or False?

  • Compare the original and recovered hashes by running this command:
(Get-FileHash .\samples\report-good.txt -Algorithm SHA256).Hash -eq (Get-FileHash .\evidence\report-recovered.txt -Algorithm SHA256).Hash

How Does This Prove Recovery?

  • The first hash represents the original approved sample.
  • The second hash represents the fresh download of the recovered current blob.
  • PowerShell compares the two hash strings for exact equality.

PowerShell should return True. That is the recovery proved. Your current blob is byte-for-byte identical to the approved source.

Hash Comparison Returned False?

Confirm that evidence/report-recovered.txt was downloaded after the recovery copy completed. Check that the selected version contains the approved amount and owner.

Repeat the promotion with the approved version ID if the recovered file still contains corrupted content.

Ask for help with diagnosing the hash mismatch.

Your approved report is current again while every incident revision remains available. Next, you will turn these recovery results into a compact evidence package.

Capture the Recovery Evidence

Your recovered current blob now matches the approved report. The earlier approved revision remains available beside the corrupted revision in Azure Blob Storage.

This step captures the post-recovery history. It records the selected version. It proves file integrity with matching hashes.

In this step, get ready to:
  • Save the post-recovery version history as a JSON evidence file.
  • Complete the incident record with the selected version plus matching SHA256 hashes.
  • Review the complete evidence folder in Visual Studio Code.
Save the post-recovery history

The Azure CLI can return the version table as structured data. Saving that output preserves the current flag plus every retained version ID.

  • Write the current version listing to evidence/versions-after-recovery.json by running this command in PowerShell:
az storage blob list --account-name $storageAccount --container-name workfiles --prefix report.txt --include v --auth-mode login --query "[].{Name:name,VersionId:versionId,Current:isCurrentVersion}" --output json | Set-Content .\evidence\versions-after-recovery.json

What Does This Command Capture?

  • The version listing targets report.txt inside the private workfiles container.
  • The --include v option includes current plus noncurrent versions.
  • The --auth-mode login option uses your signed-in Microsoft Entra identity.
  • The --output json option produces structured version data.
  • The Set-Content .\evidence\versions-after-recovery.json command saves the output in your evidence folder.
  • Confirm that PowerShell returns to the prompt without reporting an error.

Good progress. Your evidence folder now has a point-in-time record of the recovery history.

Version History Was Not Saved?

Confirm that your current PowerShell folder contains the existing evidence folder. Check that $storageAccount still prints the deployed storage account name.

Ask for help with diagnosing the version-history command.

Complete the incident record

The incident record connects the chosen recovery point to the integrity result. Its existing lifecycle management observation also records that policy execution was configured without waiting for asynchronous processing.

  • Print the four learner-specific values for the incident record by running these commands:
$storageAccount
$goodVersionId
$goodHash
(Get-FileHash .\evidence\report-recovered.txt -Algorithm SHA256).Hash

Which Value Goes Where?

  • The first output belongs on the Storage account line.
  • The second output belongs on the Selected good version ID line.
  • The third output belongs on the Original SHA256 line.
  • The fourth output belongs on the Recovered SHA256 line.
  • The Hash comparison result line records True because the two hashes match.

A Recovery Value Is Missing?

Keep using the same PowerShell session from the recovery step. That session retains $storageAccount plus $goodVersionId plus $goodHash.

Confirm that evidence/report-recovered.txt still contains the approved report before recalculating its hash.

Ask for help with recovering the missing evidence value.

  • Press the Windows key to open Windows search.
  • Type Visual Studio Code into Windows search.
  • Press Enter to open Visual Studio Code.
  • Select File from the menu.
  • Select Open Folder.
  • Select the existing evidence folder from the folder that also contains infra plus samples.
  • Select Select Folder.
  • Select Yes, I trust the authors if the Workspace Trust dialog appears.

What Belongs in the Record?

The record names the storage account plus the immutable version selected for recovery. The two hash lines provide the integrity proof.

The lifecycle observation explains why this lab verifies policy configuration without waiting for policy execution. That distinction keeps the evidence accurate.

  • Select recovery-notes.md in the Explorer sidebar.
  • Replace every replace-with- placeholder with the corresponding value from PowerShell.
  • Press Ctrl+S to save recovery-notes.md.

✔️ Awesome, I've got everything!

Your incident record now contains the deployed storage account name. It also contains the selected version plus both hashes plus the successful comparison result.

ⓧ I'd like to double check the full code

Compare your saved recovery-notes.md with this structure. Each learner-specific description below represents the actual value printed in your PowerShell session.

# Blob Recovery Evidence

Storage account: actual value of $storageAccount
Container: workfiles
Blob: report.txt

Incident: The approved report was overwritten by a corrupted revision.
Selected good version ID: actual selected value of $goodVersionId
Original SHA256: actual value of $goodHash
Recovered SHA256: actual SHA256 hash of evidence/report-recovered.txt
Hash comparison result: True

Recovery method: Promote the selected previous version over the base blob with Azure CLI Copy Blob, then download the new current blob with AzCopy.
Lifecycle observation: The policy is configured to target previous versions older than seven days. Execution was not awaited during this lab.

How Should the Dynamic Lines Look?

The storage account plus version ID lines hold values from your Azure session. The original plus recovered hash lines should contain the same SHA256 value.

The comparison line must contain True.

Review the proof package

The final review checks the incident as a sequence. The baseline shows the approved state. The overwritten file shows the damage. The recovered file plus version history show the repair.

  • Select report-baseline.txt in the Explorer sidebar.

You should see Quarterly status: APPROVED plus Amount: 1250.

  • Select report-overwritten.txt in the Explorer sidebar.

You should see Quarterly status: CORRUPTED plus Amount: 999999.

  • Select report-recovered.txt in the Explorer sidebar.

You should see the approved status plus the original amount. This confirms that the current download contains the recovered business data.

  • Select versions-after-recovery.json in the Explorer sidebar.

You should see the current recovered revision plus the retained approved revision plus the retained corrupted revision. The version IDs preserve the incident timeline.

Before you run the final check, do you expect any replace-with- text to remain?

  • Switch back to the PowerShell session from earlier.
  • Print the completed incident record by running this command:
Get-Content .\evidence\recovery-notes.md

What Does This Final Check Prove?

The command prints the saved incident record from disk. This confirms that your edits were written to evidence/recovery-notes.md.

You should see the actual storage account plus the selected version ID. The original plus recovered SHA256 lines should match.

The comparison result should show True. Every replace-with- placeholder should be gone.

That closes the loop. Your evidence package now shows what changed plus which recovery point you chose plus how you proved integrity.

A Placeholder Still Appears?

Return to recovery-notes.md in Visual Studio Code. Replace the remaining placeholder with its corresponding PowerShell value.

Save the file before rerunning the final check.

Ask for help with checking the completed recovery record.

Secret mission

Recover the Right Revision from a Three-Version History

Add a DRAFT revision to create a more crowded version history. Identify the original approved revision through content inspection and incident evidence before restoring it without deleting any intervening revisions.

Clean Up Your Resources

Clean Up Your Resources

Your recovery lab keeps data in Azure Blob Storage. Stored data plus storage transactions can incur pay-as-you-go charges. The options below let you preserve the lab, stop transfer activity, or remove its billable cloud resources.

Cost warning

Blob data can incur storage charges. Retained versions remain billable while stored.

Soft-deleted data is billed at the same rate as active data during its retention period. The lifecycle management policy does not make that retained data free.

Deleting rg-blob-recovery stops future storage charges from this lab after Azure completes the deletion.

Resources you used:

  • The Azure resource group named rg-blob-recovery.
  • The Standard LRS storage account referenced by $storageAccount.
  • The private workfiles container referenced by $containerUrl.
  • The current report.txt blob plus its retained previous versions.
  • Blob versioning plus seven-day blob soft delete.
  • The previous-version lifecycle management policy.
  • The container-scoped Storage Blob Data Contributor role assignment for your Microsoft Entra identity.
  • The local infra folder containing your Bicep deployment files.
  • The local samples folder containing the approved, corrupted, and draft revisions.
  • The local evidence folder containing your downloads, hashes, recovery notes, and version histories.

Keep everything running

No action needed. Choose this if you want to keep practicing version recovery with the live history.

  • Keep rg-blob-recovery available for more recovery drills.
  • Preserve the infra folder for repeatable deployments.
  • Preserve the samples folder for future overwrite scenarios.
  • Preserve the evidence folder as your incident record.

The live blob history stays available. Azure storage charges can continue while the cloud data remains.

Pause - I'll come back to this later

End the authenticated transfer session while keeping every cloud resource and local file. Azure has no compute process to pause in this lab.

  • End the authenticated AzCopy session by running this command:
& $AzCopy logout

AzCopy ends its Microsoft Entra session. The resource group plus its retained storage remain unchanged.

Your local evidence stays ready for later. Storage charges can continue until you delete the Azure resource group.

Delete - I don't want to use this again

This cleanup is deliberately broad. The resource group boundary lets one command remove every Azure resource created by the lab.

  • Start deleting the complete lab boundary by running this command:
az group delete --name rg-blob-recovery --yes --no-wait

The Azure CLI starts deleting rg-blob-recovery. PowerShell returns before Azure finishes because the command does not wait.

The resource group deletion covers the storage account plus its workfiles container. It also covers the role assignment, protection settings, lifecycle policy, current blob, and previous versions.

  • Open the resource-group list in the Azure portal to confirm rg-blob-recovery disappears after deletion completes.

That clears the billable side of the lab. The Azure resource group no longer appears once deletion is complete.

Resource Group Still Visible?

  • Refresh the resource-group list because the deletion command returns before the operation finishes.
  • Confirm your Azure CLI session still uses the subscription that owns rg-blob-recovery if the deletion request was rejected.
  • Help me troubleshoot why my Azure resource group deletion did not complete.

The cloud resources are gone after the group deletion. You can now remove the local project artifacts if you do not want to keep the recovery evidence.

  • Return to Visual Studio Code from earlier.
  • Delete the infra folder from the Explorer sidebar.
  • Delete the samples folder from the Explorer sidebar.
  • Delete the evidence folder from the Explorer sidebar.

The infra, samples, and evidence folders no longer appear in the Explorer sidebar.

Nice Work!

Nice Work!

You did it! You've built a protected Azure Blob Storage recovery lab that restores an overwritten report with hash-verified proof.

You've learned how to:

  • Use Bicep to deploy a repeatable recovery lab from infra/main.bicep. Enable Blob versioning. Retain deleted blobs for seven days with blob soft delete. Protect private workfiles access with Microsoft Entra authentication plus container-scoped Azure RBAC.
  • Trigger a controlled overwrite of report.txt with the corrupted report. Promote the approved previous version with Azure CLI Copy Blob. Preserve the earlier revisions as incident history.
  • Compare SHA256 hash values to prove byte-for-byte recovery against samples/report-good.txt. Record the chosen version in evidence/recovery-notes.md. Save the post-recovery histories as JSON evidence.
  • Secret Mission: Inspect a crowded revision history to identify the original approved content. Restore it as the current report.txt while retaining every intervening revision.

Ready to quiz yourself? See how much of the recovery workflow you can recall.