Using OMS and Runbooks to update VMs when they’re down

The aim of this post is to enable a powershell Azure Automation runbook to be fired when a VM is deallocated (Stopped from the Azure management fabric), that will perform given tasks.
This is useful if you have non-urgent infrastructural changes that need to be done, but you want it done with the least amount of user impact. When the user, or a schedule stops the VM, the runbook is trigger.

You will need an OMS workspace set-up and linked to an Azure Automation account.
In the linked Automation Account, create a new Runbook called "DeallocationAction".
This will accept the OMS log alert results and allow the required actions to be performed

Here is one I've written to give you the idea and get you started.
This runbook checks for VMs in East US, created by System and resizes them based on a mapping array.

        [object] $WebhookData
    # Collect properties of WebhookData
    $WebhookName    = $WebhookData.WebhookName
    $WebhookHeaders = $WebhookData.RequestHeader
    $WebhookBody    = $WebhookData.RequestBody

    # Collect results. Information converted from JSON.
    $SearchResults = (ConvertFrom-Json $WebhookBody).SearchResults.value

    #Retrive the RunAs Connection details
    $Conn = Get-AutomationConnection -Name 'AzureRunAsConnection'

    #Login with the RunAs Account
    Add-AzureRMAccount -ServicePrincipal -Tenant $Conn.TenantID `
        -ApplicationId $Conn.ApplicationID -CertificateThumbprint $Conn.CertificateThumbprint 
    $SizeMap = @()
    $SizeMap += @{Old='Standard_F1';New='Standard_D1'}
    $SizeMap += @{Old='Standard_F2';New='Standard_D2'}
    $SizeMap += @{Old='Standard_F4';New='Standard_D3'}
    $SizeMap += @{Old='Standard_F8';New='Standard_D4'}
    $CurrSub = $null
    $CurrSub = (Get-AzureRmContext).Subscription.Id

    foreach ($Result in $SearchResults) {
        Write-Output "Result"
        Write-Output $Result
        #Make sure it's the final succeeded action, and not Started or Accepted
        if ($Result.ActivityStatus -eq "Succeeded") {

            #Change the Subscription if required
            if ($CurrSub -ne $Result.SubscriptionId) {
                Select-AzureRmSubscription -SubscriptionId $Result.SubscriptionId
                $CurrSub = $Result.SubscriptionId
            #Get the VM details
            $ResourceGroup = $Result.ResourceGroup 
            $VMName = $Result.Resource 
            $VM = Get-AzureRmVM -ResourceGroupName $ResourceGroup -Name $VMName
            $CurrSize = $VM.HardwareProfile.VmSize
            Write-Output "$($VMName) - Current VM Size: $($CurrSize)"
            #Check the machine is still deallocated
            $VMStatus = Get-AzureRmVM -ResourceGroupName $ResourceGroup -Name $VMName -Status
            if ($VMStatus.Statuses[1].Code -eq "PowerState/deallocated") {
                #Put any additional filters in here for continuing to change VM size
                #These are negatives, start with the VM being included then remove based on filters
                $Enabled = $true
                if ($VM.Location.ToLower() -ne "eastus") {
                    $Enabled = $false
                if ($VM.Tags.CreatedBy -ne "System") {
                    $Enabled = $false
                #Continue if passed through previous filters
                if ($Enabled) {
                    #Make the required changes
                    foreach ($Size in $SizeMap) {
                        if ($CurrSize -eq $Size.Old) {
                            $NewSize = $Size.New
                    if ($CurrSize -ne $NewSize) {
                        Write-Output "$($VMName) - New VM Size: $($NewSize)"

                        #Update the VM Size
                        $VM.HardwareProfile.VmSize = $NewSize
                        #Apply the change
                        $UpdateVM = Update-AzureRmVM -VM $VM -ResourceGroupName $ResourceGroup 
                        #If the update succeeds, update the SizeChange Tag
                        if ($UpdateVM.IsSuccessStatusCode) {

                            Write-Output "$($VMName) - Change Succeeded"
                            $SizeChange = @{Old=$($CurrSize);UTC=(Get-Date (Get-Date).ToUniversalTime() -Format "dd/MM/yy HH:mm")}
                            $SizeJSON = $SizeChange | ConvertTo-Json -Compress
                            $Tags = $null
                            $Tags = (Get-AzureRmResource -ResourceId $VM.Id).Tags
                            if ($Tags -eq $null) {
                                $Tags = @{
                                    SizeChanged = $SizeJSON
                            } else {
                                if ($Tags.ContainsKey("SizeChanged")) {
                                    #Make sure the current JSON is configured as an array
                                    $a = [regex]"\[*\]"
                                    if ($a.Match($Tags.SizeChanged).Success) {
                                        $CurrSizeChange = (ConvertFrom-Json $Tags.SizeChanged)
                                    } else {
                                        $CurrSizeChange = (ConvertFrom-Json "[`n$($Tags.SizeChanged)`n]")
                                    $CurrSizeChange += $($SizeChange)
                                    $SizeJSON = $CurrSizeChange | ConvertTo-Json -Compress
                                    #Make sure Tag value is less than 256, keep dropping first record off until under 256 in length
                                    $RecSkip = 0
                                    do {
                                        $SizeJSON = $CurrSizeChange | Select-Object -Skip $RecSkip | ConvertTo-Json -Compress
                                        $RecSkip += 1
                                    } until ($SizeJSON.Length -lt 256)

                                    #Set Tag value
                                    $Tags.SizeChanged = $SizeJSON
                                } else {
                                    $Tags += @{SizeChanged=$SizeJSON}
                            Set-AzureRmResource -Tag $Tags -ResourceId $VM.Id -Force
                        } else {
                            Write-Output "$($VMName) - Change Failed"

Next, create an alert on the following OMS log query

Type=AzureActivity AND OperationName = "Microsoft.Compute/virtualMachines/deallocate/action" AND ActivityStatus="Succeeded"

Set the other attributes for the alert as follows
Time window: 5 minutes
Frequency: 5 minutes
Number of results greater than 0
Runbook: YES
Select the runbook created above

Now whenever a VM Deallocation event occurs and is logged through from Azure Activity to OMS (this can take up to 15 minutes), the Runbook you created is triggered and sent the VM information (within 5 minutes) so the required actions can be performed.

Skip to main content