Resize Azure VMs in bulk

If you need to do a bulk update of VM sizes from one type to another to take advantage of new technology being released, this script should help you out.

This script will work for Resource Manager VMs, and NOT Classic VMs.
It targets VMs that are deallocated or have failed deployments only.
VMs in a Failed deployment state will automatically start when re-sized.

I've added a select grid at the end to let you toggle the state of the VMs that have been changed.
i.e Selected VMs will be Started if Deallocated and Stopped if in any other state

I've preloaded the SizeMapping table with the mapping to move from Dv2 Promo sizes to their equivilent Dv3 and Ev3 sizes.
You can change this as to your requirements, just set the Old and New elements for each record.
Make sure only select ONE subscription from the Select Grid.

[sourcecode gutter="false" title="Resize VMs" language="powershell"]
#If you want to stop running VMs, Resize and restart again, set this to $true
$ForceStop = $false
#Prompt to confirm shutdown for each VM
$StopConfirm = $true
#Filter on VM name to restrict the list of VMs returned
$VMFilter = "*"

$AvailSubscriptions = Get-AzureRmSubscription

$ErrorActionPreference = "Stop"
$SelectedSubscription = $AvailSubscriptions | select Name, Id | Out-GridView -Title "Select ONE (only) Subscription" -PassThru

$SubscriptionGUID = $SelectedSubscription.Id

Select-AzureRmSubscription -Subscription $SubscriptionGUID

#Update this table if you have different mapping size requirements from old to new
$SizeMapping = @()
$SizeMapping += @{Old='Standard_D2_v2_Promo';New='Standard_D2_v3'}
$SizeMapping += @{Old='Standard_D3_v2_Promo';New='Standard_D4_v3'}
$SizeMapping += @{Old='Standard_D4_v2_Promo';New='Standard_D8_v3'}
$SizeMapping += @{Old='Standard_D5_v2_Promo';New='Standard_D16_v3'}
$SizeMapping += @{Old='Standard_D11_v2_Promo';New='Standard_E2_v3'}
$SizeMapping += @{Old='Standard_D12_v2_Promo';New='Standard_E4_v3'}
$SizeMapping += @{Old='Standard_D13_v2_Promo';New='Standard_E8_v3'}
$SizeMapping += @{Old='Standard_D14_v2_Promo';New='Standard_E16_v3'}
$SizeMapping += @{Old='Standard_DS2_v2_Promo';New='Standard_D2s_v3'}
$SizeMapping += @{Old='Standard_DS3_v2_Promo';New='Standard_D4s_v3'}
$SizeMapping += @{Old='Standard_DS4_v2_Promo';New='Standard_D8s_v3'}
$SizeMapping += @{Old='Standard_DS5_v2_Promo';New='Standard_D16s_v3'}
$SizeMapping += @{Old='Standard_DS11_v2_Promo';New='Standard_E2s_v3'}
$SizeMapping += @{Old='Standard_DS12_v2_Promo';New='Standard_E4s_v3'}
$SizeMapping += @{Old='Standard_DS13_v2_Promo';New='Standard_E8s_v3'}
$SizeMapping += @{Old='Standard_DS14_v2_Promo';New='Standard_E16s_v3'}

#You can use a $VMFilter, set above, to limit the script to only add filtered VM names to the array
$VMs = Get-AzureRmVM | Where-Object {$_.Name -like $VMFilter}

$AlteredVMs = @()
foreach ($VM in $VMs) {
if ($VM.HardwareProfile.VmSize -in $SizeMapping.Old) {
$Status = Get-AzureRmVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -Status
$NewSize = $SizeMapping.New[$SizeMapping.Old.IndexOf($VM.HardwareProfile.VmSize)]

#If the VM is deallocated, just resize it.
if ($Status.Statuses[1].DisplayStatus -eq "VM deallocated") {

write-output "Updating VM: $($VM.Name) from $($VM.HardwareProfile.VmSize) to $($NewSize)"

$VM.HardwareProfile.VmSize = $NewSize

Update-AzureRmVM -VM $VM -ResourceGroupName $VM.ResourceGroupName -Verbose

$AlteredVM = new-object psobject -Property @{
Name = $VM.Name
ResourceGroup = $VM.ResourceGroupName
Location = $VM.Location
ResourceID = $VM.Id
SubscriptionID = $SubscriptionGUID
OldSize =
OldProvisioningStatus = $Status.Statuses[0].DisplayStatus
OldRunStatus = $Status.Statuses[1].DisplayStatus
NewProvisioningStatus = $null
NewRunStatus = $null
}
$AlteredVMs += $AlteredVM
}

#If the VM has failed provisioning, try and stop the VM firstso it clears the allocation error and doesn't start automatically after changing size
if ($Status.Statuses[0].DisplayStatus -eq "Provisioning failed") {

$StopAction = $false
$TryCount = 0
$ErrorActionPreference = "Stop"
do {
try {
$TryCount += 1
Write-Output "Stopping VM: $($VM.Name)"
Stop-AzureRmVM -Name $VM.Name -ResourceGroupName $VM.ResourceGroupName -Force
$StopAction=$true
} catch {
sleep -10
$StopAction = $false
}
} until ($StopAction -eq $true -or $TryCount -ge 5)
$ErrorActionPreference = "Continue"

write-output "Updating VM: $($VM.Name) from $($VM.HardwareProfile.VmSize) to $($NewSize)"
$VM.HardwareProfile.VmSize = $NewSize

Update-AzureRmVM -VM $VM -ResourceGroupName $VM.ResourceGroupName -Verbose

$AlteredVM = new-object psobject -Property @{
Name = $VM.Name
ResourceGroup = $VM.ResourceGroupName
Location = $VM.Location
ResourceID = $VM.Id
SubscriptionID = $SubscriptionGUID
OldProvisioningStatus = $Status.Statuses[0].DisplayStatus
OldRunStatus = $Status.Statuses[1].DisplayStatus
NewProvisioningStatus = $null
NewRunStatus = $null
}
$AlteredVMs += $AlteredVM

}

#If you have ForceStop set to $true and the VM is running
if ($Status.Statuses[1].DisplayStatus -eq "VM running" -and $ForceStop) {

if ($StopConfirm) {
$ConfirmStop = Read-Host -Prompt "Please type 'YES' to confirm you want to stop VM $($VM.Name)"
if ($ConfirmStop -eq "YES") {
$StopAction = $false
$TryCount = 0
$ErrorActionPreference = "Stop"
do {
try {
$TryCount += 1
Write-Output "Stopping VM: $($VM.Name)"
Stop-AzureRmVM -Name $VM.Name -ResourceGroupName $VM.ResourceGroupName -Force
$StopAction=$true
} catch {
sleep -10
$StopAction = $false
}
} until ($StopAction -eq $true -or $TryCount -ge 5)
$ErrorActionPreference = "Continue"

write-output "Updating VM: $($VM.Name) from $($VM.HardwareProfile.VmSize) to $($NewSize)"
$VM.HardwareProfile.VmSize = $NewSize

Update-AzureRmVM -VM $VM -ResourceGroupName $VM.ResourceGroupName -Verbose

Write-Output "Starting VM: $($VM.Name) after resize"
Start-AzureRmVM -Name $VM.Name -ResourceGroupName $VM.ResourceGroupName

$AlteredVM = new-object psobject -Property @{
Name = $VM.Name
ResourceGroup = $VM.ResourceGroupName
Location = $VM.Location
ResourceID = $VM.Id
SubscriptionID = $SubscriptionGUID
OldProvisioningStatus = $Status.Statuses[0].DisplayStatus
OldRunStatus = $Status.Statuses[1].DisplayStatus
NewProvisioningStatus = $null
NewRunStatus = $null
}
$AlteredVMs += $AlteredVM
}
}
}
}
}

$CurrentVMState = @()

foreach ($AlteredVM in $AlteredVMs) {
$Status = Get-AzureRmVM -ResourceGroupName $AlteredVM.ResourceGroup -Name $AlteredVM.Name -Status
$AlteredVM.NewProvisioningStatus = $Status.Statuses[0].DisplayStatus
$AlteredVM.NewRunStatus = $Status.Statuses[1].DisplayStatus
$CurrentVMState += $AlteredVM
}
$ToggleStates = $null
if ($CurrentVMState -ne $null) {
$ToggleStates = $CurrentVMState | select Name, ResourceGroup, NewRunStatus | Out-GridView -Title "Select VMs to toggle state (Running to Deallocated; Deallocated to Running); Click Cancel to change none" -PassThru
foreach ($ToggleState in $ToggleStates) {
if ($ToggleState.NewRunStatus -eq "VM deallocated") {
Start-AzureRmVM -ResourceGroupName $ToggleState.ResourceGroup -Name $ToggleState.Name
} else {
Stop-AzureRmVM -ResourceGroupName $ToggleState.ResourceGroup -Name $ToggleState.Name -Force
}
}
}