Snapshot Managed Disk to Storage Account

This is a blogpost about creating an image from an existing Virtual Machine on Microsoft Azure. We are going to snapshot the managed disk of the machine and upload it to a Storage Account.

For example, you can use this blog/script to create an image for Citrix Cloud.

(The complete script is available at the bottom of the page)

Prerequisites

You have a VM and this VM runs on managed disks.

Create a storage account which has a container called, for example, images.

In addition, this is mine:

Storage Account to store managed disks snapshot

Example

In this section of the blog we are going to capture the image and removed the VM’s resources. This is the VM we are capturing:

Snapshot Managed Disk to Storage Account

Firstly, we need to install the modules needed to use the CMDlets. Use this code to do so:

$installedPackageProvider = Get-PackageProvider
if ($installedPackageProvider.Name -notmatch "NuGet") {
    Install-PackageProvider -Name NuGet -force
     Write-Host("Install powershell module NuGet")
}
$installedModules = Get-InstalledModule
if ($installedModules.Name -notmatch "Az.Compute") {
    Install-Module Az.Compute -Force -AllowClobber
     Write-Host("Install powershell module Az Compute")
}
if ($installedModules.Name -notmatch "Az.Storage") {
    Install-Module Az.Storage -Force -AllowClobber
     Write-Host("Install powershell module Az Storage")
}
if ($installedModules.Name -notmatch "Az.Accounts") {
    Install-Module Az.Accounts -Force -AllowClobber
     Write-Host("Install powershell module Az Accounts")
}
if ($installedModules.Name -notmatch "Az.Resources") {
    Install-Module Az.Resources -Force -AllowClobber
     Write-Host("Install powershell module Az Resources")
}

Next we log on to Azure. I use a Service Principal for that because I want to us this in my Azure DevOps Pipeline.

$secret = ConvertTo-SecureString -String "Service Principal App ID" -AsPlainText -Force
$username = "Service Principal Secret"

Write-Host("setting up credential")

$Credential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $username, $secret 

Write-Host("connect......")
Connect-AzAccount -Credential $Credential -Tenant "TENANT ID" -ServicePrincipal

After that, we need to select the proper subscription. Use this command:

Select-AzSubscription -SubscriptionId "SUBSCRIPTION ID" | Set-AzContext

Check if the VM is running and shut it down if it is.

$location = 'Location'
$ResourceGroupName = 'ResourceGroupName where VM resides'

$VM = Get-AzVm -ResourceGroupName $ResourceGroupName -Status

$vmName = $VM.Name

if ($VM.PowerState -eq "VM Running") {
    
    Write-Host "$($VM.Name) is running and will be powered off"
    Stop-AzVM -ResourceGroupName $ResourceGroupName -Name $VM.Name -force

}
Else{

    Write-Host "VM is already powered off"
}

This is the output shown:

Output Shutdown management disk

Now it is time to create the snapshot. Use this code:

$snapshotConfig =  New-AzSnapshotConfig -SourceUri $vm.StorageProfile.OsDisk.ManagedDisk.Id  -Location $location -CreateOption copy -SkuName Standard_LRS

$timestamp = Get-Date -Format yyMMddThhmmss
$snapshotName = ($vmName+$timestamp)
$diskName = ('TestImageCapture'+$timestamp)
 
New-AzSnapshot -Snapshot $snapshotConfig -SnapshotName $snapshotName -ResourceGroupName $resourceGroupName

$snapshot = Get-AzSnapshot -ResourceGroupName $resourceGroupName

This the output of the command:

Output Snapshot managed disk

Furthermore, you can see the snapshot created in the resourcegroup:

To upload this snapshot you need to following code:

$resourceGroupNameStorageAccount = 'ResourceGroup Storage Account Name'
$storageAccountName = 'Storage Account Name'
$storageContainerName = 'Container Name'
$destinationVHDFileName = ($diskName+'.vhd')

$storageAccountKey = Get-AzStorageAccountKey -ResourceGroupName $resourceGroupNameStorageAccount -AccountName $storageAccountName

$SAS = Grant-AzSnapshotAccess -ResourceGroupName $ResourceGroupName -SnapshotName $snapshot.name -Access 'Read' -DurationInSecond 3600;

$destinationContext = New-AzStorageContext -StorageAccountName $storageAccountName -StorageAccountKey ($storageAccountKey).Value[0]

Start-AzStorageBlobCopy -AbsoluteUri $sas.AccessSAS -DestContainer $storageContainerName -DestContext $destinationContext -DestBlob $destinationVHDFileName

This command provides the following output:

Upload output for managed disk snapshot upload

NOTE: The upload is not done after 20 seconds. To check the progress you can use this code:

$CopyStatus = Get-AzStorageAccount -ResourceGroupName $resourceGroupNameStorageAccount -Name $storageAccountName | Get-AzStorageBlobCopyState -Blob $destinationVHDFileName -Container $storageContainerName

$Count = 1
do {
    #Starting Count
    $Count
    $Count++
    $CopyStatus = Get-AzStorageAccount -ResourceGroupName $resourceGroupNameStorageAccount -Name $storageAccountName | Get-AzStorageBlobCopyState -Blob $destinationVHDFileName -Container $storageContainerName
    Write-Host "Upload to Storage Account not yet finished, starting sleep for 60 seconds"
    Start-Sleep 60
    if ($Count -ge 60) {
        Write-Host "Upload ran for more than an hour, upload FAILED"
        Break
    }

} while ($CopyStatus.Status -eq "Pending")

This “do while” loop checks the upload status every 60 seconds and reports back whether the upload has finished. For example:

Snapshot management disk upload example

It takes about 15 – 20 minutes for the upload to complete. This is what it looks like in the storage account:

uploaded snapshot of managed disk



When the upload completes it is time to clean up the remaining resources. We can do by using the following code:

Note: This will delete all resources in the resource group. If you have mixed resources in a resourcegroup DO NOT RUN this.

if ($CopyStatus.Status -eq "Success") {
    
    Write-Host "Upload to Storage Account completed! Cleaning up Resource Group!"

    #FirstRemoveVirtualMachines
    $VirtualMachines = Get-AzResource -ResourceGroupName $ResourceGroupName | Where-Object ResourceType -eq Microsoft.Compute/virtualMachines

    foreach ($Resource in $VirtualMachines){

    Remove-AzResource -Resourceid $Resource.ResourceId -force

    }
    #Revoke Access To Remove SnapShot
    Revoke-AzSnapshotAccess -ResourceGroupName $ResourceGroupName -SnapshotName $snapshot.name
    
    #Remove Other Resources
    $ResourcesToRemove = Get-AzResource -ResourceGroupName $ResourceGroupName
    
    foreach ($Resource in $ResourcesToRemove){

        Remove-AzResource -Resourceid $Resource.ResourceId -force
    
    }
}

This is the output:

It first states that the upload has completed and that the resource group will be cleaned. After that, the VM’s will be deleted. The snapshot access link will be revoked and the all remaining resources will be deleted.

That is how you snapshot a managed disk and upload it to a Storage Account, fully automated. 🙂

Complete Script

$installedPackageProvider = Get-PackageProvider
if ($installedPackageProvider.Name -notmatch "NuGet") {
    Install-PackageProvider -Name NuGet -force
     Write-Host("Install powershell module NuGet")
}
$installedModules = Get-InstalledModule
if ($installedModules.Name -notmatch "Az.Compute") {
    Install-Module Az.Compute -Force -AllowClobber
     Write-Host("Install powershell module Az Compute")
}
if ($installedModules.Name -notmatch "Az.Storage") {
    Install-Module Az.Storage -Force -AllowClobber
     Write-Host("Install powershell module Az Storage")
}
if ($installedModules.Name -notmatch "Az.Accounts") {
    Install-Module Az.Accounts -Force -AllowClobber
     Write-Host("Install powershell module Az Accounts")
}
if ($installedModules.Name -notmatch "Az.Resources") {
    Install-Module Az.Resources -Force -AllowClobber
     Write-Host("Install powershell module Az Resources")
}

$secret = ConvertTo-SecureString -String "Service Principal App ID" -AsPlainText -Force
$username = "Service Principal Secret"

Write-Host("setting up credential")

$Credential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $username, $secret 

Write-Host("connect......")
Connect-AzAccount -Credential $Credential -Tenant "TENANT ID" -ServicePrincipal

Start-Sleep 5

Select-AzSubscription -SubscriptionId "SUBSCRIPTION ID" | Set-AzContext

### Check If VM is running and power off if it is running.
$location = 'Location'
$ResourceGroupName = 'ResourceGroupName where VM resides'

$VM = Get-AzVm -ResourceGroupName $ResourceGroupName -Status

$vmName = $VM.Name

if ($VM.PowerState -eq "VM Running") {
    
    Write-Host "$($VM.Name) is running and will be powered off"
    Stop-AzVM -ResourceGroupName $ResourceGroupName -Name $VM.Name -force

}
Else{

    Write-Host "VM is already powered off"
}

#### Create SnapShot

$snapshotConfig =  New-AzSnapshotConfig -SourceUri $vm.StorageProfile.OsDisk.ManagedDisk.Id  -Location $location -CreateOption copy -SkuName Standard_LRS

$timestamp = Get-Date -Format yyMMddThhmmss
$snapshotName = ($vmName+$timestamp)
$diskName = ('IMAGENAME'+$timestamp)
 
New-AzSnapshot -Snapshot $snapshotConfig -SnapshotName $snapshotName -ResourceGroupName $resourceGroupName

$snapshot = Get-AzSnapshot -ResourceGroupName $resourceGroupName

### Upload Snapshot to Storage Account

$resourceGroupNameStorageAccount = 'ResourceGroup Storage Account Name'
$storageAccountName = 'Storage Account Name'
$storageContainerName = 'Container Name'
$destinationVHDFileName = ($diskName+'.vhd')

$storageAccountKey = Get-AzStorageAccountKey -ResourceGroupName $resourceGroupNameStorageAccount -AccountName $storageAccountName

$SAS = Grant-AzSnapshotAccess -ResourceGroupName $ResourceGroupName -SnapshotName $snapshot.name -Access 'Read' -DurationInSecond 3600;

$destinationContext = New-AzStorageContext -StorageAccountName $storageAccountName -StorageAccountKey ($storageAccountKey).Value[0]

Start-AzStorageBlobCopy -AbsoluteUri $sas.AccessSAS -DestContainer $storageContainerName -DestContext $destinationContext -DestBlob $destinationVHDFileName

## Get Copy Status and cleanup when done

$CopyStatus = Get-AzStorageAccount -ResourceGroupName $resourceGroupNameStorageAccount -Name $storageAccountName | Get-AzStorageBlobCopyState -Blob $destinationVHDFileName -Container $storageContainerName

$Count = 1
do {
    #Starting Count
    $Count
    $Count++
    $CopyStatus = Get-AzStorageAccount -ResourceGroupName $resourceGroupNameStorageAccount -Name $storageAccountName | Get-AzStorageBlobCopyState -Blob $destinationVHDFileName -Container $storageContainerName
    Write-Host "Upload to Storage Account not yet finished, starting sleep for 60 seconds"
    Start-Sleep 60
    if ($Count -ge 60) {
        Write-Host "Upload ran for more than an hour, upload FAILED"
        Break
    }

} while ($CopyStatus.Status -eq "Pending")

if ($CopyStatus.Status -eq "Success") {
    
    Write-Host "Upload to Storage Account completed! Cleaning up Resource Group!"

    #FirstRemoveVirtualMachines
    $VirtualMachines = Get-AzResource -ResourceGroupName $ResourceGroupName | Where-Object ResourceType -eq Microsoft.Compute/virtualMachines

    foreach ($Resource in $VirtualMachines){

    Remove-AzResource -Resourceid $Resource.ResourceId -force

    }
    #Revoke Access To Remove SnapShot
    Revoke-AzSnapshotAccess -ResourceGroupName $ResourceGroupName -SnapshotName $snapshot.name
    
    #Remove Other Resources
    $ResourcesToRemove = Get-AzResource -ResourceGroupName $ResourceGroupName
    
    foreach ($Resource in $ResourcesToRemove){

        Remove-AzResource -Resourceid $Resource.ResourceId -force
    
    }
}

References

Arlan Blogs Unmanaged Disk to Storage Account

Other Blogs:
RDP ShortPath for WVD
Bitlocker Encryption Azure DevOps Release Pipeline

4 thoughts on “Snapshot Managed Disk to Storage Account”

Leave a Comment