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:
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:
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:
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:
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:
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:
It takes about 15 – 20 minutes for the upload to complete. This is what it looks like in the storage account:
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”