Yammer Communities Now Covered by Groups Expiration Policy
Updated 24 April 2023
A reader question about the Microsoft 365 Groups expiration policy caused me to review some PowerShell code I wrote to report the next renewal dates for the set of groups within the scope of the expiration policy. The question was related to Yammer (now Viva Engage), to know if the Microsoft 365 Group expiration policy covers the groups by Yammer and will remove inactive groups when necessary. The answer is yes; Microsoft updated policy processing last year to accommodate the Microsoft 365 groups used by Yammer communities when networks run in Microsoft 365 native mode. Microsoft confirmed coverage for Yammer communities by the groups expiration policy in MC324202 (published today). Microsoft 365 roadmap item 82186 also deals with the scenario and says that general availability occurred in January 2022.
In 2020, Microsoft changed the way the Microsoft 365 Groups expiration policy works to introduce automatic renewal. Instead of bothering group owners with email to remind them to renew the group, a background job looks for evidence that the group is active. If the evidence exists, Microsoft 365 renews the group automatically. Unfortunately, a limited set of signals govern renewal:
SharePoint Online: View, edit, download, move, share, or upload files.
Outlook: Join group, read/write message in the group mailbox, or like a message (in OWA).
It’s debatable if a group is active if just one group member visits a Teams channel or views a post in a Yammer community. The Microsoft Graph gathers a wide array of signals about user activity and there’s surely a more precise method to determine group activity than the actions cited above. The Groups and Teams activity report is an example of how to code your own assessment of group activity.
In any case, the answer remains that you can add the Microsoft 365 groups used by Viva Engage communities to the Groups expiration policy through the Microsoft Entra admin center (Figure 1).
Figure 1: Defining groups to include in the expiration policy
The Groups expiration policy is appliable to selected groups or to all Microsoft 365 groups in the tenant. Users who are members of the groups covered by the policy must have Azure AD Premium P1 licenses.
PowerShell Code to Report Group Expiration
Writing code to report the expiration dates for groups isn’t difficult. The date when the group needs to be next renewed is in the ExpirationTime property. The only complication is to find when the group was last renewed. This data isn’t returned by the Get-UnifiedGroup cmdlet, so we need to use the Get-AzureADMSGroup cmdlet. Once we know where to get the dates, we can report what we find. This code runs after connecting to the Exchange Online and Azure AD PowerShell modules.
Write-Host "Finding Microsoft 365 Groups to check…"
[array]$ExpirationPolicyGroups = (Get-UnifiedGroup -ResultSize Unlimited | ? {$_.ExpirationTime -ne $Null} | Select DisplayName, ExternalDirectoryObjectId, WhenCreated, ExpirationTime )
If (!($ExpirationPolicyGroups)) { Write-Host "No groups found subject to the expiration policy - exiting" ; break }
Write-Host $ExpirationPolicyGroups.Count “groups found. Now checking expiration status.”
$Report = [System.Collections.Generic.List[Object]]::new(); $Today = (Get-Date)
ForEach ($G in $ExpirationPolicyGroups) {
$Days = (New-TimeSpan -Start $G.WhenCreated -End $Today).Days # Age of group
$LastRenewed = (Get-AzureADMSGroup -Id $G.ExternalDirectoryObjectId).RenewedDateTime
$DaysLeft = (New-TimeSpan -Start $Today -End $G.ExpirationTime).Days
$ReportLine = [PSCustomObject]@{
Group = $G.DisplayName
Created = Get-Date($G.WhenCreated) -format g
AgeinDays = $Days
LastRenewed = Get-Date($LastRenewed) -format g
NextRenewal = Get-Date($G.ExpirationTime) -format g
DaysLeft = $DaysLeft}
$Report.Add($ReportLine)
} # End Foreach
CLS;Write-Host "Total Microsoft 365 Groups covered by expiration policy:" $ExpirationPolicyGroups.Count
Write-Host “”
$Report | Sort DaysLeft | Select Group, @{n="Last Renewed"; e= {$_.LastRenewed}}, @{n="Next Renewal Due"; e={$_.NextRenewal}}, @{n="Days before Expiration"; e={$_.DaysLeft}}
Total Microsoft 365 Groups covered by expiration policy: 74
Group Last Renewed Next Renewal Due Days before Expiration
----- ------------ ---------------- ----------------------
Potholers (Team) 02/02/2020 07:16 21/02/2022 07:16 13
Office 365 Questions 19/05/2017 11:12 14/03/2022 15:04 34
Corona Virus News 10/03/2020 21:56 30/03/2022 22:56 51
Contract Workers 12/03/2020 08:57 01/04/2022 09:57 52
Plastic Production (Team) 25/03/2020 08:48 14/04/2022 09:48 65
The code works, with two caveats:
Get-UnifiedGroup is not a fast cmdlet. It’s OK to run it against a couple of hundred groups, but once that number grows, the time needed for the cmdlet to retrieve details of the groups to process gets longer and longer.
The Get-AzureADMSGroup cmdlet is affected by Microsoft’s decision to retire the Azure AD module. Although the cmdlet will continue to run after June 30, 2023, you don’t know when it will cease functioning.
The solution for both speed and supportability is to use a Microsoft Graph API query to fetch group details.
Using the Graph to Fetch Microsoft 365 Groups to Report Expiration Details
Essentially, what we need to do is to replace the call to Get-UnifiedGroup with a Graph API query to return the set of groups in the tenant. The bonus is that the query returns the last renewed time, so there’s no need to use Get-AzureADMSGroup.
As with any script that calls Graph queries from PowerShell, you need a registered application in Azure AD to hold the permissions required to run the queries used by the script. In this case, we only need the Group.Read.All permission. After securing an access token, we can fetch the set of groups in the tenant using a lambda filter. The code shown below uses a function (Get-GraphData) to execute the Invoke-RestMethod cmdlet to fetch the data and page until all groups are retrieved. You can see the code for the Get-GraphData function in this script.
After fetching the set of groups, we create a report detailing the group name, its creation date, the date last renewed, and expiration date. The code used to process the data returned by Get-UnifiedGroup is modified to deal with the property names returned by the Graph query.
$uri = "https://graph.microsoft.com/beta/groups?`$filter=ExpirationDateTime ge 2014-01-01T00:00:00Z AND groupTypes/any(a:a eq 'unified')&`$count=true"
[array]$Groups = Get-GraphData -AccessToken $Token -Uri $uri
If (!($Groups)) { Write-Host "No groups found subject to the expiration policy - exiting" ; break }
$Report = [System.Collections.Generic.List[Object]]::new(); $Today = (Get-Date)
ForEach ($G in $Groups) {
$Days = (New-TimeSpan -Start $G.CreatedDateTime -End $Today).Days # Age of group
#$LastRenewed = $G.RenewedDateTime
#$NextRenewalDue = $G.ExpirationDateTime
$DaysLeft = (New-TimeSpan -Start $Today -End $G.ExpirationDateTime).Days
$GroupsInPolicy++
$ReportLine = [PSCustomObject]@{
Group = $G.DisplayName
Created = Get-Date($G.CreatedDateTime) -format g
"Age in days" = $Days
"Last renewed" = Get-Date($G.RenewedDateTime) -format g
"Next renewal" = Get-Date($G.ExpirationDateTime) -format g
"Days before expiration" = $DaysLeft}
$Report.Add($ReportLine)
} # End ForeachCLS;Write-Host "Total Microsoft 365 Groups covered by expiration policy:" $Groups.Count
Write-Host “”
$Report | Sort "Days before expiration"| Select Group, "Last renewed", "Next renewal", "Days before expiration" | Out-GridView
As you’d expect, things run much faster. Retrieving data through a Graph query is always quicker than using a PowerShell cmdlet and eliminating the call to Get-AzureADMSGroup for each group helps speed things up even further. Figure 2 shows the output.
Figure 2: Expiration dates for Microsoft 365 Groups
An even easier solution is to replace the calls to Get-UnifiedGroup and Get-AzureADMSGroup with the Get-MgGroup cmdlet from the Microsoft Graph PowerShell SDK. Here’s the code, which is almost as fast as using the Graph API:
Write-Host "Finding Microsoft 365 Groups to check…"
[array]$ExpirationPolicyGroups = Get-MgGroup -Filter "groupTypes/any(c:c eq 'unified')" -All | ? {$_.ExpirationDateTime -ne $Null }
If (!($ExpirationPolicyGroups)) { Write-Host "No groups found subject to the expiration policy - exiting" ; break }
Write-Host $ExpirationPolicyGroups.Count “groups found. Now checking expiration status.”
$Report = [System.Collections.Generic.List[Object]]::new(); $Today = (Get-Date)
ForEach ($G in $ExpirationPolicyGroups) {
$Days = (New-TimeSpan -Start $G.CreatedDateTime -End $Today).Days # Age of group
$DaysLeft = (New-TimeSpan -Start $Today -End $G.ExpirationDateTime).Days
$ReportLine = [PSCustomObject]@{
Group = $G.DisplayName
Created = Get-Date($G.CreatedDateTime) -format g
AgeinDays = $Days
LastRenewed = Get-Date($G.RenewedDateTime) -format g
NextRenewal = Get-Date($G.ExpirationDateTime) -format g
DaysLeft = $DaysLeft}
$Report.Add($ReportLine)
} # End Foreach
CLS;Write-Host "Total Microsoft 365 Groups covered by expiration policy:" $ExpirationPolicyGroups.Count
Write-Host “”
$Report | Sort DaysLeft | Select Group, @{n="Last Renewed"; e= {$_.LastRenewed}}, @{n="Next Renewal Due"; e={$_.NextRenewal}}, @{n="Days before Expiration"; e={$_.DaysLeft}}
Speeding Up Queries
The question about Yammer communities forced me to look at code and find an instance where I needed to replace a cmdlet before its deprecation next June. At the same time, I managed to speed up the code by introducing a Graph query. Things worked out for the best, but it does illustrate the need to check and update old scripts on an ongoing basis.
Learn how to exploit the data available to Microsoft 365 tenant administrators through the Office 365 for IT Pros eBook. We love figuring out how things work.
I just want to know if the Autorenewal activity through Yammer that was introduced in Jan 2022 is applicable for a network that is non native. Yet the groups are M365 groups
Hi Toni,
it’s a bit trial and error, because there is no mentioning to which cloud services I should connect (Exchange, AzureAD, etc.)
Thinking broadly about Graph, is there a possibility to create a report / query on Azure admin portal without PowerShell, e.g. a Workbook based on Kusto query, or similar?
{"id":null,"mode":"button","open_style":"in_modal","currency_code":"EUR","currency_symbol":"\u20ac","currency_type":"decimal","blank_flag_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":100,"top_media_type":"featured_image","featured_image_url":"https:\/\/office365itpros.com\/wp-content\/uploads\/2022\/11\/cover-141x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Virtual Tip Jar","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"Office 365 for IT Pros","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"Office 365 for IT Pros","form_subtitle":null,"currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for supporting the work of Office 365 for IT Pros!","payment_confirmation_title":"Office 365 for IT Pros","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
Hi ,
I just want to know if the Autorenewal activity through Yammer that was introduced in Jan 2022 is applicable for a network that is non native. Yet the groups are M365 groups
I believe not. I think you need to have the network configured in native Microsoft 365 mode.
Hi Toni,
it’s a bit trial and error, because there is no mentioning to which cloud services I should connect (Exchange, AzureAD, etc.)
Thinking broadly about Graph, is there a possibility to create a report / query on Azure admin portal without PowerShell, e.g. a Workbook based on Kusto query, or similar?
I don’t believe the information about expiring groups is available through Kusto. You’ll need to use PowerShell or the Graph.
Thank you for your post… I am just wondering if A planner activity counts as an activity for the group expiration..
I don’t believe so.