Simplify user tasks like bulk creation, updates, password resets, deletions, license checks & more — all from one place.
🚀 Launch ToolkitGroups in Entra ID are critical to collaboration and access management. But when groups are deleted—whether intentionally or by mistake—it can disrupt access, break team workflows, or even pose compliance risks. To maintain visibility and governance, administrators should track “Delete group” events. This script automates the process by querying Entra ID Audit Logs for “Delete group” activities, exporting the results to CSV, and emailing the report to administrators or stakeholders.
# ===== Entra ID Audit: "Delete group" (GroupManagement) -> CSV -> Email =====
# Requires: Microsoft.Graph module
# Scopes: AuditLog.Read.All, Mail.Send
# -------- Email & Time Window ----------
$FromUser = "admin@contoso.com" # Sender (must have mailbox)
$ToList = "it-ops@contoso.com;secops@contoso.com" # Recipients (; or , separated)
$Subject = 'Entra ID Audit: "Delete group" Activity Report'
$DaysBack = 7 # Lookback window (days)
$CsvOutDir = "$env:TEMP"
# -------- Connect to Microsoft Graph ----------
Import-Module Microsoft.Graph -ErrorAction Stop
Connect-MgGraph -Scopes "AuditLog.Read.All","Mail.Send"
# -------- Build filter (UTC ISO format) ----------
$sinceIso = (Get-Date).ToUniversalTime().AddDays(-1 * $DaysBack).ToString("o")
$filter = "category eq 'GroupManagement' and activityDisplayName eq 'Delete group' and activityDateTime ge $sinceIso"
# -------- Query Audit Logs (Directory Audits) ----------
$auditEntries = Get-MgAuditLogDirectoryAudit -All -Filter $filter `
-Property "activityDateTime,activityDisplayName,category,correlationId,result,resultReason,loggedByService,initiatedBy,targetResources,additionalDetails"
# -------- Shape rows for CSV ----------
$rows = foreach ($e in $auditEntries) {
# Initiator (user or app)
$initiatorUpn = $null; $initiatorName = $null
try {
if ($e.InitiatedBy.User) {
$initiatorUpn = $e.InitiatedBy.User.UserPrincipalName
$initiatorName = $e.InitiatedBy.User.DisplayName
} elseif ($e.InitiatedBy.App) {
$initiatorUpn = $e.InitiatedBy.App.AppId
$initiatorName = $e.InitiatedBy.App.DisplayName
} else {
# fallback for older SDK shapes
$iu = $e.InitiatedBy.AdditionalProperties['user']
$ia = $e.InitiatedBy.AdditionalProperties['app']
if ($iu) { $initiatorUpn = $iu['userPrincipalName']; $initiatorName = $iu['displayName'] }
elseif ($ia) { $initiatorUpn = $ia['appId']; $initiatorName = $ia['displayName'] }
}
} catch {}
# Targets (the groups that were deleted)
$targetNames = @()
$targetIds = @()
foreach ($t in ($e.TargetResources | Where-Object { $_ })) {
try {
if ($t.DisplayName) { $targetNames += $t.DisplayName }
elseif ($t.AdditionalProperties['displayName']) { $targetNames += $t.AdditionalProperties['displayName'] }
if ($t.Id) { $targetIds += $t.Id }
elseif ($t.AdditionalProperties['id']) { $targetIds += $t.AdditionalProperties['id'] }
} catch {}
}
# Optional: capture extra details (e.g., groupType, visibility) if present
$details = @()
try {
foreach ($d in ($e.AdditionalDetails | Where-Object { $_ })) {
if ($d.Key -and $d.Value) { $details += ("{0}={1}" -f $d.Key, $d.Value) }
}
} catch {}
[PSCustomObject]@{
ActivityDateTime = $e.ActivityDateTime
Activity = $e.ActivityDisplayName
Category = $e.Category
Result = $e.Result
ResultReason = $e.ResultReason
LoggedByService = $e.LoggedByService
CorrelationId = $e.CorrelationId
InitiatedByName = $initiatorName
InitiatedByUPN = $initiatorUpn
TargetGroupNames = ($targetNames -join "; ")
TargetGroupIds = ($targetIds -join "; ")
AdditionalInfo = ($details -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 ("Entra_Audit_DeleteGroup_{0}.csv" -f $ts)
$rows | Sort-Object ActivityDateTime -Descending | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
# -------- Prepare HTML Body ----------
$totalEvents = $rows.Count
$summaryHtml = @"
<html>
<body style='font-family:Segoe UI,Arial,sans-serif'>
<h3>Entra ID Audit Report: "Delete group" (Last $DaysBack Days)</h3>
<p>Total events: <b>$totalEvents</b></p>
<p>Time window (UTC): since <b>$sinceIso</b></p>
<p>Attached CSV includes ActivityDateTime, Initiator, TargetGroupNames/Ids, Result, and AdditionalInfo.</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"
}
# -------- Build recipients array (split on ; or ,) ----------
$recipients = @()
$ToList.Split(@(';', ','), [System.StringSplitOptions]::RemoveEmptyEntries) | ForEach-Object {
$addr = $_.Trim()
if ($addr) { $recipients += @{ emailAddress = @{ address = $addr } } }
}
# -------- Prepare and Send Email ----------
$mail = @{
message = @{
subject = "$Subject"
body = @{
contentType = "HTML"
content = $summaryHtml
}
toRecipients = $recipients
attachments = @($attachment)
}
saveToSentItems = $true
}
Send-MgUserMail -UserId $FromUser -BodyParameter $mail
Write-Host "Done. CSV saved at: $csvPath" -ForegroundColor Green
Error | Cause | Solution |
---|---|---|
Authorization_RequestDenied | Missing Graph permissions | Ensure AuditLog.Read.All and Mail.Send are granted with admin consent. |
Get-MgAuditLogDirectoryAudit not recognized | Graph module missing/outdated | Install or update the Microsoft.Graph module. |
Empty CSV | No “Delete group” events in timeframe | Increase $DaysBack or confirm events in Entra admin portal. |
Email not delivered | $FromUser not mailbox-enabled | Use a licensed account with an active mailbox. |
Split error on recipients | Wrong delimiter | Separate recipients with ; or , (both supported). |
Monitoring group deletions is just as important as tracking their creation. With this script, you can automate visibility into “Delete group” audit events, generate clean CSV reports, and ensure administrators and stakeholders are always informed. Regular use of this script strengthens security oversight, supports compliance, and ensures your tenant’s group lifecycle governance remains under control.
© m365corner.com. All Rights Reserved. Design by HTML Codex