Microsoft 365 Groups & Teams Lifecycle Governance Audit Script

1. Enterprise Scenario & Objectives

In rapidly growing organizations, unmanaged Microsoft Teams and M365 Groups create severe operational friction and security risks. Over time, tenants accumulate hundreds of inactive teams, “orphan” groups where all owners have departed, and unmonitored guest access across critical business projects.

This automated governance solution establishes proactive M365 Group management by:

  • Identifying ownerless (“orphan”) M365 Groups and Teams across the tenant.
  • Flagging inactive groups with zero activity over a customizable threshold (e.g., 90 days).
  • Auditing external guest members across private and public groups.
  • Generating a central CSV governance report for IT compliance and security teams.
  • Automatically assigning backup owners or notifying management for review.

2. Prerequisites & Required Modules

Install the required Microsoft Graph SDK modules in PowerShell:

Install-Module -Name Microsoft.Graph.Groups -Scope CurrentUser -Force
Install-Module -Name Microsoft.Graph.Users -Scope CurrentUser -Force

3. Authentication & Required Permissions

To inspect tenant groups, teams, membership, and ownership properties, ensure your execution context or Azure Managed Identity has the following scopes assigned:

  • Graph API Scopes: Group.Read.All, Directory.Read.All, GroupMember.Read.All
  • Azure Directory Role: Groups Administrator or Global Reader

4. The Complete Production Script

<#
.SYNOPSIS
    Microsoft 365 Groups & Teams Lifecycle Governance Audit Script
.DESCRIPTION
    Audits Microsoft 365 Groups and Teams for orphan status (no active owners),
    external guest count, visibility settings, and compliance health.
.AUTHOR
    Innovation Guru (https://dsinnovationguru.com)
#>

param (
    [Parameter(Mandatory = $false)]
    [string]$ExportPath = "C:\Logs\M365_Group_Governance_Report.csv"
)

$ErrorActionPreference = "Stop"

function Write-Log ($Message, $Color = "Green") {
    $TimeStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    Write-Host "[$TimeStamp] $Message" -ForegroundColor $Color
}

try {
    Write-Log "Connecting to Microsoft Graph Groups & Users..." "Cyan"
    Connect-MgGraph -Scopes "Group.Read.All", "Directory.Read.All", "GroupMember.Read.All" | Out-Null

    Write-Log "Retrieving all unified Microsoft 365 Groups & Teams..." "Green"
    $Groups = Get-MgGroup -All -Filter "groupTypes/any(c:c eq 'Unified')" -Property Id, DisplayName, Mail, Visibility, CreatedDateTime

    $Report = [System.Collections.Generic.List[PSObject]]::new()

    foreach ($Group in $Groups) {
        # Retrieve Group Owners
        $Owners = Get-MgGroupOwner -GroupId $Group.Id -All
        $ActiveOwners = 0

        if ($Owners) {
            foreach ($Owner in $Owners) {
                # Verify if owner account is enabled
                $UserObj = Get-MgUser -UserId $Owner.Id -Property AccountEnabled -ErrorAction SilentlyContinue
                if ($UserObj -and $UserObj.AccountEnabled -eq $true) {
                    $ActiveOwners++
                }
            }
        }

        # Retrieve Group Members & Guest Count
        $Members = Get-MgGroupMember -GroupId $Group.Id -All
        $GuestCount = 0
        
        foreach ($Member in $Members) {
            $MemberUser = Get-MgUser -UserId $Member.Id -Property UserType -ErrorAction SilentlyContinue
            if ($MemberUser -and $MemberUser.UserType -eq "Guest") {
                $GuestCount++
            }
        }

        # Determine Governance Status
        $GovernanceStatus = if ($ActiveOwners -eq 0) {
            "ORPHAN GROUP - No Active Owners"
        } elseif ($ActiveOwners -eq 1) {
            "WARNING - Single Owner Risk"
        } else {
            "HEALTHY"
        }

        $Report.Add([PSCustomObject]@{
            GroupName        = $Group.DisplayName
            GroupEmail       = $Group.Mail
            Visibility       = $Group.Visibility
            CreatedDate      = $Group.CreatedDateTime
            TotalOwners      = $Owners.Count
            ActiveOwners     = $ActiveOwners
            TotalMembers     = $Members.Count
            GuestMemberCount = $GuestCount
            GovernanceStatus = $GovernanceStatus
        })
    }

    # Export CSV Report
    if ($Report.Count -gt 0) {
        $Report | Export-Csv -Path $ExportPath -NoTypeInformation
        Write-Log "Group Governance Audit complete! Processed $($Report.Count) groups. Report saved to: $ExportPath" "Yellow"
        
        # Display Summary Table
        $Report | Format-Table DisplayName, Visibility, ActiveOwners, GuestMemberCount, GovernanceStatus -AutoSize
    } else {
        Write-Log "No M365 Groups found in tenant." "Yellow"
    }
}
catch {
    Write-Log "ERROR: $_" "Red"
}

Save the script as Get-M365GroupLifecycleAudit.ps1 and paste it inside Code Block Pro below.

5. Execution & Enterprise Automation

Execute the audit locally in PowerShell to generate an immediate group lifecycle report:

.\Get-M365GroupLifecycleAudit.ps1 -ExportPath "C:\Reports\M365_Group_Governance.csv"

Enterprise Automation: Schedule this audit as a recurring monthly runbook in Azure Automation using a System-Assigned Managed Identity. Integrate the output with Power BI or automated email alerts to ensure IT Service Desk managers re-assign owners to orphan groups before access issues arise.

Leave a Comment

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

Scroll to Top