Entra / Microsoft 365 · Groups
Report M365 group memberships
A script to report the membership of all Microsoft 365 Groups in a tenant.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -Scope Organization.Read.All, GroupMember.Read.All -NoWelcome
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
Clear-HostConnect-MgGraph -Scope Organization.Read.All, GroupMember.Read.All -NoWelcome$OrgName = (Get-MgOrganization).displayName$Version = "2.0"$ReportFile = "c:\temp\M365MembersReport.html"$CSVFileSummary = "c:\temp\M365MembersSummaryReport.csv"$CSVFileMembers = "c:\temp\M365MembersReport.csv"$MemberList = [System.Collections.Generic.List[Object]]::new()$SummaryData = [System.Collections.Generic.List[Object]]::new()$CreationDate = Get-Date -format gClear-HostWrite-Host "Fetching user information from Entra ID..."[array]$Users = Get-MgUser -Filter "assignedLicenses/`$count ne 0 and userType eq 'Member'" -ConsistencyLevel eventual `-CountVariable Records -All -PageSize 500If (!($Users)) {Write-Host "Can't get user information from Entra ID - exiting" ; break}$Users = $Users | Sort-Object DisplayName$S1 = Get-Date# Get a list of Teams and put them into a hash table so that we can mark the groups we process as being team-enabled[array]$Teams = Get-MgTeam -All$TeamsHash = @{}$Teams.ForEach( {$TeamsHash.Add($_.Id, $_.DisplayName) } )Clear-Host# Set up progress bar$ProgDelta = 100/($Users.Count); $CheckCount = 0; $UserNumber = 0ForEach ($User in $Users) {$UserNumber++$UserStatus = $User.DisplayName + " ["+ $UserNumber +"/" + $Users.Count + "]"Write-Progress -Activity "Checking groups for user" -Status $UserStatus -PercentComplete $CheckCount$CheckCount += $ProgDelta$UserType = "Tenant user"# Find any groups for the user[array]$Groups = Get-MgUserMemberOfAsGroup -UserId $User.Id -AllIf ($Groups) { # We found some groups for this recipient - process them[string]$AllGroups = $Groups.displayName -join ", "ForEach ($Group in $Groups) {[array]$Owners = $null # Get group owners[array]$Owners = Get-MgGroupOwner -GroupId $Group | Select-Object -ExpandProperty AdditionalProperties$OwnersOutput = $Owners.displayName -join ", "$GroupOwnersEmail = $Owners.mail -join ", "If ($TeamsHash[$Group]) {$GroupName = $Group.DisplayName + " (** Team **)"} Else {$GroupName = $Group.DisplayName}$MemberLine = [PSCustomObject][Ordered]@{ # Write out details of the group"User" = $User.DisplayNameUPN = $User.UserPrincipalName"User type" = $User.UserType"Group Name" = $GroupName"Group Description" = $Group.description"Group Email" = $Group.mail"Group Owners" = $OwnersOutput"Owners Email" = $GroupOwnersEmail}$MemberList.Add($MemberLine)}$SummaryLine = [PSCustomObject][Ordered]@{ # Write out summary record for the user"User" = $User.DisplayNameUPN = $User.UserPrincipalName"User type" = $User.UserType"Groups count" = $Groups.count"Member Of" = $AllGroups}$SummaryData.Add($SummaryLine)} Else { #No groups found for this user, so just write a summary record$SummaryLine = [PSCustomObject][Ordered]@{"User" = $User.DisplayNameUPN = $User.UserPrincipalName"User type" = $UserType"Groups count" = 0"Member Of" = "No groups found for user"}$SummaryData.Add($SummaryLine)}} #End For$SummaryData = $SummaryData | Sort-Object "Groups Count" -Descending$GCount = $MemberList | Select-Object "Group Email" | Sort-Object "Group EMail" -Unique$UsersNoGroups = ($SummaryData | Where-Object {$_."Groups Count" -eq 0}).Count$UsersWithGroups = ($SummaryData.Count - $UsersNoGroups)$S2 = Get-Date$TotalSeconds = [math]::round(($S2-$S1).TotalSeconds,2)$SecondsPerUser = [math]::round(($TotalSeconds/$Users.count),2)# Create the HTML report$htmlhead="<html><style>BODY{font-family: Arial; font-size: 8pt;}H1{font-size: 22px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}H2{font-size: 18px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}H3{font-size: 16px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}TABLE{border: 1px solid black; border-collapse: collapse; font-size: 8pt;}TH{border: 1px solid #969595; background: #dddddd; padding: 5px; color: #000000;}TD{border: 1px solid #969595; padding: 5px; }td.pass{background: #B7EB83;}td.warn{background: #FFF275;}td.fail{background: #FF2626; color: #ffffff;}td.info{background: #85D4FF;}</style><body><div align=center><p><h1>Microsoft 365 Groups and Teams Membership Report</h1></p><p><h2><b>Microsoft 365 Groups in the " + $Orgname + " organization</b></h2></p><p><h3>Generated: " + (Get-Date -format g) + "</h3></p></div>"$htmlbody1 = $MemberList | ConvertTo-Html -Fragment$htmlbody1 = $htmlbody1 + '<div class="page-break"></div>'$htmlbody2 = $SummaryData | ConvertTo-Html -Fragment$htmltail = "<p>Report created for: " + $OrgName + "</p>" +"<p>Created: " + $CreationDate + "<p>" +"<p>-----------------------------------------------------------------------------------------------------------------------------</p>"+"<p>Number of users in groups: " + $UsersWithGroups + "</p>" +"<p>Number of users not in groups: " + $UsersNoGroups + "<p>"+"<p>Number of Microsoft 365 Groups: " + $GCount.Count + "</p>" +"<p>Number of Microsoft Teams: " + $Teams.Count + "</p>" +"<p>-----------------------------------------------------------------------------------------------------------------------------</p>"+"<p>Microsoft 365 Group Membership Report <b>" + $Version + "</b>"$htmlreport = $htmlhead + $htmlbody1 + "<p><p>" + $htmlbody2 + $htmltail$htmlreport | Out-File $ReportFile -Encoding UTF8$MemberList | Export-CSV -NoTypeInformation $CSVFileMembers$SummaryData | Export-CSV -NoTypeInformation $CSVFileSummaryClear-HostWrite-Host "Microsoft 365 Group Membership Report - Job Complete"Write-Host "----------------------------------------------------"Write-Host " "Write-Host "Outputs:"Write-Host "--------"Write-Host "HTML report available in" $ReportFileWrite-Host " "Write-Host "Contains all the data generated by the script."Write-Host " "Write-Host "CSV file for members in groups available in" $CSVFileMembersWrite-Host " "Write-Host "Lists details of group membership for individual user accounts."Write-Host " "Write-Host "CSV summary report available in" $CSVFileSummaryWrite-Host " "Write-Host "Summarizes the groups that users belong to."Write-Host " "Write-Host ("Total processing time {0} seconds ({1} seconds per user) for {2} user accounts and {3} Microsoft 365 Groups" -f $TotalSeconds, $SecondsPerUser, $Users.Count, $Gcount.count)
Attribution
Author
Office365itpros