đź”§ 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 Public Teams List to Microsoft 365 Administrator Using Graph PowerShell

When a tenant grows fast, public Teams can multiply without anyone noticing. They’re great for open collaboration—but if you don’t keep tabs on them, you risk oversharing, duplicated spaces, and governance blind spots.
The easiest fix is a reliable report you can scan (or hand to your security/governance folks) showing every public Team and who owns it. That’s exactly what the script below delivers: a clean CSV of all public Microsoft Teams with owner details, emailed straight to the administrator.


i) Script

# ===== Public Teams -> 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   = "Public Microsoft Teams 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 (can't filter by 'visibility' server-side) ---
$teams = Get-MgGroup -All -Filter "resourceProvisioningOptions/Any(x:x eq 'Team')" `
-Property "id,displayName,description,visibility,createdDateTime,mailNickname"

# --- Filter PUBLIC Teams client-side ---
$publicTeams = $teams | Where-Object { $_.Visibility -eq "Public" }

# --- Build rows with owner details ---
$rows = foreach ($t in $publicTeams) {
$ownerObjs = Get-MgGroupOwner -GroupId $t.Id -All -ErrorAction SilentlyContinue

$ownerNames = @()
$ownerUpns  = @()

foreach ($o in $ownerObjs) {
    try {
        # Resolve to user to get clean DisplayName + UPN
        $u = Get-MgUser -UserId $o.Id -Property DisplayName,UserPrincipalName -ErrorAction Stop
        $ownerNames += $u.DisplayName
        $ownerUpns  += $u.UserPrincipalName
    } catch {
        # Fallback if non-user or missing fields
        $dn  = $o.AdditionalProperties['displayName']
        $upn = $o.AdditionalProperties['userPrincipalName']
        if ($dn)  { $ownerNames += $dn }
        if ($upn) { $ownerUpns  += $upn }
    }
}

[PSCustomObject]@{
    TeamId       = $t.Id
    TeamName     = $t.DisplayName
    Description  = $t.Description
    Visibility   = $t.Visibility
    CreatedDate  = $t.CreatedDateTime
    OwnerNames   = ($ownerNames -join "; ")
    OwnerUPNs    = ($ownerUpns  -join "; ")
}
}

# --- 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 ("Public_Teams_{0}.csv" -f $ts)
$rows | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8

# --- Prepare HTML Body ---
$summaryHtml = @"
<html>
  <body style='font-family:Segoe UI,Arial,sans-serif'>
    <h3>Public Microsoft Teams Report</h3>
    <p>Total public Teams: <b>$($rows.Count)</b></p>
    <p>The full list (with owners) 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
                            

ii) How the Script Works

  1. Connects to Graph with the right scopes
  2. Uses Group.Read.All to list Teams, User.Read.All to resolve owners’ names/UPNs, and Mail.Send to email the CSV.

  3. Pulls all Teams-enriched groups
  4. Since filtering by visibility isn’t supported server-side, the script first fetches Teams-enabled groups using the resourceProvisioningOptions filter.

  5. Filters to only Public Teams (client-side)
  6. Applies a local Where-Object to keep Teams where Visibility -eq "Public".

  7. Collects owner details
  8. For each public Team, it fetches owners, resolves them to user objects for clean DisplayName and UPN, and aggregates multiple owners in semicolon-separated lists.

  9. Exports to CSV and emails it
  10. The CSV is timestamped for traceability and attached to an HTML email sent to the administrator.


iii) Further Enhancements

  • Owner Count / No-Owner Flag: Add columns to quickly spot Teams with zero or many owners.
  • Add Team Settings: Pull and include sensitivity labels or guest access flags for governance.
  • Schedule the Report: Run daily/weekly via Task Scheduler or Azure Automation.
  • Send to DL / Teams Channel: Deliver to a shared mailbox or governance channel for review.
  • Add Private Channel Insight: Extend to surface public Teams that host private channels (for completeness).

iv) Use Cases

  • Governance Audits: Maintain visibility over all public collaboration spaces.
  • Security Hygiene: Identify public Teams missing owners or owned by ex-employees.
  • Data Oversharing Checks: Review which public spaces could expose internal info.
  • Lifecycle Management: Flag dormant public Teams for archival or closure.

v) Possible Errors & Solutions

Error Cause Solution
Request_UnsupportedQuery on visibility filter Server doesn’t support filtering by visibility Use the provided approach: fetch Teams first, then filter client-side.
Authorization_RequestDenied Missing/insufficient scopes or consent Reconnect with Group.Read.All, User.Read.All, Mail.Send and ensure admin consent if required.
Get-MgGroup not recognized Microsoft Graph module not installed Install-Module Microsoft.Graph -Scope CurrentUser then import.
Owners missing in CSV Some owners aren’t users (e.g., service principals) or properties unavailable Script falls back to AdditionalProperties; enrich only where resolvable.
Email not sent / sender issues $FromUser has no mailbox or send rights Use a mailbox-enabled account; verify it can send to $To.
Empty results No public Teams exist Remove client-side filter temporarily to validate base Teams retrieval.

vi) Conclusion

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.


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