Share via


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.
[sourcecode gutter="false" title="Runbook - DeallocationAction" language="powershell"]
Param
(
[parameter(Mandatory=$false)]
[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 {
$RecSkip
$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

[sourcecode gutter="false" title="OMS Log Query" language="text"]
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.