Simon Vedder

Cloud Engineer · Zürich

Scheduled automation · Bicep

VM Power Management

Start and stop Azure VMs at the times you choose, without maintaining a list of which machines. Each VM carries the name of its schedule as a tag, and one hourly runbook works out what should be running. Built as the alternative to Start/Stop VMs v2.

licenseMITPowerShell7.2+galleryv0.1.6-previewdeployBicepruns inAzure Automationalternative toStart/Stop VMs v2

pwsh · read-only, 18:40 in Zurich, nothing deployed
# Office hours are over. What does the schedule want?
Get-VmPowerPlan -Schedule $office | ft Name,Schedule,Action
Name            Schedule         Action
app-test-01     office-hours-ch  Deallocate
build-agent-03  office-hours-ch  None
lab-win-11      (none)           None
Only tagged machines are touched. Nothing was performed.

Quick start

Try a schedule before you deploy anything

Read-only, against any tenant you can read. No tag has to exist, nothing is deployed and nothing changes. You describe when machines should be up, and look at what that would mean.

Needs Reader on the subscriptions you want to see · Prerequisites

  1. Install the module

    Install-Module AzureVMPowerManagement -AllowPrerelease
  2. Sign in

    Connect-AzAccount
  3. Describe when machines should be up

    A schedule is a name, a time zone and the windows it is up. Check it against a calendar before anything else sees it. Daylight saving shows up in the Note column rather than being smoothed away.

    $office = New-VmPowerSchedule -Name office-hours-ch -TimeZone 'Europe/Zurich' -Weekdays '07:30-18:30'
    $office | Show-VmPowerScheduleCalendar -Days 14
  4. See what it would do to the estate, right now

    One decision per machine, including the ones no rule touches. Action is what it would do, Reason is the short name for why — all of them are listed here. The preview and the real run share one function, so they cannot disagree.

    Get-VmPowerPlan -Schedule $office | Format-Table Name, Schedule, PowerState, Action, Reason

Ready to let it act? Deploy the controller

The alternative

Why not Start/Stop VMs v2

Microsoft's own answer to this is an application you deploy into your own subscription from a GitHub repository: a Function App, a storage account, Application Insights, a Log Analytics workspace and five Logic Apps. It works. These are the places it made me want something else.

Start/Stop VMs v2VM Power Management
Which machines are in scopeA list of subscriptions, resource groups or VM resource IDs in a JSON payload. Nothing keeps it current as machines come and go.A tag on the machine. Membership is recomputed on every run.
Changing a scheduleEdit the recurrence and a JSON request body in the Logic App designer, in code view.One cmdlet. Preview it as a calendar first, then store it.
What the identity may doContributor, per Microsoft's own multi-subscription instructions. That includes installing extensions, which is code execution on every machine in scope.A custom role with four actions: read a VM, start it, deallocate it, list resource groups.
Before it acts for the first timeNothing. The first scheduled run is the real one.It ships disarmed. The first week is a report from the same code path, with every guard evaluated.
GuardsAn exclusion list and an exclusion tag.Exclusion tag, blast radius, dwell time and the armed flag. Every refusal is recorded with its reason.
What lands in your subscriptionFunction App, storage account, Application Insights, Log Analytics workspace, five Logic Apps, a dashboard.An Automation Account, a workspace and a workbook.
MaintenanceNo further development. Two functions are deprecated, auto-update should be switched off, and new versions are pulled from GitHub by hand.Active.

Where v2 wins. It starts and stops tiers in order through the sequencestart and sequencestop tags, it can stop a machine whose CPU stays under a threshold, and it emails you. This does none of those. One more thing to know before v2 lands next to something else: its deployment tags its resource group SolutionName and removes every other tag on those resources.

What it does

What it does

The tag names the schedule

PowerSchedule=office-hours-ch. The schedule itself lives in the Automation Account, so one edit changes every machine that follows it.

Nobody maintains a list

Membership is recomputed from tags on every run. A machine created at 11:00 is managed at 12:00, one deleted at 13:00 stops mattering at 14:00.

Schedules you can read

Authored with a cmdlet, previewed as a calendar, stored under a name a policy can enforce. Not a JSON payload in a designer.

Ships disarmed

Disarmed means it decides everything and touches nothing, so the first week is a report. Arming it is one variable, not a redeployment.

Four guards

Armed or not, blast radius, dwell time, exclusion tag. Every refusal is written down with its reason.

Also finds what was left on

Machines shut down from inside the guest sit on a host doing nothing. They need no schedule and no tag to be reported.

Proof

What was actually run

Against live VMs in a lab, through the deployed runbook on a real schedule. Nothing here is inferred from the code.

  • Disarmed, two stranded machines — reports both, acts on neither
  • Armed, tagged machine — stopped to deallocated, billed to not billed
  • Armed, untagged machine — left alone
  • Armed again five minutes later — held back by the dwell guard, and said so
  • Blast radius below the plan size — whole run refused, nothing performed
  • Two subscriptions, same VM name — context follows the machine
  • Daylight saving, both directions — spring gap shifted and marked, autumn hour taken as standard time

Still unproved: an estate large enough to page Resource Graph, and a deallocation across two subscriptions in one run. Everything found so far

VM power management · Azure Monitor workbook · sample data
The VM power management workbook: whether the controller is armed, what it did over the last seven days, the machines that are powered off and still billed, and why it did nothing to the rest

FAQ

Questions people ask first

Can I see what it would do before deploying anything?

That is the quick start above. Get-VmPowerPlan reads Resource Graph and returns a decision per machine, including the ones no rule touches and why. It needs Reader and nothing else. After deployment the same applies: the controller ships disarmed, so its first week is a report.

Should I use Start/Stop VMs v2 instead?

If you need machines started in a fixed order across tiers, or stopped when their CPU falls below a threshold, yes — this does neither. Otherwise the table above is the honest comparison. The short version: v2 keeps a list of machines and hands its identity Contributor; this keeps a tag on the machine and hands its identity four actions. Microsoft also states there will be no further development of v2.

Why not just call the Azure VM scheduler?

Because the half that repeats is not there yet. Microsoft.ComputeSchedule is generally available for one-off batched Start, Deallocate and Hibernate, and it handles throttling and retries properly. The recurring half is autoActions, and on 13 September 2026 that resource type still did not resolve in a normal subscription at any API version, preview or not: the provider lists its operations, a call returns InvalidResourceType. So the controller owns the recurrence, and hands the power operation to Azure.

You also publish a Logic App that deallocates on an activity-log alert. Which one?

The Logic App reacts to one event — somebody shut a machine down — and deallocates it within minutes. If the stranded machine is your only problem, take that. This runs hourly instead, and also does the schedules, the guards and the record.

Does it save money?

Machines that are off cost no compute, so yes, as a side effect. But this is a scheduling tool and not a cost tool: it tells you what it did, per machine, with the reason, and puts no currency figure on it. Pricing per size, region and licence model is not something to guess at, and a number that looks precise and is invented is worse than none.

Agreeing the schedule is the hard part

Switching a machine off is one command. Which machines may be, who notices when they are, and what the schedule says on a holiday is the conversation. I run that as the first step and stay for the deployment.

Get in touch