| title | Create Pre-Maintenance and Post-Maintenance Events Using a Webhook with Runbooks |
|---|---|
| description | In this tutorial, you learn how to create pre-maintenance and post-maintenance events by using a webhook with Azure Automation runbooks. |
| ms.service | azure-update-manager |
| ms.date | 08/21/2025 |
| ms.topic | tutorial |
| author | habibaum |
| ms.author | v-uhabiba |
| ms.custom | sfi-image-nochange |
| ms.update-cycle | 1095-days |
Tutorial: Create pre-maintenance and post-maintenance events by using a webhook with Automation runbooks
Applies to: ✔️ Windows VMs ✔️ Linux VMs ✔️ On-premises environment ✔️ Azure VMs ✔️ Azure Arc-enabled servers.
You can use pre-maintenance and post-maintenance events to execute user-defined actions before and after scheduled patch installation. One of the most common scenarios is to start and stop a virtual machine (VM). With pre-maintenance events, you can run a script to start the VM before initiating the scheduled patching process. After the scheduled patching is complete and the server is rebooted, you can run a script to safely shut down the VM.
This tutorial explains how to create pre-maintenance and post-maintenance events to start and stop a VM in a scheduled patch workflow by using a webhook.
In this tutorial, you:
[!div class="checklist"]
- Create and publish an Azure Automation runbook.
- Add webhooks.
- Create an event subscription.
[!INCLUDE pre-post-prerequisites.md]
-
Sign in to the Azure portal and go to your Azure Automation account.
-
If you used runbooks for pre-maintenance and post-maintenance tasks in Azure Automation Update Management, it's critical that you use the following steps to avoid an unexpected impact to your machines and failed maintenance runs:
-
For your runbooks, parse the webhook payload to ensure that it's triggering on
Microsoft.Maintenance.PreMaintenanceEventorMicrosoft.Maintenance.PostMaintenanceEventevents only. By design, webhooks are triggered on other subscription events if any other event is added with the same endpoint.-
See the Azure Event Grid event schema.
-
See the Event Grid schema specific to maintenance configurations.
-
See the following code:
param ( [Parameter(Mandatory=$false)] [object] $WebhookData ) $notificationPayload = ConvertFrom-Json -InputObject $WebhookData.RequestBody $eventType = $notificationPayload[0].eventType if ($eventType -ne "Microsoft.Maintenance.PreMaintenanceEvent" -and $eventType –ne "Microsoft.Maintenance.PostMaintenanceEvent" ) { Write-Output "Webhook not triggered as part of pre or post patching for maintenance run" return }
-
-
The SoftwareUpdateConfigurationRunContext parameter contains information about lists of machines in the update deployment. It won't be passed to the scripts when you use them for pre-maintenance or post-maintenance events while using an Automation webhook. You can either query the list of machines from Azure Resource Graph or have the list of machines coded in the scripts.
-
Ensure that proper roles and permissions are granted to the managed identities that you're using in the script, to execute Resource Graph queries and to start or stop machines.
-
See the permissions related to Resource Graph queries.
-
See the Virtual Machine Contributor role.
-
See the webhook payload.
-
See the following code:
param ( [Parameter(Mandatory=$false)] [object] $WebhookData ) Connect-AzAccount -Identity # Install the Resource Graph module from PowerShell Gallery # Install-Module -Name Az.ResourceGraph $notificationPayload = ConvertFrom-Json -InputObject $WebhookData.RequestBody $maintenanceRunId = $notificationPayload[0].data.CorrelationId $resourceSubscriptionIds = $notificationPayload[0].data.ResourceSubscriptionIds if ($resourceSubscriptionIds.Count -gt 0) { Write-Output "Querying ARG to get machine details[MaintenanceRunId=$maintenanceRunId][ResourceSubscriptionIdsCount=$($resourceSubscriptionIds.Count)]" $argQuery = @"maintenanceresources | where type =~ 'microsoft.maintenance/applyupdates' | where properties.correlationId =~ '$($maintenanceRunId)' | where id has '/providers/microsoft.compute/virtualmachines/' | project id, resourceId = tostring(properties.resourceId) | order by id asc "@ Write-Output "Arg Query Used: $argQuery" $allMachines = [System.Collections.ArrayList]@() $skipToken = $null $res = Search-AzGraph -Query $argQuery -First 1000 -SkipToken $skipToken -Subscription $resourceSubscriptionIds $skipToken = $res.SkipToken $allMachines.AddRange($res.Data) } while ($skipToken -ne $null -and $skipToken.Length -ne 0) if ($allMachines.Count -eq 0) { Write-Output "No Machines were found." break } }
-
-
To customize, you can either use your existing scripts with the preceding modifications or use the following scripts.
param
(
[Parameter(Mandatory=$false)]
[object] $WebhookData
)
Connect-AzAccount -Identity
# Install the Resource Graph module from PowerShell Gallery
# Install-Module -Name Az.ResourceGraph
$notificationPayload = ConvertFrom-Json -InputObject $WebhookData.RequestBody
$eventType = $notificationPayload[0].eventType
if ($eventType -ne "Microsoft.Maintenance.PreMaintenanceEvent") {
Write-Output "Webhook not triggered as part of pre-patching for maintenance run"
return
}
$maintenanceRunId = $notificationPayload[0].data.CorrelationId
$resourceSubscriptionIds = $notificationPayload[0].data.ResourceSubscriptionIds
if ($resourceSubscriptionIds.Count -eq 0) {
Write-Output "Resource subscriptions are not present."
break
}
Write-Output "Querying ARG to get machine details [MaintenanceRunId=$maintenanceRunId][ResourceSubscriptionIdsCount=$($resourceSubscriptionIds.Count)]"
$argQuery = @"
maintenanceresources
| where type =~ 'microsoft.maintenance/applyupdates'
| where properties.correlationId =~ '$($maintenanceRunId)'
| where id has '/providers/microsoft.compute/virtualmachines/'
| project id, resourceId = tostring(properties.resourceId)
| order by id asc
"@
Write-Output "Arg Query Used: $argQuery"
$allMachines = [System.Collections.ArrayList]@()
$skipToken = $null
do
{
$res = Search-AzGraph -Query $argQuery -First 1000 -SkipToken $skipToken -Subscription $resourceSubscriptionIds
$skipToken = $res.SkipToken
$allMachines.AddRange($res.Data)
} while ($skipToken -ne $null -and $skipToken.Length -ne 0)
if ($allMachines.Count -eq 0) {
Write-Output "No Machines were found."
break
}
$jobIDs= New-Object System.Collections.Generic.List[System.Object]
$startableStates = "stopped" , "stopping", "deallocated", "deallocating"
$allMachines | ForEach-Object {
$vmId = $_.resourceId
$split = $vmId -split "/";
$subscriptionId = $split[2];
$rg = $split[4];
$name = $split[8];
Write-Output ("Subscription Id: " + $subscriptionId)
$mute = Set-AzContext -Subscription $subscriptionId
$vm = Get-AzVM -ResourceGroupName $rg -Name $name -Status -DefaultProfile $mute
$state = ($vm.Statuses[1].DisplayStatus -split " ")[1]
if($state -in $startableStates) {
Write-Output "Starting '$($name)' ..."
$newJob = Start-ThreadJob -ScriptBlock { param($resource, $vmname, $sub) $context = Set-AzContext -Subscription $sub; Start-AzVM -ResourceGroupName $resource -Name $vmname -DefaultProfile $context} -ArgumentList $rg, $name, $subscriptionId
$jobIDs.Add($newJob.Id)
} else {
Write-Output ($name + ": no action taken. State: " + $state)
}
}
$jobsList = $jobIDs.ToArray()
if ($jobsList)
{
Write-Output "Waiting for machines to finish starting..."
Wait-Job -Id $jobsList
}
foreach($id in $jobsList)
{
$job = Get-Job -Id $id
if ($job.Error)
{
Write-Output $job.Error
}
} param
(
[Parameter(Mandatory=$false)]
[object] $WebhookData
)
Connect-AzAccount -Identity
# Install the Resource Graph module from PowerShell Gallery
# Install-Module -Name Az.ResourceGraph
$notificationPayload = ConvertFrom-Json -InputObject $WebhookData.RequestBody
$eventType = $notificationPayload[0].eventType
if ($eventType -ne "Microsoft.Maintenance.PostMaintenanceEvent") {
Write-Output "Webhook not triggered as part of post-patching for maintenance run"
return
}
$maintenanceRunId = $notificationPayload[0].data.CorrelationId
$resourceSubscriptionIds = $notificationPayload[0].data.ResourceSubscriptionIds
if ($resourceSubscriptionIds.Count -eq 0) {
Write-Output "Resource subscriptions are not present."
break
}
Start-Sleep -Seconds 30
Write-Output "Querying ARG to get machine details [MaintenanceRunId=$maintenanceRunId][ResourceSubscriptionIdsCount=$($resourceSubscriptionIds.Count)]"
$argQuery = @"
maintenanceresources
| where type =~ 'microsoft.maintenance/applyupdates'
| where properties.correlationId =~ '$($maintenanceRunId)'
| where id has '/providers/microsoft.compute/virtualmachines/'
| project id, resourceId = tostring(properties.resourceId)
| order by id asc
"@
Write-Output "Arg Query Used: $argQuery"
$allMachines = [System.Collections.ArrayList]@()
$skipToken = $null
do
{
$res = Search-AzGraph -Query $argQuery -First 1000 -SkipToken $skipToken -Subscription $resourceSubscriptionIds
$skipToken = $res.SkipToken
$allMachines.AddRange($res.Data)
} while ($skipToken -ne $null -and $skipToken.Length -ne 0)
if ($allMachines.Count -eq 0) {
Write-Output "No Machines were found."
break
}
$jobIDs= New-Object System.Collections.Generic.List[System.Object]
$stoppableStates = "starting", "running"
$allMachines | ForEach-Object {
$vmId = $_.resourceId
$split = $vmId -split "/";
$subscriptionId = $split[2];
$rg = $split[4];
$name = $split[8];
Write-Output ("Subscription Id: " + $subscriptionId)
$mute = Set-AzContext -Subscription $subscriptionId
$vm = Get-AzVM -ResourceGroupName $rg -Name $name -Status -DefaultProfile $mute
$state = ($vm.Statuses[1].DisplayStatus -split " ")[1]
if($state -in $stoppableStates) {
Write-Output "Stopping '$($name)' ..."
$newJob = Start-ThreadJob -ScriptBlock { param($resource, $vmname, $sub) $context = Set-AzContext -Subscription $sub; Stop-AzVM -ResourceGroupName $resource -Name $vmname -Force -DefaultProfile $context} -ArgumentList $rg, $name, $subscriptionId
$jobIDs.Add($newJob.Id)
} else {
Write-Output ($name + ": no action taken. State: " + $state)
}
}
$jobsList = $jobIDs.ToArray()
if ($jobsList)
{
Write-Output "Waiting for machines to finish stop operation..."
Wait-Job -Id $jobsList
}
foreach($id in $jobsList)
{
$job = Get-Job -Id $id
if ($job.Error)
{
Write-Output $job.Error
}
} param
(
[Parameter(Mandatory=$false)]
[object] $WebhookData
)
Connect-AzAccount -Identity
# Install the Resource Graph module from PowerShell Gallery
# Install-Module -Name Az.ResourceGraph
$notificationPayload = ConvertFrom-Json -InputObject $WebhookData.RequestBody
$maintenanceRunId = $notificationPayload[0].data.CorrelationId
Invoke-AzRestMethod `
-Path "$maintenanceRunId`?api-version=2023-09-01-preview" `
-Payload
'{
"properties": {
"status": "Cancel"
}
}' `
-Method PUT Add webhooks to the preceding published runbooks and copy the webhook URLs.
Note
Be sure to copy the URL after you create a webhook. You can't retrieve the URL again.
-
Sign in to the Azure portal and go to Azure Update Manager.
-
Under Manage, select Machines > Maintenance Configuration.
-
On the Maintenance Configuration pane, select the configuration.
-
Under Settings, select Events.
:::image type="content" source="./media/tutorial-webhooks-using-runbooks/pre-post-select-events.png" alt-text="Screenshot that shows the menu option for events." lightbox="./media/tutorial-webhooks-using-runbooks/pre-post-select-events.png":::
-
Select +Event Subscription to create a pre-maintenance or post-maintenance event.
:::image type="content" source="./media/tutorial-webhooks-using-runbooks/select-event-subscriptions.png" alt-text="Screenshot that shows event subscriptions with the option to create an event subscription." lightbox="./media/tutorial-webhooks-using-runbooks/select-event-subscriptions.png":::
-
On the Create Event Subscription pane, in the Event Subscription Details section, provide an appropriate name. Keep the schema as Event Grid Schema.
-
In the Event Types section, for Filter to Event Types, select Pre Maintenance Event or Post Maintenance Event.
-
In the Endpoint Details section, select the Web Hook endpoint, and then select Configure an endpoint.
-
Provide the appropriate details, such as the pre-maintenance or post-maintenance event's webhook URL to trigger the event.
:::image type="content" source="./media/tutorial-webhooks-using-runbooks/create-event-subscription.png" alt-text="Screenshot that shows the options to create event subscriptions." lightbox="./media/tutorial-webhooks-using-runbooks/create-event-subscription.png":::
-
Select Create.
- Get an overview of pre-maintenance and post-maintenance events in Azure Update Manager.
- Learn more about how to create pre-maintenance and post-maintenance events.
- Learn how to manage pre-maintenance and post-maintenance events or to cancel a scheduled run.
- Learn how to create pre-maintenance and post-maintenance events by using Azure Functions.