Entra / Microsoft 365 · Compliance & audit
Check retention policy updates
Script to check for changes made to a retention policy.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
# Review required modules and connection steps before running.# Connect to Microsoft Graph or Exchange Online as needed for this script.
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([int] $LookbackDays = 30,[string] $StartDate = (Get-Date).AddDays(-$LookbackDays); $EndDate = (Get-Date),[string] $EndDate = (Get-Date))CLS# Check that we are connected to Exchange Online$ModulesLoaded = Get-Module | Select NameIf (!($ModulesLoaded -match "ExchangeOnlineManagement")) {Write-Host "Please connect to the Exchange Online Management module and then restart the script"; break}# Look for audit records over the last 30 days$OutputCSVFile = "C:\Temp\RetentionPolicyUpdates.csv"$AuditRulesReport = "C:\Temp\RetentionPolicyRulesUpdates.csv"Write-Host "Checking for Retention Policies"# Build a hash table of retention policies we can use to resolve Guids into policy names$RetentionPolicies = @{}Try {[array]$RP = Get-RetentionCompliancePolicy }Catch {Write-Host "Error fetching details of the tenant's retention policies - please make sure your session is connected to the compliance endpoint" ; break}$RP.ForEach( {$RetentionPolicies.Add([String]$_.Guid, $_.Name) } )# Now do the same for the app-specific retention policies (Teams private channels and Yammer)Try {[array]$RP = Get-AppRetentionCompliancePolicy }Catch {Write-Host "Error fetching details of the tenant's app retention policies - please make sure your session is connected to the compliance endpoint" ; break}$RP.ForEach( {$RetentionPolicies.Add([String]$_.Guid, $_.Name) } )# Find the audit recordsWrite-Host "Finding audit records"[array]$Records = (Search-UnifiedAuditLog -Operations SetRetentionCompliancePolicy, SetRetentionComplianceRule -StartDate $StartDate -EndDate $EndDate -Formatted -ResultSize 2000)If (!($Records)) {Write-Host "No audit records found - exiting!"; break}# Strip out records for policy updates (operation = SetRetentionCompliancePolicy)[array]$AuditRecords = $Records | ? {$_.Operations -eq "SetRetentionCompliancePolicy"}# Build array of retention rule changes[array]$RuleRecords = $Records | ? {$_.Operations -eq "SetRetentionComplianceRule"}$AuditRules = [System.Collections.Generic.List[Object]]::new()ForEach ($Rule in $RuleRecords) {$AuditData = $Rule.AuditData | ConvertFrom-Json$DataLine = [PSCustomObject] @{Date = $Rule.CreationDateUser = $AuditData.UserIdPolicy = $AuditData.ExtendedProperties | ?{$_.Name -eq "PolicyName"} | Select -ExpandProperty ValueRetentionAction = $AuditData.ExtendedProperties | ?{$_.Name -eq "RetentionAction"} | Select -ExpandProperty ValueRetentionDuration = $AuditData.ExtendedProperties | ?{$_.Name -eq "RetentionDuration"} | Select -ExpandProperty ValueRetentionType = $AuditData.ExtendedProperties | ?{$_.Name -eq "RetentionType"} | Select -ExpandProperty ValueActions = $AuditData.Parameters | ?{$_.Name -eq "CmdletOptions"} | Select -ExpandProperty Value }$AuditRules.Add($DataLine)}# Report audit records for policy updates$AuditReport = [System.Collections.Generic.List[Object]]::new()ForEach ($AuditRecord in $AuditRecords) {$AuditData = $AuditRecord.AuditData | ConvertFrom-Json$PolicyDetails = $AuditData.Parameters | ?{$_.Name -eq "CmdletOptions"} | Select -ExpandProperty Value$PolicyName = $Null; $PolicyGuid = $Null; $Encodedtext = $NullIf ($PolicyDetails -Like "*RetryDistribution*") { # The change is to restart distributions to target locations$Start = $PolicyDetails.IndexOf('"')+1$End = $PolicyDetails.IndexOf("-Retry")-13$PolicyName = $PolicyName = $PolicyDetails.SubString($Start,$End) }Else { # Update made to the policy$Start = $PolicyDetails.IndexOf('"')+1$EncodedText = $PolicyDetails.SubString($Start,48)$PolicyGuid = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($EncodedText))$PolicyName = $RetentionPolicies.Item($PolicyGuid) # See if we can find the retention policy name}$DataLine = [PSCustomObject] @{Date = $AuditRecord.CreationDateUser = $AuditData.UserIdPolicy = $PolicyNamePolicyGuid = $PolicyGuidDetailsLogged = $PolicyDetailsEC = $EncodedText }$AuditReport.Add($DataLine)}$AuditReport | Export-CSV -NoTypeInformation $OutputCSVFile$AuditRules | Export-CSV -NoTypeInformation $AuditRulesReport$AuditReport | Out-GridViewWrite-Host "All done. Policy update report available in" $OutputCSVFile "and details of audit rule updates in" $AuditRulesReport
Parameters
ParameterDefaultNotes
-LookbackDays30How many days back to search for newly created mailboxes or recent activity.-StartDate(Get-Date).AddDays(-30); $EndDate = (Get-Date)Start of the reporting window.-EndDate(Get-Date)End of the reporting window.Attribution
Author
Office365itpros