forked from 12Knocksinna/Office365itpros
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ReportDLMembershipsCountsGraph.PS1
219 lines (189 loc) · 9.74 KB
/
ReportDLMembershipsCountsGraph.PS1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# ReportDLMembershipsCountsGraph.PS1
# Report the membership and counts for distribution lists in Exchange Online - the Graph version. The pure PowerShell version is in
# https://github.com/12Knocksinna/Office365itpros/blob/master/ReportDLMembershipsCountsGraph.PS1
#
# The registered app needs Directory.Read.All, Group.Read.All, User.Read.All application permissions.
function Get-GraphData {
# Based on https://danielchronlund.com/2018/11/19/fetch-data-from-microsoft-graph-with-powershell-paging-support/
# GET data from Microsoft Graph.
param (
[parameter(Mandatory = $true)]
$AccessToken,
[parameter(Mandatory = $true)]
$Uri
)
# Check if authentication was successful.
if ($AccessToken) {
$Headers = @{
'Content-Type' = "application\json"
'Authorization' = "Bearer $AccessToken"
'ConsistencyLevel' = "eventual" }
# Create an empty array to store the result.
$QueryResults = @()
# Invoke REST method and fetch data until there are no pages left.
do {
$Results = ""
$StatusCode = ""
do {
try {
$Results = Invoke-RestMethod -Headers $Headers -Uri $Uri -UseBasicParsing -Method "GET" -ContentType "application/json"
$StatusCode = $Results.StatusCode
} catch {
$StatusCode = $_.Exception.Response.StatusCode.value__
if ($StatusCode -eq 429) {
Write-Warning "Got throttled by Microsoft. Sleeping for 45 seconds..."
Start-Sleep -Seconds 45
}
else {
Write-Error $_.Exception
}
}
} while ($StatusCode -eq 429)
if ($Results.value) {
$QueryResults += $Results.value
}
else {
$QueryResults += $Results
}
$uri = $Results.'@odata.nextlink'
} until (!($uri))
# Return the result.
$QueryResults
}
else {
Write-Error "No Access Token"
}
}
$ModulesLoaded = Get-Module | Select Name
If (!($ModulesLoaded -match "ExchangeOnlineManagement")) {Write-Host "Please connect to the Exchange Online Management module and then restart the script"; break}
# OK, we seem to be fully connected to Exchange Online.
# Define all the stuff necessary to use a registered app to interact with the Graph APIs
$AppId = "87c31534-ca1f-4d46-959a-6159fcb2f77a"
$TenantId = "a662313f-14fc-43a2-9a7a-d2e27f4f3478"
$AppSecret = "7xP4Nj~kiU.yBXY9~yQB3sMrvpLv5Rx_._"
# Construct URI and body needed for authentication
$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
$body = @{
client_id = $AppId
scope = "https://graph.microsoft.com/.default"
client_secret = $AppSecret
grant_type = "client_credentials"
}
# Get OAuth 2.0 Token
$tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing
# Unpack Access Token
$token = ($tokenRequest.Content | ConvertFrom-Json).access_token
$Headers = @{
'Content-Type' = "application\json"
'Authorization' = "Bearer $Token"
'ConsistencyLevel' = "eventual" }
Write-Host "Finding Exchange Online Distribution Lists..."
# Find distribution lists - excluding room lists
[array]DLs = Get-DistributionGroup -RecipientTypeDetails "MailUniversalDistributionGroup", "MailUniversalSecurityGroup" -ResultSize Unlimited
# This is the Graph method -includes security groups, but it's faster - use it if you like
# $Uri = "https://graph.microsoft.com/V1.0/groups?`$filter=Mailenabled eq true AND NOT groupTypes/any(c:c+eq+'Unified')&`$count=true"
# [array]$DLs = Get-GraphData -AccessToken $Token -Uri $uri
If (!($DLs)) { Write-Host "No distribution lists found... sorry! "; break }
Else { Write-Host ("{0} distribution lists found" -f $DLs.count) }
CLS; $Report = [System.Collections.Generic.List[Object]]::new()
# Loop down through each DL and fetch the membership using the Graph transitivemembers call to return a complete set.
$DLNumber = 0
ForEach ($DL in $DLs) {
$DLNumber++
$ProgressBar = "Processing distribution list " + $DL.DisplayName + " (" + $DLNumber + " of " + $DLs.Count + ")"
Write-Progress -Activity "Analzying membership of distribution list " -Status $ProgressBar -PercentComplete ($DLNumber/$DLs.Count*100)
# Retrieve transitive membership for the distribution list
$Uri = "https://graph.microsoft.com/v1.0/groups/" + $DL.ExternalDirectoryObjectId + "/transitiveMembers"
[array]$Members = Get-GraphData -AccessToken $Token -Uri $uri
$CountContacts = 0; $CountTenantMembers = 0; $CountGuests = 0; $CountGroups = 0
$MembersNames = [System.Collections.Generic.List[Object]]::new()
$CountOfMembers = $Members.Count
# Loop through each member and figure out what type of member they are and their display name
ForEach ($Member in $Members) {
Switch ($Member."@odata.type") {
"#microsoft.graph.orgContact" { # Mail contact
$MemberDisplayName = $Member.DisplayName
$CountContacts++ }
"#microsoft.graph.user" { # Tenant user (including guests
$MemberDisplayName = $Member.DisplayName
If ($Member.UserPrincipalName -Like "*#EXT#*") { $CountGuests++ }
Else { $CountTenantMembers++ }
}
"#microsoft.graph.group" { #Another group
$MemberDisplayName = $Member.DisplayName
$CountGroups++ }
} #End Switch
# Update member table
$MemberData = [PSCustomObject][Ordered]@{
MemberName = $MemberDisplayName
MemberId = $Member.Id }
$MembersNames.Add($MemberData)
} #End Foreach
# Remove any duplicates and sort the member list alphabetically
$MembersNames = $MembersNames | Sort MemberId -Unique | Sort MemberName
$OutputNames = $MembersNames.MemberName -join ", "
# Figure out DL manager names - can't be done using the Graph at present
$ManagerList = [System.Collections.Generic.List[Object]]::new()
ForEach ($Manager in $DL.ManagedBy) {
$Recipient = Get-Recipient -Identity $Manager -ErrorAction SilentlyContinue
If (!($Recipient)) { # Can't resolve manager
$Recipient = "Unknown user" }
$ManagerLine = [PSCustomObject][Ordered]@{
DisplayName = $Recipient.DisplayName
UPN = $Recipient.WIndowsLiveID }
$ManagerList.Add($ManagerLine)
} # End processing managers
$Managers = $ManagerList.DisplayName -join ", "
$ReportLine = [PSCustomObject][Ordered]@{
DLName = $DL.DisplayName
ManagedBy = $Managers
"Members" = $CountOfMembers
"Tenant Users" = $CountTenantMembers
"Groups" = $CountGroups
"Guest members" = $CountGuests
"Mail contacts" = $CountContacts
"Member names" = $OutputNames }
$Report.Add($ReportLine)
} # End processing DLs
# Create output files
$DLCSVOutput = "c:\temp\DLData.CSV"
$ReportFile = "c:\temp\DLData.html"
# Create the HTML report
$OrgName = (Get-OrganizationConfig).Name
$CreationDate = Get-Date -format g
$Version = "1.0"
$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>Distribution List Manager Report</h1></p>
<p><h2><b>For the " + $Orgname + " organization</b></h2></p>
<p><h3>Generated: " + (Get-Date -format g) + "</h3></p></div>"
$htmlbody1 = $Report | ConvertTo-Html -Fragment
$htmltail = "<p>Report created for: " + $OrgName + "</p>" +
"<p>Created: " + $CreationDate + "<p>" +
"<p>-----------------------------------------------------------------------------------------------------------------------------</p>"+
"<p>Number of distribution lists found: " + $DLs.Count + "</p>" +
"<p>-----------------------------------------------------------------------------------------------------------------------------</p>"+
"<p>Distribution List Manager Report<b> " + $Version + "</b>"
$htmlreport = $htmlhead + $htmlbody1 + $htmltail
$htmlreport | Out-File $ReportFile -Encoding UTF8
Write-Host ("All done. {0} distribution lists analyzed. CSV file is available at {1} and a HTML report at {2}" -f $DLs.Count, $DLCSVOutput, $ReportFile)
$Report | Out-GridView
$Report | Export-CSV -NoTypeInformation $DLCSVOutput
# An example script used to illustrate a concept. More information about the topic can be found in the Office 365 for IT Pros eBook https://gum.co/O365IT/
# and/or a relevant article on https://office365itpros.com or https://www.practical365.com. See our post about the Office 365 for IT Pros repository # https://office365itpros.com/office-365-github-repository/ for information about the scripts we write.
# Do not use our scripts in production until you are satisfied that the code meets the need of your organization. Never run any code downloaded from the Internet without
# first validating the code in a non-production environment.