Orphaned Groups Cleanup Report
Weekly flow identifies Office 365 Groups with no owners or activity and sends admins a report to review for deletion.
Provided as-is, without warranty of any kind. Review and test each pattern in a non-production environment before deploying it to live automations. See our Terms.
Overview
This flow runs every Monday morning to inventory all Microsoft 365 (Unified) Groups in the tenant, examine each group's owner roster and last-renewed date, and email an HTML digest to the IT admin recipient. Groups are flagged as "orphaned" if either condition is met:
1. No owners assigned — the group cannot be managed and represents a governance gap. 2. Inactive for longer than the threshold — the group's renewedDateTime (or createdDateTime if never renewed) is older than the configured number of days.
The report includes per-group rows (group name, email, owner count, last renewed date, days inactive, reason flagged) plus a run-summary table that distinguishes "no-owner" groups from "stale" groups so the admin can triage cleanup work.
Use Case
Microsoft 365 Groups accumulate quickly when self-service group creation is enabled, and groups outlive the people who created them. Common scenarios:
- The original owner leaves the company and the group is never reassigned. - A project completes and the group is forgotten in the directory. - A test/demo group is left in place after the proof-of-concept ends.
These groups create real costs: storage in the linked SharePoint site, mailbox sprawl, addressable surface area in the Global Address List, audit findings during compliance reviews, and confusion for end users searching for the "official" group. This flow surfaces them on a weekly cadence so IT can triage before they become a backlog.
The flow is ideal for teams that:
- The original owner leaves the company and the group is never reassigned.
- A project completes and the group is forgotten in the directory.
- A test/demo group is left in place after the proof-of-concept ends.
Flow Architecture
Weekly Orphaned Groups Audit
RecurrenceEvery Monday at 09:00 Eastern Time.
Init varAdminEmail
Initialize Variable (string)Loads `flowlibs_ITAdminEmail`.
Init varActivityDays
Initialize Variable (integer)Parses `flowlibs_OrphanedGroupActivityDays` to int.
Init varOrphanedRowsHtml
Initialize Variable (string)Empty string accumulator for HTML `<tr>` rows.
Init varOrphanedCount
Initialize Variable (integer)Counter for total flagged groups.
Init varTotalGroups
Initialize Variable (integer)Counter for groups iterated.
Init varGroupsWithoutOwners
Initialize Variable (integer)Sub-counter: zero-owners cohort.
Init varStaleGroups
Initialize Variable (integer)Sub-counter: inactive-too-long cohort.
List M365 Groups
Office 365 Groups – ListGroups`$filter=groupTypes/any(c:c eq 'Unified')`, `$top=200`.
Apply To Each Group
Foreach (concurrency 1)For each group: gets owners via Graph HTTP, computes days since renewal, increments total counter, then evaluates the orphaned condition. Sequential to keep accumulators deterministic. Sub-actions: Get Group Owners (Office 365 Groups HttpRequest to `GET /v1.0/groups/{id}/owners?$select=id,displayName,userPrincipalName,mail&$top=20`); Compose Owners Array using `coalesce(body(...)?['value'], createArray())` for empty array on 404/failure; Compose Days Since Renewal as `(ticks(utcNow()) - ticks(renewedDateTime)) / 864000000000`; Increment Total Groups by 1.
Environment Variables
| Schema name | Type | Default | Description |
|---|---|---|---|
| flowlibs_ITAdminEmail | String | it-admin@contoso.com | Recipient for the weekly digest. |
| flowlibs_OrphanedGroupActivityDays | String | 180 | Inactivity threshold (parsed to int). Groups whose `renewedDateTime` (or `createdDateTime` if never renewed) is older than this many days are flagged as stale; groups with zero owners are flagged regardless. |
Connectors & Connections
| Connector | API name | Actions used |
|---|---|---|
| Office 365 Groups | shared_office365groups | ListGroups (Lists Unified groups with `$filter=groupTypes/any(c:c eq 'Unified')`, `$top=200`.) HttpRequest (Calls `GET /v1.0/groups/{id}/owners` via the connector so authentication is handled inside the connection reference (no manual AAD OAuth).) |
| Office 365 Outlook | shared_office365 | SendEmailV2 (Sends the styled HTML report to the admin recipient.) |
Note — All connections are referenced as solution connection references; the flow is portable between environments as long as a connection is mapped at import time.
Customization Guide
Almost every realistic variant of this flow can be implemented by changing environment variable values. A few cases require small edits inside the flow definition — those are called out explicitly below.
- Activate after import
- Open the solution and authorize the two connection references against tenant accounts that have Group.Read.All and Mail.Send scopes. Update flowlibs_ITAdminEmail to the actual recipient (a security distribution list works well). Adjust flowlibs_OrphanedGroupActivityDays if 180 days is the wrong threshold — 90 days is common for high-velocity orgs, 365 for low-churn. Turn the flow on.
- Change the schedule
- Edit the Weekly_Orphaned_Groups_Audit trigger's recurrence block. The defaults are weekly Mondays at 09:00 Eastern; daily, monthly, or different days/hours work without other edits.
- Add Security Groups or Distribution Lists
- Remove the $filter parameter on List_M365_Groups. The owners check + renewedDateTime check works for any group type, though renewedDateTime is meaningful primarily for Unified groups.
- Enrich the report with site activity
- Add a SharePoint GetItems or Graph /sites/{id}/analytics call inside the Foreach to pull last-modified timestamps from the linked SharePoint site and surface them as an additional column. Last-message-on-the-mailbox via Exchange is another high-signal source.
- Take action automatically
- Chain a StartAndWaitForAnApproval after Compose_Email_Body and use the response to drive RemoveMemberFromGroup (to unassign owners), DeleteGroup via Graph HTTP, or write the cleanup decision to a SharePoint audit log.
Key Expressions
The flow is intentionally light on Power Fx / WDL gymnastics — the heaviest expressions are the branch-name concatenation and the approval outcome check. They are listed below in the order they appear in the flow.
EXPR.01Days since last renewal
Falls back to createdDateTime when renewedDateTime is null, and to a sentinel 1900 date if both are missing so the math never throws.
EXPR.02Owners count (zero-safe)
Combined with widened runAfter on the owners HTTP call, a 404/permission failure surfaces as zero owners rather than terminating the run.
EXPR.03Orphaned condition
Flag if either no owners OR inactivity beyond the threshold.
EXPR.04Reason label
Resolves to one of three human-readable reasons so the admin sees why a group was flagged.
EXPR.05Subject summary
Email subject line — surfaces the counts before the admin opens the message.
Comments
Sign in to join the conversation.
Sign inNo comments yet. Be the first to share your experience with this flow.