Created on

Quickly migrate multiple virtual machines to Standard SSD


I wanted to get a script to convert multiple virtual machines from Standard HHD to Standard SSD if they were ready for it.
By ready I mean; a virtual machine that I have prepared a scheduled maintenance window for.

There are many ways, but I took the choice of using a specific tag that have to be present on the virtual machines that will be converted in bulk. This approach give me the opportunity to ask a coworker or a customer to determine which virtual machine can be converted doing the scheduled maintenance window, without having to deal with things like an input list in .CSV format or other ways of agreeing to which virtual machines can be converted. One could argue that it would be nice to have support for multiple scheduled maintenance windows – if needed, this could easily be done by changing the input tag to a read-host.

I asked Copilot for GitHub to write me the whole script (this would have taken a few seconds and then I was done). But I think my scripting skills outrun my prompting skills, because the PowerShell script I got, would take all VMs with the tag, and shut them down before looking if the VMs OS disk was actually Standard HHD. So I decided to take the output and rewrite and add output feature so I could monitor if disk conversion failed. I also added a tagging to each completed VM.

The script will:

  • Get all virtual machines in an Azure subscription with the tag “ready-for-disk-conversion” if value of that tag is “true”
  • Loop over each eligible VM
    • Check if the VM’s OS disk is Standard_HHD
    • Deallocate the VM
    • Convert the OS disk to StandardSSD
    • Set tag “disk-conversion-status” to either completed or “failed” on the VM
    • Boot the VM and monitor if success
    • Output results to .CSV file: “.\VM_Disk_Migration_Results-$Today.csv”
# Define variables
$TagName = "ready-for-disk-conversion"
$TagValue = "true"

$Today = Get-Date -Format "yyyy-MM-dd"

# Load Azure modules
Import-Module Az.Compute
Import-Module Az.Resources
Import-Module Az.Accounts

# Login to Azure account
Connect-AzAccount

# Select subscription (interactively)
$subscription = Get-AzSubscription | Out-GridView -Title "Select an Azure Subscription" -PassThru
Set-AzContext -SubscriptionId $subscription.Id

# Get all VMs with the specified tag
$vmList = Get-AzVM -Status | Where-Object {
    $_.Tags.ContainsKey($TagName) -and $_.Tags[$TagName] -eq $TagValue
}

$vmListResults = @()

foreach ($vm in $vmList) {
    write-information "Processing VM: $($vm.Name)" -ForegroundColor Cyan

    $resourceGroup = $vm.ResourceGroupName

    # Get OS disk
    $osDiskName = $vm.StorageProfile.OsDisk.Name
    $osDisk = Get-AzDisk -ResourceGroupName $resourceGroup -DiskName $osDiskName

    if ($osDisk.Sku.Name -eq "StandardHDD_LRS") {
        $Status = "N/A"
        $VMBoot = "N/A"

        # Deallocate the VM
        write-information "Deallocating VM: $($vm.Name)..."
        Stop-AzVM -Name $vm.Name -ResourceGroupName $resourceGroup -Force -NoWait
        # Wait for deallocation to complete
        do {
            Start-Sleep -Seconds 5
            $vmStatus = (Get-AzVM -ResourceGroupName $resourceGroup -Name $vm.Name -Status).Statuses |
                        Where-Object { $_.Code -like 'PowerState/*' }
        } while ($vmStatus.DisplayStatus -ne "VM deallocated")

        try {
            write-information "Converting OS disk to StandardSSD_LRS..."
            $osDisk.Sku.Name = "StandardSSD_LRS"
            Update-AzDisk -ResourceGroupName $resourceGroup -DiskName $osDiskName -Disk $osDisk
            $Status = "Success"

            # Remove the migration tag
            write-information "Removing tag '$TagName' from VM..."
            $vm.Tags.Remove($TagName)
            Set-AzVM -VM $vm

            # Set failed tag 'disk-migration-status=completed'
            $vm.Tags["disk-conversion-status"] = "completed"
            Set-AzVM -VM $vm
        }
        catch {
            $Status = "Failed"

            # Set failed tag 'disk-migration-status=failed'
            $vm.Tags["disk-conversion-status"] = "failed"
            Set-AzVM -VM $vm
        }

        # Start the VM
        write-information "Starting VM: $($vm.Name)..."
        try {
            Start-AzVM -Name $vm.Name -ResourceGroupName $resourceGroup -ErrorAction Continue
            Write-Host "VM '$($vm.Name)' started successfully."
            $VMBoot = "Success"
        }
        catch {
            Write-Error "Failed to start VM '$($vm.Name)'"
            $VMBoot = "Failed"
        }


        write-information "Disk conversion completed for VM: $($vm.Name)" -ForegroundColor Green

        $vmListResults += [PSCustomObject]@{
            VMName         = $vm.Name
            ResourceGroup  = $resourceGroup
            Status         = $Status
            VMBoot         = $VMBoot
            NewDiskSku     = $osDisk.Sku.Name
        }
    }
}

$vmListResults | Export-CSV -Path ".\VM_Disk_Migration_Results-$Today.csv" -NTI

Use this script to get all virtual machines in the Azure subscription that failed to convert, if you prefer this method over reading the outout .CSV file:

# Define variables
$TagName = "disk-conversion-status"
$TagValue = "failed"

# Load Azure modules
Import-Module Az.Compute
Import-Module Az.Resources
Import-Module Az.Accounts

# Login to Azure account
Connect-AzAccount

# Select subscription (interactively)
$subscription = Get-AzSubscription | Out-GridView -Title "Select an Azure Subscription" -PassThru
Set-AzContext -SubscriptionId $subscription.Id

# Get all VMs with the specified tag
Get-AzVM -Status | Where-Object {$_.Tags.ContainsKey($TagName) -and $_.Tags[$TagName] -eq $TagValue}