Skip to content

Commit 473afdf

Browse files
authored
Feature added import script (#33)
* feature: enhanced error handling resource scripts * feature: import script * Fix: optimized audit loging * Update CHANGELOG.md * Fix: make sure displayname has a value
1 parent e7674ea commit 473afdf

File tree

8 files changed

+314
-116
lines changed

8 files changed

+314
-116
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
44

5+
## [3.4.0] - 14-03-2025
6+
7+
List of changes:
8+
- Added account import script
9+
- Enhanced error handling resource scripts ([#32](https://github.com/Tools4everBV/HelloID-Conn-Prov-Target-Topdesk/issues/32))
10+
- Optimized audit logging
11+
512
## [3.3.0] - 10-12-2024
613

714
Added examples for adding additional endpoints and removed debug toggle

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@
3030

3131
## Introduction
3232

33+
Supported features:
34+
| Feature | Supported | Actions | Remarks |
35+
| ----------------------------------- | --------- | --------------------------------------- | ------------------------------ |
36+
| **Account Lifecycle** || Create, Update, Enable, Disable, Delete | |
37+
| **Permissions** || Retrieve, Grant, Revoke | Creating changes and incidents |
38+
| **Resources** || - | |
39+
| **Entitlement Import: Accounts** || - | |
40+
| **Entitlement Import: Permissions** || - | |
41+
3342
_HelloID-Conn-Prov-Target-Topdesk_ is a _target_ connector. Topdesk provides a set of REST APIs that allow you to programmatically interact with its data. The [Topdesk API documentation](https://developers.topdesk.com/explorer/?page=supporting-files#/) provides details of API commands that are used.
3443

3544
| Endpoint | Description |

delete.ps1

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -334,19 +334,6 @@ try {
334334
'UpdateAndDisable' {
335335
Write-Information "Archiving Topdesk person for: [$($personContext.Person.DisplayName)]"
336336

337-
$accountChangedPropertiesObject = [PSCustomObject]@{
338-
OldValues = @{}
339-
NewValues = @{}
340-
}
341-
342-
foreach ($accountOldProperty in ($accountOldProperties | Where-Object { $_.Name -in $accountNewProperties.Name })) {
343-
$accountChangedPropertiesObject.OldValues.$($accountOldProperty.Name) = $accountOldProperty.Value
344-
}
345-
346-
foreach ($accountNewProperty in $accountNewProperties) {
347-
$accountChangedPropertiesObject.NewValues.$($accountNewProperty.Name) = $accountNewProperty.Value
348-
}
349-
350337
# Unarchive person if required
351338
if ($TopdeskPerson.status -eq 'personArchived') {
352339

@@ -378,7 +365,7 @@ try {
378365
$TopdeskPersonUpdated = Set-TopdeskPerson @splatParamsPersonUpdate
379366
}
380367
else {
381-
Write-Warning "DryRun would update Person. Old values: $($accountChangedPropertiesObject.oldValues | ConvertTo-Json). New values: $($accountChangedPropertiesObject.newValues | ConvertTo-Json)"
368+
Write-Warning "DryRun would update Person."
382369
}
383370

384371
# Archive person
@@ -401,10 +388,10 @@ try {
401388
$outputContext.PreviousData = $TopdeskPerson
402389

403390
if (-Not($actionContext.DryRun -eq $true)) {
404-
Write-Information "Account with id [$($TopdeskPerson.id)] and dynamicName [($($TopdeskPerson.dynamicName))] successfully updated and archived. Old values: $($accountChangedPropertiesObject.oldValues | ConvertTo-Json). New values: $($accountChangedPropertiesObject.newValues | ConvertTo-Json)"
391+
Write-Information "Account with id [$($TopdeskPerson.id)] and dynamicName [($($TopdeskPerson.dynamicName))] successfully updated and archived."
405392

406393
$outputContext.AuditLogs.Add([PSCustomObject]@{
407-
Message = "Account with id [$($TopdeskPerson.id)] and dynamicName [($($TopdeskPerson.dynamicName))] successfully updated and archived. Old values: $($accountChangedPropertiesObject.oldValues | ConvertTo-Json). New values: $($accountChangedPropertiesObject.newValues | ConvertTo-Json)"
394+
Message = "Account with id [$($TopdeskPerson.id)] and dynamicName [($($TopdeskPerson.dynamicName))] successfully updated and archived."
408395
IsError = $false
409396
})
410397
}

import.ps1

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#####################################################
2+
# HelloID-Conn-Prov-Target-Topdesk-Import
3+
# PowerShell V2
4+
#####################################################
5+
6+
# Enable TLS1.2
7+
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12
8+
9+
try {
10+
Write-Information 'Starting target account import'
11+
12+
$importFields = $($actionContext.ImportFields)
13+
14+
# Remove all '.' and the value behind. For example branch instead of branch.name
15+
$importFields = $importFields -replace '\..*', ''
16+
17+
# Gender is not a supported field to query (APIv2), if exists it will be filtered
18+
$importFields = $importFields | Where-Object { $_ -ne 'gender' }
19+
20+
# Add mandatory fields for HelloID to query and return
21+
if ('id' -notin $importFields) { $importFields += 'id' }
22+
if ('archived' -notin $importFields) { $importFields += 'archived' }
23+
if ('dynamicName' -notin $importFields) { $importFields += 'dynamicName' }
24+
if ('tasLoginName' -notin $importFields) { $importFields += 'tasLoginName' }
25+
26+
# Remove fields from other endpoints
27+
if ('privateDetails' -in $importFields) { $importFields = $importFields | Where-Object { $_ -ne 'privateDetails' } }
28+
if ('contract' -in $importFields) { $importFields = $importFields | Where-Object { $_ -ne 'contract' } }
29+
30+
# Convert to a ',' string
31+
$fields = $importFields -join ','
32+
Write-Information "Querying fields [$fields]"
33+
34+
# Create basic authentication string
35+
$username = $actionContext.Configuration.username
36+
$apikey = $actionContext.Configuration.apikey
37+
$bytes = [System.Text.Encoding]::ASCII.GetBytes("${username}:${apikey}")
38+
$base64 = [System.Convert]::ToBase64String($bytes)
39+
40+
# Set authentication headers
41+
$headers = [System.Collections.Generic.Dictionary[string, string]]::new()
42+
$headers.Add("Authorization", "BASIC $base64")
43+
$headers.Add('Accept', 'application/x.topdesk-collection-person-v2+json')
44+
45+
$existingAccounts = @()
46+
$pageSize = 5000
47+
$uri = "$($actionContext.Configuration.baseUrl)/tas/api/persons?pageStart=0&pageSize=$pageSize&fields=$fields"
48+
49+
do {
50+
$splatParams = @{
51+
Uri = $uri
52+
Headers = $headers
53+
Method = 'GET'
54+
ContentType = 'application/json; charset=utf-8'
55+
}
56+
57+
$partialResultUsers = Invoke-RestMethod @splatParams
58+
$existingAccounts += $partialResultUsers.item
59+
$uri = $partialResultUsers.next
60+
61+
Write-Information "Successfully queried [$($existingAccounts.count)] existing accounts"
62+
} while ($uri)
63+
64+
# Map the imported data to the account field mappings
65+
foreach ($account in $existingAccounts) {
66+
$enabled = $true
67+
# Convert archived to disabled
68+
if ($account.archived) {
69+
$enabled = $false
70+
}
71+
72+
# Make sure the DisplayName has a value
73+
if ([string]::IsNullOrEmpty($account.dynamicName)) {
74+
$account.dynamicName = $account.id
75+
}
76+
77+
# Make sure the Username has a value
78+
if ([string]::IsNullOrEmpty($account.tasLoginName)) {
79+
$account.tasLoginName = $account.id
80+
}
81+
82+
# Return the result
83+
Write-Output @{
84+
AccountReference = $account.id
85+
DisplayName = $account.dynamicName
86+
UserName = $account.tasLoginName
87+
Enabled = $enabled
88+
Data = $account
89+
}
90+
}
91+
Write-Information 'Target account import completed'
92+
}
93+
catch {
94+
$ex = $PSItem
95+
if ($($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or
96+
$($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) {
97+
98+
if (-Not [string]::IsNullOrEmpty($ex.ErrorDetails.Message)) {
99+
Write-Information "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.ErrorDetails.Message)"
100+
Write-Error "Could not import account entitlements. Error: $($ex.ErrorDetails.Message)"
101+
}
102+
else {
103+
Write-Information "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)"
104+
Write-Error "Could not import account entitlements. Error: $($ex.Exception.Message)"
105+
}
106+
}
107+
else {
108+
Write-Information "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)"
109+
Write-Error "Could not import account entitlements. Error: $($ex.Exception.Message)"
110+
}
111+
}

resources/branch/resources.ps1

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -189,60 +189,89 @@ try {
189189

190190
# Process
191191
foreach ($HelloIdBranch in $rRefSourceData) {
192-
193-
# Mapping how to create a branch. https://developers.topdesk.com/explorer/?page=supporting-files#/Branches/createBranch
194-
$branch = [PSCustomObject]@{
195-
name = $HelloIdBranch.name
196-
postalAddress = @{
197-
country = @{id = $country.id }
198-
}
199-
branchType = 'independentBranch' # valid values: 'independentBranch' 'headBranch' 'hasAHeadBranch'.
200-
}
201-
if (-not($TopdeskBranches.name -eq $branch.name)) {
202-
if (-not ($actionContext.DryRun -eq $true)) {
203-
Write-Information "Creating TOPdesk branch with the name [$($branch.name)] in TOPdesk..."
204-
# Create branch
205-
$splatParamsCreateBranch = @{
206-
Headers = $authHeaders
207-
BaseUrl = $actionContext.Configuration.baseUrl
208-
Branch = $branch
192+
try {
193+
# Mapping how to create a branch. https://developers.topdesk.com/explorer/?page=supporting-files#/Branches/createBranch
194+
$branch = [PSCustomObject]@{
195+
name = $HelloIdBranch.name
196+
postalAddress = @{
197+
country = @{id = $country.id }
209198
}
210-
$newBranch = New-TOPdeskBranch @splatParamsCreateBranch
199+
branchType = 'independentBranch' # valid values: 'independentBranch' 'headBranch' 'hasAHeadBranch'.
200+
}
201+
if (-not($TopdeskBranches.name -eq $branch.name)) {
202+
if (-not ($actionContext.DryRun -eq $true)) {
203+
Write-Information "Creating TOPdesk branch with the name [$($branch.name)] in TOPdesk..."
204+
# Create branch
205+
$splatParamsCreateBranch = @{
206+
Headers = $authHeaders
207+
BaseUrl = $actionContext.Configuration.baseUrl
208+
Branch = $branch
209+
}
210+
$newBranch = New-TOPdeskBranch @splatParamsCreateBranch
211211

212-
$outputContext.AuditLogs.Add([PSCustomObject]@{
213-
Action = "CreateResource"
214-
Message = "Created Topdesk branch with the name [$($newBranch.name)] and ID [$($newBranch.id)]"
215-
IsError = $false
216-
})
212+
$outputContext.AuditLogs.Add([PSCustomObject]@{
213+
Action = "CreateResource"
214+
Message = "Created Topdesk branch with the name [$($newBranch.name)] and ID [$($newBranch.id)]"
215+
IsError = $false
216+
})
217+
}
218+
else {
219+
Write-Warning "Preview: Would create Topdesk branch $($branch.name)"
220+
}
217221
}
218222
else {
219-
Write-Warning "Preview: Would create Topdesk branch $($branch.name)"
223+
Write-Information "Not creating branch [$($branch.name)] as it already exists in Topdesk"
220224
}
221225
}
222-
else {
223-
Write-Information "Not creating branch [$($branch.name)] as it already exists in Topdesk"
226+
catch {
227+
$ex = $PSItem
228+
229+
if ($($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or
230+
$($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) {
231+
if (-Not [string]::IsNullOrEmpty($ex.ErrorDetails.Message)) {
232+
$errorMessage = $($ex.ErrorDetails.Message)
233+
}
234+
else {
235+
$errorMessage = $($ex.Exception.Message)
236+
}
237+
$auditMessage = "Could not create branch [$($branch.name)]. Error: $($errorMessage)"
238+
Write-Warning "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($errorMessage)"
239+
}
240+
else {
241+
$auditMessage = "Could not create branch [$($branch.name)]. Error: $($ex.Exception.Message)"
242+
Write-Warning "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)"
243+
}
244+
$outputContext.AuditLogs.Add([PSCustomObject]@{
245+
Action = "CreateResource"
246+
Message = $auditMessage
247+
IsError = $true
248+
})
224249
}
225250
}
226251
}
227252
catch {
228253
$ex = $PSItem
229-
254+
230255
if ($($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or
231256
$($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) {
232-
$errorMessage = "Could not create branch [$($branch.name)]. Error: $($ex.Exception.Message) $($ex.ScriptStackTrace)"
257+
if (-Not [string]::IsNullOrEmpty($ex.ErrorDetails.Message)) {
258+
$errorMessage = $($ex.ErrorDetails.Message)
259+
}
260+
else {
261+
$errorMessage = $($ex.Exception.Message)
262+
}
263+
$auditMessage = "Could not create branches. Error: $($errorMessage)"
264+
Write-Warning "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($errorMessage)"
233265
}
234266
else {
235-
$errorMessage = "Could not create branch [$($branch.name)]. Error: $($ex.Exception.Message) $($ex.ScriptStackTrace)"
236-
}
237-
238-
# Only log when there are no lookup values, as these generate their own audit message
239-
if (-Not($ex.Exception.Message -eq 'Error(s) occured while looking up required values')) {
240-
$outputContext.AuditLogs.Add([PSCustomObject]@{
241-
Action = "CreateResource"
242-
Message = $errorMessage
243-
IsError = $true
244-
})
267+
$auditMessage = "Could not create branches. Error: $($ex.Exception.Message)"
268+
Write-Warning "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)"
245269
}
270+
$outputContext.AuditLogs.Add([PSCustomObject]@{
271+
Action = "CreateResource"
272+
Message = $auditMessage
273+
IsError = $true
274+
})
246275
}
247276
finally {
248277
# Check if auditLogs contains errors, if errors are found, set success to false

0 commit comments

Comments
 (0)