When to use it
RDP or SSH is broken, a firewall rule locked you out, or you need to run the same check on twenty VMs. Run Command sends a script to the VM through the Azure VM agent and returns the output. You do not need network access to the VM, a public IP or Bastion.
It needs:
- The Azure VM agent running and reporting Ready.
- Outbound access from the VM to the Azure platform so the agent can fetch the script and report back.
- The
Microsoft.Compute/virtualMachines/runCommand/actionpermission (for example, Virtual Machine Contributor). Plain Reader cannot run commands.
Scripts run as SYSTEM on Windows and root on Linux.
Caution
Anyone with Run Command permission has full control of the guest OS. Treat that role assignment like local admin and review who has it.
Two flavours
| Action Run Command | Managed Run Command | |
|---|---|---|
| CLI | az vm run-command invoke | az vm run-command create / show / delete |
| Best for | Quick, one-off checks | Longer scripts, async runs, keeping a record |
| Output | Returned in the response (truncated) | Instance view, or uploaded to a blob |
| Concurrency | One at a time per VM | Several named commands per VM |
| Stored on VM resource | No | Yes, until you delete it |
Check the current limits (output size, timeout) in the Microsoft documentation before relying on them for long jobs.
Action Run Command: everyday commands
Windows
az vm run-command invoke -g rg-app -n vm-win01 \
--command-id RunPowerShellScript \
--scripts "Get-Service | Where-Object Status -eq 'Stopped' | Select-Object -First 20 Name"
Run a local script file:
az vm run-command invoke -g rg-app -n vm-win01 \
--command-id RunPowerShellScript --scripts @check-disk.ps1
Pass parameters (the script declares a param() block):
az vm run-command invoke -g rg-app -n vm-win01 \
--command-id RunPowerShellScript \
--scripts 'param([string]$svc) Get-Service -Name $svc' \
--parameters "svc=W3SVC"
Linux
az vm run-command invoke -g rg-app -n vm-lnx01 \
--command-id RunShellScript \
--scripts "df -h; free -m; systemctl --failed --no-pager"
Read just the output
The response is JSON. Pull out the message text:
az vm run-command invoke -g rg-app -n vm-lnx01 --command-id RunShellScript \
--scripts "uptime" --query "value[0].message" -o tsv
Built-in command IDs
List what your region offers:
az vm run-command list --location westeurope -o table
Commonly useful ones (names vary by region and over time; check the list):
| Command ID | OS | Use |
|---|---|---|
RunPowerShellScript | Windows | Any PowerShell script |
RunShellScript | Linux | Any shell script |
IPConfig | Windows | Show IP configuration |
EnableRemotePS | Windows | Turn on PowerShell remoting |
RDPSettings | Windows | Show RDP port and settings |
SetRDPPort | Windows | Change the RDP port |
ResetRDPCert | Windows | Recreate the RDP TLS certificate |
DisableNLA | Windows | Disable Network Level Authentication |
EnableAdminAccount | Windows | Re-enable the local administrator |
ifconfig | Linux | Show network interfaces |
Troubleshooting recipes
| Problem | Command |
|---|---|
| RDP refused, firewall suspected | --scripts "Get-NetFirewallRule -DisplayGroup 'Remote Desktop' | Select DisplayName, Enabled" |
| RDP service stopped | --scripts "Set-Service TermService -StartupType Automatic; Start-Service TermService" |
| SSH refused | --scripts "systemctl status sshd --no-pager; ss -tlnp | grep :22" |
| Disk full | Windows: Get-PSDrive -PSProvider FileSystem; Linux: du -xh / --max-depth=2 | sort -h | tail -20 |
| High CPU | Windows: Get-Process | Sort CPU -Desc | Select -First 10; Linux: top -b -n1 | head -20 |
| DNS failing | Windows: Resolve-DnsName contoso.com; Linux: getent hosts contoso.com |
| Outbound test | Windows: Test-NetConnection login.microsoftonline.com -Port 443; Linux: curl -sS -o /dev/null -w '%{http_code}' https://login.microsoftonline.com |
| Recent errors | Windows: Get-WinEvent -LogName System -MaxEvents 30 | Where Level -le 2; Linux: journalctl -p err -n 50 --no-pager |
Warning
A script that restarts the network stack, changes the firewall or reboots the VM will cut off the Run Command reply. The change may still be applied. Check the result with a second, read-only run.
Managed Run Command
Use it for scripts that run longer, should run asynchronously, or need their output kept.
az vm run-command create -g rg-app --vm-name vm-win01 \
--name collect-logs \
--script "Get-WinEvent -LogName Application -MaxEvents 500 | Export-Csv C:\Temp\app.csv" \
--timeout-in-seconds 1800 \
--async-execution false
Check status and output:
az vm run-command show -g rg-app --vm-name vm-win01 --name collect-logs \
--instance-view --query "instanceView.{state:executionState, exit:exitCode, out:output, err:error}"
Send large output to storage instead of the instance view. Both URIs are blob SAS URLs with write permission:
az vm run-command create -g rg-app --vm-name vm-lnx01 --name inventory \
--script "dpkg -l || rpm -qa" \
--output-blob-uri "https://stops.blob.core.windows.net/runcmd/inventory.out?<sas>" \
--error-blob-uri "https://stops.blob.core.windows.net/runcmd/inventory.err?<sas>"
List and clean up:
az vm run-command list -g rg-app --vm-name vm-win01 -o table
az vm run-command delete -g rg-app --vm-name vm-win01 --name collect-logs --yes
Managed commands stay on the VM resource until deleted. Clean them up so the next operator is not confused by an old failed run.
PowerShell (Az.Compute)
# Action run command
Invoke-AzVMRunCommand -ResourceGroupName rg-app -VMName vm-win01 `
-CommandId RunPowerShellScript -ScriptPath .\check-disk.ps1
# Managed run command
Set-AzVMRunCommand -ResourceGroupName rg-app -VMName vm-win01 -Location westeurope `
-RunCommandName collect-logs -SourceScript "Get-Service | Out-String"
Get-AzVMRunCommand -ResourceGroupName rg-app -VMName vm-win01 -RunCommandName collect-logs -Expand InstanceView
Many VMs at once
Run the same check across a resource group:
for vm in $(az vm list -g rg-app --query "[?storageProfile.osDisk.osType=='Linux'].name" -o tsv); do
echo "== $vm"
az vm run-command invoke -g rg-app -n "$vm" --command-id RunShellScript \
--scripts "df -h / | tail -1" --query "value[0].message" -o tsv
done
For scale sets, target an instance:
az vmss run-command invoke -g rg-app -n vmss-web --instance-id 0 \
--command-id RunShellScript --scripts "systemctl is-active nginx"
When Run Command fails
| Error or symptom | Cause | Fix |
|---|---|---|
AuthorizationFailed on runCommand/action | Caller lacks the permission | Assign Virtual Machine Contributor or a custom role with that action |
| Hangs, then times out | VM agent not Ready, or VM cannot reach the platform | az vm get-instance-view -g rg-app -n vm-win01 --query instanceView.vmAgent; check outbound rules and guest proxy |
Conflict, another run in progress | An earlier action run is still executing | Wait for it, or use a managed run command with a new name |
| Output cut short | Action output is truncated | Write to a file and read the tail, or use managed run command with --output-blob-uri |
| VM stopped or deallocated | Agent is not running | Start the VM first; for boot failures use Serial Console |
Prevent it next time
- Keep the VM agent healthy and auto-updating; Run Command and most extensions depend on it.
- Restrict who has
runCommand/actionand alert on it. The Activity Log records each run, so you can see who ran a command and when. - Keep a small library of tested read-only scripts in source control for the checks you run most.
- Delete managed run commands after use.