Azure VM custom script extension
Lists the extensions installed on a virtual machine, and covers installing the CustomScript extension to run a shell command on it. CustomScript is the standard way to load content onto a server after provisioning - fetch a file, install a package, start a service - without needing SSH access from the caller.
Query
SELECT
name,
publisher,
type_handler_version,
provisioning_state
FROM azure.compute.virtual_machine_extensions
WHERE subscription_id = '{{subscription_id}}'
AND resource_group_name = '{{resource_group_name}}'
AND vm_name = '{{vm_name}}';
Installing the CustomScript extension
The command runs as root on the VM. Here it fetches a page and serves it on port 8080:
INSERT INTO azure.compute.virtual_machine_extensions(
resource_group_name,
subscription_id,
vm_extension_name,
vm_name,
location,
properties
)
SELECT
'{{resource_group_name}}',
'{{subscription_id}}',
'customScript',
'{{vm_name}}',
'eastus',
'{"publisher": "Microsoft.Azure.Extensions", "type": "CustomScript", "typeHandlerVersion": "2.1", "settings": {"commandToExecute": "wget -O index.html https://example.com/index.html && nohup busybox httpd -f -p 8080 &"}}';
Removing the extension
DELETE FROM azure.compute.virtual_machine_extensions
WHERE subscription_id = '{{subscription_id}}'
AND resource_group_name = '{{resource_group_name}}'
AND vm_extension_name = 'customScript'
AND vm_name = '{{vm_name}}';
Notes
The extension is a child resource, so every statement carries both vm_name and vm_extension_name, and the create additionally requires location - which must match the VM's region. publisher, type and typeHandlerVersion go inside the properties document, not as columns. The command runs as root and its output is not returned by the API: check provisioning_state to see whether the extension succeeded, then verify the effect out of band, for example by requesting the port the script opened. Reaching that port also needs a network security group rule allowing it and a public IP attached to the VM's NIC - the extension itself opens no firewall. Use the settings key for non-sensitive parameters and protectedSettings for anything secret, since settings is readable back from the API. This entry is draft: the read and delete shapes are confirmed against the provider but the install has not been executed, because no VM could be provisioned on the test subscription.