Skip to content

Lab 03: Custom Script Extensions

Use Custom Script Extension to bootstrap software safely, inspect extension state, and recover from common execution errors.

Prerequisites

  • Azure subscription with contributor-level access to compute, network, backup, and monitoring resources
  • Azure CLI installed and signed in
  • Variables set for the lab:
    • RG
    • VM_NAME
    • LOCATION
    • VNET_NAME
    • SUBNET_NAME
  • A Log Analytics workspace or backup vault where the lab requires it

Architecture Diagram

graph TD
    A[Lab 03: Custom Script Extensions] --> B[Resource group and networking baseline]
    B --> C[Virtual machine deployment]
    C --> D[Security and access controls]
    D --> E[Observability and validation]
    E --> F[Cleanup and cost review]

Lab Metadata

Field Value
Lab file lab-03-custom-script-extensions.md
Estimated duration 45-75 minutes
Difficulty Intermediate
Focus technologies Custom Script Extension, logs, idempotent bootstrap
Cost profile Moderate; deallocate or clean up immediately after validation

Step-by-step Instructions

Step 1: Create the resource group and network baseline

az group create \
    --name $RG \
    --location $LOCATION \
    --output json

az network vnet create \
    --resource-group $RG \
    --name $VNET_NAME \
    --address-prefixes 10.40.0.0/16 \
    --subnet-name $SUBNET_NAME \
    --subnet-prefixes 10.40.1.0/24 \
    --output json
Command Purpose
az group create Create the resource group for the lab.
--name Name of the resource group to create.
--location Azure region for the resource group.
--output Output format for the response (table here).
az network vnet create Create the virtual network and its initial subnet.
--resource-group Resource group that will contain the virtual network.
--name Name of the virtual network to create.
--address-prefixes Address space (CIDR) for the virtual network.
--subnet-name Name of the initial subnet to create.
--subnet-prefixes CIDR range assigned to the subnet.
--output Output format for the response (JSON here).

Expected outcome:

  • The resource group exists in the intended region.
  • The virtual network and subnet are available for VM deployment.

Step 2: Deploy the base VM

az vm create \
    --resource-group $RG \
    --name $VM_NAME \
    --image Ubuntu2204 \
    --size Standard_D4s_v5 \
    --admin-username azureuser \
    --generate-ssh-keys \
    --vnet-name $VNET_NAME \
    --subnet $SUBNET_NAME \
    --public-ip-sku Standard \
    --storage-sku Premium_LRS \
    --output json
Command Purpose
az vm create Create a virtual machine in the lab virtual network.
--resource-group Resource group that will contain the virtual machine.
--name Name of the virtual machine to create.
--image Marketplace image or alias for the OS (Ubuntu2204).
--size VM size (SKU) to provision.
--admin-username Administrator user name for the guest OS.
--generate-ssh-keys Generate an SSH key pair if one does not already exist.
--vnet-name Existing virtual network to attach the VM to.
--subnet Subnet within the virtual network for the VM NIC.
--public-ip-sku SKU for the public IP address (Standard).
--storage-sku Managed-disk storage SKU for the OS disk (Premium_LRS).
--output Output format for the response (JSON here).

Expected outcome:

  • The VM deploys with Premium SSD-backed storage and a predictable network baseline.
  • You have enough CPU, memory, and NIC capability to test the scenario without using a tiny burstable SKU.

Step 3: Capture the extension target state before bootstrap

Before you test Custom Script Extension, confirm that the VM does not already have conflicting extension state and record the clean target you expect your bootstrap to modify.

az vm extension list \
    --resource-group $RG \
    --vm-name $VM_NAME \
    --output table
Command Purpose
az vm extension list List the VM extensions currently installed on the virtual machine.
--resource-group Resource group that contains the virtual machine.
--vm-name Name of the virtual machine whose extensions you want to inspect.
--output Output format for the response (JSON here).

Recommended operator notes:

  • Record whether any extension is already present so you can distinguish bootstrap issues from preexisting extension drift.
  • Note package repository, storage, and outbound connectivity dependencies required by the script.
  • Record how the script proves idempotency so reruns do not become a hidden production risk.

Step 4: Validate the scenario end to end

Run both control-plane and workload validation so you can compare extension deployment results with a known-good VM baseline.

az vm get-instance-view \
    --resource-group $RG \
    --name $VM_NAME \
    --output json

az monitor activity-log list \
    --resource-group $RG \
    --offset 2h \
    --output table
Command Purpose
az vm get-instance-view Retrieve the runtime instance view (power and provisioning state) of the VM.
--resource-group Resource group that contains the virtual machine.
--name Name of the virtual machine to inspect.
--output Output format for the response (JSON here).
az monitor activity-log list List recent control-plane activity-log events.
--resource-group Resource group to scope activity-log events to.
--offset Look-back window for events (2h here).
--output Output format for the response (table here).

Step 5: Optional operational hardening

  • Review whether the bootstrap should move to cloud-init, image baking, or a configuration-management tool after the lab.
  • Review whether extension retries, logging, and rollback behavior are sufficient for production rollout.
  • Review whether script secrets or storage dependencies need a safer delivery path.

Validation Steps

Use the following validation checklist before marking the lab complete:

  • [ ] The VM is in the expected power and provisioning state
  • [ ] The intended feature change is visible in Azure resource properties
  • [ ] At least one CLI verification command was captured after the change
  • [ ] You can explain how the lab outcome would change production design or troubleshooting

Cleanup Instructions

az vm delete \
    --resource-group $RG \
    --name $VM_NAME \
    --yes

az network nic delete \
    --resource-group $RG \
    --name "${VM_NAME}VMNic"

az group delete \
    --name $RG \
    --yes \
    --no-wait
Command Purpose
az vm delete Delete the virtual machine.
--resource-group Resource group that contains the virtual machine.
--name Name of the virtual machine to delete.
--yes Skip the confirmation prompt.
az network nic delete Delete the network interface left behind by the VM.
--resource-group Resource group that contains the network interface.
--name Name of the network interface to delete.
az group delete Delete the resource group and all remaining lab resources.
--name Name of the resource group to delete.
--yes Skip the confirmation prompt.
--no-wait Return immediately without waiting for deletion to finish.

Cleanup notes:

  • Delete associated disks, public IPs, Bastion hosts, vault items, or replication resources if the lab created them.
  • Review whether backup vaults or recovery services still hold retained items that continue billing after VM deletion.

See Also

Sources