Simplify user tasks like bulk creation, updates, password resets, deletions, license checks & more — all from one place.
🚀 Launch ToolkitIt’s not unusual for Microsoft Teams environments to accumulate empty Teams over time. Maybe a Team was created for a project that never kicked off, or its members shifted elsewhere. These empty Teams clutter your environment, confuse administrators, and increase the attack surface if left unmanaged.
This Graph PowerShell script addresses that by scanning your tenant, finding Teams with no members, and emailing the results (as a CSV file) to the administrator. The report also includes useful details like TeamId and CreatedDate for easier governance decisions.
# ===== Empty Microsoft Teams -> CSV -> Email to Admin =====
# Requires: Microsoft.Graph module
# Scopes: Team.ReadBasic.All, Group.Read.All, Mail.Send
# --- Email variables ---
$FromUser = "admin@contoso.com" # Sender (must have mailbox)
$To = "it-ops@contoso.com" # Recipient
$Subject = "Empty Microsoft Teams report"
$CsvOutDir = "$env:TEMP"
# --- Connect to Microsoft Graph with the necessary scopes ---
Import-Module Microsoft.Graph -ErrorAction Stop
Connect-MgGraph -Scopes "Team.ReadBasic.All","Group.Read.All","Mail.Send"
# --- Fetch all teams ---
$teams = Get-MgGroup -Filter "resourceProvisioningOptions/Any(x:x eq 'Team')" `
-Property Id, DisplayName, Description, Visibility, CreatedDateTime -All
# --- Prepare an array to store the data of empty teams ---
$emptyTeamsData = @()
foreach ($team in $teams) {
# Get the member count for each team
$memberCount = (Get-MgGroupMember -GroupId $team.Id -All).Count
# If the team is empty (no members), add its information to the array
if ($memberCount -eq 0) {
# Determine the team type based on visibility
$teamType = if ($team.Visibility -eq "Public") { "Public" } else { "Private" }
# Add the team's information to the array
$emptyTeamsData += [pscustomobject]@{
TeamId = $team.Id
TeamName = $team.DisplayName
Description = $team.Description
TeamType = $teamType
CreatedDate = $team.CreatedDateTime
}
}
}
# --- Output the data in a table format (console) ---
$emptyTeamsData | Format-Table -AutoSize
# --- 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 ("Empty_Teams_{0}.csv" -f $ts)
$emptyTeamsData | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
# --- Prepare HTML Body ---
$summaryHtml = @"
<html>
<body style='font-family:Segoe UI,Arial,sans-serif'>
<h3>Empty Microsoft Teams Report</h3>
<p>Total empty Teams: <b>$($emptyTeamsData.Count)</b></p>
<p>The full list (with TeamId and CreatedDate) is attached as a CSV.</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
Uses the Microsoft Graph PowerShell SDK with the scopes Team.ReadBasic.All, Group.Read.All, and Mail.Send.
Filters groups that have resourceProvisioningOptions containing "Team", ensuring only Teams are included.
Iterates through each Team and counts members using Get-MgGroupMember. If count = 0, the Team is flagged as empty.
Captures TeamId, TeamName, Description, Visibility (converted to Public/Private), and CreatedDate.
The results are exported to a timestamped CSV and emailed to the administrator with a quick HTML summary.
Error | Cause | Solution |
---|---|---|
Authorization_RequestDenied | Scopes missing or consent not granted | Connect with Team.ReadBasic.All, Group.Read.All, and Mail.Send; ensure admin consent. |
Get-MgGroup not recognized | Microsoft Graph module missing | Install via Install-Module Microsoft.Graph -Scope CurrentUser. |
Empty results though Teams exist | No empty Teams in tenant | Confirm Teams exist; remove membership filter to validate. |
Email not sent | $FromUser not mailbox-enabled | Use a licensed mailbox-enabled account for sending. |
CSV file missing | Invalid path for $CsvOutDir | Change $CsvOutDir to a valid directory path. |
Public Teams are fantastic for open collaboration—but they need oversight. This script gives you a dependable single-source report of every public Team plus its owners, delivered as a CSV to your inbox. Schedule it, share it with governance, and expand it over time (owner counts, settings, activity) to keep your Microsoft 365 environment tidy and safe.
© m365corner.com. All Rights Reserved. Design by HTML Codex