This repository was archived by the owner on Aug 5, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathAzure-AD-B2B-Invite-Script.ps1
More file actions
316 lines (230 loc) · 13.9 KB
/
Azure-AD-B2B-Invite-Script.ps1
File metadata and controls
316 lines (230 loc) · 13.9 KB
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
####################################################################################################################
## ##
## -- Azure AD (AAD) B2B Auto-Invite Script -- ##
## ##
## Automatically invite users from specific AAD groups in source AAD tenant to target AAD tenant as B2B-users ##
## The script can also report groups and members in source to the target for further processing (group invites ++) ##
## ##
## Written by Stian A. Strysse, Lumagate AS - stian.strysse@lumagate.com ##
## 15 Jan 2018 - First version ##
## 14 Mar 2018 - Added features (report groups and members in source to target tenant for further processing) ##
## 09 Dec 2022 - Upgraded to support modern auth. Moved to GitHub Jørgen, Robin and Nils ##
## ##
## Please read the configuration guide for details on how to set up service accounts, scheduled tasks and logging ##
## Note: Only edit values within the 'Script Configuration: Source/Target Azure AD Tenant' regions ##
## ##
####################################################################################################################
#region Script Configuration: Source Azure AD Tenant
# Add source AAD tenant ID
$configSourceTenantID = "organisasjon.onmicrosoft.com"
# Add UserPrincipalName for AAD Service Account in source AAD tenant
$configSourceServiceAccountUPN = "Samhandlingb2binviteUser@organisasjon.no"
# Add UserPrincipalName for PnP Service Account in samhandling AAD tenant (provided by samhandling.org host county)
$configSourcePnPServiceAccountUPN = "organisasjon-pnp-user@samhandling.onmicrosoft.com"
# Add SecureString for AAD Service Account password in source AAD tenant. See readme how to create this
$configSourceServiceAccountSecurePassword = 'longsupersecurestring'
# Add AAD Groups to scope B2B Users in source AAD tenant
$configSourceGroupsToInvite = "TILGANGSGRUPPE1", "TILGANGSGRUPPE2", "TILGANGSGRUPPE3"
# Add a filename and path for saving local logfile with invited users data
$configSourceLogfilePreviouslyInvitedUsers = "D:\localScriptPath\b2binvitedusers-organization.txt"
# Add a filepath for creating local csv file with membership data (exports to Master Organization)
$configSourceMembershipDataCsv = "D:\localScriptPath\export-membershipdata-organization.csv"
# Enable extensive logging to EventLog
$configSourceExtensiveLogging = $true
#endregion
#region Script Configuration: Target Azure AD Tenant
# Add target AAD tenant ID
$configTargetTenantID = "samhandling.onmicrosoft.com"
# Add target SPO Site URL
$configTargetSPOSiteUrl = "https://samhandling.sharepoint.com/sites/b2bmembershipdata"
# Add target SPO Library
$configTargetSPODocLibraryName = "membershipcsvdata"
#endregion
###################################################################################################################
#### Warning: Do not edit below this line! ########################################################################
###################################################################################################################
#region Import Required Modules
try {
Import-Module $($PSScriptRoot + "\Azure-AD-B2B-Invite-Module.psm1") -Force -ErrorAction Stop
Import-Module AzureAD -Force -ErrorAction Stop
}
catch {
# Catch any Errors and report to EventLog before exiting script
Write-EventLogB2B "Azure AD B2B Auto-Invite Script aborted due to an error: $($Error -join "`n`n")" -EventType Error
exit
}
#endregion
#region Set Parameters
# Set parameters
$GLOBAL:Configuration = @{
SourceTenantID = $configSourceTenantID
SourceSvcUPN = $configSourceServiceAccountUPN
SourcePnPSvcUPN = $configSourcePnPServiceAccountUPN
SourceSvcPwd = $configSourceServiceAccountSecurePassword
SourceAADInviteGroups = $configSourceGroupsToInvite
TargetTenantID = $configTargetTenantID
TargetSPOSiteUrl = $configTargetSPOSiteUrl
TargetSPODocLibrary = $configTargetSPODocLibraryName
LogfilePreviouslyInvitedUsers = $configSourceLogfilePreviouslyInvitedUsers
CsvfileMembershipData = $configSourceMembershipDataCsv
ExtensiveLogging = $configSourceExtensiveLogging
}
# Create ResultsLog
$ResultsLog = New-Object System.Collections.ArrayList
# Create array for scoped users
$ScopedUsers = New-Object System.Collections.ArrayList
# Create array for eligible users
$FilteredScopedUsers = New-Object System.Collections.ArrayList
#endregion
#region Connect to source Azure AD and get scoped users
try {
# Start script
Write-EventLogB2B "Azure AD B2B Auto-Invite Script started" -ErrorAction Stop
# Connect to source Azure AD
Write-EventLogB2B "Connecting to source Azure AD ($($GLOBAL:Configuration.SourceTenantID)) as $($GLOBAL:Configuration.SourceSvcUPN)" -VerboseOnly:$true
$AzureADSourceCredential = New-Object System.Management.Automation.PSCredential($GLOBAL:Configuration.SourceSvcUPN, (ConvertTo-SecureString $GLOBAL:Configuration.SourceSvcPwd)) -ErrorAction Stop
$AzureADSourceSession = Connect-AzureAD -Credential $AzureADSourceCredential -TenantId $GLOBAL:Configuration.SourceTenantID -ErrorAction Stop
# Get members from scoped groups and add to scoped users array
$GLOBAL:Configuration.SourceAADInviteGroups | ForEach-Object {
Write-EventLogB2B "Searching for AAD Group: $($_)" -VerboseOnly:$true
$AzureADGroup = Get-AzureADGroup -Filter "DisplayName eq '$($_)'" -ErrorAction Stop
if ($AzureADGroup) {
Write-EventLogB2B "Retrieving members in AAD Group: $($_)" -VerboseOnly:$true
$AzureADGroupMembers = Get-AzureADGroupMember -All:$true -ObjectId $AzureADGroup.ObjectId
Write-EventLogB2B "Found $($AzureADGroupMembers.count) members in AAD Group: $($_)" -VerboseOnly:$true
$AzureADGroupMembers | Where-Object { $_.AccountEnabled } | Select-Object @{Name = "SourceTenant"; Expression = { $GLOBAL:Configuration.SourceTenantID } }, @{Name = "MemberOf"; Expression = { $AzureADGroup.DisplayName } }, DisplayName, Mail, UserPrincipalName, UserType | ForEach-Object {
$ScopedUsers.Add($_) | Out-Null
}
}
else {
Write-EventLogB2B "Could not find Azure AD Group: $($_)" -EventType Warning
}
}
}
catch {
# Catch any Errors and report to EventLog before exiting script
Write-EventLogB2B "Azure AD B2B Auto-Invite Script aborted due to an error: $($Error -join "`n`n")" -EventType Error
exit
}
finally {
# Disconnect from source Azure AD if connected
if ($AzureADSourceSession) {
Write-EventLogB2B "Disconnecting from source Azure AD ($($GLOBAL:Configuration.SourceTenantID))" -VerboseOnly:$true
Disconnect-AzureAD
}
}
#endregion
#region Report Group Memberships to Target tenant
try {
# Verify that $ScopedUsers contains data
if ($ScopedUsers.count -ne 0) {
Write-EventLogB2B "$($ScopedUsers.count) scoped group memberships to report to Targent tenant" -VerboseOnly:$true
# Export CSV file locally
Write-EventLogB2B "Exporting CSV to local file pending transfer at $($GLOBAL:Configuration.CsvfileMembershipData)" -VerboseOnly:$true
$ScopedUsers | Select-Object SourceTenant, MemberOf, Mail, UserPrincipalName | Export-Csv -Path $GLOBAL:Configuration.CsvfileMembershipData -NoTypeInformation -Encoding UTF8 -ErrorAction Stop
# Transfer data to Master Organization
Invoke-CsvFileUpload -ExportFile $GLOBAL:Configuration.CsvfileMembershipData -SourceTenantID $GLOBAL:Configuration.SourceTenantID -SPOSiteUrl $GLOBAL:Configuration.TargetSPOSiteUrl -SPODocLibraryName $GLOBAL:Configuration.TargetSPODocLibrary -ErrorAction Stop
Write-EventLogB2B "Scoped group memberships reported successfully to Target tenant"
}
else {
Write-EventLogB2B "No scoped group memberships to report to Target tenant"
}
}
catch {
# Catch any Errors and report to EventLog before exiting script
Write-EventLogB2B "Azure AD B2B Auto-Invite Script aborted due to an error: $($Error -join "`n`n")" -EventType Error
exit
}
#endregion
#region Verify Data
try {
# Import logfile of previously invited users as hashtable
$PreviouslyInvitedUsers = @{} ; Get-Content -Path $GLOBAL:Configuration.LogfilePreviouslyInvitedUsers -ErrorAction SilentlyContinue | ForEach-Object {
$PreviouslyInvitedUsers[$_] = $true
}
# Verify if list with previously invited users exists, else write Event Warning
if ($PreviouslyInvitedUsers.Count -eq 0) {
Write-EventLogB2B "Could not locate a list with previously invited email addresses at $($GLOBAL:Configuration.LogfilePreviouslyInvitedUsers). All eligible users will be invited." -EventType Warning
}
# Filter scoped users: remove duplicates, users without email, already invited users, guests
foreach ($User in $ScopedUsers) {
# Verify if user has email address
if (($User.Mail -ne $null) -and ($User.Mail -ne '')) {
# Verify if user is not a Guest in source AAD tenant
if ($User.UserType -ne 'Guest') {
# Verify if user is not present in several scoped groups
if ($FilteredScopedUsers.UserPrincipalName -notcontains $User.UserPrincipalName) {
# Add user to eligible list if not already invited
if (!$PreviouslyInvitedUsers.ContainsKey($User.Mail)) {
Write-EventLogB2B "Added to eligible list: $($User.UserPrincipalName)" -VerboseOnly:$true
$FilteredScopedUsers.Add($User) | Out-Null
}
else {
Write-EventLogB2B "Skipped - user is previously invited according to logfile: $($User.UserPrincipalName)" -VerboseOnly:$true
}
}
}
else {
Write-EventLogB2B "Skipped - user is a Guest in source AAD tenant: $($User.UserPrincipalName)" -VerboseOnly:$true
}
}
else {
Write-EventLogB2B "Skipped - user does not have an email address in source AAD tenant: $($User.UserPrincipalName)" -VerboseOnly:$true
}
}
# Check if eligible users list is empty after filtering, if so exit script
if ($FilteredScopedUsers.Count -eq 0) {
Write-EventLogB2B "Azure AD B2B Auto-Invite Script ended - no new users to process"
exit
}
Write-EventLogB2B "Found a total of $($FilteredScopedUsers.count) unique, eligible users in source AAD ($($ScopedUsers.count) before filtering and removing duplicates)" -VerboseOnly:$true
}
catch {
# Catch any Errors and report to EventLog before exiting script
Write-EventLogB2B "Azure AD B2B Auto-Invite Script aborted due to an error: $($Error -join "`n`n")" -EventType Error
exit
}
#endregion
#region Connect to target Azure AD and B2B-invite eligible users from $ScopedUsers
try {
# Connect to target Azure AD
Write-EventLogB2B "Connecting to target Azure AD ($($GLOBAL:Configuration.TargetTenantID)) as $($GLOBAL:Configuration.SourceSvcUPN)" -VerboseOnly:$true
$AzureADTargetCredential = New-Object System.Management.Automation.PSCredential($GLOBAL:Configuration.SourceSvcUPN, (ConvertTo-SecureString $GLOBAL:Configuration.SourceSvcPwd)) -ErrorAction Stop
$AzureADTargetSession = Connect-AzureAD -Credential $AzureADTargetCredential -TenantId $GLOBAL:Configuration.TargetTenantID -ErrorAction Stop | Out-Null
Foreach ($User in $FilteredScopedUsers) {
# Do guest invitation
$InvitationResult = New-AzureADMSInvitation -InvitedUserDisplayName $User.DisplayName -InvitedUserEmailAddress $User.Mail -SendInvitationMessage:$false -InviteRedirectUrl "https://myapps.microsoft.com" -ErrorAction Continue
$ResultsLog.Add($InvitationResult) | Out-Null
# Verify guest invitation
if (($InvitationResult.Status -eq 'Accepted') -or ($InvitationResult.Status -eq 'PendingAcceptance')) {
Write-EventLogB2B "Invite of $($User.Mail) was successfull" -VerboseOnly:$true
$User.Mail | Out-File -FilePath $GLOBAL:Configuration.LogfilePreviouslyInvitedUsers -Append -Encoding utf8 -ErrorAction Stop
}
# Write Error if guest invitation was not successfull
else {
Write-EventLogB2B "Error during invite of $($User.Mail): $($Error -join "`n`n")" -EventType Error
$ErrorCounter++
if ($ErrorCounter.count -ge 5) {
Write-EventLogB2B "Azure AD B2B Auto-Invite Script aborted due to 5 failed invitations, see event log for more details" -EventType Error
}
}
}
Write-EventLogB2B "Azure AD B2B Auto-Invite Script ended: $((($ResultsLog.Status -eq 'Accepted').count + ($ResultsLog.Status -eq 'PendingAcceptance').count)) new invitations completed successfully"
}
catch {
# Catch any Errors and report to EventLog before exiting script
Write-EventLogB2B "Azure AD B2B Auto-Invite Script aborted due to an error: $($Error -join "`n`n")" -EventType Error
exit
}
finally {
# Disconnect from source Azure AD if still connected
if ($AzureADTargetSession) {
Write-EventLogB2B "Disconnecting from source Azure AD ($($GLOBAL:Configuration.SourceTenantID))" -VerboseOnly:$true
Disconnect-AzureAD
}
}
#endregion
###################################################################################################################
#### End of Script ################################################################################################
###################################################################################################################