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

When users forget their passwords, administrators often step in to reset them. While this is a routine operation, it’s also a high-impact action that can affect account security. Tracking who reset which user’s password and when is vital for both compliance and forensic investigations. The following script queries Entra ID Audit Logs for the “Reset user password” activity in the UserManagement category, exports the results to CSV, and emails the report to administrators or stakeholders.


Script


    # ===== Entra ID Audit: "Update user" (UserManagement) -> CSV -> Email =====
    # Requires: Microsoft.Graph module
    # Scopes: AuditLog.Read.All, Mail.Send
    
    # -------- Email & Time Window ----------
    $FromUser   = "samadmin@7xh7fj.onmicrosoft.com"                      # Sender (must have mailbox)
    $ToList     = "dexter@7xh7fj.onmicrosoft.com"  # Recipients (; or , separated)
    $Subject    = 'Entra ID Audit: "Password Resets by Admin" Activity Report'
    $DaysBack   = 7                                        # Lookback window (days)
    $CsvOutDir  = "$env:TEMP"
    
    # -------- Build filter (UTC ISO format) ----------
    $sinceIso = (Get-Date).ToUniversalTime().AddDays(-1 * $DaysBack).ToString("o")
    $filter = "category eq 'UserManagement' and activityDisplayName eq 'Reset user password' 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 users that were updated)
      $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 {}
      }
    
      # Optional: summarize changed properties
      $changedProps = @()
      try {
        foreach ($d in ($e.AdditionalDetails | Where-Object { $_ })) {
          if ($d.Key -and $d.Value) { $changedProps += ("{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
        TargetNames      = ($targetNames -join "; ")
        TargetUPNs       = ($targetUpns  -join "; ")
        ChangedDetails   = ($changedProps -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_UpdateUser_{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: "Password Resets By Admin" (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 ChangedDetails (when available).</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

                            

How the Script Works

  1. Connects to Microsoft Graph using the required scopes:
    • AuditLog.Read.All to read Entra ID audit logs.
    • Mail.Send to email the report.
  2. Defines the timeframe ($DaysBack) and converts it to UTC ISO format.
  3. Filters audit logs for events where category = 'UserManagement' and activityDisplayName = 'Reset user password'.
  4. Processes entries to extract details about the initiator (admin), the target users whose passwords were reset, the result, and any additional properties.
  5. Exports to CSV with a timestamped filename.
  6. Builds and sends an email with a concise HTML summary and the full CSV attached.

Further Enhancements

  • Scope expansion: Track other high-risk events like Delete user or Add member to role.
  • Enriched details: Include IP addresses, service names, or client apps for richer forensic context.
  • Threshold alerts: Send a high-priority notification if multiple resets occur in a short timeframe.
  • Scheduling: Run this script daily/weekly using Task Scheduler or Azure Automation.
  • Forward to SIEM: In addition to email, send logs to Splunk, Sentinel, or other SIEM platforms.

Use Cases

  • Account recovery oversight: Ensure only authorized admins reset user passwords.
  • Security monitoring: Detect suspicious spikes in password resets that may indicate compromise.
  • Compliance audits: Provide auditors with a report proving that password resets are tracked.
  • Incident response: Quickly identify who reset a user’s password during investigations.

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 Graph module missing/outdated Install or update Microsoft.Graph module.
Empty CSV No password reset events in the timeframe Increase $DaysBack; confirm events exist in Entra admin portal.
Email not delivered $FromUser not mailbox-enabled Ensure the sender is licensed and mailbox-enabled.
Recipient parsing error Incorrect delimiter handling Ensure recipients are separated by ; or , (script supports both).

Conclusion

Password reset by administrators is a critical audit log event—it directly affects user access and security. With this script, you gain automated visibility into who reset which user’s password, when, and why, with results emailed straight to your inbox. By scheduling and enhancing this script, organizations can strengthen their compliance posture and improve identity governance.


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