Skip to content

Commit ce13f68

Browse files
committed
refactors the aad ensure owners are members m365 groups script
1 parent 28621f2 commit ce13f68

File tree

2 files changed

+97
-35
lines changed

2 files changed

+97
-35
lines changed

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

Lines changed: 88 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -49,44 +49,99 @@ $m365GroupCollection | sort-object "Site Name" | Export-CSV $OutPutView -Force -
4949
# [CLI for Microsoft 365](#tab/cli-m365-ps)
5050

5151
```powershell
52-
$m365Status = m365 status
53-
if ($m365Status -match "Logged Out") {
54-
m365 login
52+
[CmdletBinding()]
53+
param(
54+
[Parameter(HelpMessage = "Directory path where the CSV report will be stored.")]
55+
[string]$OutputDirectory,
56+
57+
[Parameter(HelpMessage = "Optional custom file name (with or without .csv) for the owners-not-members report.")]
58+
[string]$ReportFileName
59+
)
60+
61+
begin {
62+
m365 login --ensure
63+
64+
if (-not $OutputDirectory) {
65+
$OutputDirectory = if ($MyInvocation.MyCommand.Path) {
66+
Split-Path -Path $MyInvocation.MyCommand.Path
67+
} else {
68+
(Get-Location).Path
69+
}
70+
}
71+
72+
if (-not (Test-Path -Path $OutputDirectory -PathType Container)) {
73+
New-Item -ItemType Directory -Path $OutputDirectory -Force | Out-Null
74+
}
75+
76+
if (-not $ReportFileName) {
77+
$ReportFileName = 'm365OwnersNotMembers-{0}.csv' -f (Get-Date -Format 'yyyyMMdd-HHmmss')
78+
} elseif (-not $ReportFileName.EndsWith('.csv')) {
79+
$ReportFileName = "$ReportFileName.csv"
80+
}
81+
82+
$script:ReportPath = Join-Path -Path $OutputDirectory -ChildPath $ReportFileName
83+
$script:ReportItems = [System.Collections.Generic.List[psobject]]::new()
84+
$script:Summary = [ordered]@{
85+
GroupsEvaluated = 0
86+
OwnersAdded = 0
87+
OwnersFailed = 0
88+
}
89+
90+
Write-Host "Starting owner membership audit..."
91+
Write-Host "Report will be saved to $ReportPath"
5592
}
5693
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";
94+
process {
95+
$sites = m365 spo site list --query "[?Template == 'GROUP#0' && Template != 'RedirectSite#0'].{GroupId:GroupId, Url:Url, Title:Title}" --output json | ConvertFrom-Json
96+
97+
foreach ($site in $sites) {
98+
$Summary.GroupsEvaluated++
99+
Write-Host "Processing group '$($site.Title)' ($($site.Url))"
100+
101+
$groupId = $site.GroupId -replace "/Guid\((.*)\)/", '$1'
102+
$owners = m365 entra m365group user list --role Owner --groupId $groupId --output json | ConvertFrom-Json
103+
104+
foreach ($owner in $owners) {
105+
$ownerDisplayName = $owner.displayName
106+
$isMember = m365 entra m365group user list --role Member --groupId $groupId --query "[?displayName == '$ownerDisplayName']" --output json | ConvertFrom-Json
107+
108+
if (-not $isMember) {
109+
Write-Host " Owner '$ownerDisplayName' missing from members, attempting to add..."
110+
111+
$ReportItems.Add([pscustomobject]@{
112+
'Site Name' = $site.Title
113+
'Site URL' = $site.Url
114+
'Owner Name' = $ownerDisplayName
115+
})
116+
117+
$addResult = m365 entra m365group user add --role Member --groupId $groupId --userName $owner.userPrincipalName --output json 2>&1
118+
119+
if ($LASTEXITCODE -ne 0) {
120+
Write-Warning "Failed to add $ownerDisplayName as member in $($site.Url). CLI returned: $addResult"
121+
$Summary.OwnersFailed++
122+
continue
123+
}
124+
125+
Write-Host " Added $ownerDisplayName as member in $($site.Url)"
126+
$Summary.OwnersAdded++
127+
} else {
128+
Write-Host " Owner '$ownerDisplayName' already a member; skipping"
129+
}
82130
}
83131
}
84132
}
85-
# Export the result array to CSV file
86-
$m365GroupCollection | sort-object "Site Name" | Export-CSV $OutPutView -Force -NoTypeInformation
87133
88-
#Disconnect SharePoint online connection
89-
m365 logout
134+
end {
135+
if ($ReportItems.Count -gt 0) {
136+
$ReportItems | Sort-Object 'Site Name' | Export-Csv -Path $ReportPath -NoTypeInformation -Force
137+
Write-Host "Report exported to $ReportPath"
138+
} else {
139+
Write-Host "No discrepancies detected across the evaluated groups."
140+
}
141+
142+
Write-Host ("Summary: {0} groups checked, {1} owners added as members, {2} owners failed to add." -f $Summary.GroupsEvaluated, $Summary.OwnersAdded, $Summary.OwnersFailed)
143+
}
144+
90145
```
91146

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

107163

108164
[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)]

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/45694692?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)