Using tags with Azure runbook automation to control your costs

I recently wrote an article here at TechGenix on how to start/stop VMs in Azure automation based on a schedule and life was good. I went to a customer this week that has a need to start and stop VMs, so I thought, Great! Copy and paste my previous article and Bob’s your uncle! Well, the conditions are more complex than my initial attempt. The goal of this article is to show how to address more complex requirements and apply them to your Azure environment using tags.

Here is a summary of their requirements. Basically, they have a group of servers that have to obey these following rules:

  • They are grouped into a single resource group.
  • They are a considerable number of servers (20+) that require a specific order to be turned on and turned off.
  • They have two tags to control the resource group, which are: PowerOnDays (a number for each day of the week, where Sunday is 1) and PowerOnHours, which is a range using a 24-hour format.
  • The developer team may need to test some new features. They want to change a tag to start the entire environment in a couple of hours, instead of leaving that to operations.

Based on the requirements, we are going to create an automation runbook. This runbook will run every hour, and based on the conditions will start taking action on the VMs.

Building a consistent array of servers

The first challenge in our script was to get the logic straight to create a list of servers. The customer threw me a curveball when he said that they had several environments and “sometimes” the names of the servers may have slight changes between environments. The script has to be dynamic and use a set of rules instead of static names.

After some discussion with the customer, I came up with the following list of servers and their proper order to be either started or shutdown. I highlighted the unique string of each server, so any changes to the prefix (which includes PROD in the example) and the number of servers should not impact the current automation process.

To address the current requirement, we created a functional called AddVMArray(), and I added the Startup Order above in the variable $vTiers. The script looks for a server with the string FS0 and if it finds, adds to the array, and if there is more than one server (for example AzProdFS01 and AZProdFS02), they will both be added to the array.

Function AddVMArray ($rg){
$vTiers = ‘*FS0*’,’*APP0*’,’*FE0*’,’*Sec0*’,’*RDS0*’
$fArray = @()
For ($i=0; $i -le (($vTiers).Count - 1); $i++){
#Debug: Write-Output $vtiers[$i]
$tVM = Get-AzVM -Name $vTiers[$i] -ResourceGroupName $rg
If ((($tVM).Count) -eq 0) {
$fArray += ‘none’
$tVM | ForEach{$fArray += $tVM.Name}
return $fArray

Bringing servers up and down

At this stage, we have a consistent list of the servers that are consistent with the servers in any given resource group. We know that going from 1 to 5, we have a list to start the servers correctly, and from 5 to 1, we have a list to stop the servers.

A function called VMGroup was created, the function receives two parameters: the action (on or off) and the resource group.

The function will go on every server of our array and either start or stop the given VM and add a delay defined in a variable between operations. This function will test the current state of the server before issuing a command to start and stop, this way we save some time.

Function VMGroup ($action, $ResourceGroup){
Write-Output "VMGroup Funtion --> " $ResourceGroup
Write-Output "VMGroup Funtion --> " $action
If (($action -eq $null) -or ($ResourceGroup -eq $null)) { Write-Output "Parameter Null in VMGroup Function"; break}
$vArray = @()
$vArray = AddVMArray($ResourceGroup)
$vNumberofServers= ($varray).count - 1
If ($action -eq "On") {
for ($i=0; $i -le $vNumberofServers; $i++){
if ((get-azvm -Name $varray[$i]) -eq $null) {
Write-Output "-> Status: VM " $varray[$i] " does not exist"
} Else {
If ((Get-AZVM -Name $varray[$i] -status).PowerState -ne "VM running") {
Start-AZVM -Name $varray[$i] -ResourceGroupName $ResourceGroup
Write-Output "-> Status: VM " $varray[$i] " is being started."
Start-Sleep -Seconds $StartupDelay
} Else {
Write-Output "-> Status: VM " $varray[$i] " is already running."
If ($action -eq "Off") {
for ($i=$vNumberofServers; $i -ge 0; $i=$i-1){
if ((get-azvm -Name $varray[$i]) -eq $null) {
Write-Output "-> Status: VM " $varray[$i] " does not exist"
} Else {
If ((Get-AZVM -Name $varray[$i] -status).PowerState -ne "VM deallocated") {
Stop-AZVM -Name $varray[$i] -ResourceGroupName $ResourceGroup -Force
Write-Output "-> Status: VM " $varray[$i] " is being deallocated."
Start-Sleep -Seconds $ShutdownDelay
} Else {
Write-Output "-> Status: VM " $varray[$i] " is already deallocated."

Putting all the pieces together

Using the functions that we created previously, we have enough tools to create a list of servers and perform start/stop operations on them.

The beauty of using functions is that all those instructions are reusable code, and can adapt based on the information provided.

The script itself is small, and there is a simple block where we define some initial variables.

$ShutdownDelay = 120
$StartupDelay = 300
$vCurrentDayofWeek = Get-Date -UFormat %u
$vCurrentHour = (Get-Date).Hour – 4

The next step is to narrow down as much as possible. We will use where-object to gather only resource groups that have a specific name, and we are looking for two particular tags. The tags are going to be the PowerOnHours and PowerOnDaysofWeek, the first must not be empty, and the second must match today’s day.

$gResourceGroups = Get-AzResourceGroup | where-object {($_.ResourceGroupName -like ‘*grpServer*’) -and ($_.Tags.PowerOnHours -ne $Null) -and ($_.Tags.PowerOnDaysOfWeek -like ("*" + $vCurrentDayofWeek + "*") }

Now that we have a list of all resource groups where we can take some action, we will perform a series of tests. The first one is to retrieve the tag PowerOnHours and compare against the current hour. If the current hour belongs to the interval, the script will turn on the VMs, if not then the VMs will be turned off.

$gResourceGroups | % {
Write-Output "==Working on " $_.ResourceGroupName " ====="
Write-Output "Working Days......:" $_.Tags.PowerOnDaysOfWeek
Write-Output "Hours of the Day..:" $_.Tags.PowerOnHours
$vHourTemp = $_.Tags.PowerOnHours.Split("-")
$vHourStart = $vHourTemp[0]
$vHourEnd = $vHourTemp[1]
If (($_.Tags.PowerOnDaysOfWeek -like $vCurrentDayofWeek) -and ( ($vCurrentHour -ge $vHourStart) -and ($vCurrentHour -le $vHourEnd))){
$vAction = "On"
$vAction = "Off"
Write-Output $vAction
Write-Output $_.ResourceGroupName
Write-Output $vEnv
Write-Output "Action: " $vaction
Write-Output "rg....: " $_.ResourceGroupName
VMGroup $vAction $_.ResourceGroupName

Wrapping it up

In this article, we went over a script that was designated to start and stop VMs based on tags associated at the resource group level. The script is dynamic and uses a list of unique strings to create a list of servers.

The entire runbook code can be found on GitHub here.

Featured image: Shutterstock

Anderson Patricio

Anderson Patricio is a Canadian MVP in Cloud and Datacenter Management, and Office Server and Services, besides of the Microsoft Award he also holds a Solutions Master (MCSM) in Exchange, CISSP and several other certifications. Anderson contributes to the Microsoft Community with articles, tutorials, blog posts, twitter, forums and book reviews. He is a regular contributor here at,, and Anderson (Portuguese).

Published by
Anderson Patricio

Recent Posts

Key to success: Tracking down and unlocking locked files in Windows

Locked files in Windows can be a maddening experience. Thankfully, it is usually relatively easy to get a locked file…

3 hours ago

‘Made By Google’ 2019: Pixel 4 and Pixel 4 XL are finally official

The release of Google’s much-awaited new smartphones is official. The tech giant has unveiled the Pixel 4 and Pixel 4…

7 hours ago

COBIT 2019: An effective governance framework for IT pros

Every business with IT as part of its foundation needs a comprehensive governance strategy. This is where COBIT 2019 comes…

10 hours ago

WAN optimization: Fast tips to get your network up to speed

A wide-area network gradually slows down over time for several reasons. These WAN optimization tips can help you regain some…

1 day ago

Review: Self-service key recovery solution Specops Key Recovery

Helpdesks spend way too much of their time unlocking users’ computers. Specops Key Recovery is a self-service solution for this…

1 day ago

CSI: Enterprise Software (Episode 23): Follow the breadcrumbs

Managing software in today’s enterprise is often like working a crime scene. But by following the breadcrumbs, you can keep…

1 day ago