Skip to content

๐Ÿ’ฐ Saving Azure Costs with Scheduled VM Start/Stop using Custom Azure Automation Runbooks

As part of my ongoing commitment to FinOps practices, I've implemented several strategies to embed cost-efficiency into the way we manage cloud infrastructure. One proven tactic is scheduling virtual machines to shut down during idle periods, avoiding unnecessary spend.

In this post, Iโ€™ll share how Iโ€™ve built out custom Azure Automation jobs to schedule VM start and stop operations. Rather than relying on Microsoftโ€™s pre-packaged solution, Iโ€™ve developed a streamlined, purpose-built PowerShell implementation that provides maximum flexibility, transparency, and control.


โœ๏ธ Why I Chose Custom Runbooks Over the Prebuilt Solution

Microsoft provides a ready-made โ€œStart/Stop VMs during off-hoursโ€ solution via the Automation gallery. While functional, itโ€™s:

  • A bit over-engineered for simple needs,
  • Relatively opaque under the hood, and
  • Not ideal for environments where control and transparency are priorities.

My custom jobs:

  • Use native PowerShell modules within Azure Automation,
  • Are scoped to exactly the VMs I want via tags,
  • Provide clean logging and alerting, and
  • Keep things simple, predictable, and auditable.

๐Ÿ› ๏ธ Step 1: Set Up the Azure Automation Account

๐Ÿ”— Official docs: Create and manage an Azure Automation Account

  1. Go to the Azure Portal and search for Automation Accounts.
  2. Click + Create.
  3. Fill out the basics:
  4. Name: e.g. vm-scheduler
  5. Resource Group: Create new or select existing
  6. Region: Preferably where your VMs are located
  7. Enable System-Assigned Managed Identity
  8. Once created, go to the Automation Account and ensure the following modules are imported using the Modules blade in the Azure Portal:
  9. Az.Accounts
  10. Az.Compute

โœ… Tip: These modules can be added from the gallery in just a few clicks via the UIโ€”no scripting required.

๐Ÿ’ก Prefer scripting? You can also install them using PowerShell:

Install-Module -Name Az.Accounts -Force
Install-Module -Name Az.Compute -Force
  1. Assign the Virtual Machine Contributor role to the Automation Account's managed identity at the resource group or subscription level.

โš™๏ธ CLI or PowerShell alternatives

# Azure CLI example to create the automation account
az automation account create \
  --name vm-scheduler \
  --resource-group MyResourceGroup \
  --location uksouth \
  --assign-identity

๐Ÿ“… Step 2: Add VM Tags for Scheduling

Apply consistent tags to any VM you want the runbooks to manage.

Key Value
AutoStartStop devserver

You can use the Azure Portal or PowerShell to apply these tags.

โš™๏ธ Tag VMs via PowerShell

$vm = Get-AzVM -ResourceGroupName "MyRG" -Name "myVM"
$vm.Tags["AutoStartStop"] = "devserver"
Update-AzVM -VM $vm -ResourceGroupName "MyRG"

๐Ÿ“‚ Step 3: Create the Runbooks

๐Ÿ”— Official docs: Create a runbook in Azure Automation

โ–ถ๏ธ Create a New Runbook

  1. In your Automation Account, go to Process Automation > Runbooks.
  2. Click + Create a runbook.
  3. Name it something like Stop-TaggedVMs.
  4. Choose PowerShell as the type.
  5. Paste in the code below (repeat this process for the start runbook later).

๐Ÿ”น Runbook Code: Auto-Stop Based on Tags

Param
(    
    [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()]
    [String]
    $AzureVMName = "All",

    [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]
    [String]
    $AzureSubscriptionID = "<your-subscription-id>"
)

try {
    "Logging in to Azure..."
    # Authenticate using the system-assigned managed identity of the Automation Account
    Connect-AzAccount -Identity -AccountId "<managed-identity-client-id>"
} catch {
    Write-Error -Message $_.Exception
    throw $_.Exception
}

$TagName  = "AutoStartStop"
$TagValue = "devserver"

Set-AzContext -Subscription $AzureSubscriptionID

if ($AzureVMName -ne "All") {
    $VMs = Get-AzResource -TagName $TagName -TagValue $TagValue | Where-Object {
        $_.ResourceType -like 'Microsoft.Compute/virtualMachines' -and $_.Name -like $AzureVMName
    }
} else {
    $VMs = Get-AzResource -TagName $TagName -TagValue $TagValue | Where-Object {
        $_.ResourceType -like 'Microsoft.Compute/virtualMachines'
    }
}

foreach ($VM in $VMs) {
    Stop-AzVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -Verbose -Force
}

๐Ÿ”— Docs: Connect-AzAccount with Managed Identity

๐Ÿ”น Create the Start Runbook

Duplicate the above, replacing Stop-AzVM with Start-AzVM.

๐Ÿ”— Docs: Start-AzVM


๐Ÿ”— Docs: Create schedules in Azure Automation

  1. Go to the Automation Account > Schedules > + Add a schedule.
  2. Create two schedules:
  3. DailyStartWeekdays โ€” Recurs every weekday at 07:30
  4. DailyStopWeekdays โ€” Recurs every weekday at 18:30
  5. Go to each runbook > Link to schedule > Choose the matching schedule.

๐Ÿ“Š You can get creative here: separate schedules for dev vs UAT, or different times for different departments.


๐Ÿงช Testing Your Runbooks

You can test each runbook directly in the portal:

  • Open the runbook
  • Click Edit > Test Pane
  • Provide test parameters if needed
  • Click Start and monitor output

This is also a good time to validate:

  • The identity has permission
  • The tags are applied correctly
  • The VMs are in a stopped or running state as expected

๐Ÿ“Š The Results

Even this lightweight automation has produced major savings in our environment. Non-prod VMs are now automatically turned off outside office hours, resulting in monthly compute savings of up to 60% without sacrificing availability during working hours.


๐Ÿง  Ideas for Further Enhancement

  • Pull tag values from a central config (e.g. Key Vault or Storage Table)
  • Add logic to check for active RDP sessions or Azure Monitor heartbeats
  • Alert via email or Teams on job success/failure
  • Track savings over time and visualize them

๐Ÿ’ญ Final Thoughts

If youโ€™re looking for a practical, immediate way to implement FinOps principles in Azure, VM scheduling is a great place to start. With minimal setup and maximum flexibility, custom runbooks give you control without the complexity of the canned solutions.

Have you built something similar or extended this idea further? Iโ€™d love to hear about itโ€”drop me a comment or reach out on LinkedIn.

Stay tuned for more FinOps tips coming soon!

Share on Share on