A governance audit is how you find out whether what's actually configured in your tenant matches what you documented. In my experience, there's almost always a gap — and the gap is rarely deliberate. It's the result of incremental changes over time, admin turnover, quick fixes that became permanent, and the slow drift that happens in any living system without periodic review.
Running a governance audit doesn't require specialist consultants or expensive tooling. It requires a structured approach, the right PowerShell cmdlets, and a willingness to act on what you find. This article provides that structure.
Scope the Audit
A full Teams governance audit covers six areas: policy configurations, team ownership, guest and external access, sensitivity labels, lifecycle management, and security settings. For a first audit, don't try to cover everything simultaneously — prioritise the areas with the highest risk exposure for your organisation. A financial services firm might prioritise security settings and DLP. A large enterprise might prioritise team ownership and lifecycle management. A healthcare organisation might prioritise DLP and sensitivity labels.
Audit Area 1: Policy Configurations
For each policy type (meeting, messaging, app permission, app setup, calling), verify:
- What are the current settings of the Global (org-wide default) policy?
- What named policies exist, what are their settings, and are those settings intentional?
- Which users or groups are assigned each named policy?
- Are there any named policies that aren't assigned to anyone (orphaned policies)?
- Are any named policies assigned directly to users who are also in a group assignment, creating a direct-over-group override that the rank table does not show?
- When was each named policy last changed, and does that date match a change request, or is the change unexplained?
PowerShell approach:
# Get all meeting policies and their key settings
Get-CsTeamsMeetingPolicy | Select-Object Identity, AllowCloudRecording, AllowTranscription, AutoAdmittedUsers
# Get all group policy assignments for meeting policies
Get-CsGroupPolicyAssignment -PolicyType TeamsMeetingPolicy | Sort-Object Rank
Audit Area 2: Team Ownership
Team ownership is one of the most commonly neglected areas of governance. The key questions:
- How many teams have fewer than two owners?
- How many teams have no active owners (all owners have left the organisation)?
- How many teams have an ownership vacancy (one owner was removed but a replacement wasn't assigned)?
# Find teams with zero or one owner
Get-Team | ForEach-Object {
$owners = Get-TeamUser -GroupId $_.GroupId -Role Owner
if ($owners.Count -lt 2) {
[PSCustomObject]@{
TeamName = $_.DisplayName
GroupId = $_.GroupId
OwnerCount = $owners.Count
Owners = ($owners.User -join ", ")
}
}
} | Export-Csv -Path "C:\Temp\LowOwnerTeams.csv" -NoTypeInformation
Audit Area 3: Guest and External Access
Guest access review questions:
- How many active guest accounts are in the tenant?
- What is the last sign-in date for each guest account?
- Which teams is each guest a member of?
- Are there guests who are members of teams with a "Confidential" sensitivity classification?
# Get all guest users and their last sign-in
Get-MgUser -Filter "userType eq 'Guest'" -Property DisplayName,UserPrincipalName,SignInActivity |
Select DisplayName, UserPrincipalName, @{N='LastSignIn';E={$_.SignInActivity.LastSignInDateTime}}
Audit Area 4: Sensitivity Labels
For sensitivity label governance:
- What percentage of teams have a sensitivity label applied?
- Are there teams with no label (unclassified)?
- Are the label settings (guest access restrictions, external sharing settings) still appropriate?
- Have any label-protected teams been found to have inappropriate guest access despite the label settings?
The Microsoft Purview compliance portal provides a label activity report that shows label distribution across groups and sites. Use this as the primary data source for the sensitivity label portion of the audit.
Audit Area 5: Lifecycle Management
- Are Microsoft 365 group expiration policies configured and functioning?
- How many teams have been inactive for 90+ days?
- How many teams have been inactive for 180+ days?
- Are archived teams being managed (not left in limbo indefinitely)?
Audit Area 6: Security Settings
Cross-check the security settings audit checklist from the dedicated article (article 09 in this series). Key items: MFA enforcement status, Conditional Access policy coverage, external access federation scope, app permission policy settings, and DLP policy status.
Documenting and Acting on Findings
An audit generates value only if its findings are acted on. Document findings in three categories:
- Critical: Requires immediate remediation (ownerless teams with sensitive content, misconfigured security settings).
- High: Requires remediation within 30 days (stale guest accounts, teams inactive for 180+ days, policy orphans).
- Medium: Requires attention in the next governance cycle (teams with a single owner, unclassified teams, minor policy configuration drift).
Assign ownership for each finding and track remediation to completion. An audit report that sits in a folder without follow-up is worse than no audit — it creates documentation of known problems without evidence of correction.
The Same Audit, Six Months Later
A governance audit of Microsoft Teams is a comparison between the written standard and the tenant, plus a list of defects with owners. Run once, it is a project. Run on a cadence, it is a control. The second run is the one that shows whether anyone did the work.
A professional body with about 500 staff completed a first audit in spring and filed 28 findings. The autumn run repeated the same six areas with the same exports. Eleven findings were closed, nine were unchanged, and eight were new, mostly teams created by a new membership app that had not existed in spring. The unchanged nine were the real result. They had been accepted in a meeting and then not assigned. The audit report had felt like progress because it was long.
Structure the second run so it starts from the previous finding list, not from a blank template. For each old finding, record closed, still open, or no longer applicable, with the evidence. Then run the exports again for new defects. A report that only describes the current state, with no reference to last time, cannot answer the only question leadership has: are we getting tighter or not.
Keep the exports identical in shape so they can be diffed. Owner count, guest count, label, last activity, and effective policy name, one row per team, is enough for the team-level half. Policy settings belong in a second sheet, one row per policy per setting you care about. Changing the columns between runs makes the comparison a manual reading exercise, and manual reading is where findings get softened.
Severity should stay stable too. If a team with no owner was critical in spring, it is critical in autumn. Reclassifying it as medium because there are fewer of them hides the ones that remain. Counts can improve while the definition stays put.
Close the loop in the same quarter. An audit that delivers findings in week one and reviews them in week eight, with names against the critical rows only, will outperform an audit that delivers a forty-page pack and a promise to "take it away". The pack can still exist. It is not the control. The dated closure is the control.
Store both runs' exports. A finding that cannot be traced to a row will be argued rather than fixed. Separate defects in integrations from defects in human-created teams. They have different owners. If a finding is accepted as a risk, write the acceptance, the role, and the review date. Silence is not acceptance. Do not expand the checklist every run. Add an item only when a real miss has shown the current list is blind to it. Sample teams that look healthy as well as teams that look broken. A label that is applied and wrong is worse than a missing label, and a missing-label query will not see it. Give the next auditor the queries. An audit that lives in someone's console history will not be repeated faithfully. Count open critical findings on one line at the top. The narrative can follow. The line is what gets read. A policy that matches the standard and is assigned to nobody is not a pass. Note it as unused, and decide whether to keep it. Ownerless teams that hold a label for sensitive work outrank ownerless teams that are empty. Let the report say that. Schedule the next run before the current one is presented. A cadence that depends on spare time will slip. The second run should take less time than the first. If it does not, the exports are still too manual. Findings accepted as risk should reappear on the next report automatically, or acceptance becomes a way to delete them. A new integration since the last run is a section of its own, even if the rest of the tenant improved. Critical findings open for two runs in a row are the first slide, ahead of anything that improved. Do not relabel severity to make the trend look better. Change the tenant or leave the label. The queries and the report template belong in the same folder as the exports. Sample size should be written down. A review of twenty teams is not a review of the tenant, and it should not be titled as one. Owner names on findings should be roles where possible, so the finding survives a staff change.