Azure Bicep: Update Management - Maintenance Configuration

How to configure Maintenance Configuration Plans and Assignments using Bicep

Introduction

Let’s be honest—no one really enjoys patch management. It’s one of those necessary evils in IT: time-consuming, repetitive, and often thankless. From chasing down zero-day exploits to auditing machines for compliance, it’s a task that keeps systems secure but rarely sparks joy.

The good news? You can take some of the pain out of the process by baking patching into your Infrastructure as Code deployments.

In this post, we’ll look at how to use Azure Update Management, along with Maintenance Configurations and Assignments, all defined using Bicep, to automate and streamline your patching strategy right from the start.

What is Azure Update Manager

Azure Update Manager (formerly known as Azure Automation Update Management) is a native Azure service that helps you keep your Windows and Linux virtual machines patched and secure, without needing complex tooling or manual effort.

Here’s what it offers:

  • Centralized patching: Manage updates across Azure, on-premises, and even multi-cloud VMs from a single place.
  • Flexible scheduling: Define when and how updates are applied using maintenance configurations, so you’re in control.
  • Compliance reporting: Get real-time insight into update status, compliance, and patch failures across your environment.
  • Agentless option: Works without dependency on the legacy Log Analytics agent (no more MMA/OMS!).

It integrates directly with Azure Resource Manager, making it perfect for Infrastructure as Code (IaC) workflows using Bicep or ARM templates.

If you want to find out more information on the Azure Update Manager (AUM) you can check the following links:

Infrastructure As Code

For the deployment, There are two modules that will be using, the first module we will be using will create the Maintenance Configuration resource and then second module will be used to Assign resources to the Maintenance Configuration

For those who want a “real world example” I’ve spent some time pre-creating some bicep which you can look at, this is living over in the “BWC Bicep Repository”

VirtualMachineTag

In this example, we are dynamically applying tags to virtual machines deployed in Azure. To achieve this, we use the union function to merge predefined tags with an additional UpdateManagement tag.

1
tags: union(tags, { UpdateManagement: 'mc-${customerName}-${environmentType}-linux' })

This approach results in a tag value like: UpdateManagement = mc-bwc-prod-linux

This method ensures that your virtual machines are consistently tagged with environment-specific values, making them easier to manage and identify within Azure Update Manager and other automation tools.

For those interested in find out more about the Bicep Union function, You can check the MSFT Docs

createMaintenanceConfiguration

If you’re wanting more information on the module, Check the AVM Documentation Here

Linux

Maintenance Configuration Parameters

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Maintenance configuration for Linux VMs
param maintenanceConfiguration = [
  {
    name: 'mc-${customerName}-${environmentType}-linux'
    location: location
    maintenanceScope: 'InGuestPatch'
    extensionProperties: {
      InGuestPatchMode: 'User'
    }
    maintenanceWindow: {
      timeZone: 'W. Europe Standard Time'
      expirationDateTime: '9999-12-31 23:59:59'
      startDateTime: '2025-08-07 17:00'   // Custom maintenance window
      duration: '04:00'                   // Extended duration
      recurEvery: 'Day'                   // Daily
    }
    installPatches: {
      rebootSetting: 'IfRequired'
      linuxParameters: {
        classificationsToInclude: [
          'Security'
          'Critical'
        ]
      }
    }
  }
]

Maintenance Configuration Module

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
module createMaintenanceConfiguration 'br/public:avm/res/maintenance/maintenance-configuration:0.3.1' = {
  name: 'create-maintenance-configuration'
  scope: resourceGroup(resourceGroupName)
  params: {
    name: maintenanceConfiguration[0].name
    location: location
    maintenanceScope: maintenanceConfiguration[0].maintenanceScope
    extensionProperties: maintenanceConfiguration[0].extensionProperties
    maintenanceWindow: maintenanceConfiguration[0].maintenanceWindow
    installPatches: maintenanceConfiguration[0].installPatches
    tags: tags
  }
  dependsOn: [
    createVirtualMachine
  ]
}

Windows

Maintenance Configuration Parameters

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Maintenance configuration for Windows VMs
param maintenanceConfiguration = [
  {
    name: 'mc-${customerName}-${environmentType}-windows'
    location: location
    maintenanceScope: 'InGuestPatch'
    extensionProperties: {
      InGuestPatchMode: 'User'
    }
    maintenanceWindow: {
      timeZone: 'W. Europe Standard Time'
      expirationDateTime: '9999-12-31 23:59:59'
      startDateTime: '2025-08-07 17:00'   // Custom maintenance window
      duration: '04:00'                   // Extended duration
      recurEvery: 'Day'                   // Daily
    }
    installPatches: {
      rebootSetting: 'IfRequired'
      windowsParameters: {
      classificationsToInclude: [
          'Critical'
          'Security'
          'UpdateRollup'
          'Definition'
          'Updates'
      ]
      }
    }
  }
]

Maintenance Configuration Module

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
module createMaintenanceConfiguration 'br/public:avm/res/maintenance/maintenance-configuration:0.3.1' = {
  name: 'create-maintenance-configuration'
  scope: resourceGroup(resourceGroupName)
  params: {
    name: maintenanceConfiguration[0].name
    location: location
    maintenanceScope: maintenanceConfiguration[0].maintenanceScope
    extensionProperties: maintenanceConfiguration[0].extensionProperties
    maintenanceWindow: maintenanceConfiguration[0].maintenanceWindow
    installPatches: maintenanceConfiguration[0].installPatches
    tags: tags
  }
  dependsOn: [
    createVirtualMachine
  ]
}

Once the Maintenance Configuration Plans are created, the next step is to apply them to your resources using Dynamic Scopes. This ensures that Azure Update Manager targets the correct set of virtual machines automatically, based on tags or other filter criteria.

applyMaintenanceConfiguration

To simplify this process, we can use the AVM Bicep module for configuration assignment module. This abstracts away the boilerplate and helps you declaratively assign maintenance configurations to your resources.

Linux

Maintenance Configuration Module

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module assignMaintenanceConfiguration 'br/public:avm/res/maintenance/configuration-assignment:0.2.0' = {
  name: 'assign-maintenance-configuration'
  scope: resourceGroup(resourceGroupName)
  params: {
    name: 'ma-${customerName}-${environmentType}-linux'
    location: location
    filter: {
      osTypes: [
        'Linux'
      ]
      resourceTypes: [
        'Virtual Machines'
      ]
      tagSettings: {
        filterOperator: 'All'
        tags: {
          UpdateManagement: [
              'mc-${customerName}-${environmentType}-linux'
          ]
        }
      }
    }
    maintenanceConfigurationResourceId: createMaintenanceConfiguration.outputs.resourceId
  }
  dependsOn: [
    createMaintenanceConfiguration
  ]
}

Windows

Maintenance Configuration Module

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module assignMaintenanceConfiguration 'br/public:avm/res/maintenance/configuration-assignment:0.2.0' = {
  name: 'assign-maintenance-configuration'
  scope: resourceGroup(resourceGroupName)
  params: {
    name: 'ma-${customerName}-${environmentType}-windows'
    location: location
    filter: {
      osTypes: [
        'Windows'
      ]
      resourceTypes: [
        'Virtual Machines'
      ]
      tagSettings: {
        filterOperator: 'All'
        tags: {
          UpdateManagement: [
              'mc-${customerName}-${environmentType}-windows'
          ]
        }
      }
    }
    maintenanceConfigurationResourceId: createMaintenanceConfiguration.outputs.resourceId
  }
  dependsOn: [
    createMaintenanceConfiguration
  ]
}

Next Steps

In this post, we’ve covered the foundations for getting started with Bicep and Update Management techniques. However, this is only part of the overall solution. Azure Policy also plays a crucial role in helping to enforce governance and compliance for update management—something I’ll be covering in an upcoming post!

With Azure Policy, you can ensure that every machine has the Periodic Assessment configuration applied, and that regular compliance checks are performed automatically. This helps maintain consistency across your environment and reinforces your patch management strategy.

If you’re curious about the Periodic Assessment functionality you can check out some more information below:

Wrap Up

Automating patch management with Azure Update Manager and Bicep not only saves time but also ensures your infrastructure remains secure and compliant with minimal manual effort. By leveraging maintenance configurations and dynamic assignments, you can streamline updates for both Windows and Linux VMs, making patching a seamless part of your DevOps workflow.

Ready to take control of your patching strategy? Try implementing these Bicep modules in your next deployment, explore the BWC Bicep Repository for real-world examples, and let me know how it works for you! If you have questions, feedback, or want to share your own experiences, drop a comment below or reach out on GitHub.

Share with your network!

Built with Hugo - Theme Stack designed by Jimmy