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.
Hi, thanks for the guide.
I added this parameter in the script:
[bool] $ManagedIdentity = $true,
Next I changed the authentication part of the script:
# 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
}
etc…………
And finally I changed the login-azureautomation command:
if ($Login) {
Login-AzureAutomation $UseAzModule $ManagedIdentity
}
When I test the runbook I receive this error:
Failed
Connection to Azure was not able to be established. (Connection to Azure was not able to be established.)
……..
Importing cmdlet ‘Update-AzureRmAutomationSourceControl’.
Importing alias ‘Import-AzureRmAutomationModule’.
Logging in to Azure as Managed Identity…
AzureRM.Profile already loaded. Az and AzureRM modules cannot be imported in the same session or used in the same script or runbook. If you are running PowerShell in an environment you control you can use the ‘Uninstall-AzureRm’ cmdlet to remove all AzureRm modules from your machine. If you are running in Azure Automation, take care that none of your runbooks import both Az and AzureRM modules. More information can be found here: https://aka.ms/azps-migration-guide.
AzureRM.Profile already loaded. Az and AzureRM modules cannot be imported in the same session or used in the same script or runbook. If you are running PowerShell in an environment you control you can use the ‘Uninstall-AzureRm’ cmdlet to remove all AzureRm modules from your machine. If you are running in Azure Automation, take care that none of your runbooks import both Az and AzureRM modules. More information can be found here: https://aka.ms/azps-migration-guide.
The ‘Connect-AzAccount’ command was found in the module ‘Az.Accounts’, but the module could not be loaded. For more
information, run ‘Import-Module Az.Accounts’.
Can you help me please?
Is this your complete script?
Your logs indicate that an AzureRM Module is being loaded into the automation account.
Importing cmdlet ‘Update-AzureRmAutomationSourceControl’.
Importing alias ‘Import-AzureRmAutomationModule’.
the complete script is the microsoft one, I only modified the parts indicated.
i was referring to this error:
Failed
Connection to Azure was not able to be established. (Connection to Azure was not able to be established.)
when starting to log in with the managed identity it seems to give an error:
Logging in to Azure as Managed Identity…
.
.
.
.
The ‘Connect-AzAccount’ command was found in the module ‘Az.Accounts’, but the module could not be loaded. For more
information, run ‘Import-Module Az.Accounts’.
Is there any specific role assignments that need to be granted to the Managed Id?