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

When new groups are created in Entra ID, they can open the door to collaboration—or, if unchecked, to risks like overexposed data or unmonitored access. Tracking the “Add group” activity in audit logs helps administrators ensure that only authorized people are creating groups, and that every new group is visible to IT and security. This script automates the process by querying Entra ID Audit Logs for “Add group” events, exporting them to CSV, and emailing the results to administrators or stakeholders.


i) Script


    # ===== Entra ID Audit: "Add 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: "Add 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 'Add 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 added)
      $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: pick a few additional details if present (e.g., groupType, visibility)
      $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_AddGroup_{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: "Add group" (Last $DaysBack Days)</h3>
    <p>Total events: <b>$totalEvents</b></p>
    <p>Time window since (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 Graph using AuditLog.Read.All and Mail.Send scopes.
  2. Builds a filter to capture “Add group” events in the GroupManagement category within the last 7 days.
  3. Queries audit logs via Get-MgAuditLogDirectoryAudit.
  4. Processes results to extract event details, including initiator (user or app), target group names/IDs, and additional metadata.
  5. Exports the data to CSV with a timestamped filename.
  6. Prepares an HTML email summary and attaches the CSV file.
  7. Sends the report via email to the specified recipients.

iii) Further Enhancements

  • Add filters for specific initiators (e.g., track only non-admin group creations).
  • Enrich the report with group type (security, M365, dynamic) and visibility (public/private).
  • Include counts per initiator for detecting mass group creation.
  • Schedule the script for daily or weekly automation with Task Scheduler or Azure Automation.
  • Forward logs to a SIEM (e.g., Sentinel, Splunk) for deeper analysis.

iv) Use Cases

  • Security oversight: Detect unauthorized group creation.
  • Compliance reporting: Provide auditors with evidence of group governance.
  • Operational tracking: Monitor how often new groups are created across the tenant.
  • Incident response: Quickly identify who created groups during security incidents.

v) Possible Errors & Solutions

Error Cause Solution
Authorization_RequestDenied Missing Graph permissions Reconnect with AuditLog.Read.All and Mail.Send; ensure admin consent.
Get-MgAuditLogDirectoryAudit not recognized Module not installed/outdated Install or update Microsoft.Graph.
Empty CSV No “Add group” events in the timeframe Extend $DaysBack or validate in Entra Admin Center.
Email not sent $FromUser not mailbox-enabled Ensure the sender has a valid mailbox.
Split error in recipients Incorrect delimiter usage Use ; or , (both supported by script).

vi) Conclusion

Monitoring group creation is a cornerstone of identity governance. With this Graph PowerShell script, admins can automatically track “Add group” events in Entra ID, export the details to CSV, and deliver them directly to stakeholders. By scheduling it regularly and extending it with filters or SIEM integration, organizations can ensure group creation is always audited, transparent, and controlled.


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