Azure Managed Identities Work for Some but Not All Microsoft 365 Modules

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).

The permissions for a Service Principal for a Managed Identity
Figure 1: The permissions for a Service Principal for a Managed Identity

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. A helpful article explained how to obtain an access token for the managed identity and you can run the Connect-MicrosoftTeams cmdlet with an Identity parameter to use a managed identity, which leads to:

#Obtain AccessToken for Microsoft Graph via the managed identity
$ResourceURL = "https://graph.microsoft.com/" 
$Response = [System.Text.Encoding]::Default.GetString((Invoke-WebRequest -UseBasicParsing -Uri "$($env:IDENTITY_ENDPOINT)?resource=$resourceURL" -Method 'GET' -Headers @{'X-IDENTITY-HEADER' = "$env:IDENTITY_HEADER"; 'Metadata' = 'True'}).RawContentStream.ToArray()) | ConvertFrom-Json 
$AccessToken = $response.access_token 

#Connect to the Microsoft Graph using the aquired AccessToken
Connect-Graph -AccessToken $AccessToken
#Define the desired graph endpoint
Select-MgProfile  Beta

Connect-MicrosoftTeams -Identity

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.

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).

Testing that the managed identity can update Teams channel membership
Figure 2: Testing that the managed identity can update Teams channel membership

Posting to Teams

As mentioned above, I wanted the script to output details of new members in a channel. Two issues presented themselves:

  1. 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.
  2. 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).

The managed identity can post a message to a Teams shared channel
Figure 3: The managed identity can post a message to a Teams shared channel

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.

Meet other Azure AD experts at The Experts Conference 2022, December 6-7.

100% Free and Virtual! You don't want to miss the Microsoft training event of the year!

Learn More

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Comments

  1. Tom

    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.

    1. Avatar photo
      Tony Redmond

      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…

Leave a Reply