Reporting Office 365 Group Deletions

Soft and Hard Office 365 Group Deletes

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 Office 365 Groups (and Teams) are removed because they expire due to the settings in the group expiration policy.

The simple answer is that the Office 365 audit log records all group deletions, including when a group expires and is soft-deleted, followed by its permanent removal 30 days later. As is often the case with Office 365, the simple answer hides some complexity.

Understanding Group Deletion Records

When you examine the audit records for group deletions, you find three conditions to handle:

  1. A user deletes a 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 some PowerShell to extract records from the 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]    
      Foreach ($Prop in $AuditData.ExtendedProperties) { If ($Prop.Name -eq "targetName") { $GroupName = $Prop.Value }}
          Switch ($User)
          {
            "Certificate"  { # Hard delete of a group 
                 $HardDeletes++ 
                 $Reason = "Group permanently removed" 
                 $User = $User + " (System Process)" }
            "ServicePrincipal" { #Soft delete - expiration policy 
                 $PolicySoftDeletes++
                 $Reason = "Group removed by expiration policy"
                 $User = $User + " (System Process)" }
            default { #Regular delete by a user 
                 $UserSoftDeletes++ 
                 $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) }
}
Cls
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 | Format-Table Timestamp, Group, Reason, User -AutoSize

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.

Out-GridView shows group deletion records
Figure 1: Out-GridView shows group deletion records

Once you’re happy with the data generated, it’s easy to output a CSV file to keep using the Export-CSVFile cmdlet. PowerShell and the audit log are a very flexible tool to understand what happens behind the scenes in Office 365.


Need more insight into mining the valuable information stored the Office 365 audit log? Read the chapter in the Office 365 for IT Pros eBook.

Leave a Reply

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