Skip to content

Commit d4c46c6

Browse files
authored
Merge pull request #887 from Adam-it/refactors-aad-ensure-ownersaremembers-m365groups
Refactors aad ensure ownersaremembers m365groups
2 parents 79c1eac + dd0ffc4 commit d4c46c6

File tree

3 files changed

+101
-35
lines changed

3 files changed

+101
-35
lines changed

scripts/aad-ensure-ownersaremembers-m365groups/README.md

Lines changed: 92 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
It may happen that owners are not members of the m365 group because of the various methods of managing M365 group permissions, such as through the Teams admin center, Microsoft Teams, SharePoint admin center, SharePoint connected sites, Planner, or scripting using PowerShell. The script will help identify these discrepancies and ensures m365 group owners are also m365 group members.
88

9+
CLI for Microsoft 365 script sample usage example:
10+
11+
![Example Screenshot of CLI for Microsoft 365 sample](assets/exampleCLI.png)
12+
913
# [PnP PowerShell](#tab/pnpps)
1014

1115
```powershell
@@ -49,44 +53,99 @@ $m365GroupCollection | sort-object "Site Name" | Export-CSV $OutPutView -Force -
4953
# [CLI for Microsoft 365](#tab/cli-m365-ps)
5054

5155
```powershell
52-
$m365Status = m365 status
53-
if ($m365Status -match "Logged Out") {
54-
m365 login
56+
[CmdletBinding()]
57+
param(
58+
[Parameter(HelpMessage = "Directory path where the CSV report will be stored.")]
59+
[string]$OutputDirectory,
60+
61+
[Parameter(HelpMessage = "Optional custom file name (with or without .csv) for the owners-not-members report.")]
62+
[string]$ReportFileName
63+
)
64+
65+
begin {
66+
m365 login --ensure
67+
68+
if (-not $OutputDirectory) {
69+
$OutputDirectory = if ($MyInvocation.MyCommand.Path) {
70+
Split-Path -Path $MyInvocation.MyCommand.Path
71+
} else {
72+
(Get-Location).Path
73+
}
74+
}
75+
76+
if (-not (Test-Path -Path $OutputDirectory -PathType Container)) {
77+
New-Item -ItemType Directory -Path $OutputDirectory -Force | Out-Null
78+
}
79+
80+
if (-not $ReportFileName) {
81+
$ReportFileName = 'm365OwnersNotMembers-{0}.csv' -f (Get-Date -Format 'yyyyMMdd-HHmmss')
82+
} elseif (-not $ReportFileName.EndsWith('.csv')) {
83+
$ReportFileName = "$ReportFileName.csv"
84+
}
85+
86+
$script:ReportPath = Join-Path -Path $OutputDirectory -ChildPath $ReportFileName
87+
$script:ReportItems = [System.Collections.Generic.List[psobject]]::new()
88+
$script:Summary = [ordered]@{
89+
GroupsEvaluated = 0
90+
OwnersAdded = 0
91+
OwnersFailed = 0
92+
}
93+
94+
Write-Host "Starting owner membership audit..."
95+
Write-Host "Report will be saved to $ReportPath"
5596
}
5697
57-
$dateTime = (Get-Date).toString("dd-MM-yyyy")
58-
$invocation = (Get-Variable MyInvocation).Value
59-
$directorypath = Split-Path $invocation.MyCommand.Path
60-
$fileName = "m365OwnersNotMembers-" + $dateTime + ".csv"
61-
$OutPutView = $directorypath + "\" + $fileName
62-
# Array to Hold Result - PSObjects
63-
$m365GroupCollection = @()
64-
#Write-host $"$ownerName not part of member in $siteUrl";
65-
$m365Sites = m365 spo site list --query "[?Template == 'GROUP#0' && Template != 'RedirectSite#0'].{GroupId:GroupId, Url:Url, Title:Title}" --output json | ConvertFrom-Json
66-
$m365Sites | ForEach-Object {
67-
$groupId = $_.GroupId -replace "/Guid\((.*)\)/",'$1';
68-
$siteUrl = $_.Url;
69-
$siteName = $_.Title
70-
#if owner is not part of m365 group member
71-
(m365 entra m365group user list --role Owner --groupId $groupId --output json | ConvertFrom-Json) | foreach-object {
72-
$owner = $_;
73-
$ownerDisplayName = $owner.displayName
74-
if (!(m365 entra m365group user list --role Member --groupId $groupId --query "[?displayName == '$ownerDisplayName']" --output json | ConvertFrom-Json)) {
75-
$ExportVw = New-Object PSObject
76-
$ExportVw | Add-Member -MemberType NoteProperty -name "Site Name" -value $siteName
77-
$ExportVw | Add-Member -MemberType NoteProperty -name "Site URL" -value $siteUrl
78-
$ExportVw | Add-Member -MemberType NoteProperty -name "Owner Name" -value $ownerDisplayName
79-
$m365GroupCollection += $ExportVw
80-
m365 entra m365group user add --role Owner --groupId $groupId --userName $owner.userPrincipalName
81-
Write-host "$ownerDisplayName has been added as member in $siteUrl";
98+
process {
99+
$sites = m365 spo site list --query "[?Template == 'GROUP#0' && Template != 'RedirectSite#0'].{GroupId:GroupId, Url:Url, Title:Title}" --output json | ConvertFrom-Json
100+
101+
foreach ($site in $sites) {
102+
$Summary.GroupsEvaluated++
103+
Write-Host "Processing group '$($site.Title)' ($($site.Url))"
104+
105+
$groupId = $site.GroupId -replace "/Guid\((.*)\)/", '$1'
106+
$owners = m365 entra m365group user list --role Owner --groupId $groupId --output json | ConvertFrom-Json
107+
108+
foreach ($owner in $owners) {
109+
$ownerUserPrincipalName = $owner.userPrincipalName
110+
$isMember = m365 entra m365group user list --role Member --groupId $groupId --query "[?userPrincipalName == '$ownerUserPrincipalName']" --output json | ConvertFrom-Json
111+
112+
if (-not $isMember) {
113+
Write-Host " Owner '$ownerUserPrincipalName' missing from members, attempting to add..."
114+
115+
$ReportItems.Add([pscustomobject]@{
116+
'Site Name' = $site.Title
117+
'Site URL' = $site.Url
118+
'Owner Name' = $ownerUserPrincipalName
119+
})
120+
121+
$addResult = m365 entra m365group user add --role Member --groupId $groupId --userNames $ownerUserPrincipalName --output json 2>&1
122+
123+
if ($LASTEXITCODE -ne 0) {
124+
Write-Warning "Failed to add $ownerUserPrincipalName as member in $($site.Url). CLI returned: $addResult"
125+
$Summary.OwnersFailed++
126+
continue
127+
}
128+
129+
Write-Host " Added $ownerUserPrincipalName as member in $($site.Url)"
130+
$Summary.OwnersAdded++
131+
} else {
132+
Write-Host " Owner '$ownerUserPrincipalName' already a member; skipping"
133+
}
82134
}
83135
}
84136
}
85-
# Export the result array to CSV file
86-
$m365GroupCollection | sort-object "Site Name" | Export-CSV $OutPutView -Force -NoTypeInformation
87137
88-
#Disconnect SharePoint online connection
89-
m365 logout
138+
end {
139+
if ($ReportItems.Count -gt 0) {
140+
$ReportItems | Sort-Object 'Site Name' | Export-Csv -Path $ReportPath -NoTypeInformation -Force
141+
Write-Host "Report exported to $ReportPath"
142+
} else {
143+
Write-Host "No discrepancies detected across the evaluated groups."
144+
}
145+
146+
Write-Host ("Summary: {0} groups checked, {1} owners added as members, {2} owners failed to add." -f $Summary.GroupsEvaluated, $Summary.OwnersAdded, $Summary.OwnersFailed)
147+
}
148+
90149
```
91150

92151
[!INCLUDE [More about CLI for Microsoft 365](../../docfx/includes/MORE-CLIM365.md)]
@@ -103,6 +162,7 @@ Sample first appeared on [Ensuring Owners Are Members](https://reshmeeauckloo.co
103162
| ----------------------------------------- |
104163
| [Reshmee Auckloo (Main author)](https://github.com/reshmee011) |
105164
| [Michał Kornet (CLI for M365 version)](https://github.com/mkm17) |
165+
| Adam Wójcik |
106166

107167

108168
[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)]
227 KB
Loading

scripts/aad-ensure-ownersaremembers-m365groups/assets/sample.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"title": "Ensuring m365 group owners are m365 group members",
88
"url": "https://pnp.github.io/script-samples/aad-ensure-ownersaremembers-m365groups/README.html",
99
"creationDateTime": "2023-10-29",
10-
"updateDateTime": "2024-06-14",
10+
"updateDateTime": "2025-11-11",
1111
"shortDescription": "Ensuring m365 group owners are m365 group members",
1212
"longDescription": ["M365 group owners are not always m365 group members because of the various methods of managing M365 group permissions, such as through the Teams admin center, Microsoft Teams, SharePoint admin center, SharePoint connected sites, Planner, or scripting using PowerShell. The script will help identify these discrepancies and ensures m365 group owners are also m365 group members."],
1313
"products": [
@@ -37,7 +37,7 @@
3737
},
3838
{
3939
"key": "CLI-FOR-MICROSOFT365",
40-
"value": "7.7.0"
40+
"value": "11.0.0"
4141
}
4242
],
4343
"thumbnails": [
@@ -61,6 +61,12 @@
6161
"company": "",
6262
"pictureUrl": "https://avatars.githubusercontent.com/u/7693852?v=4",
6363
"name": "Reshmee Auckloo"
64+
},
65+
{
66+
"gitHubAccount": "Adam-it",
67+
"company": "",
68+
"pictureUrl": "https://avatars.githubusercontent.com/u/58668583?v=4",
69+
"name": "Adam Wójcik"
6470
}
6571
],
6672
"references": [
@@ -73,7 +79,7 @@
7379
"name": "Want to learn more about CLI for Microsoft 365 and the commands",
7480
"description": "Check out the CLI for Microsoft 365 site to get started and for the reference to the commands.",
7581
"url": "https://aka.ms/cli-m365"
76-
},
82+
}
7783
]
7884
}
7985
]

0 commit comments

Comments
 (0)