Automating Azure VM Start/Stop from Local Scripts
Introduction
Manually starting and stopping Azure VMs is time-consuming.
With Azure Automation you can use Runbooks and Webhooks to control VMs automatically, and trigger them from your local PC.
Step 1. Configure Automation Account and Runbook
1.1 Create Automation Account
- In Azure Portal, search Automation Accounts → Create.
- Fill in:
- Resource Group:
auto-rg
- Name:
vm-auto-control
- Region: choose same as VM if possible
1.2 Create a Runbook
- Go to the Automation Account → Runbooks → Create a runbook.
- Name:
Start-Stop-VM
- Type: PowerShell
- Runtime version : 7.2
- Create – Edit in portal
- Paste the script below:
param([object]$WebhookData) $ErrorActionPreference = 'Stop' # Parse JSON body $body = if($WebhookData.RequestBody){$WebhookData.RequestBody}else{$WebhookData} $p = $body | ConvertFrom-Json $op = ($p.operation+"").ToLower().Trim() if($op -notin @('start','stop')){throw "operation must be start|stop"} $t = if($p.target){$p.target}elseif($p.targets){$p.targets[0]}else{throw "target required"} $sub,$rg,$vm = $t.subscriptionId,$t.resourceGroup,$t.vmName if(!$sub -or !$rg -or !$vm){throw "subscriptionId, resourceGroup, vmName required"} Connect-AzAccount -Identity | Out-Null Set-AzContext -Subscription $sub | Out-Null if($op -eq 'start'){Start-AzVM -Name $vm -ResourceGroupName $rg -NoWait:$false | Out-Null} if($op -eq 'stop'){Stop-AzVM -Name $vm -ResourceGroupName $rg -Force -NoWait:$false | Out-Null}
- Save → Publish the runbook.
1.3 Create a Webhook
- Open the Runbook → Webhook → Create new webhook.
- Copy the Webhook URL (keep it safe, it’s the trigger endpoint).
- Set expiry as required → Enable.
Step 2. Assign Permissions to the Runbook Identity
Runbook runs under the Automation Account’s Managed Identity.
We need to grant Virtual Machine Contributor role to this identity.
- Scope: Single VM
- Go to the VM → Access control (IAM).
- Add role assignment:
- Role =
Virtual Machine Contributor
- Assign access to = Managed identity
- Select your Automation Account
- Role =
- Scope: Subscription (all VMs)
- Go to Subscriptions → Access control (IAM).
- Add role assignment with the same settings.
Step 3. Manage VM Information with vms.json
For better flexibility, store VM metadata in a local JSON file (vms.json
).
This allows you to manage multiple VMs (Windows, Linux, servers, clients) in one place, without hardcoding values in scripts.
vms.json Example
{ "vms": [ { "vmName": "Windows-01", "deviceType": "Windows Client", "subscriptionId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "resourceGroup": "VSRG", "webhookUrl": "https://prod-00.westus.logic.azure.com/.../webhook" }, { "vmName": "Linux-01", "deviceType": "Linux Server", "subscriptionId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "resourceGroup": "DevRG", "PublicIP": "", "webhookUrl": "https://prod-00.westus.logic.azure.com/.../webhook" } ] }
Step 4. Build Local Trigger Scripts
Now we can call the Runbook via webhook.
We’ll create a PowerShell script and a .bat
wrapper for convenience.
4.1 PowerShell Script (vm_webhook.ps1)
param([string]$Config="vms.json") $ErrorActionPreference="Stop" if(-not(Test-Path $Config)){Write-Host "Config not found: $Config";exit 1} try{$cfg=(Get-Content -LiteralPath $Config -Raw -Encoding UTF8)|ConvertFrom-Json}catch{Write-Host "Bad JSON: $_";exit 1} $vms = if($cfg.vms){$cfg.vms}else{$cfg}; if(-not $vms -or $vms.Count -eq 0){Write-Host "No VMs in config.";exit 1} while($true){ Write-Host "`n==== VM List ====" for($i=0;$i -lt $vms.Count;$i++){ $vm=$vms[$i] $line="[{0}] {1}" -f ($i+1),$vm.vmName if($vm.publicIp){$line+=" (PublicIP: {0})" -f $vm.publicIp} Write-Host $line } Write-Host "[0] Exit" $n=Read-Host "Pick VM number" if($n -eq "0" -or $n -eq "exit"){break} if($n -notmatch '^\d+$' -or [int]$n -lt 1 -or [int]$n -gt $vms.Count){Write-Host "Invalid VM.";continue} $dev=$vms[[int]$n-1] Write-Host "Operation: [1] start (default), [2] stop" $opSel=Read-Host "Pick op number" $op = if($opSel -eq "2"){"stop"}else{"start"} if(-not $dev.webhookUrl){Write-Host "Missing webhookUrl on VM.";continue} $payload=@{ operation=$op targets=@(@{subscriptionId=$dev.subscriptionId;resourceGroup=$dev.resourceGroup;vmName=$dev.vmName}) } try{ Invoke-RestMethod -Method POST -Uri $dev.webhookUrl -Body ($payload|ConvertTo-Json -Compress) -ContentType "application/json" -TimeoutSec 10 | Out-Null $ipNote=if($dev.publicIp){" ($($dev.publicIp))"}else{""} Write-Host ("OK: {0} -> {1}{2}" -f $op,$dev.vmName,$ipNote) }catch{ Write-Host ("FAIL: {0} -> {1} : {2}" -f $op,$dev.vmName,$_.Exception.Message) } }
4.2 Batch Wrapper (run.bat)
@echo off setlocal set PS=powershell -ExecutionPolicy Bypass -NoProfile %PS% -File "%~dp0vm_webhook.ps1" -Config "%~dp0vms.json" endlocal
- Double-click
run.bat
. - Select an option → script will send webhook request.
- Runbook executes in Azure → VM starts/stops accordingly.
Recent Comments