Simplify user tasks like bulk creation, updates, password resets, deletions, license checks & more — all from one place.
🚀 Launch ToolkitWhen a Team is deleted, it isn’t gone forever immediately. Microsoft 365 retains deleted Teams (as deleted groups) for 30 days, giving admins the ability to restore them if needed. But in a busy environment, multiple Teams might be deleted in a short time — whether by mistake, by intentional cleanup, or by user request. Without a clear view of what’s been deleted recently, administrators may struggle to track changes, validate compliance, or prepare for audits.
This Graph PowerShell script solves that problem. It scans your tenant for Teams deleted within the last 30 days, compiles vital details such as TeamId, DisplayName, Description, Visibility, CreatedDate, and DeletedDate, exports the report into a CSV, and then emails the results directly to the administrator.
# ===== Recently Deleted Microsoft Teams (Last 30 Days) -> CSV -> Email =====
# Requires: Microsoft.Graph module
# Scopes: Directory.Read.All, Group.Read.All, Mail.Send
# --- Email variables ---
$FromUser = "admin@contoso.com" # Sender (must have mailbox)
$To = "it-ops@contoso.com" # Recipient
$Subject = "Recently Deleted Microsoft Teams (Last 30 Days)"
$CsvOutDir = "$env:TEMP"
# --- Connect to Microsoft Graph ---
Import-Module Microsoft.Graph -ErrorAction Stop
Connect-MgGraph -Scopes "Directory.Read.All","Group.Read.All","Mail.Send"
# --- Time window (last 30 days) ---
$since = (Get-Date).AddDays(-30)
# --- Get deleted groups directly (not other objects) ---
$uri = "https://graph.microsoft.com/v1.0/directory/deletedItems/microsoft.graph.group"
$deletedGroups = @()
do {
$response = Invoke-MgGraphRequest -Method GET -Uri $uri
$deletedGroups += $response.value
$uri = $response.'@odata.nextLink'
} while ($null -ne $uri)
# --- Filter Teams and build report rows ---
$rows = @()
foreach ($grp in $deletedGroups) {
$rpo = $grp.resourceProvisioningOptions
$isTeam = $false
if ($rpo) { $isTeam = ($rpo -contains "Team") -or ($rpo -match "Team") }
if ($isTeam -and $grp.deletedDateTime) {
$deletedDate = [datetime]$grp.deletedDateTime
if ($deletedDate -ge $since) {
$rows += [PSCustomObject]@{
TeamId = $grp.id
TeamName = $grp.displayName
Description = $grp.description
Visibility = $grp.visibility
CreatedDate = $grp.createdDateTime
DeletedDate = $deletedDate
}
}
}
}
# --- 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 ("Recently_Deleted_Teams_{0}.csv" -f $ts)
$rows | Sort-Object DeletedDate -Descending | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
# --- Prepare HTML Body ---
$totalDeletedGroups = $deletedGroups.Count
$totalTeamsInWindow = $rows.Count
$summaryHtml = @"
<html>
<body style='font-family:Segoe UI,Arial,sans-serif'>
<h3>Recently Deleted Microsoft Teams Report (Last 30 Days)</h3>
<p>Total deleted groups scanned: <b>$totalDeletedGroups</b></p>
<p>Teams deleted in last 30 days: <b>$totalTeamsInWindow</b></p>
<p>The full list (TeamId, Name, Visibility, CreatedDate, DeletedDate) 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
Connects using the Graph PowerShell SDK with three required scopes:
Instead of using Get-MgDirectoryDeletedItem (which doesn’t support -All), the script calls the REST endpoint directory/deletedItems/microsoft.graph.group via Invoke-MgGraphRequest. This ensures it retrieves deleted groups only, not deleted users or devices.
Deleted groups with resourceProvisioningOptions containing "Team" are identified as Teams.
Only Teams whose deletedDateTime falls within the last 30 days are included in the report.
Exports the details to CSV (TeamId, TeamName, Description, Visibility, CreatedDate, DeletedDate) and emails the file to the admin with a quick HTML summary.
Error | Cause | Solution |
---|---|---|
Authorization_RequestDenied | Missing Graph permissions or admin consent | Reconnect with Directory.Read.All, Group.Read.All, Mail.Send and grant admin consent. |
Invoke-MgGraphRequest not recognized | Older Microsoft.Graph module version | Update module with Update-Module Microsoft.Graph. |
CSV file empty | No Teams deleted in the last 30 days | Verify deletions exist via Teams Admin Center. |
Email not sent | $FromUser not mailbox-enabled | Use a licensed mailbox-enabled account as sender. |
Script misses results | Deleted items exceed 100 per page | Handled by pagination loop using @odata.nextLink. |
Deleted Teams don’t vanish instantly — they linger for 30 days, giving admins the chance to restore them. But keeping track of these recently deleted Teams is crucial for compliance, security, and lifecycle management. This script automates the process: fetching all Teams deleted in the last 30 days, compiling their key details, and emailing the report. By scheduling this script, administrators gain continuous visibility into deletions, ensuring a healthier and more accountable Microsoft 365 environment.
© m365corner.com. All Rights Reserved. Design by HTML Codex