Azure Managed Identities Work for Some but Not All Microsoft 365 Modules
Updated 15 July 2023
Using Azure Automation runbooks is a great way to run PowerShell scripts on a regularly scheduled basis. In previous articles, I’ve explored using runbooks to process Exchange Online data, sending a welcome email to new employees with the Microsoft Graph PowerShell SDK, and creating files in SharePoint Online. All are good examples of how to take advantage of Azure Automation. In this article, I explore using a Managed Identity for authentication in Azure Automation runbooks.
A managed identity is a system-assigned and managed identity that can be used to access resources. Two types of managed identities are available: system and user. In this article, I cover system-managed identities rather than user-managed identities, System managed identities are tied to a resource like an automation account. As we’ll see later, being able to assign Graph API permissions and Azure AD administrative roles to the service principal of an automation account is a critical part of the implementation.
Microsoft documents the set of Azure services that can authenticate managed identities, but there’s a lack of documentation for the Microsoft 365 PowerShell modules. My assessment is:
- Microsoft Graph PowerShell SDK (Azure AD accounts and groups): works.
- Microsoft Teams: works.
- Exchange Online management: not yet supported for the Exchange Online management (V2) module. You can connect the older Exchange V1 module and use it with a managed identity. We’ll cover that in another article.
- SharePoint Online: There are examples of using SharePoint Online PowerShell with a managed identity (here’s one and a second example). The SharePoint PnP module supports a limited set of functionality with managed identities. Parsing the finer points of using SharePoint Online with a managed identity lies outside the scope of this article. As we’ll see later, to write a message into a Teams channel, I use the SharePoint PnP module.
The overall impression is that Microsoft designed Azure Automation and managed identities to deal with Azure resources and hasn’t paid much attention to making Azure Automation work as well with Microsoft 365 resources. Still, life (and IT) is a journey, so let’s explore the possibilities.
Creating an Azure Automation Account
To get started, you need an Azure Automation account that’s associated with an Azure subscription. Some recommend that you use a separate account for managed identities, but you can use an existing account if you want. I discuss how to create an Azure Automation account in this article, the big difference being that RunAs accounts are not needed for managed identities.
Permissions and Roles for Managed Identities
Microsoft’s documentation explains how a system-assigned managed identity works. The same article also covers how to create a managed identity. For this article, I use an automation account called ManagedIdentitiesAutomation. This account stores the resources we’ll use in the example, primarily the PowerShell modules.
When you create a managed identity, Azure AD creates a service principal object for the managed identity. This is critical because the service principal is “a convenient way to assign permissions.” In other words, like the service principals of other Azure AD registered and enterprise apps, you can assign permissions and administrative roles to a managed identity’s service principal to make those rights available to the managed identity.
To see details of the roles and permissions available to the service principal, access the Azure AD admin center, select Enterprise applications, and filter for Managed identities. When you find the managed identity you created in Azure Automation, you can see the permissions assigned to its service principal (Figure 1).
Many permissions are listed in Figure 1. That’s because I used the same account for different tests. To figure out what permissions you need for cmdlets that use Graph APIs, you can follow the advice in this article. We’ll come back to permission management shortly.
Working Example
A working example is a great way to explore the possibilities of any technology. In my case, I decided to move a script I wrote to populate the membership of a shared channel in a team to Azure Automation. The script searches for new Azure AD accounts and adds them to the shared channel to make sure that everyone in the organization can access the channel. Moving the script to Azure Automation allowed me to schedule it to run periodically to detect new and add new accounts.
The script uses the Microsoft Teams and Microsoft Graph PowerShell SDK modules. As a bonus, I updated the script to post the results of its processing to the shared channel so that everyone would see details of new members. We’ll get to that part soon.
Connecting, Authenticating, and Permissions
The script needs to connect to the Microsoft Graph and Microsoft Teams endpoints. The easiest way to get an accesss token is to run the Connect-AzAccount cmdlet. You can run the Connect-MicrosoftTeams cmdlet with an Identity parameter to use a managed identity, which leads to:
# Connect to Microsoft Graph in Azure Automation Connect-AzAccount -Identity $AccessToken = Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com" # Connect to the Graph SDK with the acquired access token Connect-Graph -AccessToken $AccessToken.Token -Scopes AppRoleAssignment.ReadWrite.All #Define the desired graph endpoint Select-MgProfile Beta # Connect to Teams Connect-MicrosoftTeams -Identity
Microsoft Graph PowerShell SDK V2.0 (or later) supports managed identities, so you can replace the connection with:
Connect-MgGraph -Identity -Scopes AppRoleAssignment.ReadWrite.All
Remember that V2 of the SDK divides cmdlets into production and beta modules. If your code uses beta cmdlets, remember to import the necessary beta modules (like Microsoft.Graph.Beta.Users) into the Azure Automation account. The Select-MgProfile cmdlet is not in the SDK V2 and should be removed from runbooks.
Checking Permissions
We’ve now connected to the Graph and Teams, but the access token generated by Azure AD won’t allow us to do much unless the managed identity has permissions to run cmdlets or Graph requests. Before moving on, I had to:
- Add the Teams management role to the service principal of the managed identity.
- Assign the necessary Graph API permissions for the tasks performed in the script to the service principal.
When looking at the set of permissions assigned to the managed identity in Figure 1, you don’t see options to add or remove permissions (the full screen isn’t shown, but no options are available). The Azure AD admin center says: “The ability to consent to this application is disabled as the app does not require consent.” In practical terms, this means that all management of permissions must be done through PowerShell.
After making sure that the account used has the Directory.ReadWrite.All and Application.ReadWrite.All permissions (to update Azure AD and the application details), here’s how I assigned the Teams management role to the service principal:
# Fetch details of the Teams management app $TeamsApp = Get-MgServicePrincipal -Filter "AppId eq '48ac35b8-9aa8-4d74-927d-1f4a14a0b239'" $AppPermission = $TeamsApp.AppRoles | Where-Object {$_.DisplayName -eq "Application_access"} # Create the payload for the assignment $AppRoleAssignment = @{ "PrincipalId" = $ManagedIdentity.Id "ResourceId" = $TeamsApp.Id "AppRoleId" = $AppPermission.Id } # Assign the role to the service principal for the managed identity. New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $ManagedIdentity.Id -BodyParameter $AppRoleAssignment
And after that, I followed up by assigning the necessary Graph permissions to the service principal. Here’s how I added the permission needed to add a new member to a channel:
$GraphApp = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'" # Microsoft Graph $Role = $GraphApp.AppRoles | Where-Object {$_.Value -eq 'ChannelMember.ReadWrite.All'} $AppRoleAssignment = @{ "PrincipalId" = $ManagedIdentity.Id "ResourceId" = $GraphApp.Id "AppRoleId" = $Role.Id } # Assign the Graph permission New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $ManagedIdentity.Id -BodyParameter $AppRoleAssignment
If you make a mistake and assign a permission that isn’t necessary, you can remove it by finding the identifier for the assignment of the permission in the set held by the service principal and running the Remove-MgServicePrincipalAppRoleAssignment cmdlet. Here’s what I did to remove the TeamWork.Migrate.All permission.
[Array]$SPPermissions = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $ManagedIdentity.Id $Role = $GraphApp.AppRoles | Where-Object {$_.Value -eq "TeamWork.Migrate.All"} $Assignment = $SpPermissions | Where-Object {$_.AppRoleId -eq $Role.Id} Remove-MgServicePrincipalAppRoleAssignment -AppRoleAssignmentId $Assignment.Id -ServicePrincipalId $ManagedIdentity.Id
Two things might go through your mind at this point. First, are all these steps documented by Microsoft? And second, why is the process of permission management for a managed identity so complex? My view is that the area lacks coherent documentation. Microsoft covers the basics in different places but bringing everything together to make permission management for a managed identity a straightforward operation doesn’t appear to have happened. Instead, Microsoft leaves it to others to turn the theory into practice.
In any case, after adding the necessary roles and permissions to the managed identity, when the script authenticates, the access token generated by Azure AD includes all the permissions and the script can do some real work (Figure 2).
Posting to Teams
As mentioned above, I wanted the script to output details of new members in a channel. Two issues presented themselves:
- We’re posting to a shared channel. Access to the channel is limited to the channel membership. The managed identity isn’t a member of the channel and there doesn’t seem to be a way to impersonate a channel member.
- Shared channels don’t support connectors, so it’s not possible to use the incoming webhook connector to post a message (unless you decide to post to another channel in the host team).
I used Azure KeyVault to store the user credentials and other information needed to connect to PnP. Storing this information in Azure Key Vault makes it easy to change the account used to post messages or the target channel. To post the message, I used much the same code as explained in this article to create an HTML body part containing details of the new channel members (Figure 3).
After I got everything working, I published the runbook and added it to a schedule so that Azure Automation would run the script every week. The script has hummed away quite happily for a couple of weeks, so I’m calling that a success. You can download the full script from GitHub.
Learnings and Conclusion
Even with some rough edges, the combination of the Microsoft Graph APIs, Azure Automation, and managed identities is a nice way to offload the processing of resource-intensive scripts, like those that scan all tenant members to generate reports. Things would be even better if all the mainline PowerShell modules used for Microsoft 365 management supported managed identities more thoroughly and elegantly than they do today. That might come in time.
Cybersecurity Risk Management for Active Directory
Discover how to prevent and recover from AD attacks through these Cybersecurity Risk Management Solutions.
Thanks, Tony. Have you tried to connect to a remote tenant from a runbook in a source tenant? I read somewhere that you can have a remote tenant approve it using https://login.microsoftonline.com/{TARGET_TENANT_ID}/adminconsent?client_id={APP_REGISTRATION_CLIENT_ID}&redirect_uri={REDIRECT_URI}. (This would reference the application reg associated with the managed identity for the automation account).
It seems to work, but then when I try to get a token and pass it into get-pnpteamsteam, the results are still coming from my source tenant. Would be interested to hear what you think?
Nope. Never tried. What’s the source of this information?
I had the same issue with Get-MgServicePrincipal. Returned “Insufficient privileges”
Using “Find-MgGraphCommand -Command Get-MgServicePrincipal | Select -First 1 -ExpandProperty Permissions” it told me I needed the permissions below. After adding them to the -Scopes parameter the command worked.
Application.Read.All
Application.ReadWrite.All
Directory.Read.All
Directory.ReadWrite.All
The permissions to manage service principals require you to read and update information about apps and the directory, hence the ones that you found you needed.
Hi Tony, does the account connecting to Powershell Graph needs to be a global admin to set permissions to the managed identities SPN? If I can use a non global admin, what would I need to do on the managed identities SPN to be able to set permissions with this account? I tried adding the account into the Managed Identities App “Cloud Application Administrator” role but when running the New-MgServicePrincipalAppRoleAssignment, it says “Insufficient privileges to complete the operation.”
Thanks!
Forgot to mention I’m connecting to MgGraph this way using my non global admin account: Connect-MgGraph -UseDeviceAuthentication -Scope AppRoleAssignment.ReadWrite.All
You definitely need administrative rights to set permissions for a service principal. If you didn’t, then anyone could assign permissions and that wouldn’t be a good thing. I’m not sure exactly which admin role is necessary: https://learn.microsoft.com/en-us/azure/active-directory/roles/concept-understand-roles offers guidance that “Azure AD-specific roles: These roles grant permissions to manage resources within Azure AD only. For example, User Administrator, Application Administrator, Groups Administrator all grant permissions to manage resources that live in Azure AD.” As a matter of routine, I use Global Administrator. Maybe Security administrator would work too.
I tried to assign the Graph API Permissions via New-MgServicePrincipalAppRoleAssignment.
However it always fails with “New-MgServicePrincipalAppRoleAssignment_Create1: Insufficient privileges to complete the operation.”
I am signed in with a User having the Global Admin permissions. Maybe I am missing a Permission Scope? but I cannot get it to work.
Using the old AzureAD cmdlet it worked immediately.
Which Scopes do I need to provide when connecting to mg-graph in order to be able to execute “New-MgServicePrincipalAppRoleAssignment.”?
I believe that you need the AppRoleAssignment.ReadWrite.All. At least, that’s what I use in https://office365itpros.com/2022/10/13/exchange-online-powershell-app/
Hey Tony,
Insightful article. I’m working on using an runbook to do some automating of my teams administration task. Most of these cmdlets are the ‘grant-cs*’ variety and return a 404 when I run them. I’ve read your article about using the cloud shell for Teams and how the ‘-UseDeviceAuthentication’ switch will allow these cmdlets to work.
Are you aware of any means of getting the ‘cs*’ cmdlets to work with a Managed Identity? I’m flexible and will configure a RunAs account if needed, however, as the Managed Identity is the ‘new’ way of doing thing, I’d like to implement solutions that won’t need re-engineering in the near future. An alternative, of course, would be to do away with the ‘cs*’ cmdlets all together. However, I was unable to find the endpoints to configure things like Emergency calling policies, et al.
I see the same error when I run Grant-CSTeamsMessagingPolicy. It might be that Microsoft hasn’t modernized these old cmdlets (inherited from Skype for Business Online) sufficiently to work with a managed identity. I’d try a Runbook. It’ll be supported for years…