Using PowerShell to Add Teams to the Groups Expiration Policy

Easily Done, But Prepare for Teams That Expire Afterwards

A contribution to the Microsoft Technical Community offered a solution for how to add all the Teams in an Office 365 tenant to the Office 365 Groups Expiration Policy. Once teams are added to the policy, the groups they belong to expire at the end of the policy lifetime (say, 750 days) and must be renewed by a team owner. If not, the group is soft-deleted for 30 days, during which time it can be recovered. At the end of that period, Office 365 permanently removes the group for the team and all the associated resources.

In any case, the script worked on the basis of finding all the Office 365 Groups in the tenant and then figuring out which groups are team-enabled before adding those groups to the policy. It’s a valid approach, but a better method is to use the Get-Team cmdlet in the Teams PowerShell module because it only returns the set of teams and you don’t have to fiddle around checking what groups are team-enabled.

Once you have the set of teams, it’s easy to add them to the expiration policy using the Add-AzureADMSLifecyclePolicyGroup cmdlet.

Tuning The Solution

A script showing how to add multiple groups to the expiration policy is included in the chapter covering how to manage Groups and Teams in the Office 365 for IT Pros eBook. It was simple to take that script and amend it to process Teams rather than Groups. We can improve the solution further by implementing a check for teams already covered by the policy so we don’t trigger an error when running Add-AzureADMSLifecyclePolicyGroup.

One way to do this is to update one of the fifteen custom attributes available for all mail-enabled objects. In the example, we write the GUID of the policy into this attribute, meaning that we can check for its existence before trying to add a team to the policy. Here’s the code:

# Add all Teams that aren't already covered by the Groups expiration policy 
# to the policy
$PolicyId = (Get-AzureADMSGroupLifecyclePolicy).Id
$TeamsCount = 0
Write-Host "Fetching list of Teams in the tenant…"
$Teams = Get-Team
ForEach ($Team in $Teams) {
  $CheckPolicy = (Get-UnifiedGroup -Identity $Team.GroupId).CustomAttribute3
  If ($CheckPolicy -eq $PolicyId) {
    Write-Host "Team" $Team.DisplayName "is already covered by the expiration policy" }
  Else { 
    Write-Host "Adding team" $Team.DisplayName "to group expiration policy"
    Add-AzureADMSLifecyclePolicyGroup -GroupId $Team.GroupId -Id $PolicyId -ErrorAction SilentlyContinue
    Set-UnifiedGroup -Identity $Team.GroupId -CustomAttribute3 $PolicyId
    $TeamsCount++ }}
Write-Host "All done." $TeamsCount "teams added to policy"

Another advantage of using a custom attribute is that many cmdlets support server-side filtering for this attributes. For instance, to find the set of teams that have been added to the expiration policy, we can run the command:

Get-UnifiedGroup -Filter {CustomAttribute3 -ne $null} | Format-Table DisplayName, CustomAttribute3

Update After Groups Expiration Policy Update

After I wrote the original post, Microsoft updated the Groups expiration policy to be activity based. A side effect of the change was that the Get-UnifiedGroup cmdlet returns the calculated expiration date for groups covered by the policy, meaning that you could use this instead of a custom attribute to figure out what groups are covered by the policy. Thus, we can now base the script on the expiration date as shown below.

$PolicyId = (Get-AzureADMSGroupLifecyclePolicy).Id
Write-Host "Fetching list of Teams in the tenant…"
[array]$Teams = Get-Team
$TeamsCount = 0
ForEach ($Team in $Teams) {
  $CheckPolicy = $Null
  $CheckPolicy = (Get-UnifiedGroup -Identity $Team.GroupId).ExpirationTime
  If ($CheckPolicy -ne $Null) {
    Write-Host "Team" $Team.DisplayName "covered by expiration policy and will expire on" (Get-Date($CheckPolicy) -format g)}
  Else { 
    Write-Host "Adding team" $Team.DisplayName "to group expiration policy" -Foregroundcolor Red
    Add-AzureADMSLifecyclePolicyGroup -GroupId $Team.GroupId -Id $PolicyId -ErrorAction SilentlyContinue
    Set-UnifiedGroup -Identity $Team.GroupId -CustomAttribute3 $PolicyId
    $TeamsCount++ }}
Write-Host "All done." $TeamsCount "Teams added to policy"

In turn, this means that to find the groups covered by the policy you can do the following (unfortunately the ExpirationTime property is not supported for server-side filtering):

[array]$Groups = Get-UnifiedGroup -ResultSize Unlimited |? {$_.ExpirationTime -ne $Null}  
$Groups | Sort Expirationtime | Format-Table DisplayName, ExpirationTime 

Expiring Groups

After you add a bunch of groups to the expiration policy, the likelihood exists that some of those groups will expire because they are already older than the expiration period. For this reason, it’s a good idea to prepare team owners to let them know what to do if they see an expiration notice. If the group is team-enabled, the notification appears in the activity feed of the team owner (Figure 1).

Expiring teams show up in the team owner's activity feed
Figure 1: Expiring teams show up in the team owner’s activity feed

They can then extend the lifetime of the team by editing its settings. Select the team expiration option to view the current expiration date and then click Renew now (Figure 2) if needed.

Renewing an expired group via Teams settings
Figure 2: Renewing an expired group via Teams settings

Need more examples of how to manage Teams and Office 365 Groups (and many other things) with PowerShell? Look no further than the Office 365 for IT Pros eBook. At the last count, the text included over a thousand examples.

7 Replies to “Using PowerShell to Add Teams to the Groups Expiration Policy”

  1. Why not use Get-UnifiedGroup | select ExpirationTime instead of managing your own attribute?

    1. Because that attribute wasn’t available with Get-UnifiedGroup when the post was written last year…

  2. Thanks, I had been using Graph API to pull in the expiration date, didn’t catch the new ExpirationTime Attribute

Leave a Reply

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