Entra / Microsoft 365 · Compliance & audit
Report user update audit records
How to find unified audit records for user account update events and analyze what actions/changes.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -Scopes Directory.Read.AllConnect-ExchangeOnline
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([int] $LookbackDays = 180)Connect-MgGraph -Scopes Directory.Read.AllConnect-ExchangeOnline# Find some audit records to play with from the last 180 daysWrite-Host "Searching for audit records for update user events..."[array]$Records = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-$LookbackDays) -EndDate (Get-Date).AddDays(1) -Formatted -ResultSize 5000 -Operations "update user"If (!($Records)) {Write-Host "No audit records found for update user events - exiting" ; break} Else {Write-Host ("{0} audit records found - processing..." -f $Records.count) }$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($Rec in $Records) {$AuditData = $Rec.AuditData | ConvertFrom-Json$OldValue = $Null; $NewValue = $Null[array]$ModifiedProperties = $AuditData.ModifiedProperties[array]$Properties = $ModifiedProperties | Where-Object { $_.Name -eq 'Included Updated Properties' } | Select-Object -ExpandProperty NewValueIf (!([string]::IsNullOrEmpty($Properties))) {# Create a list of updated properties[array]$ListOfProperties = $Properties.Split(",")ForEach ($P in $ListOfProperties) {$Property = $P.Trim()$Command = '$NewValue = (($modifiedproperties' + " | where {`$_.Name -eq '" + $Property + "'}).NewValue -replace '[\[\]]', '').Trim()"Invoke-Expression $Command$Command = '$OldValue = (($modifiedproperties' + " | where {`$_.Name -eq '" + $Property + "'}).OldValue -replace '[\[\]]', '').Trim()"Invoke-Expression $Command$NewValue = $NewValue -Replace '["]'$OldValue = $OldValue -Replace '["]'If ($Property -eq "StrongAuthenticationPhoneAppDetail") {# Handle an update of the MFA authentication method$MFADetails = $OldValue.replace("}", '')$MFADetails = $MFADetails.replace("{", '').trim()[array]$MFAData = $MFADetails.Split(",")$OldValue = ("{0} {1}" -f ($MFAData[0].toString()), ($MFAData[3].toString().trim()))$MFADetails = $NewValue.replace("}", '')$MFADetails = $MFADetails.replace("{", '').trim()[array]$MFAData = $MFADetails.Split(",")$NewValue = ("{0} {1}" -f ($MFAData[0].toString()), ($MFAData[3].toString().trim()))}If ($Property -eq "AssignedLicense") {# License updates - this is an example of where we could do more to extract data from the audit records$NewValue = "License Update " + $NewValue }If ($Property -eq "AssignedPlan") {$NewValue = "See audit record for assigned plan information" }If ($Property -eq "LicenseAssignmentDetail") {$NewValue = "See audit record for license assignment detail" }If ([string]::IsNullOrEmpty($OldValue)) {$OldValue = "Not filled" }# Some audit events are caused by background processing - so let's find out what background process is involved for these ecentsIf ($Rec.UserIds.SubString(0,16) -eq "ServicePrincipal") {$UserId = Get-MgServicePrincipal -ServicePrincipalId $Rec.Userids.Substring(17,($Rec.Userids.Length-17)) | Select-Object -ExpandProperty DisplayName} Else {# It's a normal user who performed the action$UserId = $Rec.UserIds }# Report what happened$ReportLine = [PSCustomObject][Ordered]@{Timestamp = $Rec.CreationDateUser = $UserIdAccount = $AuditData.ObjectIdProperty = $Property'Old value' = $OldValue'New value' = $NewValue }$Report.Add($ReportLine)} # End For to extract updated properties} # End If Properties} # End For audit events$Report | Out-GridView
Parameters
ParameterDefaultNotes
-LookbackDays180Number of days back to search the unified audit log.Attribution
Author
Office365itpros