PortiBlog

Azure Automation - Part 3: Dynamic scheduling

2 april 2021

Azure Automation is one of my favorite go-to severless compute services when it comes to automating workloads. Because you can use PowerShell and import lots of neat Modules (like PnP PowerShell), Runbooks are ideal for admins. But when workloads are getting complex and need orchestration, are Runbook still a good option? Well yes of course! In this blog I'll demonstrate how you can use dynamic scheduling to orchestrate workloads. 

This blog is the third post in a series of posts about Azure Automation. Also see: Azure Automation - Part 1: Create a SharePoint site and Azure Automation Part 2: Triggering runbooks.

Why would I need dynamic scheduling? 


Consider the following use case: A customer has a retention policy in place on SharePoint Online but wants to clean up old versions of documents to save storage. If you want to delete versions from a file in SharePoint, you first must turn off the retention policy or at least exclude the site that holds these documents from the policy. When cleanup is done, you can again include the site to the retention policy.

These are basically two tasks that depend on each other. To add another level of complexity we need to perform these two tasks for hundreds of sites and thousands of documents. This presents the following challenges:
  • SharePoint only allows up to 100 sites to be excluded from the retention policy at the same time. 
  • Excluding sites from retention triggers a Timer Job that re-applies the retention policy on all sites except for the excluded ones.
We know that classic SharePoint Timer Jobs have a maximum execution time of 15 minutes. How this works exactly in the Cloud is unknown. After putting this to the test, the policy was always re-applied in under the 20 minutes so for this case we wanted to perform task 1 (Exclude from retention) then schedule task 2 (Cleanup versions for excluded sites) after 20 minutes. When task 2 was done it runs task 1 again with different parameters (next batch) and so on. 
RetentionExclusionFlow

The setup.

Because the focus of this blog is on dynamic scheduling the following examples only show some basic PowerShell Runbooks. If you're interested in the SharePoint part, you can check out this post on how to actually remove versions from a file in SharePoint using PnP cmd-lets. 

Azure Automation Account.

First, you'll need an Azure Automation Account. I've posted a video on how to create one in the first part of this blog series: Azure Automation - Part 1: Create a SharePoint site. For our purpose it's important to leave the "Create Azure Run As account" to Yes. This account is needed to connect back to Azure and our automation account in order to create schedules. 

Import modules.

To create schedules and start runbooks from PowerShell we need to import two modules from the Module Gallery.




Creating the Runbooks.

To demonstrate the scheduling mechanism, I create 2 runbooks: "DynamicSchedulingRunbook" which in our use case will handle the exclusion of sites in batches of 100. 

Param(
    [int]$BatchNo = 0,
    [int]$BatchSize = 1,
    [string]$TaskName = "MyFirstRun"
)

try {
    # Get the connection "AzureRunAsConnection "
    $ServicePrincipalConnection = Get-AutomationConnection -Name "AzureRunAsConnector"

    "Logging in to Azure..."
    Connect-AzAccount `
        -ServicePrincipal `
        -TenantId $servicePrincipalConnection. TenantId `
        -ApplicationId $servicePrincipalConnection.ApplicationId `
        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint `
}
catch {
    if (!$ServicePrincipalConnection) {
        $ErrorMessage = "Connection $ConnectionName not found."
        throw $ErrorMessage
    }
    else {
        Write-Error -Message $_.Exception
        throw $_.Exception
    }
}

$StartTime = (Get-Date).AddMinutes(6) # 5 minutes is minimum of results in error

New-AzAutomationSchedule -AutomationAccountName "Automata" -Name "Task_Batch$BatchNo" -
StartTime $StartTime -OneTime -ResourceGroupName "Automata-Demo"
Register-AzAutomationScheduledRunbook -AutomationAccountName "Automata" -
Name "DynamicSchedulingTask" -ScheduleName "Task_Batch$BatchNo" -ResourceGroupName "Automata-Demo" -Parameters @{"BatchNo"=$BatchNo;"BatchSize"=$BatchSize;"TaskName"=$TaskName}
Write-Output "Task scheduled for batch $BatchNo"

Then there is "DynamicScheduleTask" that handles the deletion of versions.

Param(
    [int]$BatchNo,
    [int]$BatchSize,
    [string]$TaskName
)

try {
    # Get the connection "AzureRunAsConnection "
    $ServicePrincipalConnection = Get-AutomationConnection -Name "AzureRunAsConnector"

    "Logging in to Azure..."
    Connect-AzAccount `
        -ServicePrincipal `
        -TenantId $servicePrincipalConnection. TenantId `
        -ApplicationId $servicePrincipalConnection.ApplicationId `
        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint `
}
catch {
    if (!$ServicePrincipalConnection) {
        $ErrorMessage = "Connection $ConnectionName not found."
        throw $ErrorMessage
    }
    else {
        Write-Error -Message $_.Exception
        throw $_.Exception
    }
}

Write-Output $BatchNo
Write-Output $BatchSize
Write-Output $TaskName

#Cleanup SharePoint Sites by removing versions from documents

$BatchNo = $BatchNo + 1
If ($BatchNo -It2) {
    Start-AzAutomationRunbook -AutomationAccountName "Automata" -Name "DynamicSchedulingRunbook" - ResourceGroupName "Automata-Demo" -Parameters @("BatchNo"=$BatchNo}
}

For demo purposes I hard limit the run to 2 batches. Also, because in this testcase we are not actually bound to 20 minutes, I set the scheduling on 6 minutes. This is due to the fact that you can only use scheduling when the start time is at least 5 minutes after scheduling time. Adding 5 minutes to current time does not work because creating the schedule also takes a couple of seconds, so we choose 6 minutes. 

Azure Automation Scheduling.

In order to schedule a runbook you need to create a new schedule. 

New-AzAutomationSchedule -AutomationAccountName "Automata" -Name "Task_Batch$BatchNo" -
StartTime $StartTime -OneTime -ResourceGroupName "Automata-Demo"


After that you need to register a Runbook with the created schedule. 

Register-AzAutomationScheduledRunbook -AutomationAccountName "Automata" -
Name "DynamicSchedulingTask" -ScheduleName "Task_Batch$BatchNo" -ResourceGroupName "Automata-Demo" -Parameters @{"BatchNo"=$BatchNo;"BatchSize"=$BatchSize;"TaskName"=$TaskName}


Because we are working in batches of 100 sites (max number of exclusions) we need to execute these tasks multiple times to make sure our tasks have touched all sites. 

Passing parameters.

Because these runbooks need to be aware of the progress and what batch is next, we need to pass parameters back and forth between them. We use the parameter $BatchNo for this. We start at zero and in the DynamicSchedulingTask we increment this variable so the DynamicSchedulingRunbook knows to schedule the next batch. 

Important! When passing parameters to a schedule, these parameters will remain the same when a schedule is reused. This seems to be a bug, since our code is passing the new parameters to the Register-AzAutomationScheduledRunbook. However, when the name is exactly the same the other properties are not being overwritten. This is why we add the $BatchNo to the name of the schedule to make it "unique". 
Batch Name
However, when you need to run the entire orchestration again (let's say we want to do this every month), it's best to dispose the schedules that are dynamically created. We can add a line of code to DynamicSchedulingTask to make this happen. 

#Cleanup SharePoint Sites by removing versions from documents

# Remove schedule that started this very task
Remove-AzAutomationSchedule -AutomationAccountName "Automata" -Name "Task_Batch$BatchNo" -
ResourceGroupName "Automata-Demo" -Force

$BatchNo = $BatchNo + 1
If ($BatchNo -It2) {
    Start-AzAutomationRunbook -AutomationAccountName "Automata" -Name "DynamicSchedulingRunbook" - ResourceGroupName "Automata-Demo" -
Parameters @("BatchNo"=$BatchNo;"BatchSize="$BatchSize;"TaskName"=$TaskName}
}


Is this all? 

Yes, it is. But there are some things to consider when moving forward with this approach. 

Error handling.

Since runbooks are not aware of any form of orchestration, good error handling is needed. When task 1 fails, we shouldn't execute task 2. 

Logging.

Runbooks do not always have great logging on themselves. Make sure your script implements extensive logging to get to the issues at hand. 

Failsafe.

When orchestrating runbooks and schedules, make sure you know how to stop the chain when something goes wrong. You can cancel a scheduled runbook before it runs, you can stop a runbook while running. 

Running costs.

Using runbooks costs money. Even though the costs are low at the start you should plan ahead and figure out the running costs of your operation. Use the Azure Calculator for this to get an idea. Make sure you select "Process Automation" in the Capability dropdown. 



Nieuws.

Portiva gaat verder met Rapid Circle. Binnenkort organiseren wij onze webinars dan ook onder de naam Rapid Circle. Lees meer over ons en Rapid Circle in het nieuwsbericht.

LogoMorphNew-transparent-light-background

Submit a comment