Azure VM Custom Script Extensions: Post-Boot Automation
The Custom Script Extension lets you run any shell or PowerShell script on an Azure VM after it provisions. It is the bridge between “bare OS image” and “configured, ready-to-run server” — useful for installing packages, writing configuration files, joining a domain, or registering with a configuration management system. This page shows how to use it, what the limitations are, and when you should switch to a different approach.
What VM extensions are
Azure VM extensions are small agent-managed programs that run inside a VM in response to commands from the Azure control plane. The Azure VM Agent (installed by default on all Azure Marketplace images) listens for extension commands and executes them. Extensions are available for common tasks: running scripts (Custom Script Extension), enrolling in antivirus, deploying monitoring agents, and joining Azure Active Directory.
The Custom Script Extension specifically allows you to point at a script file or specify inline commands, and Azure executes them inside the VM as root (Linux) or SYSTEM (Windows). The script runs once unless you update the extension configuration to trigger a new run.
Running a script on a Linux VM
The simplest usage: run an inline command on an already-provisioned VM.
az vm extension set \
--resource-group my-rg \
--vm-name my-vm \
--name customScript \
--publisher Microsoft.Azure.Extensions \
--settings '{"commandToExecute": "apt-get update && apt-get install -y nginx"}'For longer scripts, store the script in Azure Blob Storage and reference it by URI:
# Upload script to blob storage first
az storage blob upload \
--account-name mystorageaccount \
--container-name scripts \
--name setup.sh \
--file ./setup.sh
# Run the script on the VM
az vm extension set \
--resource-group my-rg \
--vm-name my-vm \
--name customScript \
--publisher Microsoft.Azure.Extensions \
--settings '{
"fileUris": ["https://mystorageaccount.blob.core.windows.net/scripts/setup.sh"],
"commandToExecute": "bash setup.sh"
}'If your blob storage container is private (which it should be for production scripts), pass a SAS token in the protected settings, not the public settings. Protected settings are encrypted and not visible in the Azure portal or deployment history. Use —protected-settings for any value you do not want logged.
Running a script on a Windows VM
Windows VMs use a different extension name and publisher:
az vm extension set \
--resource-group my-rg \
--vm-name my-windows-vm \
--name CustomScriptExtension \
--publisher Microsoft.Compute \
--settings '{
"fileUris": ["https://mystorageaccount.blob.core.windows.net/scripts/setup.ps1"],
"commandToExecute": "powershell -ExecutionPolicy Unrestricted -File setup.ps1"
}'PowerShell scripts on Windows have the same pattern: reference a file in Blob Storage, execute it with PowerShell. For simple inline commands, use commandToExecute directly with the PowerShell invocation inline.
Checking extension status and logs
After running an extension, check whether it succeeded:
# Check extension status on the VM
az vm extension list \
--resource-group my-rg \
--vm-name my-vm \
--output table
# Get detailed status including error messages
az vm extension show \
--resource-group my-rg \
--vm-name my-vm \
--name customScriptFor deeper diagnostics, connect to the VM and check the log file. On Linux:
# Script output and error logs
cat /var/log/azure/custom-script/handler.log
# See the actual stdout/stderr from the last script run
ls /var/lib/waagent/custom-script/download/
cat /var/lib/waagent/custom-script/download/0/stdout
cat /var/lib/waagent/custom-script/download/0/stderrRunning a script at VM creation time
You can attach a Custom Script Extension at the same time you create the VM, so the script runs immediately after the OS boots:
az vm create \
--resource-group my-rg \
--name my-vm \
--image Ubuntu2204 \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--custom-data cloud-init.yamlNote the shift to —custom-data here: for at-creation bootstrapping on Linux, cloud-init is generally preferred over Custom Script Extensions. Cloud-init is a widely supported Linux initialisation system that runs before the Azure agent processes extensions, supports multi-step configurations, and has better logging. Custom Script Extension is more flexible for ad hoc post-boot automation and works equally well on Linux and Windows.
For Scale Sets where every instance needs the same bootstrap, consider whether a custom VM image (baked with your software pre-installed) would be better than a bootstrap script. Images boot faster, reduce the risk of script failures at scale, and avoid dependency on external script storage being available at boot time.
When to use Custom Script Extension vs alternatives
| Approach | Best for | Limitations |
|---|---|---|
| Custom Script Extension | One-off post-boot tasks, Windows or Linux, ad hoc changes to running VMs | No idempotency guarantees, runs once per configuration change |
| Cloud-init | Initial Linux VM setup — packages, users, files, services | Linux only, at-creation time only |
| Custom VM image | Consistent baseline across many VMs, fast Scale Set boot | Image rebuild needed for updates, more complex pipeline |
| Ansible / Chef / Puppet | Ongoing configuration management, drift detection, complex multi-step config | Requires a configuration management setup, steeper learning curve |
| Terraform provisioners | Simple commands run once as part of a Terraform deployment | Anti-pattern in Terraform — avoid for anything complex |
Common mistakes
- Putting credentials in the public settings. Extension settings appear in the Azure portal and in deployment logs. API keys, passwords, and SAS tokens belong in
—protected-settings, which are encrypted and never displayed in the portal after submission. - Not testing the script locally before running it as an extension. An extension failure leaves the VM in an inconsistent state. Test your scripts on a local VM or test VM first. Check syntax, verify package names, and make sure all file paths exist before deploying to production VMs via an extension.
- Using extensions for ongoing configuration management. Extensions are not designed for idempotent drift correction. If you need to enforce ongoing configuration state across many VMs, invest in a proper configuration management tool. Extensions are for initial setup and targeted one-off tasks.
Summary
- Custom Script Extensions run scripts inside VMs via the Azure VM Agent, without needing SSH access or manual connection.
- Store scripts in Azure Blob Storage for production use. Pass SAS tokens or managed identity access in protected settings, not public settings.
- For Linux at-creation bootstrapping, cloud-init is often a better choice. For Windows or post-creation automation, Custom Script Extension is straightforward.
- Check extension logs at
/var/log/azure/custom-script/handler.log(Linux) when a script fails — the VM itself stays running. - For a large fleet of VMs, bake configuration into a custom image rather than relying on per-boot scripts.
Frequently asked questions
Can I run a Custom Script Extension on a VM that is already running?
Yes. Extensions can be applied to running VMs at any time, not just at creation. The Azure agent inside the VM receives the extension request and executes the script. You can also run a new script by updating the extension — change the file name or URI to trigger a new execution, since Azure only re-runs an extension if its configuration changes.
Where can the script file be stored?
Azure Blob Storage (public or private with a SAS token or managed identity), GitHub raw URLs (public), or a base64-encoded inline command in the extension settings. For production use, store scripts in Azure Blob Storage with access controlled through a managed identity rather than embedding credentials in the extension settings or using publicly accessible storage.
What happens if the script fails?
The extension reports a failure status that you can see in the portal or via az vm extension list. The VM itself continues running — a failed extension does not roll back the VM. Check the extension log at /var/log/azure/custom-script/handler.log (Linux) or C:\\Packages\\Plugins\\Microsoft.Compute.CustomScriptExtension\\... (Windows) for the script output and error details.