Find inactive Microsoft Teams with Azure Automation and GraphAPI

In many projects I get the question about how to find inactive Microsoft Teams and how to deal with them. In this blog post, I show you how to find inactive Microsoft Teams with Azure Automation and GraphAPI. The script adds all inactive Teams or Microsoft 365 to a SharePoint Online list. In a second blog post, I will describe how to archive these inactive groups. Of course, Microsoft offers a built-in solution for these scenarios. But perhaps the solution doesn’t fit to 100% to your business needs and then an own solution comes into the game.


Microsoft’s way to find inactive Teams

Microsoft has a built-in solution finding inactive Teams: Microsoft 365 Group expiration. To use this feature, you need at least the Azure Premium license in your tenant. If you have that luck, then you can define a policy and the system will check the activity for each M365 group. If the algorithm detects an inactive group, it will send a mail to the owner and asks to delete the M365 group. Also, you can enable an auto-renew feature and groups renew automatically based on their activity.

Find inactive Teams with Azure Automation - Screenshot of group expiration policy settings in Entra Id

One of the biggest disadvantages: the process just deletes the expired group. There is no option to archive an inactive Microsoft 365 group.

The second disadvantage: you can enable this feature for all groups or select some. An option to exclude some groups and apply the policy to all other is missing.


Build your own solution

As mentioned in the intro, you can build your own lifecycle management process for Microsoft 365 groups. It will find inactive Microsoft Teams with Azure Automation and GraphAPI. All you need, is a script to detect the group activity and based on this, decide if a group is active or inactive. And then, it depends on your needs, what happens with an inactive group:

  • notify by mail the Teams Service team
  • ask the owner how to continue with the inactive Team
  • archive the Team
  • delete the Team
  • just report

As you can see, there are a lot of options thanks to the Microsoft 365 and Azure platform.

In this blog post series, I will describe the way to notify and ask the Teams owner if the Team should be archived or not. I’m using the following tools to archive this:

  • Microsoft Azure Automation with Managed Identity
  • Microsoft GraphAPI
  • Microsoft Power Automate

In the first part, I describe how to get the required data to find inactive M365 groups. In part two, I describe how to start an approval process to archive a Teams and in the last part, you will get the information how to archive the Teams.


Create a SharePoint list to store inactive Microsoft365 groups

As in my other blog post, I’m using a SharePoint Online list to store the inactive M365 groups. Why I’m using SharePoint Online List:

First, all organizations have a SharePoint Online license. Second, it is really easy to setup and manage. Of course, you can store the data in Dataverse for example and adjust the script, too.

The SharePoint Online list to store the inactive M365 groups should include the following columns:

  • Report Refresh Date
  • Report Period
  • Group Id
  • Group Display Name
  • Group Owners
  • Group Type
  • Is Deleted
  • Last Activity Date
  • Member Count
  • External Member Count
  • Exchange Received Email Count
  • Exchange Mailbox Total Item Count
  • Exchange Mailbox Storage Used Byte
  • SharePoint Active Files Count
  • SharePoint Site Storage Used by Byte
  • Should Be Archived
  • Approved to Archive
  • Approved By
  • Is Archived

You can find a PowerShell script in my GitHub repository to create the required list. Managed Identity mode is not used because it only needs to be run once and you can use your SharePoint Admin account for it.

Create an Azure Automation account

Next, you need to create an Azure Automation Account and use it as Managed Identity. The Managed Identity will run all required PowerShell scripts to find inactive Microsoft 365 groups and add it to the SharePoint Online list.

Find inactive Teams with Azure Automation - Screenshot Creating a new Azure Automation Account

By default, the wizard creates a System Managed identity.

Find inactive Teams with Azure Automation - Screenshot of the summary page Azure Automation account creation

In the next step, you need to grant required permissions to the newly Managed Identity, so it has access to the automation account and can run your script.

Find inactive Teams with Azure Automation - PowerShell output Get-AzRoleAssigment for Managed Identity

Also, it needs permissions to gather the Microsoft 365 group reports. Currently, you need to assign all these permissions by PowerShell and you can find the script in my GitHub.

Find inactive Teams with Azure Automation - Grant new permission to Managed Identity by using New-AzureAdServiceAppRoleAssigment
Find inactive Teams with Azure Automation - Overview of granted permissions to Managed Identity

Then, you need to prepare the Azure Automation environment and add the required PowerShell modules. The following modules are required to run the scripts:

  • PnP.PowerShell
  • Microsoft.Graph.Authentication
  • Microsoft.Graph.Reports

It is important to use the PowerShell version 7.2 environment in Azure.

You can add them by using the Browse from gallery option, search for the modules and select 7.2 (preview) as Runtime version. Press the Import button at the bottom to start the Import process of the PowerShell module.

Find inactive Teams with Azure Automation - Add a module to Azure Automation account
Find inactive Teams with Azure Automation - Overview of required modules in Azure Automation Account

When you have imported all modules, it is time to import my scripts to find inactive Teams. You can find the script in my GitHub repository, as all other script snippets for the permissions, too.

Find inactive Teams with Azure Automation - Import script to find inactive Teams

After you’ve imported and published all scripts, you can run it for the first time.


How to find inactive Teams with Microsoft GraphAPI

When you start the script, it uses the Microsoft GraphAPI to get M365 group activity report and finds inactive groups. The Microsoft 365 group activity report includes all necessary data for this job:

  • Report Refresh Date
  • Group Display Name
  • Is Deleted
  • Owner Principal Name
  • Last Activity Date
  • Group Id
  • Report Period

The endpoint provides the data as CSV file. In the next step, a script imports the CSV file, and analysed the records regarding the last activity date.

If there were never ever any activities in the M365 group, the last activity date for a M365 group is empty and we can assume that the M365 group is inactive. Therefor the script adds the dataset to a SharePoint Online list as “to archive”.

If the imported data record includes a last activity date, the script checks if it is older than 30 days. Of course, you can modify this value. If it is older than 30 days, the script assumes an inactive M365 group and adds the dataset to the SharePoint Online list.

If the M365 group is already deleted or active, the script skips the dataset.

You can find the script “How to find inactive Teams” in my GitHub repository.

After writing the data to the SharePoint Online list, you can start a Power Automate flow or use some other fantastic Microsoft 365 tools to process the generated data.


Microsoft 365 group expiration

Microsoft 365 groups activity reports

How to find ownerless Microsoft Teams – Part 2

12 Replies to “Find inactive Microsoft Teams with Azure Automation and GraphAPI”

    1. Hi Barbara,
      Microsoft states in their official Learn article that at least Microsoft Entra P1 is required for each member of a monitored group. For example, if you enable the Group Expiration Policy for all your Microsoft 365 groups, all members need the Microsoft Entra ID P1 license.

  1. Hi Thorsten,
    i’m tryng to create this job, but it seems that there’s a conflict using Microsoft.Graph and PnP.PowerShell:

    Write-Error: System.TypeLoadException: Could not load type ‘Microsoft.Graph.Authentication.AzureIdentityAccessTokenProvider’ from assembly ‘Microsoft.Graph.Core, Version=1.25.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’. at Microsoft.Graph.PowerShell.Authentication.Helpers.HttpHelpers.GetGraphHttpClient() at Microsoft.Graph.PowerShell.Module.BeforeCreatePipeline(InvocationInfo invocationInfo, HttpPipeline& pipeline) at Microsoft.Graph.PowerShell.Module.CreatePipeline(InvocationInfo invocationInfo, String parameterSetName) at Microsoft.Graph.PowerShell.Cmdlets.GetMgReportOffice365GroupActivityDetail_Get1.ProcessRecordAsync()

    There’s a workaround on this?

    1. I have checked the PowerShell modules and the PNP.Powershell module is no longer compatible since the Microsoft Graph modules from version 2.90. There are several people reporting this problem.
      As a workaround, you can install Microsoft Graph version 2.80. I have tested it and it is compatible with PNP.Powershell version 2.1.1, 2.2.0 and 2.3.0.

      1. Hi Thorsten,

        Thanks for this guide its been very helpful. I was having that same issue that Andrea mentioned above and I made the changes that you suggested with Microsoft Graph and PnP PowerShell but the runtime version that is installed on my modules for the Automation account is 5.1 and I need 7.2 from what you described.

        I’m new to this, is there a way I can change the runtime version to 7.2 from 5.1? thank you.

        1. Hi Ross, thanks for reading my blog and leaving a comment. There is curently no option I’m aware off to migrate your Azure Runbooks from 5.1 to 7.2. From my knowledge you have to import the required PowerShell modules in runtime version 7.2 and recreate your scripts with runtime version 7.2. When you create a new PowerShell script, you can select which runtime to use for.

          1. Hi Thorsten,

            Thanks for getting back to me I appreciate it, I’m new to all this but I managed to get the modules installed and the list is working.

            One question I have, the list seems to export SharePoint sites to the list that don’t have Teams enabled.

            Is there somewhere in the runbook that I can add a line to specify to only export SharePoint sites with Teams enabled to reduce the size of the list that is being generated? Thank you again!

            Kind regards,
            Ross

          2. Hi Ross,

            let me check my script and will give you feedback shortly. I’m only using Microsoft Teams enabled M365 groups and therefor hadn’t the fun of mixed environment. But of course it makes sense to create a filter and just extract the Teams enabled groups.

            I will keep you updated.

            Best regards,
            Thorsten

          3. Hi Ross, I’ve updated the GitHub repository and added new code to the Find-InactiveTeams Script. It will now collect all Microsoft Teams and checks each M365 group from the activity reports, if it is Teams enabled or not. If it is not enabled than the script will skip the entry and won’t add it to the SharePoint Online list. Thanks for the hint.

  2. “https://.sharepoint.com/teams/TeamsAutomation/”

    I think it should be “sites”, not “teams” in the URL.

    1. Hi Baybars, thanks for reading my blog and leaving a comment. It depends on your SharePoint Online configuration. You can select between “/teams/” or “/sites/” and defines the URL of your SharePoint Online Team Sites. You can find more details in the official Microsoft Learn article.

Leave a Reply

Your email address will not be published. Required fields are marked *