Shrink Azure disk
Nem támogatott megoldás, de jól működik, gondoltam ledokumentálom, hátha másoknak is hasznos lesz.
Az alap Windows OS lemez az Azureban 127Gb, vagy a smalldiks Windows lemezek 30Gb méretűek (ahogyan a Linux lemezképek is)
Sokszor előfordul, hogy a diszk méretét utólag szeretnénk csökkenteni, pl.: 127Gb-ról pl.: 64 Gb-ra.
Ebben az esetben első lépésként az operációs rendszerben nyomunk egy shrink-et (pl.: diskmanagerben vagy PS-ből: Get-Partition -DiskNumber <disknumber> -PartitionNumber <partitionnumber> | Resize-Partition -Size 64GB), majd egy Azure PS segítségével utána állítjuk az Azure Managed Disk méretét is.
Githubon több megoldás is fellelhető, nekem végül ez a script vált be:
.\ShrinkAzureOSDisk.ps1 -VMName "VMName" -ResourceGroup "RGName" -SubscriptionID "SubID" -DiskSizeGB "64"
terminalban/ps-ben futtatni, majd be kell loginolni az Azureba és ha ELŐTTE az OS-ben is shrinkeltük már a diszket akkor mehet neki az ENTER és vmi ilyesmi fog történni:
azure portálon pedig kettő diszket kell látnotok, az új diszk végén _64 jelöléssel! (a diszk méretétől függően akár 30-60 percet is eltarthat a folyamat!)
Ha a VM-be belépve is azt tapasztaljátok, hogy minden oké, akkor a folyamat végén ne felejtsétek el letörölni a forrás diszket, különben azt is fizetni kell :-)
Fontos: azure disk műveletek után maximum 24 óra, míg a teljesítmény beáll a kívánt sebességre!
A script:
<#
.SYNOPSIS
Shrinks an Azure Managed Disk via a resize process. Insipiration is to enable Ephemeral Disk use post image builds (if the OS disk is too big for the instance size)
Similar to how Nerdio, Project Hydra and WVD Admin handle the exercise
.DESCRIPTION
Shrinks an Azure Managed Disk via a resize process. Original source code basis here https://jrudlin.github.io/2019-08-27-shrink-azure-vm-osdisk/
Additional snippets of code borrowed from Nerdio (for in guest partition shrink) https://github.com/Get-Nerdio/NMW/blob/main/scripted-actions/azure-runbooks/Shrink%20OS%20Disk.ps1
Updated code, added error handling, simplified and added logic
.PARAMETER LogPath
Logpath output for all operations
.PARAMETER LogRollover
Number of days before logfiles are rolled over. Default is 5
.PARAMETER VMName
Name of which to target
.PARAMETER SubscriptionID
Subscription ID (not name) of the VM
.PARAMETER ResourceGroup
Resource Group of the VM
.PARAMETER DiskSizeGB
Size to set the new disk too (integer)
.PARAMETER Cleanup
If set, will remove all non critical components. Will NOT delete snapshot and source OS disk
.PARAMETER TakeSnapshot
Will create a snapshot prior to any work
.PARAMETER PartitionGuestOSDisk
Will attempt to repartition the OS disk within the guest
.EXAMPLE
Will target the VM named VMName in Resource Group RGName in Subscription SubID. Will resize the guest partition to 63 GiB, shrink the disk to 64 GiB, and cleanup after itself
.\ShrinkAzureOSDisk.ps1 -VMName "VMName" -ResourceGroup "RGName" -SubscriptionID "SubID" -DiskSizeGB "64" -Cleanup -PartitionGuestOSDisk
#>
#region Params
# ============================================================================
# Parameters
# ============================================================================
Param(
[Parameter(Mandatory = $false)]
[string]$LogPath = "C:\Logs\ShrinkAzureOSDisk.log",
[Parameter(Mandatory = $false)]
[int]$LogRollover = 5, # number of days before logfile rollover occurs
[Parameter(Mandatory = $true)]
[string]$SubscriptionID = "",
[Parameter(Mandatory = $true)]
[string]$VMName = "",
[Parameter(Mandatory = $true)]
[string]$ResourceGroup = "",
[Parameter(Mandatory = $true)]
[int]$DiskSizeGB = "64",
[Parameter(Mandatory = $false)]
[switch]$Cleanup,
[Parameter(Mandatory = $false)]
[switch]$TakeSnapshot,
[Parameter(Mandatory = $false)]
[switch]$PartitionGuestOSDisk
)
#endregion
#region Functions
# ============================================================================
# Functions
# ============================================================================
function Write-Log {
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true,
ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[Alias("LogContent")]
[string]$Message,
[Parameter(Mandatory = $false)]
[Alias('LogPath')]
[string]$Path = $LogPath,
[Parameter(Mandatory = $false)]
[ValidateSet("Error", "Warn", "Info")]
[string]$Level = "Info",
[Parameter(Mandatory = $false)]
[switch]$NoClobber
)
Begin {
# Set VerbosePreference to Continue so that verbose messages are displayed.
$VerbosePreference = 'Continue'
}
Process {
# If the file already exists and NoClobber was specified, do not write to the log.
if ((Test-Path $Path) -AND $NoClobber) {
Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name."
Return
}
# If attempting to write to a log file in a folder/path that doesn't exist create the file including the path.
elseif (!(Test-Path $Path)) {
Write-Verbose "Creating $Path."
$NewLogFile = New-Item $Path -Force -ItemType File
}
else {
# Nothing to see here yet.
}
# Format Date for our Log File
$FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
# Write message to error, warning, or verbose pipeline and specify $LevelText
switch ($Level) {
'Error' {
Write-Error $Message
$LevelText = 'ERROR:'
}
'Warn' {
Write-Warning $Message
$LevelText = 'WARNING:'
}
'Info' {
Write-Verbose $Message
$LevelText = 'INFO:'
}
}
# Write log entry to $Path
"$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append
}
End {
}
}
function Start-Stopwatch {
Write-Log -Message "Starting Timer" -Level Info
$Global:StopWatch = [System.Diagnostics.Stopwatch]::StartNew()
}
function Stop-Stopwatch {
Write-Log -Message "Stopping Timer" -Level Info
$StopWatch.Stop()
if ($StopWatch.Elapsed.TotalSeconds -le 1) {
Write-Log -Message "Script processing took $($StopWatch.Elapsed.TotalMilliseconds) ms to complete." -Level Info
}
else {
Write-Log -Message "Script processing took $($StopWatch.Elapsed.TotalSeconds) seconds to complete." -Level Info
}
}
function RollOverlog {
$LogFile = $LogPath
$LogOld = Test-Path $LogFile -OlderThan (Get-Date).AddDays(-$LogRollover)
$RolloverDate = (Get-Date -Format "dd-MM-yyyy")
if ($LogOld) {
Write-Log -Message "$LogFile is older than $LogRollover days, rolling over" -Level Info
$NewName = [io.path]::GetFileNameWithoutExtension($LogFile)
$NewName = $NewName + "_$RolloverDate.log"
Rename-Item -Path $LogFile -NewName $NewName
Write-Log -Message "Old logfile name is now $NewName" -Level Info
}
}
function StartIteration {
Write-Log -Message "--------Starting Iteration--------" -Level Info
RollOverlog
Start-Stopwatch
}
function StopIteration {
Stop-Stopwatch
Write-Log -Message "--------Finished Iteration--------" -Level Info
}
#endregion
#region Variables
# ============================================================================
# Variables
# ============================================================================
# Set Variables
#endregion
#Region Execute
# ============================================================================
# Execute
# ============================================================================
StartIteration
#region Azure prep
#----------------------------------------------------------------------------
# Handle Modules
#----------------------------------------------------------------------------
If (Get-Module -ListAvailable -Name "Az.Storage") {
Write-Log -Message "Az.Storage module present, continuing" -Level Info
} else {
try {
Write-Log -Message "Az.Storage module not present. Attempting import of Az Module" -Level Warn
Import-Module -name Az -force -ErrorAction Stop
Write-Log -Message "Sucess: Az Module Imported"
}
catch {
Write-Log -Message "Cannot import Az Module. Exit script"
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
}
# Provide Azure admin credentials
#----------------------------------------------------------------------------
# Connect to Azure
#----------------------------------------------------------------------------
write-Log -Message "Connecting to Azure Account" -Level Info
try {
Connect-AzAccount -ErrorAction Stop | Out-Null
Write-Log -Message "Success: connected to Azure" -Level Info
}
catch {
Write-Log -Message "Failed to connect to Azure. Exit script" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
# Provide the subscription Id of the subscription
Write-Log -Message "Setting Azure Subscription to: $($SubscriptionID)" -Level Info
try {
Select-AzSubscription -Subscription $SubscriptionID -ErrorAction Stop | Out-Null
}
catch {
Write-Log -Message "Failed to connect to Azure. Exit script" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#endregion
#region Machine power and snapshots
#----------------------------------------------------------------------------
# Check Machine
#----------------------------------------------------------------------------
Write-Log -Message "Checking source VM: $($VMName) power state" -Level Info
if (((Get-AzVM -Name $VMName -ResourceGroupName $ResourceGroup -Status).Statuses[1].DisplayStatus) -eq "VM running") {
Write-Log -Message "VM: $($VMName) is running, powering off" -Level Info
try {
Stop-AzVM -Name $VMName -ResourceGroupName $ResourceGroup -force -ErrorAction Stop | Out-Null
Write-Log -Message "Success: stopped VM: $($VMName)" -Level Info
}
catch {
Write-Log -Message "Failed to power off VM: $($VMName). Exit script" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
}
else {
Write-Log -Message "VM: $($VMName) is in state $((Get-AzVM -Name $VMName -ResourceGroupName $ResourceGroup -Status).Statuses[1].DisplayStatus). Proceeding"
}
#----------------------------------------------------------------------------
# Snapshot
#----------------------------------------------------------------------------
if ($TakeSnapshot.IsPresent) {
Write-Log -Message "TakeSnapshot has been selected. A snapshot of the OS Disk will be created, and not cleaned up" -Level Info
try {
Write-Log -message "Get VM: $($VMName) details for snapshot"
$VM = Get-AzVM -Name $VMName -ResourceGroupName $ResourceGroup -ErrorAction Stop
Write-Log -Message "Create Snapshot config" -Level Info
$Snap = New-AzSnapshotConfig -SourceUri ($VM.StorageProfile.OsDisk.ManagedDisk.Id) -Location $VM.Location -CreateOption "Copy" -ErrorAction Stop
Write-Log -Message "Attempting to create snapshot" -Level Info
$NewSnap = New-AzSnapshot -Snapshot $Snap -SnapshotName ("snap-" + $VM.StorageProfile.OsDisk.Name) -ResourceGroupName $ResourceGroup -ErrorAction Stop
Write-Log -Message "Success: Snapshot created: $($NewSnap.Name)"
}
catch {
Write-Log -Message "Failed to create snapshot. Exit script" -Level Warn
Write-Log -Message $_
StopIteration
Exit 1
}
}
else {
Write-Log -message "Takesnapshot is not present, no snapshot will be created" -Level Warn
}
#endregion
#region PartitionOSDisk
#----------------------------------------------------------------------------
# Partition Disk within guest sourced from Nerdio Library
# https://github.com/Get-Nerdio/NMW/blob/main/scripted-actions/azure-runbooks/Shrink%20OS%20Disk.ps1
#----------------------------------------------------------------------------
if ($PartitionGuestOSDisk.IsPresent) {
Write-Log -Message "PartitionGuestOSDisk is present. Attempting to resize partition in guest" -Level Info
$NewPartitionSize = $DiskSizeGB - 1
$PartitionScriptBlock = @"
if ((Get-Service -Name defragsvc).Status -eq "Stopped") {
write-output "Defragsvc started"
Set-Service -Name defragsvc -Status Running -StartupType Manual
}
`$Partition = get-partition | Where-Object isboot -eq `$true
`$Disk = Get-CimInstance -ClassName Win32_LogicalDisk | Where-Object DeviceID -eq `$(`$partition.DriveLetter + ':')
`$DiskUsed = `$Disk.Size - `$Disk.FreeSpace
write-output ("Disk space used: " + `$DiskUsed / 1GB + "GB")
if (`$DiskUsed / 1GB -lt $NewPartitionSize) {
`$Partition | Resize-Partition -Size $NewPartitionSize`GB
}
else {
Throw "Not enough free space to resize partition"
}
"@
$PartitionScriptBlock | Out-File .\partitionscriptblock.ps1
try {
Write-Log -Message "Attempting to start VM $($VMName)"
Start-AzVM -ResourceGroupName $ResourceGroup -Name $VMName -ErrorAction Stop | Out-Null
Write-Log -Message "Attempting to re-partition OS disk within the VM $($VMName)" -Level Info
$Result = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroup -VMName $VMName -ScriptPath .\partitionscriptblock.ps1 -CommandId runpowershellscript -ErrorAction Stop
if ($Result.Value[1].Message -match "Not enough free space") {
Write-Log -Message "Not enough free space to resize partition" -Level Warn
Write-Log -Message $Result.Value[1].Message -Level Warn
}
if ($Result.Value[1].Message -match "The partition is already the requested size") {
Write-Log -Message "The partition is already the requested size." -Level Info
Write-Log -Message $Result.Value[1].Message -Level Info
}
Write-Log -Message $Result.Value[0].Message -Level Info
Write-Log -Message "Attempting to Stop VM $($VMName)"
Stop-AzVM -ResourceGroupName $ResourceGroup -Name $VMName -Force -ErrorAction Stop | Out-Null
Write-Log -Message "Success: Stopped VM $($VMName)"
}
catch {
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
}
#endregion
#region VM Spec and OS Disk details
#----------------------------------------------------------------------------
# Get VM Details
#----------------------------------------------------------------------------
try {
Write-Log -Message "Getting VM details for VM: $($VMName)"
$VM = Get-AzVM -Name $VMName -ResourceGroupName $ResourceGroup -ErrorAction Stop
# Get OS Disk Details and check size
$CurrentDiskSize = (Get-AzDisk -Name ($VM.StorageProfile.OsDisk.Name) -ResourceGroupName $VM.ResourceGroup).DiskSizeGB
if ($CurrentDiskSize -lt $DiskSizeGB) {
Write-Log -Message "Source Disk is already smaller than the target disk size. Cannot continue. Exit script" -Level Warn
StopIteration
Exit 0
}
else {
Write-Log -message "Source Disk is $($CurrentDiskSize) GiB and will be resized to $($DiskSizeGB) GiB" -Level Info
}
Write-Log -Message "Getting OS disk details" -Level Info
$Disk = Get-AzDisk -DiskName ($VM.StorageProfile.OsDisk.Name) -ErrorAction Stop
# Get SAS URI for the Managed disk
Write-Log -Message "Attempting to create and retrieve SAS URI for disk $($Disk.Name)" -Level Info
$SAS = Grant-AzDiskAccess -ResourceGroupName $ResourceGroup -DiskName $Disk.Name -Access "Read" -DurationInSecond 600000 -ErrorAction Stop
Write-Log -Message "Success: SAS URI is $($SAS.AccessSAS) " -Level Info
}
catch {
Write-Log "Could not retrieve VM Details for VM: $($VMName), disk details or create a SAS URI for the OS disk: $($VM.StorageProfile.OsDisk.Name). Exit script"
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#endregion
#region storage accounts
#----------------------------------------------------------------------------
# Handle Storage Accounts
#----------------------------------------------------------------------------
# Provide Temp storage account details for transfer
Write-Log -Message "Setting storage account details" -Level Info
$StorageAccountName = "shrink" + [system.guid]::NewGuid().tostring().replace('-', '').substring(1, 18)
Write-Log -Message "Storage account name is: $($StorageAccountName)" -Level Info
# Name of the storage container where the temp disk will be stored
$StorageContainerName = $StorageAccountName
Write-Log -Message "Storage account container name is: $($StorageAccountName)" -Level Info
# Provide the temp name of the VHD file
$DestinationVHDFileName = "$($VM.StorageProfile.OsDisk.Name).vhd"
Write-Log -Message "Destination VHD file name is: $($DestinationVHDFileName)" -Level Info
try {
# Create the context for the storage account which will be used to copy the disk to the storage account
Write-Log -Message "Attempting to create storage account: $($StorageAccountName)" -Level Info
$StorageAccount = New-AzStorageAccount -ResourceGroupName $ResourceGroup -Name $StorageAccountName -SkuName "Standard_LRS" -Location $VM.Location -ErrorAction Stop
$DestinationContext = $StorageAccount.Context
Write-Log -Message "Attempting to create storage account container: $($StorageAccountName)" -Level Info
$Container = New-AzStorageContainer -Name $StorageContainerName -Permission "Off" -Context $DestinationContext -ErrorAction Stop
Write-Log -Message "Success: created storage account: $($StorageAccountName) and container: $($StorageAccountName)" -Level Info
}
catch {
Write-Log -Message "Failed to create storage account for transfer. Exit script" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#endregion
#region copy source data
#----------------------------------------------------------------------------
# Copy the OS disk to the storage account
#----------------------------------------------------------------------------
try {
Write-Log -Message "Attempting disk transfer for: $($VM.StorageProfile.OsDisk.Name) to storage account container" -Level Info
Start-AzStorageBlobCopy -AbsoluteUri $SAS.AccessSAS -DestContainer $StorageContainerName -DestBlob $DestinationVHDFileName -DestContext $DestinationContext -ErrorAction Stop | Out-null
$Sleep = "30"
while (($State = Get-AzStorageBlobCopyState -Context $DestinationContext -Blob $DestinationVHDFileName -Container $StorageContainerName).Status -ne "Success") {
Write-Log -Message "Copy status is $($State.Status), Bytes copied: $($State.BytesCopied) of: $($State.TotalBytes). Sleeping for $($Sleep) seconds" -Level Info
Start-Sleep -Seconds $Sleep
}
Write-Log -Message "Copy status is $($State.Status). disk transfer to storage account container complete" -Level Info
}
catch {
Write-Log -Message "Failed to transfer disk: $($VM.StorageProfile.OsDisk.Name). Exit script" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#----------------------------------------------------------------------------
# Revoke SAS token
#----------------------------------------------------------------------------
try {
Write-Log -Message "Attempting to revoke disk access for disk $($Disk.Name)" -Level Info
Revoke-AzDiskAccess -ResourceGroupName $ResourceGroup -DiskName $Disk.Name | Out-Null
Write-Log -Message "Success: revoked disk access for disk $($Disk.Name)" -Level Info
}
catch {
Write-Log -Message "Failed to remove SAS Token" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#endregion
#region empty disk prep and transfer
#----------------------------------------------------------------------------
# Create empty disk to get footer
#----------------------------------------------------------------------------
$Emptydiskforfootername = "$($VM.StorageProfile.OsDisk.Name)-empty.vhd"
Write-Log -Message "Diskname for empty disk is: $($Emptydiskforfootername)" -Level Info
# Empty disk URI
Write-Log -Message "Attempting to create empty disk: $($Emptydiskforfootername)" -Level Info
try {
# Handle zones
if ($null -eq $Disk.zones) {
$DiskConfig = New-AzDiskConfig -Location $VM.Location -CreateOption "Empty" -DiskSizeGB $DiskSizeGB -HyperVGeneration $Disk.HyperVGeneration
} else {
$DiskConfig = New-AzDiskConfig -Location $VM.Location -Zone $VM.Zones -CreateOption "Empty" -DiskSizeGB $DiskSizeGB -HyperVGeneration $Disk.HyperVGeneration
}
$DataDisk = New-AzDisk -ResourceGroupName $ResourceGroup -DiskName $Emptydiskforfootername -Disk $DiskConfig
Write-Log -Message "Succesfully created empty disk: $($Emptydiskforfootername)" -Level Info
}
catch {
Write-Log -Message "Failed to create disk" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#----------------------------------------------------------------------------
# Add disk to VM
#----------------------------------------------------------------------------
Write-Log -Message "Attempting to attach empty disk: $($Emptydiskforfootername) to VM: $($VMName)" -Level Info
try {
Add-AzVMDataDisk -VM $VM -Name $Emptydiskforfootername -CreateOption "Attach" -ManagedDiskId $DataDisk.Id -Lun "63" | Out-Null
Update-AzVM -ResourceGroupName $ResourceGroup -VM $VM | Out-Null
Write-Log -Message "Success: attached empty disk: $($Emptydiskforfootername) to VM: $($VMName)" -Level Info
}
catch {
Write-Log -Message "Failed to attach disk" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#----------------------------------------------------------------------------
# Get SAS token for the empty disk
#----------------------------------------------------------------------------
try {
Write-Log -Message "Attempting to create and retrieve SAS URI for disk $($Emptydiskforfootername)" -Level Info
$SAS = Grant-AzDiskAccess -ResourceGroupName $ResourceGroup -DiskName $Emptydiskforfootername -Access 'Read' -DurationInSecond 600000
Write-Log -Message "Success: SAS URI is $($SAS.AccessSAS) " -Level Info
}
catch {
Write-Log -Message "Failed to get SAS URI for disk $($Emptydiskforfootername)" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#----------------------------------------------------------------------------
# Copy the empty disk to blob storage
#----------------------------------------------------------------------------
try {
Write-Log -Message "Attempting disk transfer for: $($Emptydiskforfootername) to storage account container" -Level Info
Start-AzStorageBlobCopy -AbsoluteUri $SAS.AccessSAS -DestContainer $storageContainerName -DestBlob $Emptydiskforfootername -DestContext $DestinationContext -ErrorAction Stop | Out-Null
$Sleep = "30"
while (($state = Get-AzStorageBlobCopyState -Context $destinationContext -Blob $emptydiskforfootername -Container $storageContainerName).Status -ne "Success") {
Write-Log -Message "Copy status is $($State.Status), Bytes copied: $($State.BytesCopied) of: $($State.TotalBytes). Sleeping for $($Sleep) seconds" -Level Info
Start-Sleep -Seconds $Sleep
}
Write-Log -Message "Copy status is $($State.Status). disk transfer to storage account container complete" -Level Info
}
catch {
Write-Log -Message "Failed to transfer disk: $($Emptydiskforfootername). Exit script" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#----------------------------------------------------------------------------
# Revoke SAS token
#----------------------------------------------------------------------------
try {
Write-Log -Message "Attempting to revoke Disk Access for disk $($Emptydiskforfootername)" -Level Info
Revoke-AzDiskAccess -ResourceGroupName $resourceGroup -DiskName $Emptydiskforfootername | Out-Null
Write-Log -Message "Success: Revoked Disk Access for disk $($Emptydiskforfootername)" -Level Info
}
catch {
Write-Log -Message "Failed to remove SAS Token" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#----------------------------------------------------------------------------
# Remove temp empty disk from VM
#----------------------------------------------------------------------------
try {
Write-Log -Message "Attempting to removing temp disk $($Emptydiskforfootername) from VM: $($VMName)" -Level Info
Remove-AzVMDataDisk -VM $VM -DataDiskNames $Emptydiskforfootername -ErrorAction Stop | Out-Null
Update-AzVM -ResourceGroupName $resourceGroup -VM $VM -ErrorAction Stop | Out-Null
Write-Log -Message "Success: Removed temp disk $($Emptydiskforfootername) from VM: $($VMName)" -Level Info
}
catch {
Write-Log -Message "Failed to remove temp disk $($Emptydiskforfootername) from VM: $($VMName)" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#----------------------------------------------------------------------------
# Delete temp disk
#----------------------------------------------------------------------------
try {
Write-Log -Message "Attempting to delete temp disk $($Emptydiskforfootername)" -Level Info
Remove-AzDisk -ResourceGroupName $resourceGroup -DiskName $Emptydiskforfootername -Force -ErrorAction Stop | Out-Null
Write-Log -Message "Success: deleted temp disk $($Emptydiskforfootername)" -Level Info
}
catch {
Write-Log -Message "Failed to delete temp disk $($Emptydiskforfootername)" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#endregion
#region footer management
#----------------------------------------------------------------------------
# Get Blobs
#----------------------------------------------------------------------------
try {
Write-Log -Message "Attempting to get blob info" -Level Info
$EmptyDiskblob = Get-AzStorageBlob -Context $DestinationContext -Container $StorageContainerName -Blob $Emptydiskforfootername -ErrorAction Stop
$OsDisk = Get-AzStorageBlob -Context $DestinationContext -Container $StorageContainerName -Blob $DestinationVHDFileName
Write-Log -Message "Get footer details for empty disk" -Level Info
$Footer = New-Object -TypeName byte[] -ArgumentList 512
$Downloaded = $EmptyDiskblob.ICloudBlob.DownloadRangeToByteArray($Footer, 0, $EmptyDiskblob.Length - 512, 512)
$OsDisk.ICloudBlob.Resize($EmptyDiskblob.Length)
$footerStream = New-Object -TypeName System.IO.MemoryStream -ArgumentList (, $Footer)
Write-Log -Message "Write footer of empty disk to OSDisk" -Level Info
$OsDisk.ICloudBlob.WritePages($FooterStream, $EmptyDiskblob.Length - 512)
Write-Log -Message "Removing empty disk blobs" -Level Info
$EmptyDiskblob | Remove-AzStorageBlob -Force
Write-Log -Message "Success: Footers written and blobs cleaned" -Level Info
}
catch {
Write-Log -Message "Failed to manage blob info and set footers" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#endregion
#region new disk
#----------------------------------------------------------------------------
# New Managed Disk
#----------------------------------------------------------------------------
$NewDiskName = $Disk.Name + "_" + $DiskSizeGB
Write-Log -Message "New disk name: $($NewDiskName)" -Level Info
# Create the new disk with the same SKU as the current one
# Get the new disk URI
$vhdUri = $OsDisk.ICloudBlob.Uri.AbsoluteUri
Write-Log -Message "VHD URI is: $($vhdUri)" -Level Info
#----------------------------------------------------------------------------
# Disk Options
#----------------------------------------------------------------------------
try {
Write-Log -Message "Attempting to create new disk: $($NewDiskName)" -Level Info
# Handle zones
if ($null -eq $Disk.zones) {
Write-Log -Message "Zone configuration not found on source disk" -Level Info
$DiskConfig = New-AzDiskConfig -AccountType $Disk.Sku.Name -Location $VM.location -DiskSizeGB $DiskSizeGB -SourceUri $vhdUri -CreateOption "Import" -StorageAccountId $StorageAccount.Id -HyperVGeneration $Disk.HyperVGeneration -ErrorAction Stop
} else {
Write-Log -Message "Zone configuration found on source disk" -Level Info
$DiskConfig = New-AzDiskConfig -Zone $Disk.zones -AccountType $Disk.Sku.Name -Location $VM.location -DiskSizeGB $DiskSizeGB -SourceUri $vhdUri -CreateOption "Import" -StorageAccountId $StorageAccount.Id -HyperVGeneration $Disk.HyperVGeneration -ErrorAction Stop
}
# Set SecurityType for new OS Disk
$DiskConfig = Set-AzDiskSecurityProfile -Disk $DiskConfig -SecurityType $Disk.SecurityProfile.SecurityType
# Create Managed disk
$NewManagedDisk = New-AzDisk -DiskName $NewDiskName -Disk $DiskConfig -ResourceGroupName $ResourceGroup -ErrorAction Stop
Write-Log -Message "Success: created new disk: $($NewDiskName)" -Level Info
}
catch {
Write-Log -Message "Failed to create new disk: $($NewDiskName)" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#endregion
#region VM update
#----------------------------------------------------------------------------
# Set the new OS disk for the VM
#----------------------------------------------------------------------------
try {
Write-Log -Message "Attempting to set OS disk to: $($NewManagedDisk.Name) for VM: $($VMName)" -Level Info
Set-AzVMOSDisk -VM $VM -ManagedDiskId $NewManagedDisk.Id -Name $NewManagedDisk.Name | Out-Null
# Update the VM with the new OS disk
Update-AzVM -ResourceGroupName $ResourceGroup -VM $VM | Out-Null
Write-Log -Message "Success: set OS disk to: $($NewManagedDisk.Name) for VM: $($VMName)" -Level Info
try {
Write-Log -Message "Attempting to start VM $($VMName)" -Level Info
$VM | Start-AzVM | Out-null
Write-Log -Message "Success: Started VM $($VMName)" -Level Info
}
catch {
Write-Log -Message "Failed to Started VM $($VMName)" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
#Exit 1
}
}
catch {
Write-Log -Message "Failed to set OS disk to: $($NewManagedDisk.Name) for VM: $($VMName)" -Level Warn
Write-Log -Message $_ -Level Warn
StopIteration
Exit 1
}
#----------------------------------------------------------------------------
# Wait for VM to boot
#----------------------------------------------------------------------------
Write-Log -Message "Starting to sleep for 90 seconds to allow VM boot"
start-sleep 90
Write-Log -Message "Attempting VM tests for $($VMName)"
#----------------------------------------------------------------------------
# Post switch boot tests
#----------------------------------------------------------------------------
$VmTestScriptBlock = @'
$env:ComputerName
'@
$VmTestScriptBlock | Out-File .\vmtestscriptblock.ps1
Try {
$Result = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroup -VMName $VMName -ScriptPath .\vmtestscriptblock.ps1 -CommandId runpowershellscript
if ($Result.Status -eq 'Succeeded') {
Write-Log -Message "Success: $($VMName) booted with new disk $($NewManagedDisk.Name)" -Level Info
}
else {
Write-Log -Message "Fail: $($VMName) did not boot with new disk $($NewManagedDisk.Name). Attempting to revert disks" -Level Warn
try {
Write-Log -Message "Attempting stop VM $($VMName)" -Level Info
$VM | Stop-AzVM -Force -ErrorAction Stop | Out-Null
Write-Log -Message "Attempting to revert OS disk to $($VM.StorageProfile.OsDisk.Name)" -Level Info
Set-AzVMOSDisk -VM $VM -ManagedDiskId ($VM.StorageProfile.OsDisk.ManagedDisk.Id) -Name ($VM.StorageProfile.OsDisk.Name) -ErrorAction Stop
Write-Log -Message "Attempting to update VM" -Level Info
Update-AzVM -ResourceGroupName $ResourceGroup -VM $VM -ErrorAction Stop
}
catch {
Write-Log -Message $_ -Level Warn
#StopIteration
}
}
}
Catch {
Write-Log -Message "Fail: $($VMName) did not boot with new disk $($NewManagedDisk.Name). Attempting to revert disks" -Level Warn
Write-Log -Message "Attempting stop VM $($VMName)" -Level Info
$VM | Stop-AzVM -Force -ErrorAction Stop | Out-Null
Write-Log -Message "Attempting to revert OS disk to $($VM.StorageProfile.OsDisk.Name)" -Level Info
Set-AzVMOSDisk -VM $VM -ManagedDiskId ($VM.StorageProfile.OsDisk.ManagedDisk.Id) -Name ($VM.StorageProfile.OsDisk.Name) -ErrorAction Stop
Write-Log -Message "Attempting to update VM" -Level Info
Update-AzVM -ResourceGroupName $ResourceGroup -VM $VM -ErrorAction Stop | Out-Null
}
#endregion
#region cleanup
#----------------------------------------------------------------------------
# Cleanup everything except OS disk and snapshot
#----------------------------------------------------------------------------
if ($Cleanup.IsPresent) {
Write-Log -Message "Cleanup switch is present. Deleting temp blobs and temp storage accounts" -Level Warn
try {
Write-Log -Message "Attempting to remove storage blob: $($OSDisk.Name)" -Level Info
# Delete old blob storage
$OsDisk | Remove-AzStorageBlob -Force -ErrorAction Stop
Write-Log -Message "Success: removed blob: $($OSDisk.Name)" -Level Info
}
catch {
Write-Log -Message "Failed to remove storage blob: $($OSDisk.Name)" -Level Warn
Write-Log -Message $_ -Level Warn
}
try {
Write-Log -Message "Attempting to remove storage account: $($StorageAccount.StorageAccountName)" -Level Info
# Delete temp storage account
$StorageAccount | Remove-AzStorageAccount -Force -ErrorAction Stop
Write-Log -Message "Success: removed storage account: $($StorageAccount.StorageAccountName)" -Level Info
}
catch {
Write-Log -Message "Failed to remove storage account: $($StorageAccount.StorageAccountName)" -Level Warn
Write-Log -Message $_ -Level Warn
}
}
else {
Write-Log -Message "Cleanup switch not present. Manual cleanup required of temp blobs and storage accounts" -Level Warn
}
#endregion
StopIteration
Exit 0
#endregion
Megjegyzések
Megjegyzés küldése