-
Notifications
You must be signed in to change notification settings - Fork 4
/
persons.ps1
487 lines (393 loc) · 20.9 KB
/
persons.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
#####################################################
# HelloID-Conn-Prov-Source-AFAS-Profit-Persons
#
# Version: 2.0.0.2
#####################################################
# Set TLS to accept TLS, TLS 1.1 and TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
$VerbosePreference = "SilentlyContinue"
$InformationPreference = "Continue"
$WarningPreference = "Continue"
$c = $configuration | ConvertFrom-Json
$baseUri = $c.BaseUrl
$token = $c.Token
$positionsAction = $c.positionsAction
# Set debug logging
switch ($($c.isDebug)) {
$true { $VerbosePreference = 'Continue' }
$false { $VerbosePreference = 'SilentlyContinue' }
}
Write-Information "Start person import: Base URL: $baseUri, Using positionsAction: $positionsAction, token length: $($token.length)"
#region functions
function Resolve-HTTPError {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline
)]
[object]$ErrorObject
)
process {
$httpErrorObj = [PSCustomObject]@{
FullyQualifiedErrorId = $ErrorObject.FullyQualifiedErrorId
MyCommand = $ErrorObject.InvocationInfo.MyCommand
RequestUri = $ErrorObject.TargetObject.RequestUri
ScriptStackTrace = $ErrorObject.ScriptStackTrace
ErrorMessage = ''
}
if ($ErrorObject.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') {
$httpErrorObj.ErrorMessage = $ErrorObject.ErrorDetails.Message
}
elseif ($ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException') {
$httpErrorObj.ErrorMessage = [System.IO.StreamReader]::new($ErrorObject.Exception.Response.GetResponseStream()).ReadToEnd()
}
Write-Output $httpErrorObj
}
}
function Get-ErrorMessage {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline
)]
[object]$ErrorObject
)
process {
$errorMessage = [PSCustomObject]@{
VerboseErrorMessage = $null
AuditErrorMessage = $null
}
if ( $($ErrorObject.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or $($ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException')) {
$httpErrorObject = Resolve-HTTPError -Error $ErrorObject
$errorMessage.VerboseErrorMessage = $httpErrorObject.ErrorMessage
$errorMessage.AuditErrorMessage = $httpErrorObject.ErrorMessage
}
# If error message empty, fall back on $ex.Exception.Message
if ([String]::IsNullOrEmpty($errorMessage.VerboseErrorMessage)) {
$errorMessage.VerboseErrorMessage = $ErrorObject.Exception.Message
}
if ([String]::IsNullOrEmpty($errorMessage.AuditErrorMessage)) {
$errorMessage.AuditErrorMessage = $ErrorObject.Exception.Message
}
Write-Output $errorMessage
}
}
function Get-AFASConnectorData {
param(
[parameter(Mandatory = $true)]$Token,
[parameter(Mandatory = $true)]$BaseUri,
[parameter(Mandatory = $true)]$Connector,
[parameter(Mandatory = $true)]$OrderByFieldIds,
[parameter(Mandatory = $true)][ref]$data
)
try {
Write-Verbose "Starting downloading objects through get-connector [$connector]"
$encodedToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($Token))
$authValue = "AfasToken $encodedToken"
$Headers = @{ Authorization = $authValue }
$Headers.Add("IntegrationId", "45963_140664") # Fixed value - Tools4ever Partner Integration ID
$take = 1000
$skip = 0
$uri = $BaseUri + "/connectors/" + $Connector + "?skip=$skip&take=$take&orderbyfieldids=$OrderByFieldIds"
$dataset = Invoke-RestMethod -Method Get -Uri $uri -Headers $Headers -UseBasicParsing
foreach ($record in $dataset.rows) { [void]$data.Value.add($record) }
$skip += $take
while (@($dataset.rows).count -eq $take) {
$uri = $BaseUri + "/connectors/" + $Connector + "?skip=$skip&take=$take&orderbyfieldids=$OrderByFieldIds"
$dataset = Invoke-RestMethod -Method Get -Uri $uri -Headers $Headers -UseBasicParsing
$skip += $take
foreach ($record in $dataset.rows) { [void]$data.Value.add($record) }
}
Write-Verbose "Downloaded [$($data.Value.count)] records through get-connector [$connector]"
}
catch {
$data.Value = $null
$ex = $PSItem
$errorMessage = Get-ErrorMessage -ErrorObject $ex
Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
throw "Error querying data from [$uri]. Error Message: $($errorMessage.AuditErrorMessage)"
}
}
#endregion functions
# Query Persons
try {
Write-Verbose "Querying Persons"
$persons = [System.Collections.ArrayList]::new()
Get-AFASConnectorData -Token $token -BaseUri $baseUri -Connector "T4E_HelloID_Users_v2" -OrderByFieldIds "Medewerker" ([ref]$persons)
# Sort on Medewerker (to make sure the order is always the same)
$persons = $persons | Sort-Object -Property Medewerker
Write-Information "Successfully queried Persons. Result count: $($persons.count)"
}
catch {
$ex = $PSItem
$errorMessage = Get-ErrorMessage -ErrorObject $ex
Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
throw "Could not query Persons. Error Message: $($errorMessage.AuditErrorMessage)"
}
# Query OrganizationalUnits
try {
Write-Verbose "Querying OrganizationalUnits"
$organizationalUnits = [System.Collections.ArrayList]::new()
Get-AFASConnectorData -Token $token -BaseUri $baseUri -Connector "T4E_HelloID_OrganizationalUnits_v2" -OrderByFieldIds "ExternalId" ([ref]$organizationalUnits)
# #region Example to add boolean value if person is manager (more configuration required in person loop, see below) 1/2
# $managersList = ($organizationalUnits | Group-Object ManagerExternalId).name | Sort-Object -Unique
# #endregion Example to add boolean value if person is manager (more configuration required in person loop, see below) 1/2
# Sort on ExternalId (to make sure the order is always the same)
$organizationalUnits = $organizationalUnits | Sort-Object -Property ExternalId
# Group on ExternalId (to match to employments and positions)
$organizationalUnitsGrouped = $organizationalUnits | Group-Object ExternalId -AsString -AsHashTable
Write-Information "Successfully queried OrganizationalUnits. Result count: $($organizationalUnits.count)"
}
catch {
$ex = $PSItem
$errorMessage = Get-ErrorMessage -ErrorObject $ex
Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
throw "Could not query OrganizationalUnits. Error Message: $($errorMessage.AuditErrorMessage)"
}
# Query Employments
try {
Write-Verbose "Querying Employments"
$employments = [System.Collections.ArrayList]::new()
Get-AFASConnectorData -Token $token -BaseUri $baseUri -Connector "T4E_HelloID_Employments_v2" -OrderByFieldIds "ExternalID,Medewerker,Begindatum_functie" ([ref]$employments)
# Sort on ExternalID (to make sure the order is always the same)
$employments = $employments | Sort-Object -Property ExternalID
# Add extra data to employment object
$employments | Foreach-Object {
# Add all organizational units (all "layers") to employment object
$allDepartments = [System.Collections.ArrayList]::new()
$deparmentId = [String]$_.Organisatorische_eenheid_code
$department = $organizationalUnitsGrouped["$deparmentId"]
if ($null -ne $department) {
[void]$allDepartments.add($department)
while (-NOT[String]::IsNullOrEmpty([String]$department.ParentExternalId)) {
$deparmentParentId = $department.ParentExternalId
if (-NOT[String]::IsNullOrEmpty([String]$deparmentParentId)) {
# In case multiple departments are found with same id, always select first we encounter
$department = $organizationalUnitsGrouped["$deparmentParentId"] | Select-Object -First 1
[void]$allDepartments.add($department)
}
}
}
# Add a single property with a comma seperated list of values to employment object
$_ | Add-Member -MemberType NoteProperty -Name "All_Department_ExternalIds" -Value ('"{0}"' -f ($allDepartments.ExternalId -Join '","')) -Force
}
# Group on Medewerker (to match to medewerker)
$employmentsGrouped = $employments | Group-Object Medewerker -AsHashTable
Write-Information "Successfully queried Employments. Result count: $($employments.count)"
}
catch {
$ex = $PSItem
$errorMessage = Get-ErrorMessage -ErrorObject $ex
Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
throw "Could not query Employments. Error Message: $($errorMessage.AuditErrorMessage)"
}
# Query Positions
if ($positionsAction -ne "onlyEmployments") {
try {
Write-Verbose "Querying Positions"
$positions = [System.Collections.ArrayList]::new()
Get-AFASConnectorData -Token $token -BaseUri $baseUri -Connector "T4E_HelloID_Positions_v2" -OrderByFieldIds "ExternalID,EmploymentExternalID,Medewerker,Begindatum_functie" ([ref]$positions)
# Sort on ExternalID (to make sure the order is always the same)
$positions = $positions | Sort-Object -Property ExternalID
# Add extra data to position object
$positions | Foreach-Object {
# Add all organizational units (all "layers") to position object
$allDepartments = [System.Collections.ArrayList]::new()
$deparmentId = [String]$_.Organisatorische_eenheid_code
$department = $organizationalUnitsGrouped["$deparmentId"]
if ($null -ne $department) {
[void]$allDepartments.add($department)
while (-NOT[String]::IsNullOrEmpty([String]$department.ParentExternalId)) {
$deparmentParentId = $department.ParentExternalId
if (-NOT[String]::IsNullOrEmpty([String]$deparmentParentId)) {
# In case multiple departments are found with same id, always select first we encounter
$department = $organizationalUnitsGrouped["$deparmentParentId"] | Select-Object -First 1
[void]$allDepartments.add($department)
}
}
}
# Add a single property with a comma seperated list of values to position object
$_ | Add-Member -MemberType NoteProperty -Name "All_Department_ExternalIds" -Value ('"{0}"' -f ($allDepartments.ExternalId -Join '","')) -Force
}
# Group on EmploymentExternalID (to match to employment)
$positionsGrouped = $positions | Group-Object EmploymentExternalID -AsHashTable
Write-Information "Successfully queried Positions. Result count: $($positions.count)"
}
catch {
$ex = $PSItem
$errorMessage = Get-ErrorMessage -ErrorObject $ex
Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
throw "Could not query Positions. Error Message: $($errorMessage.AuditErrorMessage)"
}
}
# # Example to add boolean values for each group membership 1/2
# try{
# Write-Verbose "Querying Groups"
# $groups = [System.Collections.ArrayList]::new()
# Get-AFASConnectorData -Token $token -BaseUri $baseUri -Connector "T4E_HelloID_Groups_v2" -OrderByFieldIds "GroupId" ([ref]$groups)
# Write-Information "Successfully queried Groups. Result count: $($groups.count)"
# }
# catch {
# $ex = $PSItem
# $errorMessage = Get-ErrorMessage -ErrorObject $ex
# Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
# throw "Could not query Groups. Error Message: $($errorMessage.AuditErrorMessage)"
# }
# try{
# Write-Verbose "Querying UserGroups"
# $userGroups = [System.Collections.ArrayList]::new()
# Get-AFASConnectorData -Token $token -BaseUri $baseUri -Connector "T4E_HelloID_UserGroups_v2" -OrderByFieldIds "UserId" ([ref]$userGroups)
# # Group on UserId (to match to user)
# $userGroupsGrouped = $userGroups | Group-Object UserId -AsHashTable
# Write-Information "Successfully queried UserGroups. Result count: $($userGroups.count)"
# }
# catch {
# $ex = $PSItem
# $errorMessage = Get-ErrorMessage -ErrorObject $ex
# Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
# throw "Could not query UserGroups. Error Message: $($errorMessage.AuditErrorMessage)"
# }
# try {
# Write-Verbose 'Enhancing person objects with groups'
# # Enhance person object with properties for the groups
# $allowedGroupIds = @("ABB","APL")
# $groups | Where-Object {$_.GroupId -in $allowedGroupIds} | ForEach-Object {
# $persons | Add-Member -MemberType NoteProperty -Name "Role_$($_.GroupId)" -Value $false -Force
# }
# Write-Information "Successfully enhanced person objects with groups"
# }
# catch {
# $ex = $PSItem
# $errorMessage = Get-ErrorMessage -ErrorObject $ex
# Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
# throw "Could not enhance person objects with groups. Error Message: $($errorMessage.AuditErrorMessage)"
# }
# # End Example (more configuration required in person loop, see below) 1/2
try {
Write-Verbose 'Enhancing and exporting person objects to HelloID'
# Set counter to keep track of actual exported person objects
$exportedPersons = 0
# Enahnce person model with required properties
$persons | Add-Member -MemberType NoteProperty -Name "Contracts" -Value $null -Force
$persons | Add-Member -MemberType NoteProperty -Name "ExternalId" -Value $null -Force
$persons | ForEach-Object {
# Create person object to log on which person the error occurs
$personInProcess = $_
# Set required fields for HelloID
$_.ExternalId = $_.Medewerker
# Include ExternalId in DisplayName of HelloID Raw Data
$_.DisplayNAme = $_.DisplayName + " ($($_.ExternalId))"
$contractsList = [System.Collections.ArrayList]::new()
# Get employments for person
$employments = $employmentsGrouped[$_.Medewerker]
if ($positionsAction -eq "onlyEmployments") {
if ($null -ne $employments) {
# Create custom contract object to include prefix of properties
$employments | ForEach-Object {
$employmentObject = [PSCustomObject]@{}
$_.psobject.properties | ForEach-Object {
$employmentObject | Add-Member -MemberType $_.MemberType -Name "empl_$($_.Name)" -Value $_.Value -Force
}
[Void]$contractsList.Add($employmentObject)
}
}
else {
Write-Warning "No employments found for person: $($_.ExternalId)"
}
}
else {
if ($null -ne $employments) {
$employments | ForEach-Object {
# Get positions for employment
$positions = $positionsGrouped[$_.ExternalID]
# Add position and employment data to contracts
if ($null -ne $positions) {
foreach ($position in $positions) {
# Create custom position object to include prefix in properties
$positionObject = [PSCustomObject]@{}
# Add employment object with prefix for property names
$_.psobject.properties | ForEach-Object {
$positionObject | Add-Member -MemberType $_.MemberType -Name "empl_$($_.Name)" -Value $_.Value -Force
}
# Add position object with prefix for property names
$position.psobject.properties | ForEach-Object {
$positionObject | Add-Member -MemberType $_.MemberType -Name "pos_$($_.Name)" -Value $_.Value -Force
}
# Add employment and position data to contracts
[Void]$contractsList.Add($positionObject)
}
}
else {
# Add employment only data to contracts (in case of employments without positions)
$employmentObject = [PSCustomObject]@{}
$_.psobject.properties | ForEach-Object {
$employmentObject | Add-Member -MemberType $_.MemberType -Name "empl_$($_.Name)" -Value $_.Value -Force
}
[Void]$contractsList.Add($employmentObject)
}
}
}
else {
Write-Warning "No employments found for person: $($_.ExternalId)"
}
}
# Add Contracts to person
if ($null -ne $contractsList) {
## This example can be used by the consultant if you want to filter out persons with an empty array as contract
## *** Please consult with the Tools4ever consultant before enabling this code. ***
# if ($contractsList.Count -eq 0) {
# Write-Warning "Excluding person from export: $($_.ExternalId). Reason: Contracts is an empty array"
# return
# }
# else {
$_.Contracts = $contractsList
# }
}
## This example can be used by the consultant if the date filters on the person/employment/positions do not line up and persons without a contract are added to HelloID
## *** Please consult with the Tools4ever consultant before enabling this code. ***
# else {
# Write-Warning "Excluding person from export: $($_.ExternalId). Reason: Person has no contract data"
# return
# }
## Group membership example (person part) 2/2
# if (-Not[String]::IsNullOrEmpty($_.Gebruiker)){
# $groupMemberships = $userGroupsGrouped[$_.Gebruiker]
# if ($null -ne $groupMemberships){
# foreach ($groupMembership in ($groupMemberships | Where-Object {$_.GroupId -in $allowedGroupIds}) ) {
# $_."Role_$($groupMembership.GroupId)" = $true
# }
# } else {
# Write-Verbose "Person $($_.Gebruiker) has no groupmembership (within specified allowed groups)"
# }
# } else {
# Write-Verbose "User $($_.ExternalId) has no linked user"
# }
## End Group membership example (person part) 2/2
# #region Example to add boolean value if person is manager 2/2
# if ($managersList -contains $_.ExternalId ) {
# $_ | Add-Member -MemberType NoteProperty -Name "isManager" -Value $true -Force
# }
# else {
# $_ | Add-Member -MemberType NoteProperty -Name "isManager" -Value $false -Force
# }
# #endregion Example to add boolean value if person is manager 2/2
# Sanitize and export the json
$person = $_ | ConvertTo-Json -Depth 10
$person = $person.Replace("._", "__")
$person = $person.Replace("E-mail", "Email") # HelloID doesn't support properties with '-' in the name, sanitize this
Write-Output $person
# Updated counter to keep track of actual exported person objects
$exportedPersons++
}
Write-Information "Successfully enhanced and exported person objects to HelloID. Result count: $($exportedPersons)"
Write-Information "Person import completed"
}
catch {
$ex = $PSItem
$errorMessage = Get-ErrorMessage -ErrorObject $ex
# If debug logging is toggled, log on which person and line the error occurs
if ($c.isDebug -eq $true) {
Write-Warning "Error occurred for person [$($personInProcess.ExternalId)]. Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
}
throw "Could not enhance and export person objects to HelloID. Error Message: $($errorMessage.AuditErrorMessage)"
}