-
Notifications
You must be signed in to change notification settings - Fork 0
/
Get-AllAddresses.ps1
553 lines (452 loc) · 22.3 KB
/
Get-AllAddresses.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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
<#
.SYNOPSIS
Get-AllAddresses
.DESCRIPTION
PowerShell Script to read all addresses (including aliases) from all entities
(regular mailbox, shared mailbox, mail enabled public folder, contact,
unified group, distribution group, security group, ...)
there are in a microsoft 365 tenant.
.INPUTS
None. You cannot pipe objects to Get-AllAddresses.
.OUTPUTS
None/interactive.
.NOTES
Author: Michael Schoenburg
This projects code loosely follows the PowerShell Practice and Style guide, as well as Microsofts PowerShell scripting performance considerations.
Style guide: https://poshcode.gitbook.io/powershell-practice-and-style/
Performance Considerations: https://docs.microsoft.com/en-us/powershell/scripting/dev-cross-plat/performance/script-authoring-considerations?view=powershell-7.1
.LINK
https://github.com/MichaelSchoenburg/Get-AllAddresses
#>
#-----------------------------------------------------------------------------------------------------------------
#region RULES
# Rules/Conventions:
# # This is a comment related to the following command
# <#
# This is a comment related to the next couple commands (most times a caption for a section)
# #>
# "This is a Text containing $( $variable(s) )"
# 'This is a text without variable(s)'
# Variables available in the entire script start with a capital letter
# $ThisIsAGlobalVariable
# Variables available only locally e. g. in a function start with a lower case letter
# $thisIsALocalVariable
# Assign output to $null to suppress it. E. g. $null = [System.Reflection.Assembly]::LoadWithPartialName( "System.Windows.Forms" )
#endregion RULES
#-----------------------------------------------------------------------------------------------------------------
#region INITIALIZATION
using namespace System.Management.Automation.Host
param (
[Alias( 'Lang','L' )]
[Parameter(
Mandatory = $false,
Position = 1
)]
[ValidateSet( 'DE','EN' )]
[string]
$global:Language
)
Add-Type -AssemblyName System.Windows.Forms
#endregion INITIALIZATION
#-----------------------------------------------------------------------------------------------------------------
#region DECLARATIONS
if (-not ( $global:Language )) {
switch ( $env:LANG ) {
'de_DE.UTF-8' { $global:Language = 'DE' }
Default { $global:Language = 'EN' }
}
}
switch ( $global:Language ) {
# German translations
'DE' {
$TConnecting = 'Verbinde zu'
$TDataBeingPrepared = "Bitte haben Sie einen Moment Geduld. Ihre Daten werden gesammelt und aufbereitet."
$TProcessing = 'Verarbeite'
$TType = "Typ"
$TSharedMailbox = "Freigegebenes Postfach"
$TUserMailbox = "Regulaeres Postfach"
$TMailUniversalDistributionGroup = "Verteiler"
$TGroupMailbox = "Office 365-Gruppe"
$TMailContact = "Kontakt"
$TDiscoveryMailbox = "Dienstkonto"
$TDynamicDistributionGroup = "Dynamischer Verteiler"
$TEquipmentMailbox = "Equipment-Postfach"
$TGuestMailUser = "Gast"
$TLegacyMailbox = "Legacy-Postfach"
$TLinkedMailbox = "Linked-Postfach"
$TLinkedRoomMailbox = "Linked-Raum-Postfach"
$TMailForestContact = "E-Mail-Stamm-Kontakt"
$TMailNonUniversalGroup = "Nicht universelle Gruppe"
$TMailUniversalSecurityGroup = "Universelle Sicherheits-Gruppe"
$TMailUser = "E-Mail-Benutzer"
$TPublicFolder = "Oeffentlicher Ordner"
$TPublicFolderMailbox = "Oeffentlicher Ordner (E-Mail-Aktiviert)"
$TRemoteEquipmentMailbox = "Remote-Equipment-Postfach"
$TRemoteRoomMailbox = "Remote-Raum-Postfach"
$TRemoteSharedMailbox = "Remote-Freigegebenes Postfach"
$TRemoteTeamMailbox = "Remote-Team-Postfach"
$TRemoteUserMailbox = "Remote-Benutzer-Postfach"
$TRoomList = "Raumliste"
$TRoomMailbox = "Raum-Postfach"
$TSchedulingMailbox = "Planungspostfach"
$TTeamMailbox = "Team-Postfach"
$TDisplayName = "Anzeigename"
$TPrimarySmtpAddress = "Primaere E-Mail-Adresse"
$TQ1T = 'Anwendungsbereich'
$TQ1 = 'Moechten Sie eine Tabelle aller E-Mail-Adressen aller Kunden oder alle Adressen eines spezifischen Kunden?'
$TQ1A = 'Alle Kunden'
$TQ1B = 'Spezifischer Kunde'
$TH1T = 'Hinweis'
$TH1 = 'Bitte geben Sie im folgenden die Zugangsdaten fuer einen delegierten Administrator an, der Zugriff auf alle Kunden hat.'
$TOpeningOpenFolderDialogue = 'Oeffne browse file dialogue...'
$TSelectAFolder = 'Waehlen Sie einen Ordner aus, in welchem Sie alle CSV-Dateien speichern wollen.'
$TH2T = 'Hinweis'
$TH2 = 'Bitte geben Sie im folgenden die Zugangsdaten fuer einen Administrator des online Exchanges an, von welchem Sie die E-Mail-Adressen auslesen moechten.'
$TQ2T = 'Ausgabeart'
$TQ2 = 'Moechten Sie die E-Mail-Adressen in eine CSV-Datei abspeichern oder direkt in einer interaktiven Tabelle anzeigen lassen, in welcher Sie Filtern, Sortieren und Spalten ein-/ausblenden koennen?'
$TQ2A = 'Tabelle'
$TQ2B = 'CSV-Datei'
}
# English translations
'EN' {
$TConnecting = 'Connecting to'
$TDataBeingPrepared = "Please wait a moment. Your data is being prepared."
$TProcessing = "Processing"
$TType = "Type"
$TSharedMailbox = "Shared Mailbox"
$TUserMailbox = "Regular Mailbox"
$TMailUniversalDistributionGroup = "Universal Distribution Group"
$TGroupMailbox = "Office 365 Group"
$TMailContact = "Contact"
$TDiscoveryMailbox = "Service Mailbox"
$TDynamicDistributionGroup = "Dynamic Distribution Group"
$TEquipmentMailbox = "Equipment Mailbox"
$TGuestMailUser = "Guest"
$TLegacyMailbox = "Legacy Mailbox"
$TLinkedMailbox = "Linked Mailbox"
$TLinkedRoomMailbox = "Linked Room Mailbox"
$TMailForestContact = "Forest Contact"
$TMailNonUniversalGroup = "Non Universal Group"
$TMailUniversalSecurityGroup = "Universal Security Group"
$TMailUser = "Mail User"
$TPublicFolder = "Public Folder"
$TPublicFolderMailbox = "Public Folder (mail activated)"
$TRemoteEquipmentMailbox = "Remote Equipment Mailbox"
$TRemoteRoomMailbox = "Remote Room Mailbox"
$TRemoteSharedMailbox = "Remote Shared Mailbox"
$TRemoteTeamMailbox = "Remote Team Mailbox"
$TRemoteUserMailbox = "Remote User Mailbox"
$TRoomList = "Room List"
$TRoomMailbox = "Room Mailbox"
$TSchedulingMailbox = "Scheduling Mailbox"
$TTeamMailbox = "Team Mailbox"
$TDisplayName = "Displayname"
$TPrimarySmtpAddress = "Primary SMTP Address"
$TQ1T = 'Scope'
$TQ1 = 'Do you need a table of all addresses from all customers or all addresses from one specific customer?'
$TQ1A = 'All Customers'
$TQ1B = 'Specific Customer'
$TH1T = 'Info'
$TH1 = 'Please provide credentials for an administrator account with delegated access to all customers after this.'
$TOpeningOpenFolderDialogue = 'Opening browse folder dialog...'
$TSelectAFolder = 'Select a folder to save all CSV files in'
$TH2T = 'Info'
$TH2 = 'In the next windows please provide credentials for an administrator inside the specific tenant which you want to scan.'
$TQ2T = 'Output type'
$TQ2 = 'Do you want to receive the output as an interactive table (you will be able to sort, filter and search) or in form of a CSV file?'
$TQ2A = 'Table'
$TQ2B = 'CSV file'
}
}
#endregion DECLARATIONS
#-----------------------------------------------------------------------------------------------------------------
#region FUNCTIONS
function New-Menu {
[CmdletBinding()]
param(
[Parameter( Mandatory )]
[ValidateNotNullOrEmpty()]
[string]
$Title,
[Parameter( Mandatory )]
[ValidateNotNullOrEmpty()]
[string]
$Question,
[Parameter( Mandatory )]
[ValidateNotNullOrEmpty()]
[string]
$ChoiceA,
[Parameter( Mandatory )]
[ValidateNotNullOrEmpty()]
[string]
$ChoiceB
)
$a = [ChoiceDescription]::new( "&$( $ChoiceA )", '' )
$b = [ChoiceDescription]::new( "&$( $ChoiceB )", '' )
$options = [ChoiceDescription[]]( $a, $b )
$result = $host.ui.PromptForChoice( $title, $question, $options, 0 )
$result
}
function Write-ConsoleLog {
<#
.SYNOPSIS
Logs an event to the console.
.DESCRIPTION
Writes text to the console with the current date in front of it.
.PARAMETER Text
Event/text to be outputted to the console.
.EXAMPLE
Write-ConsoleLog -Text 'Subscript XYZ called.'
Long form
.EXAMPLE
Log 'Subscript XYZ called.
Short form
#>
[Alias( 'Log' )]
[CmdletBinding()]
param (
[Parameter(
Mandatory,
Position = 0
)]
[string]
$Text
)
<#
Save current VerbosePreference
#>
$VerbosePreferenceBefore = $VerbosePreference
<#
Enable verbose output
#>
# If I changed VerbosePreference at the star to the script
# every function - even those from the Exchange Online module -
# would add their own verbose output
# This way I only have my own "verbose" output
$VerbosePreference = 'Continue'
<#
Write verbose output
#>
# (Verbose output doesn't interfere with function returns)
# If this function was executed outside this script and thus without the
# local variable $global:Language it would still work (but always choose english)
if ( $global:Language -eq 'DE' ) {
Write-Verbose "$( Get-Date -Format "dd'.'MM'.'yyyy HH':'mm':'ss" ) - $( $text )"
} else {
Write-Verbose "$( Get-Date -Format "MM'/'dd'/'yyyy HH':'mm':'ss" ) - $( $text )"
}
<#
Restore current VerbosePreference
#>
$VerbosePreference = $VerbosePreferenceBefore
}
function Get-AllTenantAddresses {
[CmdletBinding( DefaultParameterSetName = 'UseActiveSession' )]
param (
[Parameter(
ParameterSetName = 'UseCustomTenant',
Mandatory,
Position = 1,
HelpMessage = 'The onmicrosoft-domain identifying the custom tenant to connect to.'
)]
[ValidateScript({ $_ -like '*.*' })]
[string]
$TenantId,
[Parameter(
ParameterSetName = 'UseActiveSession',
Position = 1,
HelpMessage = 'Use an already connected session.'
)]
[switch]
$UseActiveSession = $false
)
process {
# Declarations
$finalAddresses = @()
$aliasCounter = @()
if ( -not ( $UseActiveSession ) ) {
try {
<#
Connect to EXO
#>
Log "$( $TConnecting ) $( $TenantId )..."
$null = Connect-ExchangeOnline -DelegatedOrganization $TenantId -ShowBanner:$false
}
catch {
if ( $_.CategoryInfo.Activity -eq 'New-ExoPSSession' ) {
Log "Unable to connect to $( $domain )."
1 # Return error code 1
} else {
2 # Return error code 2
}
}
}
<#
Get all entities present on the EXO
#>
try {
Log $TDataBeingPrepared
$Mbxs = Get-Recipient
# Classify each entity
for ( $i = 0; $i -lt $Mbxs.Count; $i++ ) {
Log "[$( $i + 1 )/$( $Mbxs.Count )] $( $TProcessing ) $( $Mbxs[$i].PrimarySmtpAddress )"
$MbxAdrFiltered = New-Object PSObject
$MbxAdrUnfiltered = Get-Recipient $Mbxs[$i].Identity | Select-Object -ExpandProperty EmailAddresses
$ACount = 0
$EntityType = $Mbxs[$i].RecipientTypeDetails
# Define which type of entity it is
switch ( $EntityType ) {
# Break when correct type was found be cause then the rest of the types don't need to be processed -> speeds up the code
"SharedMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TSharedMailbox; Break }
"UserMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TUserMailbox; Break }
"MailUniversalDistributionGroup" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TMailUniversalDistributionGroup; Break }
"GroupMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TGroupMailbox; Break }
"MailContact" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TMailContact; Break }
"DiscoveryMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TDiscoveryMailbox; Break }
"DynamicDistributionGroup" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TDynamicDistributionGroup; Break }
"EquipmentMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TEquipmentMailbox; Break }
"GuestMailUser" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TGuestMailUser; Break }
"LegacyMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TLegacyMailbox ; Break }
"LinkedMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TLinkedMailbox; Break }
"LinkedRoomMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TLinkedRoomMailbox; Break }
"MailForestContact" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TMailForestContact; Break }
"MailNonUniversalGroup" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TMailNonUniversalGroup; Break }
"MailUniversalSecurityGroup" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TMailUniversalSecurityGroup; Break }
"MailUser" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TMailUser; Break }
"PublicFolder" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TPublicFolder; Break }
"PublicFolderMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TPublicFolderMailbox; Break }
"RemoteEquipmentMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TRemoteEquipmentMailbox; Break }
"RemoteRoomMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TRemoteRoomMailbox; Break }
"RemoteSharedMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TRemoteSharedMailbox; Break }
"RemoteTeamMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TRemoteTeamMailbox; Break }
"RemoteUserMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TRemoteUserMailbox; Break }
"RoomList" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TRoomList; Break }
"RoomMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TRoomMailbox; Break }
"SchedulingMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TSchedulingMailbox; Break }
"TeamMailbox" { $MbxAdrFiltered | Add-Member -type NoteProperty -Name $TType -Value $TTeamMailbox; Break }
}
# Add displayname
$MbxAdrFiltered | Add-Member -type NoteProperty -Name $TDisplayName -Value $Mbxs[$i].DisplayName
# Search for primary SMTP address
$PrimarySmtpAddr = ( $MbxAdrUnfiltered.where({ $_ -clike '*SMTP*' }) )[0]
$MbxAdrFiltered | Add-Member -type NoteProperty -Name 'Primaere E-Mail-Adresse' -Value $PrimarySmtpAddr.Substring(5)
# Search for aliases
$Aliases = $MbxAdrUnfiltered.where({ $_ -clike '*smtp*' })
ForEach ( $e in $Aliases ) {
$ACount++
$MbxAdrFiltered | Add-Member -type NoteProperty -Name "Alias$( $ACount )" -Value $e.Substring(5)
}
$finalAddresses += $MbxAdrFiltered
$aliasCounter += $ACount
}
# Prepare output
$properties = $TType, $TDisplayName, $TPrimarySmtpAddress
# Get the highest count of aliases per entity there is
$aliasCounter = $aliasCounter | Sort-Object -Descending
For ( $i = 1; $i -le $aliasCounter[0]; $i++ ) {
$properties += "Alias$( $i )"
}
# Sort the output
$finalAddresses = $finalAddresses | Sort-Object -Property $TType, $TDisplayName
if ( -not ( $UseActiveSession ) ) {
# Close EXO session(s)
Log "Closing EXO session..."
foreach( $s in ( Get-PSSession ).where({ $_.ComputerName -eq 'outlook.office365.com' }) ){
Remove-PSSession -Id $s.Id
}
}
$finalAddresses # Finally return output
}
catch {
Log 'Error processing entries:'
Log $_.Exception.Message
3 # Return error Code 3
}
}
}
#endregion FUNCTIONS
#-----------------------------------------------------------------------------------------------------------------
#region EXECUTION
# Decide whether to process all customers or just a specific one
$ResultCustomer = New-Menu -Title $TQ1T -Question $TQ1 -ChoiceA $TQ1A -ChoiceB $TQ1B
switch ( $ResultCustomer ) {
0 {
#region ALLCUSTOMERS
<#
Set the location where to save the output
#>
Log $TOpeningOpenFolderDialogue
$null = [System.Reflection.Assembly]::LoadWithPartialName( "System.Windows.Forms" )
$DialogueOpenFolder = New-Object System.Windows.Forms.FolderBrowserDialog
$DialogueOpenFolder.Description = $TSelectAFolder
$DialogueOpenFolder.RootFolder = "MyComputer"
$DialogueOpenFolder.SelectedPath = "$( $HOME )\Desktop" # Initial folder
$DialogueOpenFolder.ShowDialog()
$Folder = $DialogueOpenFolder.SelectedPath
<#
Get all customers tenant IDs
#>
# Console text messages are often overseen when the connection request form appears,
# so I decided to show a Windows Message Form that can not be overseen.
$null = [System.Windows.Forms.MessageBox]::Show(
$TH1, # Text
$TH1T, # Title
[System.Windows.Forms.MessageBoxButtons]::OK, # Button
[System.Windows.Forms.MessageBoxIcon]::Asterisk # Icon
)
Connect-AzureAD
# Get all customers delegated access is set up for
$Domains = (Get-AzureADContract).DefaultDomainName
# Get all addresses for each customer
ForEach( $domain in $Domains ){
$AllAddresses = Get-AllTenantAddresses -TenantId $domain
if ( $AllAddresses.GetType() -eq [Int] ) {
# If something went wrong
Log "Skipping $( $domain )."
} elseif ( $AllAddresses.GetType() -eq [Object[]] ) {
<#
Save output to CSV file
#>
$Path = "$( $Folder )\$( $domain ).csv"
$properties = $TType, $TDisplayName, $TPrimarySmtpAddress
# Export-Csv -InputObject doesn't take Arrays so it has to be solved via pipe
$AllAddresses | Export-Csv -NoTypeInformation -Delimiter ';' -Path $Path -Encoding UTF8
}
}
#endregion ALLCUSTOMERS
}
1 {
#region SPECIFICCUSTOMER
$null = [System.Windows.Forms.MessageBox]::Show(
$TH2, # Text
$TH2T, # Title
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Asterisk
)
Connect-ExchangeOnline -ShowBanner:$false
$ResultOutput = New-Menu -Title $TQ2T -ChoiceA $TQ2A -ChoiceB $TQ2B -Question $TQ2
# Daten erfassen
$finalAddresses = Get-AllTenantAddresses -UseActiveSession
# Ausgabe auf Basis der zuvor gewaehlten Ausgabemethode durchfuehren
switch( $ResultOutput ){
0 {
# GUI
$finalAddresses | Select-Object -Property $Properties | Out-GridView
}
1 {
# CSV
$Dialog = New-Object System.Windows.Forms.SaveFileDialog
$Dialog.Filter = "CSV-Dateien (*.csv)|*.csv|All Files (*.*)|*.*"
$Dialog.ShowDialog()
$Dialog.SupportMultiDottedExtensions = $true;
$Dialog.InitialDirectory = "$( $HOME )\Desktop"
$Dialog.CheckFileExists = $true
$finalAddresses | Select-Object -Property $Properties | Export-Csv -NoTypeInformation -Delimiter ";" -Path $Dialog.FileName
}
}
#endregion SPECIFICCUSTOMER
}
}
#endregion EXECUTION
#-----------------------------------------------------------------------------------------------------------------
#region EXECUTION