Public/Push-AzureADUsersToBB.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
Function Push-AzureADUsersToBB {
    <#
    .SYNOPSIS
        Takes users via the pipeline from Get-MgUser or from the deprecated Get-AzureADUser, converts the information needed and process it directly in GoBright
    .DESCRIPTION
        Takes users via the pipeline from Get-MgUser or from the deprecated Get-AzureADUser, converts the information needed and process it directly in GoBright
    .PARAMETER ADUserNamePropertyName
        Optional AzureAd User Property which contains the name of the user, in case you do not want to use the default property
    .PARAMETER ADUserPincodePropertyName
        Optional AzureAd User Property which contains the pincode
    .PARAMETER ADUserMobilePropertyName
        Optional AzureAd User Property which contains the mobile phone number
    .PARAMETER ADUserNFCIdPropertyName
        Optional AzureAd User Property which contains the NFC Identifier, note that this must be in hex format, example: XX:XX:XX
    .PARAMETER ADUserDefaultCostCenterIdOrNamePropertyName
        Optional AzureAd User Property which contains the Default Cost Center for the user, which can be the Name or the Id, both the name or id can be found in the GoBright portal
    .PARAMETER BrightBookingApiUrl
        Address of the GoBright API, e.g.: https://t1b.gobright.cloud/
    .PARAMETER BrightBookingApiKey
        API key of the user to use to process the import
    .PARAMETER BrightBookingIntegrationName
        Name of the GoBright integration to link the users to
    .PARAMETER UserRoleNameForNewUsers
        Name of the GoBright userrole to link new users to
    .PARAMETER UserDefaultRoleName
        Optional default name of role the role the user should get (will be assigned to every user, except for the matches found in 'GroupUserRoleMapping')
    .PARAMETER GroupUserRoleMapping
        Optional map of AzureADRoleName and the corresponding role name that should be assigned. First match will be taken, and will override a potential given 'UserDefaultRoleName'
        Examplestructure to supply in this parameter:
        $groupToRoleMapping = @()
        $groupToRoleMapping += @{AzureADRoleName = "Group name A"; RoleName = "Bookingmanagers"}
        $groupToRoleMapping += @{AzureADRoleName = ""; RoleName = "Standard user role"; MatchType = "AddForEveryUser"} # NOTE: Here a special case, by setting MatchType = "AddForEveryUser", every user will be assigned to this "Standard user role"
    .PARAMETER DeactivateExistingUsersInSameIntegrationThatAreNotLoaded
        Deactivate users that exist in the platform in the same integration but are not loaded anymore from AD (e.g. because they are not anymore in the group you filter on)
    .PARAMETER IncludeUsersWithoutAzureADAssignedLicensesOrAssignedPlans
        Include users that do not have AzureAD licenses assigned (would otherwise be included as inactive) or AzureAD licenses assigned (would otherwise be fully excluded). Note that including these users might result in having unintended 'users' like serviceacounts, roommailbox users, etc. That can be mitigated by filtering the users before feeding them into this command.
    .PARAMETER WhatIf
        Use the WhatIf switch to print out the retreived users, without processing them to the API. This is usefull for testing purposes
    .EXAMPLE
        Get-MgUser -All -Select Id,DisplayName,Mail,UserPrincipalName,AccountEnabled,MobilePhone,AssignedLicenses | Push-AzureADUsersToBB -BrightBookingApiUrl "https://xyz.gobright.cloud/" -BrightBookingApiKey "[your api key]" -BrightBookingIntegrationName "Office 365"
        # Get all users in the AzureAD and let GoBright process it directly
    .LINK
        Get-MgUser
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
    Param(
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [System.Object[]]$pipelineInput,

        [Parameter(Mandatory = $False)]
        [string]$ADUserEmailAddressPropertyName = "Mail",

        [Parameter(Mandatory = $False)]
        [ValidateSet("None", "UserPrincipalName")]
        [string]$ADUserSpecificUsername = "None",

        [Parameter(Mandatory = $False)]
        [string]$ADUserNamePropertyName = "DisplayName",

        [Parameter(Mandatory = $False)]
        [string]$ADUserPincodePropertyName,

        [Parameter(Mandatory = $False)]
        [string]$ADUserMobilePropertyName = "MobilePhone",

        [Parameter(Mandatory = $False)]
        [string]$ADUserNFCIdPropertyName,

        [Parameter(Mandatory = $False)]
        [string]$ADUserDefaultCostCenterIdOrNamePropertyName,

        [Parameter(Mandatory = $True)]
        [string]$BrightBookingApiUrl,

        [Parameter(Mandatory = $True)]
        [string]$BrightBookingApiKey,

        [Parameter(Mandatory = $True)]
        [string]$BrightBookingIntegrationName,

        [Parameter(Mandatory = $False)]
        [string]$UserRoleNameForNewUsers,

        [Parameter(Mandatory = $False)]
        [string]$UserDefaultRoleName,

        [Parameter(Mandatory = $False)]
        [System.Object[]]$GroupUserRoleMapping,

        [switch]$DeactivateExistingUsersInSameIntegrationThatAreNotLoaded,

        [switch]$IncludeUsersWithoutAzureADAssignedLicensesOrAssignedPlans
    )

    Begin {
        If (-not $PSBoundParameters.ContainsKey('Confirm')) {
            $ConfirmPreference = $PSCmdlet.SessionState.PSVariable.GetValue('ConfirmPreference')
        }
        If (-not $PSBoundParameters.ContainsKey('WhatIf')) {
            $WhatIfPreference = $PSCmdlet.SessionState.PSVariable.GetValue('WhatIfPreference')
        }

        $convertedUsers = @()
        $pipelineAzureADUsers = [System.Collections.Generic.List[Object]]::new()
    }

    Process {
        # collect all input users
        $pipelineAzureADUsers.Add($_)
    }

    End {
        # make the list unique, so we don't have duplicates
        $pipelineAzureADUsers = $pipelineAzureADUsers | Sort-Object -Property * -Unique

        # auto detect if the new Microsoft.Graph.Users module is used, or the old AzureAD module
        $UseDeprecatedAzureADModule = $False
        Foreach ($ADUser in $pipelineAzureADUsers) {
            If ($ADUser.GetType().FullName.StartsWith("Microsoft.Open.AzureAD","CurrentCultureIgnoreCase")) {
                $UseDeprecatedAzureADModule = $True
                Write-Warning "Detected user data that is loaded from the AzureAD module, please note that the AzureAD module is deprecated from 1st July 2023, visit https://support.gobright.com to migrate to the new Microsoft.Graph.Users module"
            }
            Else {
                If ($ADUser.GetType().FullName.StartsWith("Microsoft.Graph.PowerShell","CurrentCultureIgnoreCase")) {
                    # user is loaded with Microsoft.Graph module
                }
            }
            break
        }

        # if using deprecated AzureAD module, then set the ADUserMobilePropertyName back to the default of the deprecated AzureAD module
        If ($UseDeprecatedAzureADModule) {
            If ($ADUserMobilePropertyName -eq "MobilePhone") {
                $ADUserMobilePropertyName = "Mobile"
            }
        }

        # prepare the user-role mapping
        If ($GroupUserRoleMapping) {
            Write-Output "Loading AzureAD groups for configured groups in the group to role mapping"

            # lookup a groupname, we do this in the order of the supplied key/values, and the first hit is taken.
            Foreach ($groupUserRoleMappingItem in $GroupUserRoleMapping) {
                If (-not ((-not $groupUserRoleMappingItem.RoleType) -or ($groupUserRoleMappingItem.RoleType -eq "MWV") -or ($groupUserRoleMappingItem.RoleType -eq "View"))) {
                    Write-Warning "RoleType is not correct for role '$($groupUserRoleMappingItem.RoleName)', valid RoleType values are: MWV or View, but found: '$($groupUserRoleMappingItem.RoleType)', this rolemapping will be skipped"
                    continue
                }

                If ($groupUserRoleMappingItem.MatchType -eq "AddForEveryUser") {
                    # for this special match type we should not load the users of the group, because it is designed to matche always (and probably does not even have an AzureADRoleName set)
                    Write-Output " - Special MatchType 'AddForEveryUser' found, every user will be added to the role '$($groupUserRoleMappingItem.RoleName)'"
                }
                Else {
                    $foundGroup = $null

                    # try to find the related group by name
                    If ($UseDeprecatedAzureADModule) {
                        $foundGroups = Get-AzureADGroup -Filter "DisplayName eq '$($groupUserRoleMappingItem.AzureADRoleName)'"
                    }
                    Else {
                        [array]$foundGroups = Get-MgGroup -Filter "DisplayName eq '$($groupUserRoleMappingItem.AzureADRoleName)'"
                    }

                    If ($foundGroups.Count -ge 1) {
                        # add the object id of the group to the mapping, so we can use it later on
                        $foundGroup = $foundGroups[0]
                    }

                    $memberUserObjectIds = @()
                    If ($foundGroup) {
                        If ($UseDeprecatedAzureADModule) {
                            $azureADGroupMembers = Get-AzureADGroupMember -All $true -ObjectId $foundGroup.ObjectId
                        }
                        Else {
                            $azureADGroupMembers = Get-MgGroupMember -All -GroupId $foundGroup.Id
                        }

                        Foreach ($azureADGroupMember in $azureADGroupMembers) {
                            If ($UseDeprecatedAzureADModule) {
                                $memberUserObjectIds += $azureADGroupMember.ObjectId
                            }
                            Else {
                                $memberUserObjectIds += $azureADGroupMember.Id
                            }
                        }

                        Write-Output " - Group '$($foundGroup.DisplayName)' found, contains $($memberUserObjectIds.Count) users"
                    }
                    Else {
                        Write-Warning " - Group '$($groupUserRoleMappingItem.AzureADRoleName)' not found in AzureAD, so users cannot be mapped to this group"
                    }
                    $groupUserRoleMappingItem | Add-Member MemberUserObjectIds $memberUserObjectIds -Force
                }
            }
            Write-Output "Finished loading AzureAD group info"
        }

        # process the users given in the pipeline
        Foreach ($ADUser in $pipelineAzureADUsers) {
            # load the basic information fields from the user object
            $userEmailAddress = ""
            If ($ADUserEmailAddressPropertyName) {
                $userEmailAddress = $ADUser.$ADUserEmailAddressPropertyName
            }
            Else {
                $userEmailAddress = $ADUser.Mail
            }

            $userName = ""
            If ($ADUserNamePropertyName) {
                $userName = $ADUser.$ADUserNamePropertyName
            }
            Else {
                $userName = $ADUser.DisplayName
            }

            $userAuthenticationUsername = ""
            if ($ADUserSpecificUsername -like "UserPrincipalName") {
                 $userAuthenticationUsername = $ADUser.UserPrincipalName
            }

            $userMobile = ""
            If ($ADUserMobilePropertyName) {
                $userMobile = $ADUser.$ADUserMobilePropertyName
            }

            $userNFCId = ""
            If ($ADUserNFCIdPropertyName) {
                $userNFCId = $ADUser.$ADUserNFCIdPropertyName
            }

            $userDefaultCostCenterIdOrName = ""
            If ($ADUserDefaultCostCenterIdOrNamePropertyName) {
                $userDefaultCostCenterIdOrName = $ADUser.$ADUserDefaultCostCenterIdOrNamePropertyName
            }

            $userEnabled = $false
            If ($ADUser.AccountEnabled -And ($ADUser.AssignedLicenses -Or $IncludeUsersWithoutAzureADAssignedLicensesOrAssignedPlans) -And $userEmailAddress) {
                $userEnabled = $true
            }

            $userPincode = ""
            If ($ADUserPincodePropertyName) {
                $userPincode = $ADUser.$ADUserPincodePropertyName
            }

            $userMappedRoles = @()
            If ($GroupUserRoleMapping) {
                # lookup which roles are valid for this user
                Foreach ($groupUserRoleMappingItem in $GroupUserRoleMapping) {
                    $userMatches = $false

                    # check if there is a 'special' matchtype, and otherwise match the default way
                    If ($groupUserRoleMappingItem.MatchType -eq "AddForEveryUser") {
                        $userMatches = $true
                    }
                    Else {
                        If ($UseDeprecatedAzureADModule) {
                            If ($groupUserRoleMappingItem.MemberUserObjectIds -contains $ADUser.ObjectId) {
                                $userMatches = $true
                            }
                        }
                        Else {
                            If ($groupUserRoleMappingItem.MemberUserObjectIds -contains $ADUser.Id) {
                                $userMatches = $true
                            }
                        }
                    }

                    If ($userMatches) {
                        $propertiesHash = [ordered]@{
                            RoleName = $groupUserRoleMappingItem.RoleName
                            RoleType = $groupUserRoleMappingItem.RoleType
                        }
                        $userMappedRoles += New-Object PSObject -Property $propertiesHash
                    }
                }
            }

            # if nothing matched, then add the default rolename
            If ($UserDefaultRoleName) {
                If ($userMappedRoles.Count -eq 0) {
                    $propertiesHash = [ordered]@{
                        RoleName = $UserDefaultRoleName
                    }
                    $userMappedRoles += New-Object PSObject -Property $propertiesHash
                }
            }

            If ($UseDeprecatedAzureADModule) {
                $uniqueImportID = $ADUser.ObjectId
                $hasAssignedLicenses = $ADUser.AssignedPlans
            }
            Else {
                $uniqueImportID = $ADUser.Id
                $hasAssignedLicenses = $ADUser.AssignedLicenses
            }

            $outputUserPropertiesHash = [ordered]@{
                EmailAddress    = $userEmailAddress
                Name            = $userName
                AuthenticationUsername = $userAuthenticationUsername
                TelephoneMobile = $userMobile
                Pincode         = $userPincode
                Active          = $userEnabled
                UniqueImportID  = $uniqueImportID
                UserMappedRoles = $userMappedRoles
                NFCId           = $userNFCId
                DefaultCostCenterIdOrName = $userDefaultCostCenterIdOrName
            }

            If ($hasAssignedLicenses -Or $IncludeUsersWithoutAzureADAssignedLicensesOrAssignedPlans) {
                $outputUser = New-Object PSObject -Property $outputUserPropertiesHash
                $convertedUsers += $outputUser
            }
        }

        # finishing all, and either put it to process further, or return the result as WhatIf result
        $syncIncludesUserPincode = $false
        If ($ADUserPincodePropertyName) {
            $syncIncludesUserPincode = $true
        }

        $syncIncludesUserNFCId = $false
        If ($ADUserNFCIdPropertyName) {
            $syncIncludesUserNFCId = $true
        }

        # ShouldProcess intercepts WhatIf* --> no need to pass it on
        If ($PSCmdlet.ShouldProcess("ShouldProcess?")) {
            If ($DeactivateExistingUsersInSameIntegrationThatAreNotLoaded) {
                Send-ADUsersToBB -pipelineConvertedADUsers $convertedUsers -BrightBookingApiUrl $BrightBookingApiUrl -BrightBookingApiKey $BrightBookingApiKey -BrightBookingIntegrationName $BrightBookingIntegrationName -UserRoleNameForNewUsers $UserRoleNameForNewUsers -SyncIncludesUserPincode $syncIncludesUserPincode -SyncIncludesUserNFCId $syncIncludesUserNFCId -DeactivateExistingUsersInSameIntegrationThatAreNotLoaded
            }
            Else {
                Send-ADUsersToBB -pipelineConvertedADUsers $convertedUsers -BrightBookingApiUrl $BrightBookingApiUrl -BrightBookingApiKey $BrightBookingApiKey -BrightBookingIntegrationName $BrightBookingIntegrationName -UserRoleNameForNewUsers $UserRoleNameForNewUsers -SyncIncludesUserPincode $syncIncludesUserPincode -SyncIncludesUserNFCId $syncIncludesUserNFCId
            }
        }
        Else {
            $countConvertedUsers = $convertedUsers | Measure-Object | Select-Object -ExpandProperty Count;

            Write-Output "============ Test mode (AzureAD) ============"
            Write-Output "When run in normal mode, it would now process the following $countConvertedUsers users to the API."
            Write-Output "If you want to run it for real, you should run without the WhatIf parameter."
            If ($syncIncludesUserPincode) {
                Write-Output "Sync will process user property '$ADUserPincodePropertyName' as 'PIN code'"
            }
            If ($syncIncludesUserNFCId) {
                Write-Output "Sync will process user property '$ADUserNFCIdPropertyName' as NFC ids"
            }
            Return $convertedUsers
        }
    }
}