Azure Update Manager is a quite new Azure service that went Generally Available 18th of September 2023.
Azure Update Manager has been redesigned and doesn’t depend on Azure Automation or Azure Monitor Logs, as required by the Azure Automation Update Management feature.
Since the Azure Log Analytics agent, also known as the Microsoft Monitoring Agent (MMA) will be retired in August 2024, customers are recommended to move to Azure Update Manager.
In Azure Automation Update Management there was a possibility to use pre-scripts and post-scripts to handle VMs that are turned off for cost saving purposes.
In the initial release of Azure Automation Update Management this possibility wasn’t available. This recently got changed in December 2023, where Pre and Post Events got introduced for Azure Update Manager in preview.
In this post, I will explain how to implement pre and post events for VMs that are turned off for patching in Azure Update Manager.
The following approach is implemented to start/stop VMs in a Maintenance Configuration of Update Manager

1) The Maintenance Configuration about to be triggered (T -20 minutes)
2) The Maintenance Configuration triggers an event to the Event Grid System Topic with the Event Type: Microsoft.Maintenance.PreMaintenanceEvent
3) The Event Subscription for start vm gets triggered, the Event Subscription is configured to pickup events with the Event Type: Microsoft.Maintenance.PreMaintenanceEvent, the event subscription is configured to trigger a webhook with the payload from the event.
4) The Automation Webhook for starting the start vm runbook gets triggered with the payload from the event.
5) The Automation Runbook for start vm gets triggered.
6) The Automation Runbook will get all the VMs and Arc Virtual Machines in scope and starts the deallocated or stopped ones. The runbook will tag the VMs that are started with the UpdateManagerState tag and RunId value for the maintenance configuration run.
7) The actual patching will start (T -0 minutes)
8) After the configured time window for the patching, The Maintenance Configuration triggers an event to the Event Grid System Topic with the Event Type: Microsoft.Maintenance.PostMaintenanceEvent
9) The Event Subscription for stop vm gets triggered, the Event Subscription is configured to pickup events with the Event Type: Microsoft.Maintenance.PostMaintenanceEvent, the event subscription is configured to trigger a webhook with the payload from the event.
10) The Automation Webhook for starting the stop vm runbook gets triggered with the payload from the event.
11) The Automation Runbook for stop vm gets triggered.
12) The Automation Runbook will get all the VMs and Arc Virtual Machines in scope and stops the started machines with the UpdateManagerState tag and RunId value for the maintenance configuration run. The tag will be removed after stopping the VMs and Arc Virtual Machines.
Create a User Assigned Managed Identity id-updatemanager and assign the required roles. Store the Client Id for later

Assign the following roles to the User Assigned Managed Identity at subscription scope:

We will use a Automation Runbook to start all the VMs. The Runbook will get triggered from a webhook and will user the User Assigned Managed Identity to authenticate to Azure.
The Automation Runbook requires the following PowerShell Modules:
You can import the modules using the import module guide
[Note] Make sure you select the Runtime version 7.2 for the Modules
Create a variable with the name var-clientid-id-updatemanager that contains the ClientId from the User Assigned Managed Identity that you created earlier.

There will be 2 runbooks deployed:

[Note] Make sure you select the Runtime version 7.2 for the PowerShell runbook
Here is the content of the scripts:
UpdateManager-StartVMs
param
(
    [Parameter(Mandatory=$false)]
    [object] $WebhookData
)
$miUpdateManager = Get-AutomationVariable -Name "var-clientid-id-updatemanager"
Connect-AzAccount -Identity -AccountId $miUpdateManager | Out-Null
$notificationPayload = $WebhookData.RequestBody | ConvertFrom-Json -depth 100
$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/'
    | 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 = $_.properties.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.PowerState -split " ")[1]
    if($state -in $startableStates) {
        Write-Output "Starting '$($name)' ..."
        $newJob = Start-ThreadJob -ScriptBlock { param($rg, $name, $sub) $context = Set-AzContext -Subscription $sub; Start-AzVM -ResourceGroupName $rg -Name $name -DefaultProfile $context} -ArgumentList $rg, $name, $subscriptionId
        $jobIDs.Add($newJob.Id)
        $tags = @{"UpdateManagerState"="$maintenanceRunId"}
        Update-AzTag -ResourceId $_.properties.resourceId -Tag $tags -operation Merge | Out-Null
    } 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
    }
}
UpdateManager-StopVMs
param
(
    [Parameter(Mandatory=$false)]
    [object] $WebhookData
)
$miUpdateManager = Get-AutomationVariable -Name "var-clientid-id-updatemanager"
Connect-AzAccount -Identity -AccountId $miUpdateManager | Out-Null
# 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 -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 = @"
    resources
    | where type == 'microsoft.compute/virtualmachines'
    | where tags.UpdateManagerState =~ '$($maintenanceRunId)'
"@
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 =  $_.id
    $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.PowerState -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)
        $tags = @{"UpdateManagerState"="$maintenanceRunId"}
        Update-AzTag -ResourceId $_.properties.resourceId -Tag $tags -operation Merge | Out-Null
    } 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
    }
}
Create a webhook for both runbooks.
Keep the URLs from the webhooks for later (it will be the only time to display)
Result:
start vm webhook

stop vm webhook

Follow this guide: Register your subscription for public preview
This will enable the preview feature Pre and Post Events on your subscription.

Setup a Maintenance Configuration for Update Manager for In guest patching.
Per schedule an Event Grid topic must be setup.
The Event Grid Topic can be shared for multiple subscriptions.
Here’s how to create an Event Grid subscription on the Maintenance Configuration Event with a filter on Microsoft.Maintenance.PreMaintenanceEvent to start the VM.
On the selected Maintenance configuration page, under Events, select Web Hook.

In the Create Event Subscription wizard, fill in the following:

Repeat the Create Event Subscription wizard for the stop-vm Event Subscription and select the Post Maintenance Event event type.
As result of the configuration, the Maintenance Configuration will:

