Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 901350c

Browse files
authored
Merge pull request UiPath#29 from UiPath/feature/sync-azure
Sync with Azure AD
2 parents 62e2da3 + 745356b commit 901350c

File tree

1 file changed

+189
-34
lines changed

1 file changed

+189
-34
lines changed

Examples/Sync-UiPathADUsers.ps1

Lines changed: 189 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,168 @@
11
<#
22
.SYNOPSIS
3-
Synchronises Orchestrator users with Windows Active Directory, based on AD group membership mapped to Orchestrator Roles.
3+
Synchronises Orchestrator users with Windows or Azure Active Directory, based on AD group membership mapped to Orchestrator Roles.
44
.DESCRIPTION
5-
You provide the AD domain name and a mapping from relevand AD groups to Orchestrator Roles.
65
New users in AD are added to Orchestrator and existing users added moved to the correct Role.
6+
Azure AD users are matched by comparing the Azure AD user principal name with the user Email in Orchestrator.
77
The script also handles removing Orchestrator users from roles when they were removed from the corresponding AD group.
88
AD users that were removed from all relevant AD groups (eg. an employee that changed role) or were removed from AD (eg. a former employee that left the company) become 'orphaned users'. They are still defined in Orchestrator but do not have any Role. The script supports the -OrphanedUsersAction parameter allowing to optionally List or Remove these users.
99
The script is idempotent, repeated invocations should not modify the Orchestrator users unless something changed in AD.
1010
You should first import the UiPath.PowerShell module and authenticate yourself with your Orchestrator using Get-UiPathAuthToken before running this script.
11+
The script does not modify the Admin user roles membership, even if the Email matches the AzureAD domains. This is a common scenario and can result in accidentally locking Admin user out of Administrators group.
12+
The script adds new Orchestrator users using the Azure AD DisplayName as Name and leaves Surname empty. It does not try to split the DisplayName and figure out the Surname.
1113
.PARAMETER DomainName
12-
The domain to sync users with. It does not necessarily has to be your current user or machine domain, but there must be some trust relationship so your Windows session can discover and interogate this domain AD.
14+
The Windows domain to sync users with. It does not necessarily has to be your current user or machine domain, but there must be some trust relationship so your Windows session can discover and interogate this domain AD.
15+
.PARAMETER AzureAD
16+
Use currently connected Azure AD for sync. You must first connect the PowerShell session to Azure AD using Connect-AzureAD
1317
.PARAMETER RolesMapping
1418
A Hashtable mapping AD groups to Orchestrator roles. Make sure you type the names correctly.
1519
.PARAMETER OrphanedUsersAction
1620
Optional action to handle orphaned users. You can List or Remove these users.
21+
.PARAMETER AllowUsernameTruncate
22+
Optional switch to allow truncation of imported usernames to 32 characters, the Orchestrator username length limit.
1723
.EXAMPLE
18-
Sync-UiPathADUsers MyDomain @{'RPA Admins' = 'Administrator'; 'RPA Users' = 'User'}
19-
Import AD users from MyDomain and maps the members of the 'RPA Admins' AD group to the 'Administrator' Orchestrator role and members of the 'RPA Users' AD group to the 'User' Orchestrator role.
24+
Sync-UiPathADUsers MyDomain @{'RPA Admins' = 'Administrator'; 'RPA Users' = 'Users'}
25+
Import AD users from MyDomain and maps the members of the 'RPA Admins' AD group to the 'Administrator' Orchestrator role and members of the 'RPA Users' AD group to the 'Users' Orchestrator role.
26+
.EXAMPLE
27+
Sync-UiPathADUsers -AzureAD @{'RPA Admins' = 'Administrator'; 'RPA Users' = 'Users'}
28+
Import AD users from Azure Active Directory and maps the members of the 'RPA Admins' Azure AD group to the 'Administrator' Orchestrator role and members of the 'RPA Users' Azure AD group to the 'Users' Orchestrator role.
2029
.EXAMPLE
2130
Sync-UiPathADUsers MyDomain @{} -OrphanedUsersAction Remove
2231
Import AD users from MyDomain but since there is no mapping, the effect is to orphan all exiting Orchestrator MyDomain users and then remove them because of the -OrphanedUsersAction Remove parameter. In effect this invocation removes all MyDomain users from Orchestrator.
32+
.EXAMPLE
33+
Sync-UiPathADUsers -AzureAD @{} -OrphanedUsersAction Remove
34+
Deletes all Azure Active Directory managed users from Orchestrator.
35+
Important notice: this will remove any user in Orchestrator that has an Email domain matching the Azure AD domain, even if it was not imported from Azure AD.
2336
#>
2437
param(
25-
[Parameter(Mandatory=$true, Position=0)]
38+
[Parameter(Mandatory=$true, Position=0, ParameterSetName="Windows")]
2639
[string] $DomainName,
40+
[Parameter(Mandatory=$true, Position=0, ParameterSetName="Azure")]
41+
[switch] $AzureAD,
2742
[Parameter(Mandatory=$true, Position=1)]
2843
[HashTable] $RolesMapping,
2944
[Parameter(Mandatory=$false)]
3045
[ValidateSet('Remove', 'List', 'Ignore')]
31-
[string] $OrphanedUsersAction = 'Ignore'
32-
)
46+
[string] $OrphanedUsersAction = 'Ignore',
47+
[Parameter()]
48+
[switch] $AllowUsernameTruncate)
3349

3450
$ErrorActionPreference = "Stop"
3551

52+
$isAzureAD = $PSCmdlet.ParameterSetName -eq "Azure"
53+
$isWindows = $PSCmdlet.ParameterSetName -eq "Windows"
54+
55+
function Get-IsAzureAD {
56+
return $isAzureAD
57+
}
58+
59+
function Get-IsWindows {
60+
return $isWindows
61+
}
62+
63+
function Format-UiPathUserName {
64+
param(
65+
[Parameter(Position=0, Mandatory=$True)] $userName
66+
)
67+
68+
if (-not [string]::IsNullOrWhiteSpace($domainName)) {
69+
$userName = $domainName + '\' + $userName
70+
}
71+
72+
if ($userName.Length -gt 32) {
73+
if ($AllowUsernameTruncate) {
74+
Write-Warning "AD Username $username exceeds the 32 character limit."
75+
$userName = $userName.Substring(0,32)
76+
}
77+
else {
78+
throw "AD Username $username exceeds the 32 character limit. Run the script with -AllowUsernameTruncate to force user with a truncated name creation."
79+
}
80+
}
81+
return $userName
82+
}
83+
3684
function Get-ADGroupUser {
3785
param(
38-
[Parameter(Mandatory=$true, Position=0)] $dc,
39-
[Parameter(Mandatory=$true, Position=1)] $adGroup
86+
[Parameter(Position=0)] $dc,
87+
[Parameter(Position=1)] $adGroup
4088
)
4189

90+
$users = @()
4291
Write-Verbose "Get-ADGroupUser $adGroup"
4392

44-
$users = @()
45-
$members = Get-ADGroupMember -Server $dc.PDCEmulator -Identity $adGroup
93+
if (Get-IsWindows) {
4694

47-
foreach($member in $members)
48-
{
49-
if ($member.objectClass -eq 'user')
50-
{
51-
$users += @($member.SamAccountName)
52-
}
53-
elseif ($member.objectClass -eq 'group')
95+
96+
Write-Progress -Id 1 `
97+
-Activity "Retrieve group members: $adGroup" `
98+
99+
Write-Verbose "Get-ADGroupMember -Server $($dc.PDCEmulator) -Identity $adGroup"
100+
$members = @(Get-ADGroupMember -Server $dc.PDCEmulator -Identity $adGroup)
101+
102+
foreach($member in $members)
54103
{
55-
$childUsers = Get-ADGroupUser $dc $member.SamAccountName
56-
$users += $childUsers
104+
105+
106+
if ($member.objectClass -eq 'user')
107+
{
108+
$userInfo = @{
109+
SamAccountName = $member.SamAccountName;
110+
Username = Format-UiPathUserName $member.SamAccountName
111+
}
112+
113+
$users += $userInfo
114+
}
115+
elseif ($member.objectClass -eq 'group')
116+
{
117+
$childUsers = Get-ADGroupUser $dc $member.SamAccountName
118+
$users += $childUsers
119+
}
57120
}
58121
}
122+
123+
if (Get-IsAzureAD) {
124+
$adGroupObject = Get-AzureADGroup -SearchString $adGroup | where {$_.DisplayName -eq $adGroup}
125+
Write-Verbose "Get-AzureADGroupMember $($adGroupObject.ObjectId)"
126+
$members = Get-AzureADGroupMember -ObjectId $adGroupObject.ObjectId
127+
128+
$users += $members | foreach {@{
129+
UPN = $_.UserPrincipalName;
130+
Username = Format-UiPathUserName $_.UserPrincipalName;
131+
Name=$_.DisplayName;
132+
}}
133+
}
134+
135+
$users | foreach {Write-Verbose "$adGroup AD group user for sync: $($_.UPN) Username:$($_.Username) Name:$($_.Name)"}
136+
59137
$users
60138
}
61139

140+
function Get-UiPathADUsers {
141+
param(
142+
[Parameter(Position=0)] $domain)
143+
144+
Write-Verbose "Get-UiPathUser -Type User "
145+
$allUsers = Get-UiPathUser -Type User | where {$_.Username -ne "Admin"}
146+
$OrchestratorADUsers = @()
147+
148+
if (Get-IsWindows) {
149+
$OrchestratorADUsers += $allUsers | Select UserName, Id, RolesList, EmailAddress | where {$_.UserName.StartsWith($domain + '\', [System.StringComparison]::OrdinalIgnoreCase)}
150+
}
151+
152+
if (Get-IsAzureAD) {
153+
# Orchestrator maps user to Azure AD identities based on Orchestrator user Email address
154+
Write-Verbose "Get-AzureADDomain"
155+
$domains = Get-AzureADDomain | foreach {$_.Name}
156+
$domains | foreach { Write-Verbose "AzureAD Email domain: $_"}
157+
158+
$OrchestratorADUsers += $allUsers | Select UserName, Id, RolesList, EmailAddress | where {$domains -contains $_.EmailAddress.Split('@')[1]}
159+
}
160+
161+
$OrchestratorADUsers | foreach {Write-Verbose "Orchestrator user for sync: $($_.Id) $($_.Username) $($_.EmailAddress)"}
162+
163+
return $OrchestratorADUsers
164+
}
165+
62166
try
63167
{
64168
$operationSteps = @(
@@ -91,8 +195,12 @@ try
91195
Write-Verbose "Role ok: $roleName $($role.Name)"
92196
}
93197

94-
Write-Verbose "Get-ADDomain $DomainName"
95-
$dc = Get-ADDomain -Identity $DomainName
198+
$dc = $null
199+
200+
if (Get-IsWindows) {
201+
Write-Verbose "Get-ADDomain $DomainName"
202+
$dc = Get-ADDomain -Identity $DomainName
203+
}
96204

97205
$idxOperationStep += 1
98206
Write-Progress -Activity "Sync Orchestrator AD Users" `
@@ -112,18 +220,17 @@ try
112220

113221
$mappedRole = $RolesMapping[$adGoupName]
114222

115-
$adGroupMembers = Get-ADGroupUser $dc $adGoupName | sort -Unique
223+
$adGroupMembers = Get-ADGroupUser $dc $adGoupName | Sort-Object {$_.Username} -Unique
116224

117225
foreach($adGroupMember in $adGroupMembers)
118226
{
119-
$userName = $DomainName + '\' + $adGroupMember
227+
$userName = $adGroupMember.Username
120228
$adUser = $allADUsers[$userName]
121229
if ($adUser -eq $null)
122230
{
123-
$adUser = @{ roles = @(); name = $adGroupMember.Name }
231+
$adUser = @{ roles = @(); userInfo = $adGroupMember}
124232
$null = $allADUsers.Add($userName, $adUser)
125233
}
126-
Write-Verbose "Discovered AD user $userName with role $mappedRole"
127234
$adUser.roles += @($mappedRole)
128235
}
129236
}
@@ -132,7 +239,7 @@ try
132239
Write-Progress -Activity "Sync Orchestrator AD Users" `
133240
-CurrentOperation $operationSteps[$idxOperationStep] `
134241
-PercentComplete ($idxOperationStep/$operationSteps.Count*100)
135-
$orchestratorUsers = Get-UiPathUser -Type User | Select UserName, Id, RolesList | where {$_.UserName.StartsWith($DomainName + '\', [System.StringComparison]::OrdinalIgnoreCase)}
242+
$orchestratorUsers = Get-UiPathADUsers $DomainName
136243

137244
$idxOperationStep += 1
138245
Write-Progress -Activity "Sync Orchestrator AD Users" `
@@ -173,9 +280,10 @@ try
173280
foreach($adUserName in $operations.Keys)
174281
{
175282
$op = $operations[$adUserName]
283+
176284
if ($op.isNew -eq $true)
177285
{
178-
$newUsers += @{userName = $adUserName; name = $op.adUser.name; roles = $op.adUser.roles}
286+
$newUsers += @{userName = $adUserName; userInfo = $op.adUser.userInfo; roles = $op.adUser.roles; }
179287
}
180288
else
181289
{
@@ -275,9 +383,54 @@ try
275383
-Activity $operationSteps[$idxOperationStep] `
276384
-CurrentOperation $newUser.userName `
277385
-PercentComplete ($i/$newUsers.Count * 100)
386+
387+
if (Get-IsWindows) {
388+
# We postponed getting the details until actually needed to reduce AD chit-chat
389+
Write-Verbose "Get-ADUser -Server $($dc.PDCEmulator) -Identity $($newUser.userInfo.SamAccountName) -Properties EmailAddress"
390+
$adUserInfo = Get-ADUser -Server $dc.PDCEmulator -Identity $newUser.userInfo.SamAccountName -Properties EmailAddress
391+
392+
if (-not [string]::IsNullOrWhiteSpace($adUserInfo.EmailAddress)) {
393+
$newUser.userInfo.EmailAddress = $adUserInfo.EmailAddress
394+
}
395+
elseif (-not [string]::IsNullOrWhiteSpace($adUserInfo.UserPrincipalName)) {
396+
$newUser.userInfo.EmailAddress = $adUserInfo.UserPrincipalName
397+
}
398+
399+
if (-not [string]::IsNullOrWhiteSpace($adUserInfo.GivenName)) {
400+
$newUser.userInfo.Name = $adUserInfo.GivenName
401+
}
402+
elseif (-not [string]::IsNullOrWhiteSpace($adUserInfo.Name)) {
403+
$newUser.userInfo.Name = $adUserInfo.Name
404+
}
405+
406+
if (-not [string]::IsNullOrWhiteSpace($adUserInfo.Surname)) {
407+
$newUser.userInfo.Surname =$adUserInfo.Surname
408+
}
409+
}
410+
411+
$cmdMsg = "Add-UiPathUser -Username $($newUser.userName) -RolesList ..."
412+
$cmd = 'Add-UiPathUser -Username $newUser.userName -RolesList $newUser.roles'
413+
414+
if (-not [string]::IsNullOrWhiteSpace($newUser.userInfo.name)) {
415+
$cmdMsg += " -Name $($newUser.userInfo.name)"
416+
$cmd += ' -Name $newUser.userInfo.name'
417+
}
418+
419+
if (-not [string]::IsNullOrWhiteSpace($newUser.userInfo.Surname)) {
420+
$cmdMsg += " -Surname $($newUser.userInfo.Surname)"
421+
$cmd += ' -Surname $newUser.userInfo.Surname'
422+
}
423+
424+
if (-not [string]::IsNullOrWhiteSpace($newUser.userInfo.EmailAddress)) {
425+
$cmdMsg += " -EmailAddress $($newUser.userInfo.EmailAddress)"
426+
$cmd += ' -EmailAddress $newUser.userInfo.EmailAddress'
427+
} elseif (-not [string]::IsNullOrWhiteSpace($newUser.userInfo.UPN)) {
428+
$cmdMsg += " -EmailAddress $($newUser.userInfo.UPN)"
429+
$cmd += ' -EmailAddress $newUser.userInfo.UPN'
430+
}
278431

279-
Write-Verbose "Add-UiPathUser -Username $newUser.userName -Name $($newUser.name) -RolesList $($newUser.roles)"
280-
$null = Add-UiPathUser -Username $newUser.userName -Name $newUser.name -RolesList $newUser.roles
432+
Write-Verbose $cmdMsg
433+
$null = Invoke-Expression $cmd
281434
}
282435

283436
$idxOperationStep += 1
@@ -328,7 +481,10 @@ try
328481
{
329482
foreach($orphanedUser in $orphanedUsers)
330483
{
331-
Write-Host $orphanedUser.userName
484+
New-Object PSObject |
485+
Add-Member Username $orphanedUser.userName -PassThru |
486+
Add-Member Id $orphanedUser.Id -PassThru |
487+
Write-Output
332488
}
333489
}
334490
'Ignore'
@@ -342,10 +498,9 @@ catch
342498
$e = $_.Exception
343499
$klass = $e.GetType().Name
344500
$line = $_.InvocationInfo.ScriptLineNumber
345-
$script = $_.InvocationInfo.ScriptName
346501
$msg = $e.Message
347502

348-
Write-Error "$klass $msg ($script $line)"
503+
Write-Error "$($line): $($klass): $msg"
349504
}
350505

351506

0 commit comments

Comments
 (0)