🔧 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 user” Audit Events with Graph PowerShell

Accounts get removed for many reasons—leavers, duplicate test users, cleanup after projects. When someone asks who deleted which users, and when, you want a clean report you can trust. This script queries Entra ID Audit Logs for the “Delete user” event in the UserManagement category (last N days), exports the details to CSV, and emails it to admins/stakeholders.


i) Script

# ===== Entra ID Audit: "Delete 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"  # Recipients (; or , separated)
$Subject    = 'Entra ID Audit: "Delete 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 'Delete 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 {
$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 (deleted users)
$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_DeleteUser_{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 user" (Last $DaysBack Days)</h3>
    <p>Total events: <b>$totalEvents</b></p>
    <p>Time window <b>$sinceIso</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 using AuditLog.Read.All to read audit data and Mail.Send to email the report.
  2. Defines lookback window with $DaysBack (default 7) and formats $sinceIso in UTC ISO-8601.
  3. Filters audit logs where category = 'UserManagement', activityDisplayName = 'Delete user', and the event time is within the window.
  4. Shapes results to capture when the deletion happened, who initiated it (user/app), and which users were deleted.
  5. Exports & emails a timestamped CSV and an HTML summary to all recipients listed in $ToList (supports ; or ,).

iii) Further Enhancements

  • Parameterize activity (e.g., Add user, Update user, Add member to group) to reuse the same framework.
  • Include IP or location from additional details (when present) for deeper forensics.
  • Alerting: If deletions exceed a threshold, send a separate high-priority email or Teams message.
  • Scheduling: Run daily/weekly via Task Scheduler, Azure Automation, or an Azure Function.
  • Cross-check: Match deleted users against HR records to confirm authorized actions.

iv) Use Cases

  • Joiner/Mover/Leaver governance — prove who deleted accounts and when.
  • Security investigations — quickly compile user deletions tied to incidents.
  • Change control — validate automated deprovisioning workflows.
  • Compliance audits — hand auditors a consistent CSV showing deletion activity.

v) Possible Errors & Solutions

Error Cause Solution
Authorization_RequestDenied Missing scopes or admin consent Reconnect with AuditLog.Read.All,Mail.Send; ensure admin consent.
Get-MgAuditLogDirectoryAudit not recognized Module missing/outdated Install-Module / Update-Module Microsoft.Graph; re-import.
Empty CSV No “Delete user” events in window Increase $DaysBack; validate in Entra Admin Center.
Email not sent $FromUser not mailbox-enabled Use a licensed mailbox-enabled sender; verify Send-MgUserMail.
Recipient split error Wrong .Split() overload Use .Split(@(';',','), [StringSplitOptions]::RemoveEmptyEntries) (as in script).

vi) Conclusion

With this script, user deletion activity becomes transparent and auditable. You get a dependable CSV in your inbox—who deleted whom, when, and with what result. Schedule it, extend it with IP and geo details, and plug it into your identity governance rhythm for continuous assurance.


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