diff --git a/README.md b/README.md index 52ac4ba..6e8ec50 100644 --- a/README.md +++ b/README.md @@ -69,11 +69,6 @@ The following settings are required to connect to the API. ### Remarks #### General - In the account creation process, the newly created accounts are disabled afterward using an additional web call. This is necessary because the web service does not support creating accounts in a disabled state. However, if an account already exists, the connector does not perform a disable action to avoid unintended behavior. -- The connector utilizes a custom overview call to retrieve a list of employees. This is necessary because the original web call to retrieve employee data takes 30-60 seconds, which is not feasible in HelloID. The custom overview call includes an ID that may differ between customers, so it is added as a script variable in the Create action. - ```Powershell - # Script Configuration - $employeeOverviewId = '1000000' - ``` - The end date on the employee and account is informational. The employee account has an active/inactive state, and the user account can be either deleted or created. Therefore, the end date is not actively used unless an object already has an end date, in which case the end date is cleared. - The relationship between the employee and user is 1 = 1. The employee object is managed throughout its entire lifecycle, while the user object is only managed during Enable and Disable process. Where the object is Created or Deleted. - The password is a mandatory value according to the webservice, even if it is not utilized due to Single Sign-On (SSO) being in place. @@ -144,7 +139,6 @@ Besides the configuration tab, you can also configure script variables. To decid ``` Powershell # Script Configuration - $employeeOverviewId = '1000000' $departmentLookupProperty = { $_.Department.ExternalId } $titleLookupProperty = { $_.Title.ExternalId } ``` diff --git a/create.ps1 b/create.ps1 index 1e9f113..3894baf 100644 --- a/create.ps1 +++ b/create.ps1 @@ -1,7 +1,7 @@ ########################################### # HelloID-Conn-Prov-Target-Inception-Create # -# Version: 1.0.0 +# Version: 1.0.1 ########################################### # Initialize default values $config = $configuration | ConvertFrom-Json @@ -10,21 +10,66 @@ $success = $false $auditLogs = [System.Collections.Generic.List[PSCustomObject]]::new() # Script Configuration -$employeeOverviewId = '0000000' $departmentLookupProperty = { $_.Department.ExternalId } $titleLookupProperty = { $_.Title.ExternalId } +#Generate surname conform nameconvention +function Get-LastName { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [object] + $person + ) + + if ([string]::IsNullOrEmpty($person.Name.FamilyNamePrefix)) { + $prefix = "" + } + else { + $prefix = $person.Name.FamilyNamePrefix + " " + } + + if ([string]::IsNullOrEmpty($person.Name.FamilyNamePartnerPrefix)) { + $partnerPrefix = "" + } + else { + $partnerPrefix = $person.Name.FamilyNamePartnerPrefix + " " + } + + $Surname = switch ($person.Name.Convention) { + "B" { $person.Name.FamilyName } + "BP" { $person.Name.FamilyName + " - " + $partnerprefix + $person.Name.FamilyNamePartner } + "P" { $person.Name.FamilyNamePartner } + "PB" { $person.Name.FamilyNamePartner + " - " + $prefix + $person.Name.FamilyName } + default { $prefix + $person.Name.FamilyName } + } + + $Prefix = switch ($p.Name.Convention) { + "B" { $prefix } + "BP" { $prefix } + "P" { $partnerPrefix } + "PB" { $partnerPrefix } + default { $prefix } + } + $output = [PSCustomObject]@{ + surname = $Surname + prefixes = $Prefix.Trim() + } + Write-Output $output +} + # Employee Account mapping $account = [PSCustomObject]@{ staffnumber = $p.ExternalId - firstname = $p.Name.GivenName - lastname = $p.Name.FamilyName - middlename = $p.Name.FamilyNamePrefix + firstname = $p.Name.NickName + lastname = (Get-LastName -Person $p).surname + middlename = (Get-LastName -Person $p).prefixes initials = $p.Name.Initials email = $p.Contact.Business.Email phone = $p.Contact.Business.Phone.Fixed - dateofbirth = $p.Details.BirthDate - startdate = $p.PrimaryContract.StartDate + dateofbirth = if ($null -ne $p.Details.BirthDate ) { '{0:yyyy-MM-dd}' -f ([datetime]$p.Details.BirthDate) }; + startdate = if ($null -ne $p.PrimaryContract.StartDate ) { '{0:yyyy-MM-dd}' -f ([datetime]$p.PrimaryContract.StartDate) }; enddate = $null # ((Get-Date).AddDays(-1)) positionsPerOrgUnits = @() } @@ -61,7 +106,8 @@ function Get-InceptionToken { $tokenResponse = Invoke-RestMethod @splatTokenParams -Verbose:$false Write-Output $tokenResponse.Token - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -105,14 +151,16 @@ function Update-PositionsPerOrgUnitsList { if ($DryRunFlag -eq $true) { Write-Warning "[DryRun] Added Position [OrgUnit: $($currentObject.orgunitid) Position: $($currentObject.positionid)]" } - } else { + } + else { if ($DryRunFlag -eq $true) { Write-Warning "[DryRun] Calculated Position already exists [OrgUnit: $($currentObject.orgunitid) Position: $($currentObject.positionid)]" } } - } elseif ($compareResultItem.SideIndicator -eq '<=' ) { + } + elseif ($compareResultItem.SideIndicator -eq '<=' ) { $itemToRemove = $CurrentPositionsInInception | Where-Object { $_.positionid -eq $compareResultItem.positionid -and $_.orgunitid -eq $compareResultItem.orgunitid } if (-not [string]::IsNullOrEmpty($itemToRemove)) { Write-Verbose "Removing Position [$($itemToRemove)] from Employee" @@ -121,7 +169,8 @@ function Update-PositionsPerOrgUnitsList { Message = "Removed Position [OrgUnit: $($compareResultItem.OrgunitName) Position: $($compareResultItem.PositionName)]" IsError = $false }) - } else { + } + else { if ($DryRunFlag -eq $true) { Write-Warning "[DryRun] Previously assigned Position [$($compareResultItem | Select-Object * -ExcludeProperty SideIndicator)] is already removed from Employee" } @@ -129,7 +178,8 @@ function Update-PositionsPerOrgUnitsList { } } Write-Output $CurrentPositionsInInception | Select-Object positionid, orgunitid - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -153,7 +203,8 @@ function Resolve-InceptionError { $httpErrorObj.ErrorDetails = $ErrorObject.ErrorDetails $httpErrorObj.FriendlyMessage = $ErrorObject.ErrorDetails $webresponse = $true - } elseif ((-not($null -eq $ErrorObject.Exception.Response) -and $ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException')) { + } + elseif ((-not($null -eq $ErrorObject.Exception.Response) -and $ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException')) { $streamReaderResponse = [System.IO.StreamReader]::new($ErrorObject.Exception.Response.GetResponseStream()).ReadToEnd() if (-not([string]::IsNullOrWhiteSpace($streamReaderResponse))) { $httpErrorObj.ErrorDetails = $streamReaderResponse @@ -166,10 +217,12 @@ function Resolve-InceptionError { $convertedErrorObject = ($httpErrorObj.FriendlyMessage | ConvertFrom-Json) if (-not [string]::IsNullOrEmpty($convertedErrorObject.languageString)) { $httpErrorObj.FriendlyMessage = $convertedErrorObject.LanguageString - } elseif (-not [string]::IsNullOrEmpty($convertedErrorObject.description)) { + } + elseif (-not [string]::IsNullOrEmpty($convertedErrorObject.description)) { $httpErrorObj.FriendlyMessage = $convertedErrorObject.Description } - } catch { + } + catch { Write-Warning "Unexpected webservice response, Error during Json conversion: $($_.Exception.Message)" } } @@ -204,7 +257,8 @@ function Get-InceptionPosition { }until ( $positions.count -eq $positionsResponse.total ) Write-Output $positions - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -236,7 +290,8 @@ function Get-InceptionOrgunit { }until ( $orgunits.count -eq $orgunitsResponse.total ) Write-Output $orgunits - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -318,7 +373,8 @@ function Get-InceptionIdsFromHelloIdContact { throw 'No Position Found' } Write-Output $desiredPositionList - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -338,21 +394,23 @@ try { Write-Verbose 'Determine if account already exists' $splatUserParams = @{ - Uri = "$($config.BaseUrl)/api/v2/overviews/$($employeeOverviewId)?[fld_3020]=$($account.staffnumber)" + Uri = "$($config.BaseUrl)/api/v2/hrm/employees/staffnumber/$($account.staffnumber)" Method = 'GET' Headers = $headers } + $resultGetEmployee = Invoke-RestMethod @splatUserParams -Verbose:$false - + # To prevent correlation of multiple accounts the following fix is added. This is becauce the staffnumber is not a unique proeprty in Inception # So you could end up with multiple accounts with the same staffnumber which is our correlation property. - if ($resultGetEmployee.total -gt 1) { - $activeEmployeeAccountFound = $resultGetEmployee.overviews | Where-Object { $_.values.label -eq 'status' -and $_.values.value -eq 20 } - if ($activeEmployeeAccountFound.count -gt 1) { + if ($($resultGetEmployee.total) -gt 1) { + $activeEmployeeAccountFound = $resultGetEmployee.items | Where-Object { $_.state -eq '20' } + if (($activeEmployeeAccountFound | ConvertTo-Json ).count -gt 1) { throw "More than one active account was found for [$($account.staffnumber)]. Please remove the obsolete account(s) or make sure that the obsolete account(s) are disabled and enable only the correct account." } } - $aRef = ($resultGetEmployee.overviews | Select-Object -First 1).id + + $aRef = ($resultGetEmployee.items | Select-Object -First 1).id if (-not [string]::IsNullOrEmpty($aRef)) { try { @@ -362,7 +420,8 @@ try { Headers = $headers } $employee = Invoke-RestMethod @splatUserParams -Verbose:$false # Exception if not found - } catch { + } + catch { if ( $_.Exception.message -notmatch '404' ) { throw $_ } @@ -396,14 +455,16 @@ try { DesiredPositions = $desiredPositionList CurrentPositionsInInception = $account.positionsPerOrgUnits } - } elseif ($updatePerson -eq $true) { + } + elseif ($updatePerson -eq $true) { $action = 'Update-Correlate' $splatPositionsPerOrgUnitsList = @{ AccountReference = ([System.Collections.Generic.list[object]]::new()) DesiredPositions = $desiredPositionList CurrentPositionsInInception = $employee.positionsPerOrgUnits } - } else { + } + else { $splatPositionsPerOrgUnitsList = @{ AccountReference = ([System.Collections.Generic.list[object]]::new()) DesiredPositions = $desiredPositionList @@ -465,7 +526,8 @@ try { if ($null -eq (Compare-Object $account.positionsPerOrgUnits $employee.positionsPerOrgUnits)) { Write-Verbose 'Correlating Inception Employee account' Write-Verbose 'No position update required' - } else { + } + else { Write-Verbose 'Updating Position and correlating Inception Employee account' $splatEmployeeUpdateParams = @{ Uri = "$($config.BaseUrl)/api/v2/hrm/employees/$($aRef)" @@ -486,15 +548,18 @@ try { IsError = $false }) -} catch { +} +catch { $success = $false $ex = $PSItem + Write-Verbose -Verbose $ex.Exception.Message if ($($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or $($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) { $errorObj = Resolve-InceptionError -ErrorObject $ex $auditMessage = "Could not $action Inception Employee account. Error: $($errorObj.FriendlyMessage)" Write-Verbose "Error at Line '$($errorObj.ScriptLineNumber)': $($errorObj.Line). Error: $($errorObj.ErrorDetails)" - } else { + } + else { $auditMessage = "Could not $action Inception Employee account. Error: $($ex.Exception.Message)" Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)" } @@ -506,7 +571,8 @@ try { IsError = $true }) # End -} finally { +} +finally { $accountReferenceObject = @{ AccountReference = $aRef Positions = $desiredPositionList | Select-Object * -ExcludeProperty SideIndicator @@ -517,7 +583,11 @@ try { AccountReference = $accountReferenceObject Auditlogs = $auditLogs Account = $account + ExportData = [PSCustomObject]@{ + Id = $aRef + staffNumber = $($account.staffnumber) + } } + Write-Output $result | ConvertTo-Json -Depth 10 } - diff --git a/delete.ps1 b/delete.ps1 index d39a0a7..1cc95f6 100644 --- a/delete.ps1 +++ b/delete.ps1 @@ -1,7 +1,7 @@ ########################################### # HelloID-Conn-Prov-Target-Inception-Delete # -# Version: 1.0.0 +# Version: 1.0.1 ########################################### # Initialize default values $config = $configuration | ConvertFrom-Json @@ -39,7 +39,8 @@ function Get-InceptionToken { $tokenResponse = Invoke-RestMethod @splatTokenParams -Verbose:$false Write-Output $tokenResponse.Token - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -83,14 +84,16 @@ function Update-PositionsPerOrgUnitsList { if ($DryRunFlag -eq $true) { Write-Warning "[DryRun] Added Position [OrgUnit: $($currentObject.orgunitid) Position: $($currentObject.positionid)]" } - } else { + } + else { if ($DryRunFlag -eq $true) { Write-Warning "[DryRun] Calculated Position already exists [OrgUnit: $($currentObject.orgunitid) Position: $($currentObject.positionid)]" } } - } elseif ($compareResultItem.SideIndicator -eq '<=' ) { + } + elseif ($compareResultItem.SideIndicator -eq '<=' ) { $itemToRemove = $CurrentPositionsInInception | Where-Object { $_.positionid -eq $compareResultItem.positionid -and $_.orgunitid -eq $compareResultItem.orgunitid } if (-not [string]::IsNullOrEmpty($itemToRemove)) { Write-Verbose "Removing Position [$($itemToRemove)] from Employee" @@ -99,7 +102,8 @@ function Update-PositionsPerOrgUnitsList { Message = "Removed Position [OrgUnit: $($compareResultItem.OrgunitName) Position: $($compareResultItem.PositionName)]" IsError = $false }) - } else { + } + else { if ($DryRunFlag -eq $true) { Write-Warning "[DryRun] Previously assigned Position [$($compareResultItem | Select-Object * -ExcludeProperty SideIndicator)] is already removed from Employee" } @@ -107,7 +111,8 @@ function Update-PositionsPerOrgUnitsList { } } Write-Output $CurrentPositionsInInception | Select-Object positionid, orgunitid - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -131,7 +136,8 @@ function Resolve-InceptionError { $httpErrorObj.ErrorDetails = $ErrorObject.ErrorDetails $httpErrorObj.FriendlyMessage = $ErrorObject.ErrorDetails $webresponse = $true - } elseif ((-not($null -eq $ErrorObject.Exception.Response) -and $ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException')) { + } + elseif ((-not($null -eq $ErrorObject.Exception.Response) -and $ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException')) { $streamReaderResponse = [System.IO.StreamReader]::new($ErrorObject.Exception.Response.GetResponseStream()).ReadToEnd() if (-not([string]::IsNullOrWhiteSpace($streamReaderResponse))) { $httpErrorObj.ErrorDetails = $streamReaderResponse @@ -144,10 +150,12 @@ function Resolve-InceptionError { $convertedErrorObject = ($httpErrorObj.FriendlyMessage | ConvertFrom-Json) if (-not [string]::IsNullOrEmpty($convertedErrorObject.languageString)) { $httpErrorObj.FriendlyMessage = $convertedErrorObject.LanguageString - } elseif (-not [string]::IsNullOrEmpty($convertedErrorObject.description)) { + } + elseif (-not [string]::IsNullOrEmpty($convertedErrorObject.description)) { $httpErrorObj.FriendlyMessage = $convertedErrorObject.Description } - } catch { + } + catch { Write-Warning "Unexpected webservice response, Error during Json conversion: $($_.Exception.Message)" } } @@ -176,7 +184,8 @@ try { } $employee = Invoke-RestMethod @splatUserParams -Verbose:$false $employeeFound = $true - } catch { + } + catch { if ( $_.Exception.message -notmatch '404' ) { throw $_ } @@ -185,7 +194,8 @@ try { if ($employeeFound) { $action = 'Found' $dryRunMessage = "Delete Inception account for: [$($p.DisplayName)] will be executed during enforcement" - } elseif (-not $employeeFound) { + } + elseif (-not $employeeFound) { $action = 'NotFound' $dryRunMessage = "Inception account for: [$($p.DisplayName)] not found. Possibly already deleted. Skipping action" } @@ -242,7 +252,8 @@ try { Message = 'Disable Inception Employee and Delete user account was successful' IsError = $false }) - } else { + } + else { $auditLogs.Add([PSCustomObject]@{ Message = 'Disable Inception Employee and Delete user account already processed' IsError = $false @@ -262,7 +273,8 @@ try { $success = $true } -} catch { +} +catch { $success = $false $ex = $PSItem if ($($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or @@ -270,7 +282,8 @@ try { $errorObj = Resolve-InceptionError -ErrorObject $ex $auditMessage = "Could not delete Inception account. Error: $($errorObj.FriendlyMessage)" Write-Verbose "Error at Line '$($errorObj.ScriptLineNumber)': $($errorObj.Line). Error: $($errorObj.ErrorDetails)" - } else { + } + else { $auditMessage = "Could not delete Inception account. Error: $($ex.Exception.Message)" Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)" } @@ -279,7 +292,8 @@ try { IsError = $true }) # End -} finally { +} +finally { $result = [PSCustomObject]@{ Success = $success Auditlogs = $auditLogs diff --git a/disable.ps1 b/disable.ps1 index ece9f7d..ab981f4 100644 --- a/disable.ps1 +++ b/disable.ps1 @@ -1,7 +1,7 @@ ############################################ # HelloID-Conn-Prov-Target-Inception-Disable # -# Version: 1.0.0 +# Version: 1.0.1 ############################################ # Initialize default values $config = $configuration | ConvertFrom-Json @@ -39,7 +39,8 @@ function Get-InceptionToken { $tokenResponse = Invoke-RestMethod @splatTokenParams -Verbose:$false Write-Output $tokenResponse.Token - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -63,7 +64,8 @@ function Resolve-InceptionError { $httpErrorObj.ErrorDetails = $ErrorObject.ErrorDetails $httpErrorObj.FriendlyMessage = $ErrorObject.ErrorDetails $webresponse = $true - } elseif ((-not($null -eq $ErrorObject.Exception.Response) -and $ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException')) { + } + elseif ((-not($null -eq $ErrorObject.Exception.Response) -and $ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException')) { $streamReaderResponse = [System.IO.StreamReader]::new($ErrorObject.Exception.Response.GetResponseStream()).ReadToEnd() if (-not([string]::IsNullOrWhiteSpace($streamReaderResponse))) { $httpErrorObj.ErrorDetails = $streamReaderResponse @@ -76,10 +78,12 @@ function Resolve-InceptionError { $convertedErrorObject = ($httpErrorObj.FriendlyMessage | ConvertFrom-Json) if (-not [string]::IsNullOrEmpty($convertedErrorObject.languageString)) { $httpErrorObj.FriendlyMessage = $convertedErrorObject.LanguageString - } elseif (-not [string]::IsNullOrEmpty($convertedErrorObject.description)) { + } + elseif (-not [string]::IsNullOrEmpty($convertedErrorObject.description)) { $httpErrorObj.FriendlyMessage = $convertedErrorObject.Description } - } catch { + } + catch { Write-Warning "Unexpected webservice response, Error during Json conversion: $($_.Exception.Message)" } } @@ -108,7 +112,8 @@ try { } $null = Invoke-RestMethod @splatUserParams -Verbose:$false $employeeFound = $true - } catch { + } + catch { if ( $_.Exception.message -notmatch '404' ) { throw $_ } @@ -117,7 +122,8 @@ try { if ($employeeFound) { $action = 'Found' $dryRunMessage = "Disable Inception Employee account for: [$($p.DisplayName)] will be executed during enforcement" - } elseif (-not $employeeFound) { + } + elseif (-not $employeeFound) { $action = 'NotFound' $dryRunMessage = "Inception Employee account for: [$($p.DisplayName)] not found. Possibly already deleted. Skipping action" } @@ -156,7 +162,8 @@ try { } $success = $true } -} catch { +} +catch { $success = $false $ex = $PSItem if ($($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or @@ -164,7 +171,8 @@ try { $errorObj = Resolve-InceptionError -ErrorObject $ex $auditMessage = "Could not disable Inception account. Error: $($errorObj.FriendlyMessage)" Write-Verbose "Error at Line '$($errorObj.ScriptLineNumber)': $($errorObj.Line). Error: $($errorObj.ErrorDetails)" - } else { + } + else { $auditMessage = "Could not disable Inception account. Error: $($ex.Exception.Message)" Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)" } @@ -176,7 +184,8 @@ try { IsError = $true }) # End -} finally { +} +finally { $result = [PSCustomObject]@{ Success = $success Auditlogs = $auditLogs diff --git a/enable.ps1 b/enable.ps1 index bb7874a..0bf3f7e 100644 --- a/enable.ps1 +++ b/enable.ps1 @@ -1,7 +1,7 @@ ########################################### # HelloID-Conn-Prov-Target-Inception-Enable # -# Version: 1.0.0 +# Version: 1.0.1 ########################################### # Initialize default values $config = $configuration | ConvertFrom-Json @@ -10,13 +10,6 @@ $aRef = $AccountReference | ConvertFrom-Json $success = $false $auditLogs = [System.Collections.Generic.List[PSCustomObject]]::new() -$accountUser = [PSCustomObject]@{ - id = $aRef.AccountReference - type = 14 - name = $p.Contact.Business.Email # UPN - password = 'M*U*&)H)*&hasdc32' # Only used with new creations. Proprety is mandatory but is not used when using SSO. -} - # Enable TLS1.2 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 @@ -26,6 +19,11 @@ switch ($($config.IsDebug)) { $false { $VerbosePreference = 'SilentlyContinue' } } +function Get-RandomCharacters($length, $characters) { + $random = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length } + return [String]$characters[$random] +} + #region functions function Get-InceptionToken { [CmdletBinding()] @@ -46,7 +44,8 @@ function Get-InceptionToken { $tokenResponse = Invoke-RestMethod @splatTokenParams -Verbose:$false Write-Output $tokenResponse.Token - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -70,7 +69,8 @@ function Resolve-InceptionError { $httpErrorObj.ErrorDetails = $ErrorObject.ErrorDetails $httpErrorObj.FriendlyMessage = $ErrorObject.ErrorDetails $webresponse = $true - } elseif ((-not($null -eq $ErrorObject.Exception.Response) -and $ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException')) { + } + elseif ((-not($null -eq $ErrorObject.Exception.Response) -and $ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException')) { $streamReaderResponse = [System.IO.StreamReader]::new($ErrorObject.Exception.Response.GetResponseStream()).ReadToEnd() if (-not([string]::IsNullOrWhiteSpace($streamReaderResponse))) { $httpErrorObj.ErrorDetails = $streamReaderResponse @@ -83,10 +83,12 @@ function Resolve-InceptionError { $convertedErrorObject = ($httpErrorObj.FriendlyMessage | ConvertFrom-Json) if (-not [string]::IsNullOrEmpty($convertedErrorObject.languageString)) { $httpErrorObj.FriendlyMessage = $convertedErrorObject.LanguageString - } elseif (-not [string]::IsNullOrEmpty($convertedErrorObject.description)) { + } + elseif (-not [string]::IsNullOrEmpty($convertedErrorObject.description)) { $httpErrorObj.FriendlyMessage = $convertedErrorObject.Description } - } catch { + } + catch { Write-Warning "Unexpected webservice response, Error during Json conversion: $($_.Exception.Message)" } } @@ -95,6 +97,13 @@ function Resolve-InceptionError { } #endregion +$accountUser = [PSCustomObject]@{ + id = $aRef.AccountReference + type = 14 + name = $p.Contact.Business.Email # UPN + password = (Get-RandomCharacters -length 10 -characters 'abcdefghijklmnopqrstuvwxyzABCDEFGHKLMNOPRSTUVWXYZ1234567890!@#%&+{}') # Only used with new creations. Proprety is mandatory but is not used when using SSO. +} + # Begin try { Write-Verbose "Verifying if a Inception account for [$($p.DisplayName)] exists" @@ -117,7 +126,8 @@ try { } $employee = Invoke-RestMethod @splatUserParams -Verbose:$false $employeeFound = $true - } catch { + } + catch { if ( $_.Exception.message -notmatch '404' ) { throw $_ } @@ -134,7 +144,8 @@ try { } $user = Invoke-RestMethod @splatUserParams -Verbose:$false $userFound = $true - } catch { + } + catch { if ( $_.Exception.message -notmatch '404' ) { throw $_ } @@ -147,7 +158,8 @@ try { Write-Warning "[DryRun] Enable Inception Employee account for: [$($p.DisplayName)] will be executed during enforcement" if ($userFound) { Write-Warning "[DryRun] Enable Inception User account for: [$($p.DisplayName)] will be executed during enforcement" - } else { + } + else { Write-Warning "[DryRun] Create Inception User account for: [$($p.DisplayName)] will be executed during enforcement" } } @@ -184,7 +196,8 @@ try { } $user = Invoke-RestMethod @splatEnableUser -Verbose:$false $userAuditMessage = 'Enable User account' - } else { + } + else { Write-Verbose 'Creating new User account' $splatNewUser = @{ Uri = "$($config.BaseUrl)/api/v2/security/users" @@ -201,7 +214,8 @@ try { }) } $success = $true -} catch { +} +catch { $success = $false $ex = $PSItem if ($($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or @@ -209,7 +223,8 @@ try { $errorObj = Resolve-InceptionError -ErrorObject $ex $auditMessage = "Could not enable Inception account. Error: $($errorObj.FriendlyMessage)" Write-Verbose "Error at Line '$($errorObj.ScriptLineNumber)': $($errorObj.Line). Error: $($errorObj.ErrorDetails)" - } else { + } + else { $auditMessage = "Could not enable Inception account. Error: $($ex.Exception.Message)" Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)" } @@ -221,10 +236,11 @@ try { IsError = $true }) # End -} finally { +} +finally { $result = [PSCustomObject]@{ Success = $success Auditlogs = $auditLogs } Write-Output $result | ConvertTo-Json -Depth 10 -} +} \ No newline at end of file diff --git a/resource.ps1 b/resource.ps1 index 75bc240..fb78d9b 100644 --- a/resource.ps1 +++ b/resource.ps1 @@ -1,7 +1,7 @@ ############################################# # HelloID-Conn-Prov-Target-Inception-Resource # -# Version: 1.0.0 +# Version: 1.0.1 ############################################# # Initialize default values $config = $configuration | ConvertFrom-Json @@ -9,7 +9,13 @@ $rRef = $resourceContext | ConvertFrom-Json $success = $false $auditLogs = [System.Collections.Generic.List[PSCustomObject]]::new() -$rRef.sourceData = ($rRef.sourceData | Where-Object { -not [string]::IsNullOrEmpty($_.DepartmentCode) }) +# Mapping Custom Fields in case of not using Github example customfieldnames +$DepartmentCode = 'DepartmentCode' +$DepartmentDescription = 'DepartmentDescription' +$TitleCode = 'TitleCode' +$TitleDescription = 'TitleDescription' + +$rRef.sourceData = ($rRef.sourceData | Where-Object { -not [string]::IsNullOrEmpty($_.$DepartmentCode) }) # Enable TLS1.2 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 @@ -166,12 +172,12 @@ try { $positions = Get-InceptionPositions -Headers $headers foreach ($orgUnit in $rRef.sourceData) { - $targetOrgUnit = ($OrgUnits | Where-Object -Property code -eq $orgunit.DepartmentCode) + $targetOrgUnit = ($OrgUnits | Where-Object -Property code -eq $orgunit.$DepartmentCode) if ($targetOrgUnit.count -eq 0) { $body = @{ - code = $orgUnit.DepartmentCode - name = $orgUnit.DepartmentCode - description = $orgUnit.DepartmentName + code = $orgUnit.$DepartmentCode + name = $orgUnit.$DepartmentDescription + description = $orgUnit.$DepartmentDescription state = 20 } $splatCreateOrgunitParams = @{ @@ -185,11 +191,11 @@ try { $OrgUnits += $orgunitsResponse $auditLogs.Add([PSCustomObject]@{ - Message = "Created orgUnit: [$($orgUnit.DepartmentCode)]" + Message = "Created orgUnit: [$($orgUnit.$DepartmentCode)]" IsError = $false }) } else { - $orgUnit = $OrgUnits | Where-Object -Property code -eq $orgunit.DepartmentCode + $orgUnit = $OrgUnits | Where-Object -Property code -eq $orgunit.$DepartmentCode if ($orgUnit.state -ne 20) { $body = @{ state = 20 @@ -208,21 +214,21 @@ try { } $groupedOrgUnits = $OrgUnits | Group-Object -Property code -AsString -AsHashTable - $groupedPositions = $rRef.SourceData | Group-Object -Property TitleCode -AsString -AsHashTable + $groupedPositions = $rRef.SourceData | Group-Object -Property $TitleCode -AsString -AsHashTable foreach ($key in $groupedPositions.Keys) { $targetPosition = $positions | Where-Object -Property code -eq $key $departmentIds = @() foreach ($department in $groupedPositions[$key]) { - $departmentIds += ($groupedOrgUnits[$department.DepartmentCode].id) + $departmentIds += ($groupedOrgUnits[$department.$DepartmentCode].id) } $departmentIds = [array] ($departmentIds | Select-Object -Unique) $currentPosition = $groupedPositions[$key] | Select-Object -First 1 if ($targetPosition.count -eq 0) { $body = @{ - code = $currentPosition.TitleCode - name = $currentPosition.TitleCode - description = $currentPosition.TitleDescription + code = $currentPosition.$TitleCode + name = $currentPosition.$TitleDescription + description = $currentPosition.$TitleDescription state = 20 belongstoorgunits = $departmentIds } @@ -238,7 +244,7 @@ try { $positions += $positionsResponse $auditLogs.Add([PSCustomObject]@{ - Message = "Created position: [$($currentPosition.TitleCode)] with orgunits: [$($departmentIds)]" + Message = "Created position: [$($currentPosition.$TitleCode)] with orgunits: [$($departmentIds)]" IsError = $false }) } else { @@ -260,7 +266,7 @@ try { } $positionsResponse = Invoke-RestMethod @splatUpdatePositionsParams -Verbose:$false # Exception not found $auditLogs.Add([PSCustomObject]@{ - Message = "Added orgunits: [$($allOrgUnitIdsForPosition)] to position: [$($currentPosition.TitleCode)]" + Message = "Added orgunits: [$($allOrgUnitIdsForPosition)] to position: [$($currentPosition.$TitleCode)]" IsError = $false }) @@ -279,7 +285,7 @@ try { } $positionsResponse = Invoke-RestMethod @splatUpdatePositionsParams -Verbose:$false # Exception not found } else { - Write-Verbose 'skipping action orgunit already added to position' + Write-Verbose "skipping action orgunit already added to position [$($currentPosition.$TitleCode)]" } } } @@ -327,4 +333,4 @@ try { Auditlogs = $auditLogs } Write-Output $result | ConvertTo-Json -Depth 10 -} +} \ No newline at end of file diff --git a/update.ps1 b/update.ps1 index f72e295..21a0800 100644 --- a/update.ps1 +++ b/update.ps1 @@ -1,7 +1,7 @@ ########################################### # HelloID-Conn-Prov-Target-Inception-Update # -# Version: 1.0.0 +# Version: 1.0.1 ########################################### # Initialize default values $config = $configuration | ConvertFrom-Json @@ -10,6 +10,52 @@ $aRef = $AccountReference | ConvertFrom-Json $success = $false $auditLogs = [System.Collections.Generic.List[PSCustomObject]]::new() +#Generate surname conform nameconvention +function Get-LastName { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [object] + $person + ) + + if ([string]::IsNullOrEmpty($person.Name.FamilyNamePrefix)) { + $prefix = "" + } + else { + $prefix = $person.Name.FamilyNamePrefix + " " + } + + if ([string]::IsNullOrEmpty($person.Name.FamilyNamePartnerPrefix)) { + $partnerPrefix = "" + } + else { + $partnerPrefix = $person.Name.FamilyNamePartnerPrefix + " " + } + + $Surname = switch ($person.Name.Convention) { + "B" { $person.Name.FamilyName } + "BP" { $person.Name.FamilyName + " - " + $partnerprefix + $person.Name.FamilyNamePartner } + "P" { $person.Name.FamilyNamePartner } + "PB" { $person.Name.FamilyNamePartner + " - " + $prefix + $person.Name.FamilyName } + default { $prefix + $person.Name.FamilyName } + } + + $Prefix = switch ($p.Name.Convention) { + "B" { $prefix } + "BP" { $prefix } + "P" { $partnerPrefix } + "PB" { $partnerPrefix } + default { $prefix } + } + $output = [PSCustomObject]@{ + surname = $Surname + prefixes = $Prefix.Trim() + } + Write-Output $output +} + # Script Configuration $departmentLookupProperty = { $_.Department.ExternalId } $titleLookupProperty = { $_.Title.ExternalId } @@ -17,9 +63,9 @@ $titleLookupProperty = { $_.Title.ExternalId } # Employee Account mapping $account = [PSCustomObject]@{ staffnumber = $p.ExternalId - firstname = $p.Name.GivenName - lastname = $p.Name.FamilyName - middlename = $p.Name.FamilyNamePrefix + firstname = $p.Name.NickName + lastname = (Get-LastName -Person $p).surname + middlename = (Get-LastName -Person $p).prefixes initials = $p.Name.Initials email = $p.Contact.Business.Email phone = $p.Contact.Business.Phone.Fixed @@ -58,7 +104,8 @@ function Get-InceptionToken { $tokenResponse = Invoke-RestMethod @splatTokenParams -Verbose:$false Write-Output $tokenResponse.Token - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -102,14 +149,16 @@ function Update-PositionsPerOrgUnitsList { if ($DryRunFlag -eq $true) { Write-Warning "[DryRun] Added Position [OrgUnit: $($currentObject.orgunitid) Position: $($currentObject.positionid)]" } - } else { + } + else { if ($DryRunFlag -eq $true) { Write-Warning "[DryRun] Calculated Position already exists [OrgUnit: $($currentObject.orgunitid) Position: $($currentObject.positionid)]" } } - } elseif ($compareResultItem.SideIndicator -eq '<=' ) { + } + elseif ($compareResultItem.SideIndicator -eq '<=' ) { $itemToRemove = $CurrentPositionsInInception | Where-Object { $_.positionid -eq $compareResultItem.positionid -and $_.orgunitid -eq $compareResultItem.orgunitid } if (-not [string]::IsNullOrEmpty($itemToRemove)) { Write-Verbose "Removing Position [$($itemToRemove)] from Employee" @@ -118,7 +167,8 @@ function Update-PositionsPerOrgUnitsList { Message = "Removed Position [OrgUnit: $($compareResultItem.OrgunitName) Position: $($compareResultItem.PositionName)]" IsError = $false }) - } else { + } + else { if ($DryRunFlag -eq $true) { Write-Warning "[DryRun] Previously assigned Position [$($compareResultItem | Select-Object * -ExcludeProperty SideIndicator)] is already removed from Employee" } @@ -126,7 +176,8 @@ function Update-PositionsPerOrgUnitsList { } } Write-Output $CurrentPositionsInInception | Select-Object positionid, orgunitid - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -150,7 +201,8 @@ function Resolve-InceptionError { $httpErrorObj.ErrorDetails = $ErrorObject.ErrorDetails $httpErrorObj.FriendlyMessage = $ErrorObject.ErrorDetails $webresponse = $true - } elseif ((-not($null -eq $ErrorObject.Exception.Response) -and $ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException')) { + } + elseif ((-not($null -eq $ErrorObject.Exception.Response) -and $ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException')) { $streamReaderResponse = [System.IO.StreamReader]::new($ErrorObject.Exception.Response.GetResponseStream()).ReadToEnd() if (-not([string]::IsNullOrWhiteSpace($streamReaderResponse))) { $httpErrorObj.ErrorDetails = $streamReaderResponse @@ -163,10 +215,12 @@ function Resolve-InceptionError { $convertedErrorObject = ($httpErrorObj.FriendlyMessage | ConvertFrom-Json) if (-not [string]::IsNullOrEmpty($convertedErrorObject.languageString)) { $httpErrorObj.FriendlyMessage = $convertedErrorObject.LanguageString - } elseif (-not [string]::IsNullOrEmpty($convertedErrorObject.description)) { + } + elseif (-not [string]::IsNullOrEmpty($convertedErrorObject.description)) { $httpErrorObj.FriendlyMessage = $convertedErrorObject.Description } - } catch { + } + catch { Write-Warning "Unexpected webservice response, Error during Json conversion: $($_.Exception.Message)" } } @@ -201,7 +255,8 @@ function Get-InceptionPosition { }until ( $positions.count -eq $positionsResponse.total ) Write-Output $positions - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -233,7 +288,8 @@ function Get-InceptionOrgunit { }until ( $orgunits.count -eq $orgunitsResponse.total ) Write-Output $orgunits - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -315,7 +371,8 @@ function Get-InceptionIdsFromHelloIdContact { throw 'No Position Found' } Write-Output $desiredPositionList - } catch { + } + catch { $PSCmdlet.ThrowTerminatingError($_) } } @@ -341,7 +398,8 @@ try { } $employee = Invoke-RestMethod @splatUserParams -Verbose:$false $employeeFound = $true - } catch { + } + catch { if ( $_.Exception.message -notmatch '404' ) { throw $_ } @@ -386,17 +444,20 @@ try { if ($null -ne (Compare-Object $positionsPerOrgUnits $employee.positionsPerOrgUnits)) { Write-Verbose 'Position update required' $positionsAction = 'Update' - } else { + } + else { $positionsAction = 'NoChanges' } if (($propertiesChanged -or $positionsAction -eq 'Update' ) -and ($employeeFound)) { $action = 'Update' $dryRunMessage = "Account property(s) required to update: [$($propertiesChanged.name -join ',')]" - } elseif (-not($propertiesChanged) -and ($employeeFound)) { + } + elseif (-not($propertiesChanged) -and ($employeeFound)) { $action = 'NoChanges' $dryRunMessage = 'No changes will be made to the account during enforcement' - } elseif (-not $employeeFound) { + } + elseif (-not $employeeFound) { $action = 'NotFound' $dryRunMessage = "Inception account for: [$($p.DisplayName)] not found. Possibly deleted" } @@ -461,7 +522,8 @@ try { } } } -} catch { +} +catch { $success = $false $ex = $PSItem if ($($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or @@ -469,7 +531,8 @@ try { $errorObj = Resolve-InceptionError -ErrorObject $ex $auditMessage = "Could not update Inception account. Error: $($errorObj.FriendlyMessage)" Write-Verbose "Error at Line '$($errorObj.ScriptLineNumber)': $($errorObj.Line). Error: $($errorObj.ErrorDetails)" - } else { + } + else { $auditMessage = "Could not update Inception account. Error: $($ex.Exception.Message)" Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)" } @@ -478,7 +541,8 @@ try { IsError = $true }) # End -} finally { +} +finally { $accountReferenceObject = @{ AccountReference = $aRef.AccountReference Positions = $desiredPositionList | Select-Object * -ExcludeProperty SideIndicator @@ -493,4 +557,4 @@ try { } Write-Output $result | ConvertTo-Json -Depth 10 -} +} \ No newline at end of file