🔧 New: User Management Graph PowerShell Toolkit

Simplify user tasks like bulk creation, updates, password resets, deletions, license checks & more — all from one place.

🚀 Launch Toolkit

Email Microsoft Teams Owners Report Using Graph PowerShell

As Microsoft Teams becomes the backbone of collaboration, knowing who owns which Team is critical. Owners hold the keys—they manage membership, settings, and governance. Yet, in many organizations, the ownership landscape drifts out of sync with reality: Teams without owners, Teams with too many owners, or Teams owned by accounts that no longer exist. This makes governance difficult and increases security risks.

To regain visibility, administrators need a simple way to list all Teams and their owners in one consolidated report. The following Graph PowerShell script does exactly that—it fetches all Teams, gathers their owners, and emails the results as a CSV to the administrator.


i) Script

# ===== Teams Owners -> CSV -> Email to Admin =====
# Requires: Microsoft.Graph module
# Scopes: Group.Read.All, User.Read.All, Mail.Send
                                
# --- Email variables ---
$FromUser  = "admin@contoso.com"       # Sender (must have mailbox)
$To        = "it-ops@contoso.com"      # Recipient
$Subject   = "Microsoft Teams Owners report"
$CsvOutDir = "$env:TEMP"
                                
# --- Connect to Microsoft Graph ---
Import-Module Microsoft.Graph -ErrorAction Stop
Connect-MgGraph -Scopes "Group.Read.All","User.Read.All","Mail.Send"
                                
# --- Get Teams-enabled groups ---
$teams = Get-MgGroup -All -Filter "resourceProvisioningOptions/Any(x:x eq 'Team')" `
-Property "id,displayName,description,visibility,createdDateTime,mailNickname"
                                
# --- Build owner rows (one row per owner per Team) ---
$rows = @()
                                
foreach ($t in $teams) {
    $ownerObjs = Get-MgGroupOwner -GroupId $t.Id -All -ErrorAction SilentlyContinue
                                
    foreach ($o in $ownerObjs) {
        $ownerDisplayName = $null
        $ownerUpn         = $null
                                
        try {
            # Resolve to user for clean DisplayName/UPN
            $u = Get-MgUser -UserId $o.Id -Property DisplayName,UserPrincipalName -ErrorAction Stop
            $ownerDisplayName = $u.DisplayName
            $ownerUpn         = $u.UserPrincipalName
        } catch {
            # Fallback for non-user owners or missing fields
            $ownerDisplayName = $o.AdditionalProperties['displayName']
            $ownerUpn         = $o.AdditionalProperties['userPrincipalName']
        }

        $rows += [PSCustomObject]@{
            TeamId          = $t.Id
            TeamName        = $t.DisplayName
            TeamVisibility  = $t.Visibility
            OwnerDisplayName= $ownerDisplayName
            OwnerUPN        = $ownerUpn
        }
    }
}

# --- Export to CSV ---
if (-not (Test-Path -Path $CsvOutDir)) { New-Item -ItemType Directory -Path $CsvOutDir | Out-Null }
$ts = Get-Date -Format "yyyyMMdd_HHmmss"
$csvPath = Join-Path $CsvOutDir ("Teams_Owners_{0}.csv" -f $ts)
$rows | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8

# --- Prepare HTML Body ---
$totalTeams  = $teams.Count
$totalOwners = $rows.Count
$summaryHtml = @"
                                
<html>
  <body style='font-family:Segoe UI,Arial,sans-serif'>
    <h3>Microsoft Teams Owners Report</h3>
    <p>Total Teams scanned: <b>$totalTeams</b></p>
    <p>Total owner entries: <b>$totalOwners</b></p>
    <p>The full owner list is attached as a CSV (one row per owner per Team)</p>
  </body>
</html>

"@

# --- Prepare Attachment ---
$fileBytes     = [System.IO.File]::ReadAllBytes($csvPath)
$base64Content = [System.Convert]::ToBase64String($fileBytes)
$csvFileName   = [System.IO.Path]::GetFileName($csvPath)
$attachment = @{
    "@odata.type" = "#microsoft.graph.fileAttachment"
    name          = $csvFileName
    contentBytes  = $base64Content
    contentType   = "text/csv"
}

# --- Prepare and Send Email ---
$mail = @{
  message = @{
    subject = "${Subject}"
    body    = @{
      contentType = "HTML"
      content     = $summaryHtml
    }
    toRecipients = @(@{ emailAddress = @{ address = $To } })
    attachments  = @($attachment)
  }
  saveToSentItems = $true
}

Send-MgUserMail -UserId $FromUser -BodyParameter $mail

Write-Host "Done. CSV saved at: $csvPath" -ForegroundColor Green

                            

ii) How the Script Works

  1. Connects to Microsoft Graph
  2. Uses the Graph PowerShell SDK with scopes:

    • Group.Read.All to fetch Teams-enabled groups.
    • User.Read.All to resolve user owners.
    • Mail.Send to send the email with the report.
  3. Retrieves Teams-enabled groups
  4. Filters groups that have resourceProvisioningOptions set to "Team".

  5. Collects owner details
  6. For each Team, Get-MgGroupOwner retrieves owners. Owners are resolved to users with clean DisplayName and UserPrincipalName.

  7. Exports results
  8. The script generates one row per Team-owner pair (so Teams with multiple owners have multiple rows). Data includes TeamId, TeamName, Visibility, OwnerDisplayName, and OwnerUPN.

  9. Emails the report
  10. The CSV is attached to an HTML email and sent to the administrator, summarizing the number of Teams scanned and owner entries found.


iii) Further Enhancements

  • Add Team Creation Date: Include CreatedDateTime in the CSV for lifecycle management.
  • Owner Counts: Add a summary of how many owners each Team has.
  • Flag Teams Without Owners: Highlight Teams missing owners for quick remediation.
  • Scheduled Automation: Run this script weekly/monthly using Task Scheduler or Azure Automation.
  • Send to Governance DL: Email results to a compliance/security distribution list.

iv) Use Cases

  • Governance Oversight: Identify Teams without owners or those with too many.
  • Access Reviews: Verify that Teams are still owned by active employees.
  • Security Audits: Detect Teams owned by accounts no longer in service.
  • Lifecycle Management: Inform decisions on archiving/deleting Teams with poor ownership hygiene.

v) Possible Errors & Solutions

Error Cause Solution
Authorization_RequestDenied Missing required Graph scopes or admin consent Connect with Group.Read.All, User.Read.All, Mail.Send and ensure admin consent is granted.
Get-MgGroup not recognized Graph module not installed Run Install-Module Microsoft.Graph -Scope CurrentUser.
Missing owners in CSV Some owners are service principals or external objects Script includes a fallback to capture displayName and userPrincipalName when available.
Email not sent $FromUser not mailbox-enabled Use a licensed mailbox-enabled account.
CSV is empty No Teams found or insufficient Graph permissions Verify Teams exist and permissions are correctly granted.

vi) Conclusion

Ownership visibility is key to Teams governance and security. This Graph PowerShell script provides administrators with a straightforward, automated way to inventory all Teams and their owners. The emailed CSV makes it easy to track ownership patterns, flag issues, and support audits. By scheduling and enhancing the script, IT teams can strengthen oversight and keep Microsoft Teams clean, compliant, and well-governed.


Graph PowerShell Explorer Widget

20 Graph PowerShell cmdlets with easily accessible "working" examples.


Permission Required

Example:


                


                


                

© m365corner.com. All Rights Reserved. Design by HTML Codex