đź”§ 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 “User Password Change” Audit Events with Graph PowerShell

Password changes are some of the most sensitive events in any identity system. Whether they’re legitimate self-service resets or part of an incident investigation, admins need visibility into who changed their password and when. This script queries Entra ID Audit Logs for the “Change password (self-service)” event under the UserManagement category, exports the results to a CSV, and emails the report to administrators or stakeholders automatically.


i) Script



    # ===== Entra ID Audit: "Change password (self-service)" (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: "User Password Change" 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 'Change password (self-service)' 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 (users whose passwords were changed)
      $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_UserPasswordChange_{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: "User Password Change" (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, 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 (read audit logs) and Mail.Send (send emails).
  2. Builds a time window ($DaysBack, default 7 days) to query recent password changes.
  3. Filters audit events for the “Change password (self-service)” activity under UserManagement.
  4. Processes results to capture when the change happened, who initiated it (usually the user), and who the targets were (affected accounts).
  5. Exports to CSV & emails a timestamped report, attaching it with a short HTML summary.

iii) Further Enhancements

  • Broader coverage: Extend to capture other events like “Reset user password (admin)”.
  • Alerting: Send a high-priority message if password changes spike unusually.
  • Geolocation/IP info: Parse additional properties for richer forensics.
  • Scheduling: Automate daily or weekly runs using Task Scheduler or Azure Automation.
  • Audit trail integration: Forward to SIEM (Splunk, Sentinel) in parallel with email.

iv) Use Cases

  • Security oversight: Monitor unusual volumes of self-service password changes.
  • Helpdesk tracking: Validate that users are performing their own resets successfully.
  • Compliance reporting: Prove to auditors that self-service password change activity is monitored.
  • Forensic analysis: Pinpoint when a compromised account changed its password.

v) Possible Errors & Solutions

Error Cause Solution
Authorization_RequestDenied Missing scopes or consent Reconnect with AuditLog.Read.All & Mail.Send; ensure admin consent.
Get-MgAuditLogDirectoryAudit not recognized Graph module missing/outdated Run Install-Module Microsoft.Graph -Scope CurrentUser; update if needed.
Empty CSV No “Change password (self-service)” events in timeframe Increase $DaysBack or validate in Entra Admin Center.
Email not sent $FromUser not mailbox-enabled Use a licensed mailbox-enabled sender.
Split error on recipients Incorrect .Split() usage Use .Split(@(';',','), [StringSplitOptions]::RemoveEmptyEntries) as shown.

vi) Conclusion

Tracking password changes is essential for both security visibility and compliance. With this script, you can automatically fetch “Change password (self-service)” audit logs from Entra ID, export them to a CSV, and send them straight to your inbox. Schedule it for regular runs and enhance with alerting, and you’ll have a reliable guardrail for one of the most critical user activities in your tenant.


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