đź”§ 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 user” Audit Event with Graph PowerShell

New hires, test accounts, automated joins—“Add user” events happen all the time. When auditors (or your security lead) ask “who created which accounts, and when?”, you don’t want to scramble through portals. This script pulls Entra ID (Azure AD) audit logs for “Add user” in the UserManagement category (last N days), exports the details to CSV, and emails the report to your admins/stakeholders—automatically.


i) Script

# ===== Entra ID Audit: "Add user" (UserManagement) -> 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"  # One or more recipients (semicolon/comma-separated)
$Subject    = 'Entra ID Audit: "Add user" 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 'UserManagement' and activityDisplayName eq 'Add user' and activityDateTime ge $sinceIso"

# -------- Query Audit Logs (Directory Audits) ----------
$auditEntries = Get-MgAuditLogDirectoryAudit -All -Filter $filter `
  -Property "activityDateTime,activityDisplayName,category,correlationId,result,resultReason,initiatedBy,targetResources"
                                
# -------- 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 users that were added)
    $targetNames = @()
    $targetUpns  = @()
    foreach ($t in ($e.TargetResources | Where-Object { $_ })) {
      try {
      if ($t.UserPrincipalName) { $targetUpns += $t.UserPrincipalName }
      elseif ($t.AdditionalProperties['userPrincipalName']) { $targetUpns += $t.AdditionalProperties['userPrincipalName'] }
                                
      if ($t.DisplayName) { $targetNames += $t.DisplayName }
      elseif ($t.AdditionalProperties['displayName']) { $targetNames += $t.AdditionalProperties['displayName'] }
 } catch {}
}
                                
[PSCustomObject]@{
  ActivityDateTime = $e.ActivityDateTime
  Activity         = $e.ActivityDisplayName
  Category         = $e.Category
  Result           = $e.Result
  ResultReason     = $e.ResultReason
  CorrelationId    = $e.CorrelationId
  InitiatedByName  = $initiatorName
  InitiatedByUPN   = $initiatorUpn
  TargetNames      = ($targetNames -join "; ")
  TargetUPNs       = ($targetUpns  -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_AddUser_{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 user" (Last $DaysBack Days)</h3>
    <p>Total events: <b>$totalEvents</b></p>
    <p>Time window <b>$sinceIsod</b></p>
    <p>Attached CSV includes ActivityDateTime, Initiator, Targets, Result, and CorrelationId.</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 with AuditLog.Read.All (to read audit events) and Mail.Send (to send the email).
  2. Builds a time window ($DaysBack, default 7) and formats it as UTC ISO ($sinceIso).
  3. Queries Directory Audit logs filtering by category = 'UserManagement', activityDisplayName = 'Add user', and activityDateTime >= since.
  4. Shapes the result into a CSV-friendly object showing when, by whom (user/app), who was added (targets), result, and correlationId.
  5. Exports & emails the CSV with an HTML summary to all recipients parsed from $ToList.

iii) Further Enhancements

  • Parameterize activity (e.g., “Update user”, “Add member to group”) to reuse the script for other audits.
  • Include IP/client info from loggedByService or additionalDetails when present.
  • Owner notifications: Auto-email the identity team when results exceed a threshold.
  • Scheduling: Run daily/weekly via Task Scheduler, Azure Automation, or an Azure Function.
  • Retention awareness: Add a note on your tenant’s audit log retention so reports aren’t misleading.

iv) Use Cases

  • Joiner auditing: Prove who created which users and when.
  • Change control: Validate provisioning automation is behaving as expected.
  • Security oversight: Spot unexpected user creation surges.
  • Compliance reporting: Hand auditors a signed-off CSV with a consistent format.

v) Possible Errors & Solutions

Error Cause Solution
Authorization_RequestDenied Missing scopes or admin consent Reconnect with AuditLog.Read.All, Mail.Send; ensure admin consent granted.
Get-MgAuditLogDirectoryAudit not recognized Graph module missing/outdated Install-Module Microsoft.Graph -Scope CurrentUser; Update-Module Microsoft.Graph.
Email fails / no sent item $FromUser not mailbox-enabled Use a licensed mailbox-enabled account; verify Send-MgUserMail rights.
Empty CSV No “Add user” events in the window Increase $DaysBack or validate in Entra admin center.
Split error on recipients Using incorrect .Split() overload Use the fixed split: .Split(@(';',','), [StringSplitOptions]::RemoveEmptyEntries)

vi) Conclusion

This script turns raw Entra ID audit data into a clean, shareable CSV—so you can answer “who created which users?” instantly. Tweak the lookback, schedule it, or generalize for other activities, and you’ll have a lightweight but powerful identity governance signal delivered to stakeholders on repeat.


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