Cost optimization – Azure VM’s

Cost optimization – Azure VM’s

Cost optimization has become increasingly important with the increased use of cloud services.

Cost optimization is a complex, timeconsuming task – nevertheless the task has never been more


Microsoft does provide companies with many different utilities/tools to monitor/control the cost of your

overall Azure consumption.

From our experience the majority of those tools, is managed within the single solution/resource, meaning

if you want to power off 10 computers at 18:00 in the evening, that job has to be set up 10 times.

This guide will illustrate a method to power off virtual machines, that is not needed

when your employee is no longer at work, and at the same time power those machines back on, when

your employee is getting back to work the next morning – all controlled using tag on computers.


  • Automation Account
    • Schedules
    • Variables
    • Runbooks
    • Azure Run As Account
  • Powershell
  • Intermediate understanding of Access Control (IAM)

Automation Account

First step is to create the Automation Account

Create the Automation Account.

If possible, you should select “Private access” and assign a private endpoint to your Automation Account.

This guide will not cover this part, as the focus is not Automation Accounts in depth.

“The Service Account”

Go to your newly deployed Automation Account and select “Run as accounts”

Please notice, that this account is automatically granted the contributor role on your subscription. In short

this means you will be able to do almost everything without any issues. From a security perspective, this

is, for this specific solution, way too many rights to accomplish what we want the Automation Account to


Basically by doing this, an app registration is now created within your Azure Active Directory.

The RBAC on your subscription

As just mentioned, this managed identity, was assigned with the “Contributor” role on your subscription.

We do not need this identity to contribute with much – we just want it to start/stop virtual machines.

Go to your subscription, and remove it

Next you need to add a custom role, which can start/stop/restart virtual machines

Insert this code in the editor – replace “YOURSUBSCRIPTIONID” before inserting

"properties": {
"roleName": "Virtual Machine Operators",
"description": "Users in this group will be able to start, stop, deallocate and restart virtual machines",
"assignableScopes": [ "/subscriptions/YOURSUBSCRIPTIONID" ],
"permissions": [
"actions": [ "Microsoft.Compute/virtualMachines/*/read",
"Microsoft.Compute/virtualMachines/start/action", "Microsoft.Compute/virtualMachines/powerOff/action", "Microsoft.Compute/virtualMachines/deallocate/action" ],
"notActions": [],
"dataActions": [],
"notDataActions": []

Find your custom role (Virtual Machine Operators) and make the account created in previous step,

member of this group.

Back to the Automation Account

Create the schedules you need for this solution. You need 1 schedule to power off virtual machines, and 1

schedule to power on virtual machines

Please notice this specific setup will start computers at 07:00 every morning, and power off computers at

17:00 every afternoon.

Next you need to set up the 2 runbooks, which will eventually power off/on your Virtual Machines based

on your tagging.

The power off runbook

$Computertag = Get-AutomationVariable -Name "ComputerTag"
$Conn = Get-AutomationConnection -Name AzureRunAsConnection
Connect-AzAccount -ServicePrincipal -Tenant $Conn.TenantID -ApplicationId $Conn.ApplicationID -CertificateThumbprint $Conn.CertificateThumbprint
$VirtualMachines = (Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines" | Where-Object {$_.Tags.Keys -Contains $Computertag}).Name
Foreach ($Machine in $VirtualMachines) {
$Running = (Get-AzVM -status -Name $Machine).PowerState
if (($Running -notmatch "VM deallocated") -and ($Running -notmatch "VM Stopped"))

$Resourcegroup = (Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines" -Name $Machine).ResourceGroupName
Stop-AzVM -ResourceGroupName $resourcegroup -Name $Machine -Force

After this, you need to assign a schedule to this runbook

The power on runbook

$Computertag = Get-AutomationVariable -Name "ComputerTag"
$Conn = Get-AutomationConnection -Name AzureRunAsConnection
Connect-AzAccount -ServicePrincipal -Tenant $Conn.TenantID -ApplicationId $Conn.ApplicationID -CertificateThumbprint $Conn.CertificateThumbprint
$VirtualMachines = (Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines" | Where-Object {$_.Tags.Keys -Contains $Computertag}).Name
Foreach ($Machine in $VirtualMachines) {

$Running = (Get-AzVM -status -Name $Machine).PowerState
if ($Running -notmatch "VM running")

$Resourcegroup = (Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines" -Name $Machine).ResourceGroupName
Start-AzVM -ResourceGroupName $resourcegroup -Name $Machine


Add a variable

Please pay attention to the statement, that will check for the tag on the virtual machine. The statement

has a “contains” expression.

If you have tagged other virtual machines with anything with “TESTTAG-…”, these VM’s will also be

affected by this automated power off/on runbooks.

The result

In my demolab I have 2 VM’s; VM1 & VM2

Both machines are tagged “TESTTAG”

When I run “SchedulePowerONVM” runbook, this is the result

When modifying tag on one of the VM’s to “TESTAG”, and afterwards run “SchedulePowerOFFVM”

runbook, this is the result

Stopped vs Stopped (deallocated)

This way of powering off virtual machines, will make the VM enter state “Stopped (deallocated)”.

If your VM’s, for whatever reason, may not enter this state, you should add “-StayProvisioned” parameter

in the power off runbook like below

Stop-AzVM -ResourceGroupName $resourcegroup -Name $Machine -StayProvisioned -Force

Status Stopped

When you are logged in to the operating system of an Azure VM, you can issue a command to shut down

the server. This will kick you out of the OS and stop all processes, but will maintain the allocated

hardware (including the IP addresses currently assigned). If you find the VM in the Azure console, you’ll

see the state listed as “Stopped”.

The biggest thing you need to know about this state is that you are still

being charged by the hour for this instance.

Status Stopped (deallocated)

The other way to stop your virtual machine is through Azure itself, whether that’s through the console,

Powershell or the Azure CLI. When you stop a VM through Azure, rather than through the OS, it goes into

a “Stopped (deallocated)” state.

This means that any non-static public IP’s will be released, but you’ll also

stop paying for the VM’s compute costs.

Table of Contents

Share this post
Search blog posts
Modern Workplace consultant and a Microsoft MVP in Enterprise Mobility.
Modern Workplace consultant and a Microsoft MVP in Windows and Devices for IT.

Infrastructure architect with focus on Modern Workplace and Microsoft 365 security.

Cloud & security specialist with focus on Microsoft backend products and cloud technologies.

Cloud & security specialist with focus on Microsoft 365.

Cloud & Security Specialist, with a passion for all things Cybersecurity

Cloud and infrastructure security specialist with background in networking.

Infrastructure architect with focus on design, implementation, migration and consolidation.

Infrastructure consultant with focus on cloud solutions in Office365 and Azure.

follow us in feedly

Follow on SoMe