Thumbnail Working with Microsoft Teams PowerShell in Azure Automation

Working with Microsoft Teams PowerShell in Azure Automation

I was recently creating a script that would need to connect to Microsoft Teams using Managed Identity in an Azure Automation Runbook. I used the Microsoft Teams PowerShell module and tried out a few routes before being succesful. In this blog post I’ll share my findings and the steps I took to get it working.

Enabling Managed Identity is as easy as it is on any resource. Just open the Automation Account and click on the Identity blade. You can enable a System Assigned Managed Identity by switching a simple toggle. If you want to use a User Assigned Managed Identity, you can add one by clicking on the Add button. In that case you’ll need to have created a managed identity resource first though.

enabling-identity

Enabling managed identity will create a Service Principal in Entra ID, that you can assign permissions and roles. Copy the object Id of the Service Principal from the identity blade and add it to your Automation Account variables. You’ll need it later on.

Adding permissions to service principals cannot be done using the Entra ID Portal currently. You’ll need a scripting tool for that. I personally like to use the CLI for Microsoft 365 for this.

I’ve used the documentation to add all permissions that can be used. But it’s also possible to just add what you need, which is a good thing in the sense of the ‘Least Privilege’ principle.

You can execute the below script to add the permissions:

m365 login

# Use the object Id of your system/user assigned identity here
$appObjectId = "25a56900-360d-41d9-83e5-02385a63b04c"

# Define the permission scopes you want to add
$scopes = "Organization.Read.All,User.Read.All,Channel.Delete.All,ChannelSettings.ReadWrite.All,Group.ReadWrite.All,ChannelMember.ReadWrite.All,AppCatalog.ReadWrite.All,TeamSettings.ReadWrite.All"

m365 entra approleassignment add --appObjectId $appObjectId --resource "Microsoft Graph" --scopes $scopes
Important: Use the principle of Least priviledge, and only add those permissions you really need.

The CLI for Microsoft 365 can be installed on your local machine, but if you don’t have it, you can even use it from the Azure Portal by opening the Azure Cloud Shell. No install required. The CLI for Microsoft 365 is installed there by default. Do not forget to login first in the Cloud Shell as well, using m365 login, it will not login automatically for you.

azure-cloud-shell

Thanks Garry Trinder for reminding me!

Aside from adding permissions, you’ll also need to add an Entra role to the Service Principal. This is done in the Entra ID Portal. Open ‘Entra roles and administrators’ in the Azure Portal, find and open the ‘Teams Administrator’ role and add the service principal to that role.

azure-ad-roles

teams-roles

…you’ll find your way from here, right? 😁

Ok! So having enabled an identity, and added permissions and a role, you can now start connecting to Microsoft Teams. Make sure you have added the MicrosoftTeams PowerShell Module in the Azure Automation Account. You can do this by going to the Modules blade and clicking on the Add a module button.

You can use the Connect-MicrosoftTeams cmdlet to do this:

Using System Assigned Managed Identity is the easiest route. The following script should render a nice message about how many Teams you have in your tenant.

Connect-MicrosoftTeams -Identity

$teams = Get-Team

Write-Output "Found $($teams.Count) teams"

When using a User Assigned Identity, you need an extra -AccountId option to help the module select the correct User Assigned Identity. You can use the Object Id or the Client ID of the Service Principal. I’d suggest you store these in the variables of Azure Automation Account and script it as follows:

$identityObjectId = Get-AutomationVariable -Name 'UserAssignedIdentityObjectId'

Connect-MicrosoftTeams -Identity -AccountId $identityObjectId

$teams = Get-Team

Write-Output "Found $($teams.Count) teams"

This route was not at all obvious to me, I knew you need to add a reference to the service principal, but the -AccountId parameter was not well documented. Plus the Connect-MicrosoftTeams cmdlet does have an alternate -ApplicationId parameter that can be used for app only connections. But this parameter can only be used in combination with a certificate, and I wanted to use Managed Identity instead. I concluded that it wasn’t possible to use User Assigned Identity yet.

But fortunately I did one extra test, using the -AccountId parameter, and was proven wrong. 🎉

Another route is to use the -AccessTokens parameter. This allows you to pass two access tokens that you need to retrieve manually. One access token for the Graph and one for the Teams service principal. You can use the following script to do this:

function Get-AccessTokenUsingManagedIdentity($url) {
    $identityObjectId = Get-AutomationVariable -Name 'UserAssignedIdentityObjectId'
    $endpoint=$env:IDENTITY_ENDPOINT
    $header= $env:IDENTITY_HEADER    
    $response = [System.Text.Encoding]::Default.GetString((Invoke-WebRequest -UseBasicParsing  -Uri "$($endpoint)?resource=$url&api-version=2019-08-01&object_id=$identityObjectId" -Method 'GET' -Headers @{'X-IDENTITY-HEADER' = "$header"; 'Metadata' = 'True'}).RawContentStream.ToArray()) | ConvertFrom-Json
    return $response.access_token    
}

$graphToken = Get-AccessTokenUsingManagedIdentity -url "https://graph.microsoft.com"
$teamsToken = Get-AccessTokenUsingManagedIdentity -url "48ac35b8-9aa8-4d74-927d-1f4a14a0b239"

Connect-MicrosoftTeams -AccessTokens @("$graphToken", "$teamsToken")

$teams = Get-Team

Write-Output "Found $($teams.Count) teams"

As you can see, this code uses the manual route to retrieve Managed Identity Access tokens for Azure Automation. I learned this from Rodrigo Pinto, alias Scoutman. Read his post here. It’s a very handy function that can also be used for Azure Function apps. You can use it with System Assigned as well as with User Assigned Managed Identity. The 48ac35b8-9aa8-4d74-927d-1f4a14a0b239-guid is the client Id of the ‘Skype and Teams Tenant Admin API’ service principal. This is globally the same for all Microsoft 365 tenants.

Initially, I got a lot of odd errors when using my manually retrieved tokens in this way:

IDX14102: Unable to decode the header 'System.String' as Base64Url encoded string. jwtEncodedString: 'System.String'.
The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.

I checked out my tokens and am certain that they where correct. Copying the access tokens and running the same command on my local PC even worked! It looked like more people had these issues with the -AccessTokens parameter, so I just ignored it. But then today, when writing this blog post, I checked it again, with the purpose of copying the exceptions, AND IT SUDDENLY WORKED!! 🤯

Who knows what went on here. Some internal Microsoft magic, I guess. If you can’t get it to work: never mind, try route 2.

Using Managed Identity is a great way to connect to Microsoft Teams. Once you know what you’re doing, it’s easy to set up, and you don’t need to worry about storing credentials. Once you know what you’re doing… I’m hoping this post will help some people get there faster.

Thanks for reading and happy coding!


auth teams powershell azure entraid
Support me by sharing this

More

More blogs

Using the on-behalf-of flow in Azure PowerShell Functions
Using the on-behalf-of flow in Azure PowerShell Functions

A step by step guide on how to use the on-behalf-of flow in Azure PowerShell Functions.

Read more
Getting notified of service incidents in Microsoft Teams
Getting notified of service incidents in Microsoft Teams

Part 2 on how to use the CLI for Microsoft 365 and Azure Functions: How to get notified in Teams, when a service health incident occurs on SharePoint?

Read more
Don't trust $PWD in Azure PowerShell Functions
Don't trust $PWD in Azure PowerShell Functions

Note to self: do not trust $PWD in Azure PowerShell Functions.

Read more

Thanks

Thanks for reading

Thanks for reading my blog, I hope you got what you came for. Blogs of others have been super important during my work. This site is me returning the favor. If you read anything you do not understand because I failed to clarify it enough, please drop me a post using my socials or the contact form.


Warm regards,
Martin

Microsoft MVP | Microsoft 365 Architect

Microsoft MVP horizontal