Tutorial: Create pre and post events using a webhook with Automation

Applies to: ✔️ Windows VMs ✔️ Linux VMs ✔️ On-premises environment ✔️ Azure VMs ✔️ Azure Arc-enabled servers.

Pre and post events, also known as pre/post-scripts, allow you to execute user-defined actions before and after the schedule patch installation. One of the most common scenarios is to start and stop a Virtual Machine (VM). With pre-events, you can run a prepatching script to start the VM before initiating the schedule patching process. Once the schedule patching is complete, and the server is rebooted, a post-patching script can be executed to safely shut down the VM.

This tutorial explains how to create pre and post events to start and stop a VM in a schedule patch workflow using a webhook.

In this tutorial, you learn how to:

  • Prerequisites
  • Create and publish Automation runbook
  • Add webhooks
  • Create an event subscription

Prerequisites

  1. Ensure that you are using PowerShell 7.2 runbook.

  2. Assign permission to managed identities - You can assign permissions to the appropriate managed identity. The runbook can use either the Automation account system-assigned managed identity or a user-assigned managed identity.

    You can use either portal or PowerShell cmdlets to assign permissions to each identity:

    Follow the steps in Assign Azure roles using the Azure portal to assign permissions


  1. Import the Az.ResourceGraph module, ensure the module is updated to ThreadJob with the module version 2.0.3.

Create and publish Automation runbook

  1. Sign in to the Azure portal and go to your Azure Automation account

  2. Create and Publish an Automation runbook.

  3. If you were using Runbooks that were being used for pre or post tasks in Azure Automation Update Management, it's critical that you follow the below steps to avoid an unexpected impact to your machines and failed maintenance runs.

    1. For your runbooks, parse the webhook payload to ensure that it's triggering on Microsoft.Maintenance.PreMaintenanceEvent or Microsoft.Maintenance.PostMaintenanceEvent events only. By design webhooks are triggered on other subscription events if any other event is added with the same endpoint.

      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  
      } 
      
    2. SoftwareUpdateConfigurationRunContext parameter, which contains information about list of machines in the update deployment won't be passed to the pre or post scripts when you use them for pre or post events while using 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 Virtual machines contributor role.
      • See the code listed below:
    3. See webhook payload

      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    
      }
      }
      
    4. To customize, you can use either your existing scripts with the above modifications done or use the following scripts.

Sample 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 
    } 
} 

Add webhooks

Add webhooks to the above published runbooks and copy the webhooks URLs.

Note

Ensure to copy the URL after you create a webhook as you cannot retrieve the URL again.

Create an Event subscription

  1. Sign in to the Azure portal and go to your Azure Update Manager.

  2. Under Manage, select Machines, Maintenance Configuration.

  3. On the Maintenance Configuration page, select the configuration.

  4. Under Settings, select Events.

    Screenshot that shows the options to select the events menu option.

  5. Select +Event Subscription to create a pre/post maintenance event.

    Screenshot that shows the options to select the events subscriptions.

  6. On the Create Event Subscription page, enter the following details:

    1. In the Event Subscription Details section, provide an appropriate name.
    2. Keep the schema as Event Grid Schema.
    3. In the Event Types section, Filter to Event Types.
      1. Select Pre Maintenance Event for a pre-event.
        • In the Endpoint details section, select the Webhook endpoint and select Configure an Endpoint.
        • Provide the appropriate details such as pre-event webhook URL to trigger the event.
      2. Select Post Maintenance Event for a post-event.
        • In the Endpoint details section, the Webhook endpoint and select Configure an Endpoint.
        • Provide the appropriate details such as post-event webhook URL to trigger the event. Screenshot that shows the options to create the events subscriptions.
  7. Select Create.

Next steps