Maintaining Stable Azure Automation Runtime Environments

Use Separate Production and Test Environments to Control PowerShell Module Updates

It was disappointing to discover that Microsoft Graph PowerShell SDK V2.41, which fixes long-standing assembly clashes, introduced a dependency-loading problem for SDK-based runbooks in Azure Automation PowerShell 7.2 and 7.4 runtime environments.

The silver lining in this particular cloud is that the problem prompts tenant administrators to make sure that their automation processes can continue to operate by isolating runtime environments from potential problems in PowerShell module updates.

An Azure Automation runtime environment is a combination of a language runtime (like PowerShell V7.4) and packages (for this discussion, PowerShell modules like Microsoft.Graph.Authentication). To execute runbooks successfully, the runtime and packages must be compatible.

The problem is that Microsoft Graph PowerShell SDK V2.41.0 and V2.41.1 modules are currently unusable with the PowerShell V7.2 and V7.4 runtimes because the Connect-MgGraph cmdlet fails to load. Graph SDK cmdlets must be authenticated to run, so the failure stops every Graph SDK-based runbook dead. Pull request 3813 proposes a fix, but Microsoft hasn’t said when the fix will be included in a new SDK release.

Avoiding Problems with Updated PowerShell Modules

Administrators can easily update runbooks to change their association with Azure Automation runtime environments. To avoid problems that might be present in updated PowerShell modules, the solution for stability with Graph SDK runbooks is to maintain two custom runtime environments. These are:

Production: Uses a PowerShell V7.4 custom runtime environment and the last known stable versions of the Microsoft Graph PowerShell SDK modules. For some, the last known stable version is V2.35. I am happy with V2.40. This environment is tried and trusted. Although it does not use the latest Graph SDK, it provides confidence that runbooks will execute reliably. That is what matters most, especially for scheduled runbooks that perform important processing. For example, I have a scheduled runbook to detect new Graph permissions and another to send users a weekly digest of incomplete Planner tasks.

Current: Uses a PowerShell V7.4 custom runtime environment with the latest versions of the Microsoft Graph PowerShell SDK modules. This environment exists to check that runbooks execute with the latest SDK code. If a problem is discovered, it only affects runbooks associated with the current runtime environment and production runbooks are unaffected.

Processing PowerShell Module Updates

I use PowerShell to update runtime environments when new modules are available. This script updates the current runtime environment with the latest versions of selected Microsoft Graph PowerShell SDK modules, Exchange Online, Teams, ImportExcel, and other modules that I commonly use in runbooks. The set of modules processed by the script is defined in an array, so it’s easily altered to meet your requirements.

No version picker is available to choose a specific version when importing a module into a runtime environment.
Figure 1: No version picker is available to choose a specific version when importing a module into a runtime environment

The Azure UX to add modules to a runtime environment doesn’t support a picker to allow administrators to choose a specific version of a module when importing modules into a custom runtime environment (Figure 1), but this can be done with PowerShell via the Azure REST API. When I detected the problem with SDK V2.41, I reset the current runtime environment with the following code:

First, define a set of variables to sign-into Azure and to define the target custom runtime environment to update. Naturally, the values of these variables will differ for your tenant.

# Define the identifier of the Azure subscription to use 
$SubscriptionId = "35429342-a1a5-4427-9e2d-551840f2ad25" 
# Define the resource group used by the Azure Automation account
$ResourceGroupName = "ExoAutomation"
# Define the name of the Azure Automation account
$AutomationAccountName = "M365Automation"
# Define the name of the target custom runtime environment to update
$RuntimeEnvironment = [System.Uri]::EscapeDataString("PS74-Graph") 
# Define the tenant identifier
$TenantId = 'a662313f-14fc-43a2-9a7a-d2e27f4f3478' 
# Define version of the SDK to use
$Version = '2.40.0'

Next, connect a session to Azure:

Write-Output "Connecting to Azure"
Connect-AzAccount -Subscription $SubscriptionId -Tenant $TenantId -WarningAction SilentlyContinue

Now define the set of Graph SDK modules to update:

# Process these Microsoft Graph PowerShell SDK modules
[array]$Modules = "Microsoft.Graph.Authentication", "Microsoft.Graph.Groups", "Microsoft.Graph.Users",`
     "Microsoft.Graph.Planner", "Microsoft.Graph.Users.Actions", "Microsoft.Graph.Security", "Microsoft.Graph.Teams", "Microsoft.Graph.Sites", `
     "Microsoft.Graph.Reports", "Microsoft.Graph.Mail", "Microsoft.Graph.Applications", "Microsoft.Graph.Calendar", "Microsoft.Graph.Files"

Finally, loop through the modules to update each module to the required version:

ForEach ($Module in $Modules) {
    Write-Output ("Importing version {0} of module {1}" -f $Version, $Module)
    $NupkgURI = "https://www.powershellgallery.com/api/v2/package/$Module/$Version"

    $RequestBody = @{
        properties = @{
         contentLink = @{
            uri = $NupkgUri
         }
        }
    } | ConvertTo-Json -Depth 3
    $EncodedModule = [System.Uri]::EscapeDataString($Module)
    $Path = ("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Automation/automationAccounts/{2}/runtimeEnvironments/{3}/packages/{4}/?api-version=2023-05-15-preview" -f $SubscriptionId, $ResourceGroupName, $AutomationAccountName, $RuntimeEnvironment, $EncodedModule)
  
    $Result = Invoke-AzRestMethod -Method Put -Path $Path -Payload $RequestBody
    # check if everything was OK 
    If ($Result.StatusCode -notin 200, 201, 202) {
        Write-Error "Import request for $Module failed with HTTP status $($Result.StatusCode)."
        $ErrorOutput = $Result.content | ConvertFrom-Json
        $ErrorOutput.Message
    } Else {
        Write-Output "Submitted import of $Module version $Version."
    }
}

The PUT request created with Invoke-AzRestMethod submits the request to import the package to Azure. For production code, poll the package resource until its provisioning state becomes Succeeded or Failed rather than assuming that the initial response means that Azure Automation has completed the import.

After Azure has processed all the module imports, the result should be a set of modules updated to the selected version (Figure 2).

 The Graph SDK modules in the target custom runtime environment are updated to V2.40.
Figure 2: The Graph SDK modules in the target custom runtime environment are updated to V2.40

Just Another Thing to Look After

It would be nice if life was perfect and software modules never had problems. Reality dictates otherwise, and maintaining healthy Azure Automation runtime environments is just another task for tenant administrators. Fortunately, a little PowerShell automates module management, so ensuring that the correct module version is available in each environment isn’t difficult.


Microsoft 365 for IT Pros continues to earn 5-star reviews from readers—and for good reason. If you manage a Microsoft 365 tenant, you’ll benefit from practical, real-world guidance written by experts who work with the platform every day. The subscription includes extensive coverage of Microsoft 365 administration, security, compliance, and governance, plus a 450-page book dedicated to automating Microsoft 365 with PowerShell.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.