đź”§ 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 Entra ID “Delete Group” Audit Events with Graph PowerShell

Groups 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.


i) Script



    # ===== 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
                            

ii) How the Script Works

  1. Connects to Microsoft Graph with the scopes AuditLog.Read.All (to read logs) and Mail.Send (to send reports).
  2. Defines a timeframe ($DaysBack) and converts it to UTC ISO format.
  3. Builds a filter to capture only “Delete group” activities from the GroupManagement category.
  4. Fetches audit logs using Get-MgAuditLogDirectoryAudit.
  5. Processes results to extract details such as initiator (user/app), target group names/IDs, and additional metadata.
  6. Exports the results to CSV with a timestamped filename.
  7. Prepares an HTML summary email and attaches the CSV file.
  8. Sends the email report to the specified administrators or stakeholders.

iii) Further Enhancements

  • Filter by specific initiators (e.g., only non-admin deletions).
  • Add group type and visibility details for context.
  • Include alerts for high deletion volume in a short timeframe.
  • Schedule with Task Scheduler or Azure Automation for regular reporting.
  • Integrate with SIEM tools (like Sentinel or Splunk) for security monitoring.

iv) Use Cases

  • Security monitoring: Detect unauthorized or mass group deletions.
  • Compliance reporting: Provide audit evidence of group lifecycle actions.
  • Operations: Track deletion trends across the tenant for capacity planning.
  • Incident response: Investigate who deleted groups and when, especially if access or collaboration breaks suddenly.

v) Possible Errors & Solutions

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).

vi) Conclusion

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.


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