FinOps in Action: Programmatic Resource Scheduling with Azure Automation and PowerShell

Introduction

Managing costs in Azure is a critical aspect of cloud governance. One of the most effective ways to reduce expenses is to ensure your Virtual Machines aren’t running when they don’t need to be. While Azure offers a basic auto-shutdown feature in the portal, implementing a more robust, flexible auto-shutdown solution using Azure Automation and PowerShell scripts can give you granular control over your resources.

In this article, I’ll walk you through implementing auto-shutdown schedules for Azure VMs using PowerShell scripts in Azure Automation, including troubleshooting common issues you might encounter along the way.

Why Use Azure Automation for VM Scheduling?

The built-in Azure VM auto-shutdown feature is useful for simple scenarios, but it has limitations:

  • It only handles shutdown (not startup)

  • Configuration must be done individually for each VM

  • Limited scheduling options

Azure Automation, on the other hand, provides:

  • Both shutdown and startup capabilities

  • Centralised management for multiple VMs

  • Advanced scheduling options

  • Tag-based scheduling for different groups of VMs

  • Exception handling and notifications

Step 1: Creating an Azure Automation Account

The first step is to create an Azure Automation account:

  1. Sign in to the Azure portal

  2. Navigate to “Automation Accounts”

  1. Click “+ Create”

  2. Fill in the required details (subscription, resource group, name, region)

  1. Make sure to enable “System-assigned managed identity” (this is crucial for authentication)

  1. Click “Create”

Step 2: Creating a PowerShell Runbook for VM Shutdown

Once your Automation account is created:

  1. Go to your Automation account

  2. Under “Process Automation”, click “Runbooks”

  1. Click “+ Create a runbook”

  2. Enter a name for your runbook (e.g., “VMAutoShutdown”)

  3. Set “Runbook type” to “PowerShell”

  4. Provide a description (optional)

  5. Click “Create”

Step 3: Implementing the PowerShell Script

When your runbook opens in edit mode, add the following script:

# Auto-shutdown script for Azure VMs
# Parameters that can be customized
param(
    [Parameter(Mandatory=$false)]
    [String] $ResourceGroupName = "",

    [Parameter(Mandatory=$false)]
    [String] $VMName = ""
)# Use Managed Identity for authentication
try {
    Write-Output "Logging in to Azure using Managed Identity"
    Connect-AzAccount -Identity
    Write-Output "Successfully logged into Azure"
}
catch {
    Write-Error -Message $_.Exception
    throw $_.Exception
}# Get specific VM or all VMs in a resource group or subscription
if ($VMName -ne "" -and $ResourceGroupName -ne "") {
    $VMs = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName
}
elseif ($ResourceGroupName -ne "") {
    $VMs = Get-AzVM -ResourceGroupName $ResourceGroupName
}
else {
    $VMs = Get-AzVM
}# Iterate through VMs and shut them down
foreach ($VM in $VMs) {
    Write-Output "Shutting down VM: $($VM.Name)"

    # Shut down the VM
    $StopResult = Stop-AzVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -Force

    if ($StopResult.Status -eq "Succeeded") {
        Write-Output "VM $($VM.Name) has been successfully shut down"
    }
    else {
        Write-Error "Failed to shut down VM $($VM.Name)"
    }
}

This script handles authentication using the system-assigned managed identity of your Automation account, then identifies and shuts down the specified VMs.

Step 4: Testing and Publishing Your Runbook

  1. Save your script by clicking “Save”

  1. Test the runbook by clicking “Test pane”

  2. Fill in the parameters (optional) and click “Start”

  1. Check the output to ensure it runs as expected

  1. If your test is successful, publish the runbook by clicking “Publish”

Step 5: Enabling System-Assigned Managed Identity

Managed identity is crucial for authentication. If you didn’t enable it during account creation:

  1. Go to your Automation account

  2. Select “Identity” in the left menu

  3. Under the “System assigned” tab, switch “Status” to “On”

  4. Click “Save”

Step 6: Assigning the Required Permissions

Your managed identity needs permissions to manage VMs:

  1. In the same Identity page, click on “Azure role assignments”

  1. Click “+ Add role assignment”

  2. Select the subscription

  3. Choose the “Virtual Machine Contributor” role

  4. Click “Save”

Step 7: Creating a Schedule for Your Runbook

Now, set up when your runbook should run:

  1. Go to your published runbook

  2. Click “Schedules” in the left menu

  3. Click “+ Add a schedule”

  1. Select “Create a new schedule”

  2. Enter a name and description for your schedule

  3. Set the start time, time zone, and recurrence (daily, weekly, etc.)

  4. Click “Create”

Suggested Schedule for Auto-Shutdown

SettingRecommended Value
NameAutoShutdown-Daily
DescriptionAutomatically shuts down VMs at off-hours
Start Time19:00 (7 PM) or 20:00 (8 PM)
Time ZoneMatch your region (e.g., W. Central Africa Standard Time)
RecurrenceDaily

Step 8: Setting Parameters for the Scheduled Run (Optional)

If you want to target specific VMs or resource groups:

  1. After creating the schedule, you’ll be prompted to configure parameters

  2. Fill in the parameters like ResourceGroupName or VMName as needed

  1. Click “OK” to finish setting up the schedule

Creating a Companion Startup Runbook

For a complete solution, create a similar runbook for VM startup using the same approach, but replace the Stop-AzVM command with Start-AzVM. Schedule this runbook to run at your desired VM startup time.

Azure VM Auto-Startup Runbook (Using Managed Identity)

powershellCopyEdit# Auto-start script for Azure VMs
param(
    [Parameter(Mandatory=$false)]
    [String] $ResourceGroupName = "",

    [Parameter(Mandatory=$false)]
    [String] $VMName = ""
)

# Use Managed Identity for authentication
try {
    Write-Output "Logging in to Azure using Managed Identity"
    Connect-AzAccount -Identity
    Write-Output "Successfully logged into Azure"
}
catch {
    Write-Error -Message $_.Exception
    throw $_.Exception
}

# Get specific VM or all VMs in a resource group or subscription
if ($VMName -ne "" -and $ResourceGroupName -ne "") {
    $VMs = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName
}
elseif ($ResourceGroupName -ne "") {
    $VMs = Get-AzVM -ResourceGroupName $ResourceGroupName
}
else {
    $VMs = Get-AzVM
}

# Iterate through VMs and start them
foreach ($VM in $VMs) {
    Write-Output "Starting VM: $($VM.Name)"

    $StartResult = Start-AzVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name

    if ($StartResult.Status -eq "Succeeded") {
        Write-Output "VM $($VM.Name) has been successfully started"
    }
    else {
        Write-Error "Failed to start VM $($VM.Name)"
    }
}

SettingValue
NameAutoStartup-Daily
DescriptionAutomatically starts VMs at business hours
Start Time08:00 or 09:00 AM
Time ZoneYour local time zone
RecurrenceDaily

Troubleshooting Common Issues

Issue: “Add a schedule” is disabled

If the “+ Add a schedule” button is greyed out or disabled, ensure that:

  • Your runbook is published (not in draft or edit mode)

  • You have refreshed the page after publishing

  • You have the necessary permissions

Issue: Authentication Errors

If you encounter authentication errors:

  1. Verify that the managed identity is enabled

  2. Ensure the managed identity has the correct role assignments

  3. Make sure you’re using the correct authentication method in your script

Issue: PowerShell Errors

Check for:

  • Correct syntax in your PowerShell script

  • Appropriate module versions (Az modules)

  • Properly formatted parameters

Advanced: Tag-Based Scheduling

For more flexibility, you can implement a tag-based approach where VMs are shut down based on tags:

Create a new runbook with a script that:

  • Finds all VMs with a specific tag (e.g., “AutoShutdownSchedule”)

  • Parses the schedule from the tag value

  • Shuts down or starts VMs based on the schedule

Tag your VMs with shutdown schedules (e.g., “8PM -> 6AM”)

Schedule this runbook to run regularly (e.g., hourly)

Conclusion

Implementing auto-shutdown schedules using Azure Automation and PowerShell offers a powerful, flexible way to optimise your Azure costs. By ensuring your VMs are only running when needed, you can significantly reduce your cloud spending without sacrificing availability.

The initial setup might seem complex, but once configured, this solution provides a reliable, centralised way to manage VM schedules across your entire Azure environment. The time invested in setting up proper automation will quickly pay for itself in reduced cloud costs.

Combined Start/Stop Azure VM Runbook Script

powershellCopyEditparam(
    [Parameter(Mandatory=$true)]
    [ValidateSet("Start", "Stop")]
    [string] $Action,

    [Parameter(Mandatory=$false)]
    [string] $ResourceGroupName = "",

    [Parameter(Mandatory=$false)]
    [string] $VMName = ""
)

# Authenticate using Managed Identity
try {
    Write-Output "Logging in to Azure using Managed Identity..."
    Connect-AzAccount -Identity
    Write-Output "Successfully logged in."
}
catch {
    Write-Error -Message $_.Exception
    throw $_.Exception
}

# Retrieve VMs based on parameters
if ($VMName -ne "" -and $ResourceGroupName -ne "") {
    $VMs = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName
}
elseif ($ResourceGroupName -ne "") {
    $VMs = Get-AzVM -ResourceGroupName $ResourceGroupName
}
else {
    $VMs = Get-AzVM
}

# Perform action (Start or Stop)
foreach ($VM in $VMs) {
    if ($Action -eq "Stop") {
        Write-Output "Stopping VM: $($VM.Name)"
        $Result = Stop-AzVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -Force
    }
    elseif ($Action -eq "Start") {
        Write-Output "Starting VM: $($VM.Name)"
        $Result = Start-AzVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name
    }

    # Output result
    if ($Result.Status -eq "Succeeded") {
        Write-Output "VM $($VM.Name) $Action completed successfully."
    }
    else {
        Write-Error "Failed to $Action VM $($VM.Name)."
    }
}

Example Usage When Creating a Schedule

To start VMs:

  • Set Action to Start

  • Optionally set ResourceGroupName and/or VMName

To stop VMs:

  • Set Action to Stop

  • Optionally set ResourceGroupName and/or VMName

Thank you for your Time. Never forget to delete your Rg’s if its for learning purpose

0
Subscribe to my newsletter

Read articles from Chigozie Ozoemena directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Chigozie Ozoemena
Chigozie Ozoemena

Hi there! 👋 I'm Daniel Ozoemena, a passionate Cloud Solution Architect and DevOps Engineer dedicated to building scalable, secure, and innovative cloud solutions. With hands-on experience in Azure, AWS, and Google Cloud Platform, I specialize in deploying infrastructure as code, automating workflows, and optimizing system reliability. Driven by a love for problem-solving, I constantly explore new technologies and best practices to deliver impactful results. Beyond the cloud, I enjoy mentoring, blogging about tech insights, and contributing to open-source projects. When I'm not automating deployments or creating secure virtual networks, you can find me playing chess, learning about AI, or brainstorming solutions to real-world challenges. Let’s connect and grow together on this tech journey! 🚀