Back to Blog
DevOps
4 min read

Dynamic VM Lists in Azure DevOps Pipelines

Azure DevOpsPowerShellVMsAutomationCI/CD

Hardcoding VM names in pipelines breaks when VMs are added or removed. Here's how to dynamically discover VMs and run tasks against them.

The Challenge

You want to:

  • Run a script against all VMs in a resource group
  • Install updates on VMs matching a naming pattern
  • Collect diagnostics from VMs with specific tags

But the VM list changes over time.

Discovering VMs with PowerShell

# Get all VMs in a resource group
$vms = Get-AzVM -ResourceGroupName "rg-production" |
  Select-Object Name, ResourceGroupName

# Filter by naming pattern
$webVms = Get-AzVM -ResourceGroupName "rg-production" |
  Where-Object { $_.Name -like "vm-web-*" }

# Filter by tag
$vms = Get-AzVM |
  Where-Object { $_.Tags["Environment"] -eq "Production" }

# Get running VMs only
$runningVms = Get-AzVM -ResourceGroupName "rg-production" -Status |
  Where-Object { $_.PowerState -eq "VM running" }

Passing VM List Between Stages

Option 1: Pipeline Variables

stages:
- stage: Discover
  jobs:
  - job: GetVMs
    steps:
    - task: AzurePowerShell@5
      name: DiscoverVMs
      inputs:
        azureSubscription: 'your-service-connection'
        ScriptType: 'InlineScript'
        Inline: |
          $vms = Get-AzVM -ResourceGroupName "rg-production" |
            Where-Object { $_.Tags["AutoUpdate"] -eq "true" }

          $vmJson = ($vms | Select-Object Name, ResourceGroupName | ConvertTo-Json -Compress)
          Write-Host "##vso[task.setvariable variable=vmList;isOutput=true]$vmJson"
        azurePowerShellVersion: 'LatestVersion'

- stage: Process
  dependsOn: Discover
  variables:
    vmList: $[ stageDependencies.Discover.GetVMs.outputs['DiscoverVMs.vmList'] ]
  jobs:
  - job: ProcessVMs
    steps:
    - task: PowerShell@2
      inputs:
        targetType: 'inline'
        script: |
          $vms = '$(vmList)' | ConvertFrom-Json
          foreach ($vm in $vms) {
            Write-Host "Processing: $($vm.Name)"
          }

Option 2: Pipeline Artifacts

For larger lists or complex data:

- stage: Discover
  jobs:
  - job: GetVMs
    steps:
    - task: AzurePowerShell@5
      inputs:
        azureSubscription: 'your-service-connection'
        ScriptType: 'InlineScript'
        Inline: |
          $vms = Get-AzVM -ResourceGroupName "rg-production" |
            Select-Object Name, ResourceGroupName, @{N='Tags';E={$_.Tags | ConvertTo-Json}}

          $vms | Export-Csv "$(Build.ArtifactStagingDirectory)/vms.csv" -NoTypeInformation
        azurePowerShellVersion: 'LatestVersion'

    - publish: $(Build.ArtifactStagingDirectory)/vms.csv
      artifact: vm-inventory

- stage: Process
  dependsOn: Discover
  jobs:
  - job: ProcessVMs
    steps:
    - download: current
      artifact: vm-inventory

    - task: PowerShell@2
      inputs:
        targetType: 'inline'
        script: |
          $vms = Import-Csv "$(Pipeline.Workspace)/vm-inventory/vms.csv"
          foreach ($vm in $vms) {
            Write-Host "Processing: $($vm.Name)"
          }

Running Commands on Each VM

Using Run Command

- task: AzurePowerShell@5
  inputs:
    azureSubscription: 'your-service-connection'
    ScriptType: 'InlineScript'
    Inline: |
      $vms = '$(vmList)' | ConvertFrom-Json

      foreach ($vm in $vms) {
        Write-Host "Running command on $($vm.Name)"

        Invoke-AzVMRunCommand `
          -ResourceGroupName $vm.ResourceGroupName `
          -VMName $vm.Name `
          -CommandId "RunPowerShellScript" `
          -ScriptString "Get-Service | Where-Object { $_.Status -eq 'Running' } | Measure-Object"
      }
    azurePowerShellVersion: 'LatestVersion'

Parallel Execution

For faster processing, use jobs matrix:

- stage: Process
  dependsOn: Discover
  jobs:
  - job: ProcessVM
    strategy:
      matrix:
        ${{ each vm in split(variables.vmList, ',') }}:
          ${{ vm }}:
            vmName: ${{ vm }}
    steps:
    - task: AzurePowerShell@5
      inputs:
        azureSubscription: 'your-service-connection'
        ScriptType: 'InlineScript'
        Inline: |
          $vmName = "$(vmName)"
          Write-Host "Processing: $vmName"
          # Your VM-specific task here

Scheduled VM Inventory

Create a scheduled pipeline that maintains an up-to-date inventory:

trigger: none

schedules:
- cron: "0 6 * * *"
  displayName: Daily VM Inventory
  branches:
    include:
    - main

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: AzurePowerShell@5
  displayName: 'Generate VM Inventory'
  inputs:
    azureSubscription: 'your-service-connection'
    ScriptType: 'InlineScript'
    Inline: |
      $inventory = @()

      $subscriptions = Get-AzSubscription
      foreach ($sub in $subscriptions) {
        Set-AzContext -SubscriptionId $sub.Id

        $vms = Get-AzVM -Status
        foreach ($vm in $vms) {
          $inventory += [PSCustomObject]@{
            Subscription = $sub.Name
            ResourceGroup = $vm.ResourceGroupName
            Name = $vm.Name
            Size = $vm.HardwareProfile.VmSize
            PowerState = $vm.PowerState
            OS = $vm.StorageProfile.OsDisk.OsType
            Tags = ($vm.Tags | ConvertTo-Json -Compress)
            LastUpdated = (Get-Date).ToString("o")
          }
        }
      }

      $inventory | Export-Csv "$(Build.ArtifactStagingDirectory)/inventory.csv" -NoTypeInformation
    azurePowerShellVersion: 'LatestVersion'

- publish: $(Build.ArtifactStagingDirectory)/inventory.csv
  artifact: vm-inventory

Using the Inventory in Other Pipelines

resources:
  pipelines:
  - pipeline: inventory
    source: 'VM Inventory Pipeline'
    trigger:
      branches:
        include:
        - main

steps:
- download: inventory
  artifact: vm-inventory

- task: PowerShell@2
  inputs:
    targetType: 'inline'
    script: |
      $vms = Import-Csv "$(Pipeline.Workspace)/inventory/vm-inventory/inventory.csv"
      $runningVms = $vms | Where-Object { $_.PowerState -eq "VM running" }

      Write-Host "Found $($runningVms.Count) running VMs"

Need help automating your Azure infrastructure? Get in touch - we help organisations build efficient DevOps processes.

Need help with your Azure environment?

Get in touch for a free consultation.

Get in Touch