
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:
- A user deletes a group, team, or team-enabled site.
- 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.
- 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.

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.