How to Report Microsoft 365 Groups Deletions Using the Audit Log

Deletions for Microsoft 365 Groups reported by PowerShell

Soft and Hard Deletions for Microsoft 365 Groups

Yesterday’s post addressed the topic of how to report the removals of Teams from an Office 365 tenant. This led to the logical question of how to know when Microsoft 365 Groups (and Teams) are removed because they expire due to the settings in the tenant group expiration policy.

The simple answer is that the Office 365 audit log captures details for all group deletions, including when the group expiration policy determines that a group is expired and puts the group into a soft-deleted state. Thirty days later, a background process permanently removes the soft-deleted group unless an administrator restores it beforehand. As is often the case with Office 365 technology, the simple answer hides some complexity, which we’ll dive into now.

Understanding Group Deletion Records

When you examine the records for group deletions stored in the Office 365 audit log, you find three conditions to handle:

  1. A user deletes a Microsoft 365 group, team, or team-enabled site.
  2. Microsoft background processes examine groups that come within the scope of the expiry policy and remove expired groups where no activity has occurred to force automatic renewal. These events are logged with a user identifier like ServicePrincipal_1342cefb-7a89-4ee2-af90-c8443053e1e8.
  3. Both 1 and 2 put groups into a soft-deleted state. After 30 days, another background process called the Microsoft Online services Garbage Collector permanently removes these groups and all attached resources.

With these conditions in mind, we can create a PowerShell script to extract records from the Office 365 audit log and parse the records to extract some useful information. Here’s the script I created. It looks similar to the one discussed yesterday with some extra processing to handle the three conditions.

CLS; Write-Host "Searching Office 365 Audit Records to find auto-expired group deletions"
$StartDate = (Get-Date).AddDays(-90); $EndDate = (Get-Date) 
$PolicySoftDeletes = 0; $HardDeletes = 0; $UserSoftDeletes = 0
$Records = (Search-UnifiedAuditLog -Operations "Delete Group" -StartDate $StartDate -EndDate $EndDate -ResultSize 1000)
If ($Records.Count -eq 0) {
    Write-Host "No audit records for group deletions found." }
Else {
    Write-Host "Processing" $Records.Count "team deletion audit records..."
    $Report = [System.Collections.Generic.List[Object]]::new() # Create output file 
    # Scan each audit record to extract information
    ForEach ($Rec in $Records) {
      $AuditData = ConvertFrom-Json $Rec.Auditdata
      $User = $AuditData.UserId.Split("_")[0]    
      $GroupName = $Auditdata.Target | ? {$_.Type -eq 1} | Select -ExpandProperty Id
          Switch ($User)
            "Certificate"  { # Hard delete of a group 
                 $Reason = "Group permanently removed" 
                 $User = $User + " (System Process)" }
            "ServicePrincipal" { #Soft delete - expiration policy 
                 $Reason = "Group removed by expiration policy"
                 $User = $User + " (System Process)" }
            default { #Regular delete by a user 
                 $Reason = "User deleted group" }
          $ReportLine = [PSCustomObject] @{
           TimeStamp = Get-Date($AuditData.CreationTime) -format g
           User      = $User
           Group     = $GroupName 
           Reason    = $Reason
           Action    = $AuditData.Operation
           Status    = $AuditData.ResultStatus }        
      $Report.Add($ReportLine) }
Write-Host "All done - Group deletion records for the last 90 days"
Write-Host "User deletions:"     $UserSoftDeletes
Write-Host "Policy deletions:"   $PolicySoftDeletes
Write-Host "Group hard deletes:" $HardDeletes
Write-Host "----------------------"
$Report | Sort Group, Reason -Unique | Select-Object Timestamp, Group, Reason, User | Out-GridView

If you examine the results as piped through the Out-GridView cmdlet (Figure 1), you’ll see examples where a record captures a soft-delete by user or policy followed 30 days later by a permanent removal.

Deletion records for Microsoft 365 Groups extracted from the audit log
Figure 1: Out-GridView shows group deletion records

Once you’re happy with the data generated, it’s easy to include an extra line of code to output a CSV file using the Export-CSVFile cmdlet or use the ImportExcel module to export the data to an Excel worksheet. Once again, access to this kind of information proves that PowerShell and the audit log are a very flexible tool to understand what happens behind the scenes in Microsoft 365.

Need more insight into mining the valuable information stored the Office 365 audit log? We’ve got a complete chapter covering auditing and reporting in the Office 365 for IT Pros eBook.

One Reply to “How to Report Microsoft 365 Groups Deletions Using the Audit Log”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.