Azure, Powershell, Professional

Azure Automation: Automatically Updating Az Modules w/ Managed Identities

Introduction

If you aren’t familiar with Azure Automation Accounts think of them as a method to run an automated script within your Azure Subscription. This automation account requires PowerShell modules to be installed into it to help execute any processes. One of the things that often gets overlooked is how to perform maintenance for the Automation Account to ensure all these modules are being regularly updated.

The “Recommended Approach”

Microsoft has provided an open sourced script to assist with this process. This script would be loaded into a Runbook within the Automation Account and then tied to a schedule that may at a regular cadence, something like every month. The script has the ability to update both Az and AzureRm modules.

This process does work fairly well….there’s just one issue. It does not currently support running as a Managed Identity. For information on how to configure an Automation Account to run as a Managed Identity check out my blog post on it!

Ideally one might recommend contacting Microsoft or submitting a PR against the repository. The issue with that is the repository hasn’t been updated since 2020 and even worse there are PRs out there dating that far back waiting for review/approval. I’ve open feedback issue on Microsoft Docs…we’ll see where it goes.

The Fix

To keep this script as intact and backwards compatible I added a parameter:

.PARAMETER ManagedIdentity
(Optional) If $false, leverage Run As Account; otherwise will attempt to connect as a Managed Identity.

This parameter is defaulted to $false as to ensure if it isn’t provided via an existing process the update to used Managed Identities won’t be hit.

The main update that needs to occur will be in the Login-AzureAutomation method. Which makes sense…we are switching the authentication from leveraging a Run As account to a Managed Identity. Due to this the parameters being passed in will need to updated to include the new parameter.

Login-AzureAutomation $UseAzModule $ManagedIdentity

The function then needs to check if $ManagedIdentity is true before executing the RunAs section

# Use the Run As connection to login to Azure
function Login-AzureAutomation([bool] $AzModuleOnly, [bool] $ManagedIdentity) {
    try {
        if ($ManagedIdentity) {
            Write-Output "Logging in to Azure as Managed Identity... "
            Connect-AzAccount -Identity `
                -Environment $AzureEnvironment
        }
        else {
            $RunAsConnection = Get-AutomationConnection -Name "AzureRunAsConnection"
            Write-Output "Logging in to Azure ($AzureEnvironment)..."
        
            if (!$RunAsConnection.ApplicationId) {
                $ErrorMessage = "Connection 'AzureRunAsConnection' is incompatible type."
                throw $ErrorMessage            
            }
        
            if ($AzModuleOnly) {

                Connect-AzAccount `
                    -ServicePrincipal `
                    -TenantId $RunAsConnection.TenantId `
                    -ApplicationId $RunAsConnection.ApplicationId `
                    -CertificateThumbprint $RunAsConnection.CertificateThumbprint `
                    -Environment $AzureEnvironment

                Select-AzSubscription -SubscriptionId $RunAsConnection.SubscriptionID  | Write-Verbose
            }
            else {
                Add-AzureRmAccount `
                    -ServicePrincipal `
                    -TenantId $RunAsConnection.TenantId `
                    -ApplicationId $RunAsConnection.ApplicationId `
                    -CertificateThumbprint $RunAsConnection.CertificateThumbprint `
                    -Environment $AzureEnvironment

                Select-AzureRmSubscription -SubscriptionId $RunAsConnection.SubscriptionID  | Write-Verbose
            }
        } 
    }
    catch {
        if (!$RunAsConnection) {
            $RunAsConnection | fl | Write-Output
            Write-Output $_.Exception
            $ErrorMessage = "Connection to Azure was not able to be established."
            throw $ErrorMessage
        }

        throw $_.Exception
    }
}

Managed Identities are supported only via the Az class so we just need to check if the ManagedIdentity is true…if it is then we are wanting the Az class loaded. Else perform the existing check.

Testing

Testing is kind of tricky. Think we are updating our modules on a cadence so how can we confirm the process is actually working every time? Here is a scrip that will check if the module for a specific use case exists, Az.KeyVault, Remove it, Install an older version via URI, then remotely kick off the job.

Set-AzContext -Subscription "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
$resourceGroupName = "rg-automationAccount"
$automationAccountName = "aaautomationaccount"
$runbookName = "Update-AutomationAzureModulesForAccount"
$runbookParams = @{"ResourceGroupName"=$resourceGroupName; "AutomationAccountName" = $automationAccountName; "login"= $true; "AzureModuleClass" = "Az"; "ManagedIdentity" = $true}
$installedKeyVaultModuleVersion = Get-AzAutomationModule -ResourceGroupName $resourceGroupName -AutomationAccountName $automationAccountName -Name "Az.KeyVault" | Select-Object -Property Version
Write-Host "Current version Installed is $installedKeyVaultModuleVersion"

if ($installedKeyVaultModuleVersion){
Remove-AzAutomationModule -AutomationAccountName $automationAccountName -Name "Az.KeyVault" -ResourceGroupName $resourceGroupName -Force
New-AzAutomationModule -AutomationAccountName $automationAccountName -Name "Az.KeyVault" -ContentLinkUri "https://www.powershellgallery.com/api/v2/package/Az.KeyVault/3.4.4" -ResourceGroupName $resourceGroupName
$installedKeyVaultModuleVersion = Get-AzAutomationModule -ResourceGroupName $resourceGroupName -AutomationAccountName $automationAccountName -Name "Az.KeyVault" | Select-Object -Property Version, ProvisioningState

while ($installedKeyVaultModuleVersion.ProvisioningState -ne 'Succeeded'){
    Start-Sleep -s 10
    $installedKeyVaultModuleVersion = Get-AzAutomationModule -ResourceGroupName $resourceGroupName -AutomationAccountName $automationAccountName -Name "Az.KeyVault" | Select-Object -Property Version, ProvisioningState
}
Write-Host "Current version Installed is $installedKeyVaultModuleVersion"
Start-AzAutomationRunbook –AutomationAccountName $automationAccountName `
 -Name $runbookName `
 -ResourceGroupName $resourceGroupName `
 -Parameters $runbookParams

 Start-Sleep -s 60
 $installedKeyVaultModuleVersion = Get-AzAutomationModule -ResourceGroupName $resourceGroupName -AutomationAccountName $automationAccountName -Name "Az.KeyVault" | Select-Object -Property Version, ProvisioningState
 while ($installedKeyVaultModuleVersion.ProvisioningState -ne 'Succeeded'){
    Start-Sleep -s 10
    $installedKeyVaultModuleVersion = Get-AzAutomationModule -ResourceGroupName $resourceGroupName -AutomationAccountName $automationAccountName -Name "Az.KeyVault" | Select-Object -Property Version, ProvisioningState
}
Write-Host "Current version Installed is $installedKeyVaultModuleVersion"
}

Next Steps

Now that this script runs the next step would be configure it run at a regular cadence connecting the Runbook to a Schedule and thus create a job. Read my post on leveraging Terraform for the deployments here.

Conclusion

There is a way to automatically update Az modules for Azure Automation accounts that leverage Managed Identities. Hopefully Microsoft will update the existing open source solution but until hopefully this has been useful.