Skip to content

PowerShell

๐Ÿ’ฐ 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

โฒ๏ธ Configuring UK Regional Settings on Windows Servers with PowerShell

When building out cloud-hosted or automated deployments of Windows Servers, especially for UK-based organisations, itโ€™s easy to overlook regional settings. But these seemingly small configurations โ€” like date/time formats, currency symbols, or keyboard layouts โ€” can have a big impact on usability, application compatibility, and user experience.

In this post, Iโ€™ll show how I automate this using a simple PowerShell script that sets all relevant UK regional settings in one go.


๐Ÿ” Why Regional Settings Matter

Out-of-the-box, Windows often defaults to en-US settings:

  • Date format becomes MM/DD/YYYY
  • Decimal separators switch to . instead of ,
  • Currency symbols use $
  • Time zones default to US-based settings
  • Keyboard layout defaults to US (which can be infuriating!)

For UK-based organisations, this can:

  • Cause confusion in logs or spreadsheets
  • Break date parsing in scripts or apps expecting DD/MM/YYYY
  • Result in the wrong characters being typed (e.g., @ vs ")
  • Require manual fixing after deployment

Automating this ensures consistency across environments, saves time, and avoids annoying regional mismatches.


๐Ÿ”ง Script Overview

I created a PowerShell script that:

  • Sets the system locale and input methods
  • Configures UK date/time formats
  • Applies the British English language pack (if needed)
  • Sets the time zone to GMT Standard Time (London)

The script can be run manually, included in provisioning pipelines, or dropped into automation tools like Task Scheduler or cloud-init processes.


โœ… Prerequisites

To run this script, you should have:

  • Administrator privileges
  • PowerShell 5.1+ (default on most supported Windows Server versions)
  • Optional: Internet access (if language pack needs to be added)

๐Ÿ”น The Script: Set-UKRegionalSettings.ps1

# Set system locale and formats to English (United Kingdom)
Set-WinSystemLocale -SystemLocale en-GB
Set-WinUserLanguageList -LanguageList en-GB -Force
Set-Culture en-GB
Set-WinHomeLocation -GeoId 242
Set-TimeZone -Id "GMT Standard Time"

# Optional reboot prompt
Write-Host "UK regional settings applied. A reboot is recommended for all changes to take effect."

๐Ÿš€ How to Use It

โœˆ๏ธ Option 1: Manual Execution

  1. Open PowerShell as Administrator
  2. Run the script:
.\Set-UKRegionalSettings.ps1

๐Ÿ”ข Option 2: Include in Build Pipeline or Image

For Azure VMs or cloud images, consider running this as part of your deployment process via:

  • Custom Script Extension in ARM/Bicep
  • cloud-init or Terraform provisioners
  • Group Policy Startup Script

โšก Quick Tips

  • Reboot after running to ensure all settings apply across UI and system processes.
  • For non-UK keyboards (like US physical hardware), you may also want to explicitly set InputLocale.
  • Want to validate the settings? Use:
Get-WinSystemLocale
Get-Culture
Get-WinUserLanguageList
Get-TimeZone

๐Ÿ“‚ Registry Verification: Per-User and Default Settings

Registry Editor Screenshot

If you're troubleshooting or validating the configuration for specific users, regional settings are stored in the Windows Registry under:

๐Ÿ‘ค For Each User Profile

HKEY_USERS\<SID>\Control Panel\International

You can find the user SIDs by looking under HKEY_USERS or using:

Get-ChildItem Registry::HKEY_USERS

๐Ÿงต For New Users (Default Profile)

HKEY_USERS\.DEFAULT\Control Panel\International

This determines what settings new user profiles inherit on first logon.

You can script changes here if needed, but always test carefully to avoid corrupting profile defaults.


๐ŸŒŸ Final Thoughts

Small tweaks like regional settings might seem minor, but they go a long way in making your Windows Server environments feel localised and ready for your users.

Automating them early in your build pipeline means one less thing to worry about during post-deployment configuration.

Let me know if you want a version of this that handles multi-user scenarios or works across multiple OS versions!

Share on Share on