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

Table of contents
- Introduction
- Why Use Azure Automation for VM Scheduling?
- Step 1: Creating an Azure Automation Account
- Step 2: Creating a PowerShell Runbook for VM Shutdown
- Step 3: Implementing the PowerShell Script
- Step 4: Testing and Publishing Your Runbook
- Step 5: Enabling System-Assigned Managed Identity
- Step 6: Assigning the Required Permissions
- Step 7: Creating a Schedule for Your Runbook
- Step 8: Setting Parameters for the Scheduled Run (Optional)
- Creating a Companion Startup Runbook
- Troubleshooting Common Issues
- Issue: “Add a schedule” is disabled
- Issue: Authentication Errors
- Issue: PowerShell Errors
- Advanced: Tag-Based Scheduling
- Conclusion

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:
Sign in to the Azure portal
Navigate to “Automation Accounts”
Click “+ Create”
Fill in the required details (subscription, resource group, name, region)
- Make sure to enable “System-assigned managed identity” (this is crucial for authentication)
- Click “Create”
Step 2: Creating a PowerShell Runbook for VM Shutdown
Once your Automation account is created:
Go to your Automation account
Under “Process Automation”, click “Runbooks”
Click “+ Create a runbook”
Enter a name for your runbook (e.g., “VMAutoShutdown”)
Set “Runbook type” to “PowerShell”
Provide a description (optional)
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
- Save your script by clicking “Save”
Test the runbook by clicking “Test pane”
Fill in the parameters (optional) and click “Start”
- Check the output to ensure it runs as expected
- 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:
Go to your Automation account
Select “Identity” in the left menu
Under the “System assigned” tab, switch “Status” to “On”
Click “Save”
Step 6: Assigning the Required Permissions
Your managed identity needs permissions to manage VMs:
- In the same Identity page, click on “Azure role assignments”
Click “+ Add role assignment”
Select the subscription
Choose the “Virtual Machine Contributor” role
Click “Save”
Step 7: Creating a Schedule for Your Runbook
Now, set up when your runbook should run:
Go to your published runbook
Click “Schedules” in the left menu
Click “+ Add a schedule”
Select “Create a new schedule”
Enter a name and description for your schedule
Set the start time, time zone, and recurrence (daily, weekly, etc.)
Click “Create”
Suggested Schedule for Auto-Shutdown
Setting | Recommended Value |
Name | AutoShutdown-Daily |
Description | Automatically shuts down VMs at off-hours |
Start Time | 19:00 (7 PM) or 20:00 (8 PM) |
Time Zone | Match your region (e.g., W. Central Africa Standard Time ) |
Recurrence | Daily |
Step 8: Setting Parameters for the Scheduled Run (Optional)
If you want to target specific VMs or resource groups:
After creating the schedule, you’ll be prompted to configure parameters
Fill in the parameters like ResourceGroupName or VMName as needed
- 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)"
}
}
🕒 Recommended Schedule for Startup Runbook
Setting | Value |
Name | AutoStartup-Daily |
Description | Automatically starts VMs at business hours |
Start Time | 08:00 or 09:00 AM |
Time Zone | Your local time zone |
Recurrence | Daily |
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:
Verify that the managed identity is enabled
Ensure the managed identity has the correct role assignments
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/orVMName
To stop VMs:
Set Action to
Stop
Optionally set
ResourceGroupName
and/orVMName
Thank you for your Time. Never forget to delete your Rg’s if its for learning purpose
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! 🚀