diff --git a/.github/workflows/createRelease.yaml b/.github/workflows/createRelease.yaml new file mode 100644 index 0000000..472a1f6 --- /dev/null +++ b/.github/workflows/createRelease.yaml @@ -0,0 +1,9 @@ +name: Workflow caller - create release +on: + pull_request: + types: [closed] + +jobs: + verify-changelog: + if: github.event.pull_request.merged == true + uses: Tools4everBV/.github/.github/workflows/createRelease.yaml@main diff --git a/.github/workflows/verifyChangelog.yaml b/.github/workflows/verifyChangelog.yaml new file mode 100644 index 0000000..4913991 --- /dev/null +++ b/.github/workflows/verifyChangelog.yaml @@ -0,0 +1,8 @@ +name: Workflow caller - verify changelog updated +on: + pull_request: + types: [opened, synchronize] + +jobs: + verify-changelog: + uses: Tools4everBV/.github/.github/workflows/verifyChangelog.yaml@main diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b63e173 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,43 @@ +# Change Log + +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). + +## [2.0.0] - 2026-06-04 + +Reworked release of _HelloID-Conn-Prov-Target-DynamicsEmpire_ . + +### Added + +- Updated documentation and connector metadata for the V2 target connector structure. + +### Changed + +- Reworked provisioning implementation to align with HelloID PowerShell V2 conventions. +- Updated field mapping to match current repository behavior for create flow. +- Updated README content to reflect current connector scope and configuration. + +### Deprecated + +### Removed + +- Legacy template placeholders and outdated references from documentation. + +## [1.0.0] - 2026-06-04 + +Initial (legacy) release line of _HelloID-Conn-Prov-Target-DynamicsEmpire_ before the PowerShell V2 rework. + +### Added + +- Initial connector implementation and base project files. + +### Changed + +- No further changes recorded for this legacy baseline. + +### Deprecated + +- Legacy implementation line, superseded by 2.0.0. + +### Removed + +- Not applicable. \ No newline at end of file diff --git a/Icon.png b/Icon.png new file mode 100644 index 0000000..dd07cb7 Binary files /dev/null and b/Icon.png differ diff --git a/Logo.png b/Logo.png new file mode 100644 index 0000000..dd07cb7 Binary files /dev/null and b/Logo.png differ diff --git a/configuration.json b/configuration.json index ed3b313..e4a2d99 100644 --- a/configuration.json +++ b/configuration.json @@ -1,42 +1,42 @@ [ - { - "key": "NavAdminToolsFile", - "type": "input", - "defaultValue": "", - "templateOptions": { - "label": "NavAdminToolsFile", - "description": "The full path to the NavAdminToolsFile *.ps1 file", - "required": true - } + { + "defaultValue": "", + "key": "Applicationserver", + "templateOptions": { + "description": "Server containing NAV module", + "label": "Applicationserver", + "required": true }, - { - "key": "ServerInstance", - "type": "input", - "defaultValue": "", - "templateOptions": { - "label": "ServerInstance", - "description": "The name of the Dynamics Nav server instance.", - "required": true - } + "type": "input" + }, + { + "defaultValue": "C:\\Program Files\\Microsoft Dynamics 365 Business Central\\Service\\navadmintool.ps1", + "key": "PSModulePath", + "templateOptions": { + "description": "Path to powershell NavAdminTools module", + "label": "Powershell module path", + "required": true }, - { - "key": "Tenant", - "type": "input", - "defaultValue": "", - "templateOptions": { - "label": "Tenant", - "description": "The ID of the Dynamics Nav tenant.", - "required": true - } + "type": "input" + }, + { + "defaultValue": "DEERP", + "key": "NavServerInstanceName", + "templateOptions": { + "description": "NavServer Instance Name", + "label": "NavServer Instance Name", + "required": true }, - { - "key": "IsDebug", - "type": "checkbox", - "defaultValue": true, - "templateOptions": { - "label": "Toggle debug logging", - "description": "When toggled, debug logging will be displayed", - "required": false - } - } -] + "type": "input" + }, + { + "defaultValue": "Default", + "key": "Tenant", + "templateOptions": { + "description": "Business Central Tenant, usually default", + "label": "Tenant", + "required": true + }, + "type": "input" + } +] \ No newline at end of file diff --git a/create.ps1 b/create.ps1 index 7b5cc67..bf31331 100644 --- a/create.ps1 +++ b/create.ps1 @@ -1,130 +1,217 @@ -################################################ -# HelloID-Conn-Prov-Target-DynamicsEmpire-Create -# -# Version: 1.0.0 -################################################ -# Initialize default values -$config = $configuration | ConvertFrom-Json -$p = $person | ConvertFrom-Json -$success = $false -$auditLogs = [Collections.Generic.List[PSCustomObject]]::new() - -# Default company -if($p.PrimaryContract.CostCenter.Code -eq ''){ - $defaultCompany = '' -} elseif($p.PrimaryContract.CostCenter.Code -eq ''){ - $defaultCompany = '' -} - -# FullName -if ($null -ne $p.custom.GewTussenvoegsel) { - $fullName = $firstName + " " + $middleName + " " + $lastName -} else { - $fullName = $firstName + " " + $lastName -} +################################################# +# HelloID-Conn-Prov-Target-EmpireDynamics-Create +# PowerShell V2 +################################################# -# Account mapping -$account = [PSCustomObject]@{ - displayName = $fullName - firstName = $p.Name.NickName - middleName = $p.custom.GewTussenvoegsel - lastName = $p.custom.GewAchternaam - emailAddress = $p.accounts.MicrosoftActiveDirectory.mail - samAccountName = $p.Contact.Business.Email #$p.Accounts.MicrosoftActiveDirectory.sAMAccountName - userPrincipalName = $p.Accounts.MicrosoftActiveDirectory.userPrincipalName - defaultCompany = $defaultCompany - languageID = '' - permissionSetId = '' -} +# Enable TLS1.2 +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 -# Set debug logging -switch ($($config.IsDebug)) { - $true { $VerbosePreference = 'Continue' } - $false { $VerbosePreference = 'SilentlyContinue' } +#region functions +function Resolve-EmpireError { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [object] + $ErrorObject + ) + process { + $httpErrorObj = [PSCustomObject]@{ + ScriptLineNumber = $ErrorObject.InvocationInfo.ScriptLineNumber + Line = $ErrorObject.InvocationInfo.Line + ErrorDetails = $ErrorObject.Exception.Message + FriendlyMessage = $ErrorObject.Exception.Message + } + if (-not [string]::IsNullOrEmpty($ErrorObject.ErrorDetails.Message)) { + $httpErrorObj.ErrorDetails = $ErrorObject.ErrorDetails.Message + } + elseif ($ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException') { + if ($null -ne $ErrorObject.Exception.Response) { + $streamReaderResponse = [System.IO.StreamReader]::new($ErrorObject.Exception.Response.GetResponseStream()).ReadToEnd() + if (-not [string]::IsNullOrEmpty($streamReaderResponse)) { + $httpErrorObj.ErrorDetails = $streamReaderResponse + } + } + } + try { + $errorDetailsObject = ($httpErrorObj.ErrorDetails | ConvertFrom-Json) + # Make sure to inspect the error result object and add only the error message as a FriendlyMessage. + # $httpErrorObj.FriendlyMessage = $errorDetailsObject.message + $httpErrorObj.FriendlyMessage = $httpErrorObj.ErrorDetails # Temporarily assignment + } + catch { + $httpErrorObj.FriendlyMessage = $httpErrorObj.ErrorDetails + } + Write-Output $httpErrorObj + } } +#endregion try { - Write-Verbose "Importing NavAdminToolsFile: [$($config.NavAdminToolsFile)]" - if(-not(Test-Path $($config.NavAdminToolsFile))){ - throw [System.IO.FileNotFoundException]("The file: [$($config.NavAdminToolsFile)] cannot be found") - } else { - # Dotsource the *.ps1 file to make sure its function are imported in the current session - .$($config.NavAdminToolsFile) + # Create session + $s = New-PSSession -ComputerName $actionContext.Configuration.Applicationserver + + # Define variables + $EmailAddress = $actionContext.Data.EmailAddress + $SamAccountName = $actionContext.Data.Username + $UserPrincipalName = $actionContext.Data.userPrincipalName + $Fullname = $actionContext.Data.FullName + + $PSPath = $actionContext.Configuration.PSModulePath + $NavServerInstanceName = $actionContext.Configuration.NavServerInstanceName + $tenant = $actionContext.Configuration.Tenant + + + # Import module + $Import = Invoke-Command -Session $s -ScriptBlock { + param($ImportPath) + Import-Module $ImportPath -ErrorAction Stop + } -ArgumentList $PSPath + + # Initial assignment + $outputContext.AccountReference = 'Currently not available' + + # Determine action (Create or Correlate) + if ($actionContext.CorrelationConfiguration.Enabled) { + try { + $correlatedAccount = Invoke-Command -Session $s -ScriptBlock { + param($ServerInstance, $Tenant, $UserName) + Get-NAVServerUser -ServerInstance $ServerInstance -Tenant $Tenant | + Where-Object { $_.UserName -eq $UserName } + } -ArgumentList $NavServerInstanceName, $tenant, $SamAccountName + + } + catch { + throw "Error while checking for existing NAV user: $($_.Exception.Message)" + } + } + else { + throw "Correlation for this connector is mandatory" } - Write-Verbose "Verifying if DynamicsEmpire account for [$($p.DisplayName)] must be created or correlated" - $splatGetNavUserParams = @{ - ServerInstance = $($config.ServerInstance) - Tenant = $($config.Tenant) + if ($null -ne $correlatedAccount) { + $action = 'CorrelateAccount' } - $navUser = Get-NAVServerUser @splatGetNavUserParams -ErrorAction SilentlyContinue -Verbose | Where-Object {$_.UserName -eq $($account.samAccountName)} - if (-not($navUser)) { - $action = 'Create-Correlate' - } else { - $action = 'Correlate' + else { + $action = 'CreateAccount' } - # Add a warning message showing what will happen during enforcement - if ($dryRun -eq $true) { - Write-Information "[DryRun] $action DynamicsEmpire account for: [$($p.DisplayName)], will be executed during enforcement" + # Add a message and the result of each of the validations showing what will happen during enforcement + if ($actionContext.DryRun -eq $true) { + Write-Information "[DryRun] $action Authorizationbox account for: [$($personContext.Person.DisplayName)], will be executed during enforcement" } - # Process - if (-not($dryRun -eq $true)) { - switch ($action){ - 'Create-Correlate'{ - Write-Verbose "Creating and correlating DynamicsEmpire account" - $splatCreateNavUserParams = @{ - ServerInstance = $($config.ServerInstance) - Tenant = $($config.Tenant) - WindowsAccount = $($account.samAccountName) - FullName = $($account.displayName) - AuthenticationEmail = $($account.userPrincipalName) - Company = $($account.defaultCompany) - LanguageID = $($account.languageID) - ContactEmail = $($account.emailAddress) - } - $null = New-NAVServerUser @splatCreateNavUserParams -ErrorAction Stop -Verbose + switch ($action) { + 'CreateAccount' { + Write-Information 'Creating Nav User' - $splatCreateNavPermission = @{ - ServerInstance = $($config.ServerInstance) - Tenant = $($config.Tenant) - WindowsAccount = $($account.samAccountName) - PermissionSetId = $($account.permissionSetId) - } + if (-not($actionContext.DryRun -eq $true)) { + #Create user + $createUser = Invoke-Command -Session $s -ScriptBlock { + param( + $ServerInstance, + $Tenant, + $SamAccountName, + $Displayname, + $UserPrincipalName, + $EmailAddress + ) + New-NAVServerUser ` + -ServerInstance $ServerInstance ` + -Tenant $Tenant ` + -WindowsAccount $SamAccountName ` + -FullName $Displayname ` + -AuthenticationEmail $UserPrincipalName ` + -ContactEmail $EmailAddress ` + -State Enabled ` + -Verbose + } -ArgumentList $NavServerInstanceName, $tenant, $SamAccountName, $Fullname, $UserPrincipalName, $EmailAddress + + #Set permissions + <# + $setPermissions = Invoke-Command -Session $s -ScriptBlock { + param( + $ServerInstance, + $Tenant, + $SamAccountName, + $PermissionSetId + ) + New-NAVServerUserPermissionSet ` + -ServerInstance $ServerInstance ` + -Tenant $Tenant ` + -WindowsAccount $SamAccountName ` + -PermissionSetId $PermissionSetId ` + -ErrorAction SilentlyContinue ` + -Verbose + } -ArgumentList $NavServerInstanceName, $tenant, $SamAccountName, 'SUPER' + #> - $null = New-NAVServerUserPermissionSet @splatCreateNavPermission -ErrorAction Stop -Verbose - $accountReference = (New-Guid).guid - } + #Try another get user for setting aref + $result = Invoke-Command -Session $s -ScriptBlock { + param($ServerInstance, $Tenant, $UserName) + Get-NAVServerUser -ServerInstance $ServerInstance -Tenant $Tenant | + Where-Object { $_.UserName -eq $UserName } + } -ArgumentList $NavServerInstanceName, $tenant, $SamAccountName + + $outputContext.Data.UserSecurityID = $result.UserSecurityID + $outputContext.Data.FullName = $result.Fullname + $outputContext.Data.UserName = $result.Username - 'Correlate'{ - Write-Verbose "Creating and correlating DynamicsEmpire account" - $accountReference = (New-Guid).guid + $auditLogMessage = "Create account was successful. AccountReference is: [$($outputContext.Data.UserSecurityID)]" + } + else { + Write-Information 'Dryrun prevented this action' + } + break + } + + 'CorrelateAccount' { + Write-Information 'Correlating Nav User' + + $outputContext.Data.UserSecurityID = $correlatedAccount.UserSecurityID + $outputContext.Data.Fullname = $correlatedAccount.Fullname + $outputContext.Data.UserName = $correlatedAccount.Username + + $outputContext.AccountCorrelated = $true + $auditLogMessage = "Correlated account: [$($actionContext.Data.Username)]. AccountReference is: [$($outputContext.Data.UserSecurityID)]" + break } + } - $success = $true - $auditLogs.Add([PSCustomObject]@{ - Message = "$action DynamicsEmpire account was successful. AccountReference is: [$accountReference]" - IsError = $false - }) + $accountRef = [PSCustomObject]@{ + UserSecurityID = $outputContext.Data.UserSecurityID + Fullname = $outputContext.Data.Fullname + UserName = $outputContext.Data.UserName } -} catch { - $success = $false + + $outputContext.AccountReference = $accountRef + $outputContext.Success = $true + + $outputContext.AuditLogs.Add([PSCustomObject]@{ + Action = $action + Message = $auditLogMessage + IsError = $false + }) + + # Remove session + $exit = if ($s) { Remove-PSSession $s } +} +catch { + $outputContext.Success = $false $ex = $PSItem - $auditMessage = "Could not create DynamicsEmpire account. Error: $($ex.Exception.Message)" - Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)" - $auditLogs.Add([PSCustomObject]@{ + $exit = if ($s) { Remove-PSSession $s } + if ($($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or + $($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) { + $errorObj = Resolve-EmpireError -ErrorObject $ex + $auditMessage = "Could not create or correlate EmpireDynamics account. Error: $($errorObj.FriendlyMessage)" + Write-Warning "Error at Line '$($errorObj.ScriptLineNumber)': $($errorObj.Line). Error: $($errorObj.ErrorDetails)" + } + else { + $auditMessage = "Could not create or correlate EmpireDynamics account. Error: $($ex.Exception.Message)" + Write-Warning "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)" + } + $outputContext.AuditLogs.Add([PSCustomObject]@{ Message = $auditMessage IsError = $true }) -} finally { - $result = [PSCustomObject]@{ - Success = $success - AccountReference = $accountReference - AuditLogs = $auditLogs - Account = $account - - } - Write-Output ($result | ConvertTo-Json -Depth 10) } diff --git a/delete.ps1 b/delete.ps1 deleted file mode 100644 index c727e0c..0000000 --- a/delete.ps1 +++ /dev/null @@ -1,94 +0,0 @@ -################################################ -# HelloID-Conn-Prov-Target-DynamicsEmpire-Delete -# -# Version: 1.0.0 -################################################ -# Initialize default values -$config = $configuration | ConvertFrom-Json -$p = $person | ConvertFrom-Json -$aRef = $AccountReference | ConvertFrom-Json -$success = $false -$auditLogs = [System.Collections.Generic.List[PSCustomObject]]::new() - -# Set debug logging -switch ($($config.IsDebug)) { - $true { $VerbosePreference = 'Continue' } - $false { $VerbosePreference = 'SilentlyContinue' } -} - -try { - if(-not(Test-Path $($config.NavAdminToolsFile))){ - throw [System.IO.FileNotFoundException]("The file: [$($config.NavAdminToolsFile)] cannot be found") - } else { - # Dotsource the *.ps1 file to make sure its function are imported in the current session - .$($config.NavAdminToolsFile) - } - - Write-Verbose "Verifying if a DynamicsEmpire account for [$($p.DisplayName)] exists" - $splatGetNavUserParams = @{ - ServerInstance = $($config.ServerInstance) - Tenant = $($config.Tenant) - } - $navUser = Get-NAVServerUser @splatGetNavUserParams -ErrorAction SilentlyContinue -Verbose | Where-Object {$_.UserName -eq $($account.samAccountName)} - - if ($null -ne $navUser) { - $action = 'Found' - $dryRunMessage = "Delete DynamicsEmpire account for: [$($p.DisplayName)] will be executed during enforcement" - } - elseif ($null -eq $navUser) { - $action = 'NotFound' - $dryRunMessage = "DynamicsEmpire account for: [$($p.DisplayName)] not found. Possibly already deleted. Skipping action" - } - - # Add an auditMessage showing what will happen during enforcement - if ($dryRun -eq $true) { - Write-Information "[DryRun] $dryRunMessage" - } - - # Process - if (-not($dryRun -eq $true)) { - Write-Verbose "Deleting DynamicsEmpire account with accountReference: [$aRef]" - - switch ($action){ - 'Found'{ - $splatDeleteNavUserParams = @{ - ServerInstance = $($config.ServerInstance) - Tenant = $($config.Tenant) - WindowsAccount = $p.Accounts.MicrosoftActiveDirectory.sAMAccountName - State = 'Disabled' - } - $null = Set-NAVServerUser @splatDeleteNavUserParams -ErrorAction Stop -Verbose -Force - $auditLogs.Add([PSCustomObject]@{ - Message = 'Delete account was successful' - IsError = $false - }) - break - } - - 'NotFound'{ - $auditLogs.Add([PSCustomObject]@{ - Message = "DynamicsEmpire account for: [$($p.DisplayName)] not found. Possibly already deleted. Skipping action" - IsError = $false - }) - break - } - } - - $success = $true - } -} catch { - $success = $false - $ex = $PSItem - $auditMessage = "Could not delete DynamicsEmpire account. Error: $($ex.Exception.Message)" - Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)" - $auditLogs.Add([PSCustomObject]@{ - Message = $auditMessage - IsError = $true - }) -} finally { - $result = [PSCustomObject]@{ - Success = $success - Auditlogs = $auditLogs - } - Write-Output $result | ConvertTo-Json -Depth 10 -} diff --git a/fieldmapping.json b/fieldmapping.json new file mode 100644 index 0000000..c42a3a1 --- /dev/null +++ b/fieldmapping.json @@ -0,0 +1,86 @@ +{ + "Version": "v1", + "MappingFields": [ + { + "Name": "EmailAddress", + "Description": "", + "Type": "Text", + "MappingActions": [ + { + "MapForActions": [ + "Create" + ], + "MappingMode": "Complex", + "Value": "\"function getValue(){\\r\\n let email = Person.Accounts.MicrosoftActiveDirectory.mail\\r\\n\\r\\n return email\\r\\n\\r\\n}\\r\\n\\r\\ngetValue()\"", + "UsedInNotifications": false, + "StoreInAccountData": true + } + ] + }, + { + "Name": "userPrincipalName", + "Description": "", + "Type": "Text", + "MappingActions": [ + { + "MapForActions": [ + "Create" + ], + "MappingMode": "Complex", + "Value": "\"function getValue(){\\r\\n let email = Person.Accounts.MicrosoftActiveDirectory.userPrincipalName\\r\\n \\r\\n return email\\r\\n\\r\\n}\\r\\n\\r\\ngetValue()\"", + "UsedInNotifications": false, + "StoreInAccountData": false + } + ] + }, + { + "Name": "Username", + "Description": "", + "Type": "Text", + "MappingActions": [ + { + "MapForActions": [ + "Create" + ], + "MappingMode": "Complex", + "Value": "\"function getValue(){\\r\\n let username = Person.Accounts.MicrosoftActiveDirectory.sAMAccountName\\r\\n\\r\\n let domain = 'DOMAIN\\\\\\\\'\\r\\n\\r\\n return domain + username\\r\\n\\r\\n}\\r\\n\\r\\ngetValue()\"", + "UsedInNotifications": false, + "StoreInAccountData": true + } + ] + }, + { + "Name": "UserSecurityID", + "Description": "", + "Type": "Text", + "MappingActions": [ + { + "MapForActions": [ + "Create" + ], + "MappingMode": "Fixed", + "Value": "\"\"", + "UsedInNotifications": false, + "StoreInAccountData": true + } + ] + }, + { + "Name": "FullName", + "Description": "", + "Type": "Text", + "MappingActions": [ + { + "MapForActions": [ + "Create" + ], + "MappingMode": "Complex", + "Value": "\"// Please enter the mapping logic to generate the commonName based on name convention.\\r\\nfunction generateDisplayName() {\\r\\n\\tlet firstName = Person.Name.NickName;\\r\\n\\tlet middleName = Person.Name.FamilyNamePrefix;\\r\\n\\tlet lastName = Person.Name.FamilyName;\\r\\n\\tlet middleNamePartner = Person.Name.FamilyNamePartnerPrefix;\\r\\n\\tlet lastNamePartner = Person.Name.FamilyNamePartner;\\r\\n let nameFormatted = '';\\r\\n\\r\\n\\tswitch(Person.Name.Convention) {\\r\\n\\t\\tcase 'B':\\r\\n nameFormatted = firstName\\r\\n if (typeof middleName !== 'undefined' && middleName) { nameFormatted = nameFormatted + ' ' + middleName }\\r\\n nameFormatted = nameFormatted + ' ' + lastName\\r\\n\\r\\n\\t\\t\\tbreak;\\r\\n\\t\\tcase 'BP':\\r\\n nameFormatted = firstName\\r\\n if (typeof middleName !== 'undefined' && middleName) { nameFormatted = nameFormatted + ' ' + middleName }\\r\\n nameFormatted = nameFormatted + ' ' + lastName + ' -'\\r\\n if (typeof middleNamePartner !== 'undefined' && middleNamePartner) { nameFormatted = nameFormatted + ' ' + middleNamePartner }\\r\\n nameFormatted = nameFormatted + ' ' + lastNamePartner\\r\\n\\t\\t\\tbreak;\\r\\n\\t\\tcase 'P':\\r\\n nameFormatted = firstName\\r\\n if (typeof middleNamePartner !== 'undefined' && middleNamePartner) { nameFormatted = nameFormatted + ' ' + middleNamePartner }\\r\\n nameFormatted = nameFormatted + ' ' + lastNamePartner\\r\\n\\t\\t break;\\r\\n\\t\\tcase 'PB':\\r\\n nameFormatted = firstName\\r\\n if (typeof middleNamePartner !== 'undefined' && middleNamePartner) { nameFormatted = nameFormatted + ' ' + middleNamePartner }\\r\\n nameFormatted = nameFormatted + ' ' + lastNamePartner + ' -'\\r\\n if (typeof middleName !== 'undefined' && middleName) { nameFormatted = nameFormatted + ' ' + middleName }\\r\\n nameFormatted = nameFormatted + ' ' + lastName\\r\\n\\t\\t break;\\r\\n\\t\\tdefault:\\r\\n nameFormatted = firstName\\r\\n if (typeof middleName !== 'undefined' && middleName) { nameFormatted = nameFormatted + ' ' + middleName }\\r\\n nameFormatted = nameFormatted + ' ' + lastName\\r\\n\\t\\t\\tbreak;\\r\\n\\t}\\r\\n\\r\\n\\tconst displayName = nameFormatted;\\r\\n\\r\\n\\treturn displayName;\\r\\n}\\r\\n\\r\\ngenerateDisplayName();\\r\\n\\r\\n\"", + "UsedInNotifications": false, + "StoreInAccountData": true + } + ] + } + ], + "UniqueFieldNames": [] +} \ No newline at end of file diff --git a/readme.md b/readme.md index 6d5817a..4e97999 100644 --- a/readme.md +++ b/readme.md @@ -1,80 +1,143 @@ - # HelloID-Conn-Prov-Target-DynamicsEmpire -| :warning: Warning | -|:---------------------------| -| Note that this connector is "a work in progress" and therefore not ready to use in your production environment. | + + + +> [!IMPORTANT] +> This repository contains the connector and configuration code only. The implementer is responsible to acquire the connection details such as username, password, certificate, etc. You might even need to sign a contract or agreement with the supplier before implementing this connector. Please contact the client's application manager to coordinate the connector requirements. -| :information_source: Information | -|:---------------------------| -| This repository contains the connector and configuration code only. The implementer is responsible to acquire the connection details such as username, password, certificate, etc. You might even need to sign a contract or agreement with the supplier before implementing this connector. Please contact the client's application manager to coordinate the connector requirements. | +> [!NOTE] +> This connector must be used in conjunction with the [HelloID-Conn-Prov-Target-Authorizationbox](https://github.com/Tools4everBV/HelloID-Conn-Prov-Target-Authorizationbox) connector. -| :information_source: Information | -|:---------------------------| -| This connector must be used in conjunction with the [HelloID-Conn-Prov-Target-Authorizationbox](https://github.com/Tools4everBV/HelloID-Conn-Prov-Target-Authorizationbox) connector | +

+ +

## Table of contents - [HelloID-Conn-Prov-Target-DynamicsEmpire](#helloid-conn-prov-target-dynamicsempire) - [Table of contents](#table-of-contents) - [Introduction](#introduction) + - [Supported features](#supported-features) - [Getting started](#getting-started) + - [HelloID Icon URL](#helloid-icon-url) + - [Requirements](#requirements) - [Connection settings](#connection-settings) - - [Remarks](#remarks) - - [Dotsourcing of the _`NavAdminTools.ps1`_ file](#dotsourcing-of-the-navadmintoolsps1-file) - - [Setup the connector](#setup-the-connector) + - [Correlation configuration](#correlation-configuration) + - [Field mapping](#field-mapping) + - [Account Reference](#account-reference) + - [Remarks](#remarks) + - [Dotsourcing of the `NavAdminTools.ps1` file](#dotsourcing-of-the-navadmintoolssps1-file) + - [Development resources](#development-resources) + - [API endpoints](#api-endpoints) + - [API documentation](#api-documentation) - [Getting help](#getting-help) - [HelloID docs](#helloid-docs) ## Introduction -_HelloID-Conn-Prov-Target-DynamicsEmpire_ is a _target_ connector. DynamicsEmpire provides a set ot PowerShell functions allowing you to programmatically interact with its data, similar to the PowerShell module for Active Directory. +_HelloID-Conn-Prov-Target-DynamicsEmpire_ is a target connector. DynamicsEmpire provides a set of PowerShell functions allowing you to programmatically interact with its data. + +## Supported features + +The following features are available: + +| Feature | Supported | Actions | Remarks | +| ----------------------------------------- | --------- | --------------------------------------- | ----------------- | +| **Account Lifecycle** | ✅ | Create, Correlate | Correlation is mandatory. | +| **Permissions** | ❌ | - | | +| **Resources** | ❌ | - | | +| **Entitlement Import: Accounts** | ❌ | - | | +| **Entitlement Import: Permissions** | ❌ | - | | +| **Governance Reconciliation Resolutions** | ❌ | - | | -The following lifecycle events are available: -| Event | Description | Notes | -|--- |--- |--- | -| create.ps1 | Create and/or correlate an Account | - | -| update.ps1 | Update the Account | - | -| delete.ps1 | Delete the Account | - | +Governance Reconciliation Resolutions are not yet available. ## Getting started +### HelloID Icon URL +URL of the icon used for the HelloID Provisioning target system. +``` +https://raw.githubusercontent.com/Tools4everBV/HelloID-Conn-Prov-Target-DynamicsEmpire/refs/heads/main/Icon.png +``` + +### Requirements + +- Service account for the HelloID Agent with sufficient rights within Empire to create accounts. +- Service account for the HelloID Agent must be able to connect to the instance where Empire is installed. +- Linked usage with AuthorizationBox connector + ### Connection settings -The following settings are required to connect to the API. +The following settings are required to connect to DynamicsEmpire. + +| Setting | Description | Mandatory | +| --------------------- | -------------------------------------------------------- | --------- | +| PSModulePath | Full path to the `NavAdminTools.ps1` file | Yes | +| Applicationserver | Name of the Dynamics NAV application server | Yes | +| NavServerInstanceName | Name of the Dynamics NAV server instance | Yes | +| Tenant | ID of the Dynamics NAV tenant | Yes | -| Setting | Description | Mandatory | -| ------------ | ----------- | ----------- | -| NavAdminToolsFile| The full path to the NavAdminToolsFile *.ps1 file| Yes -| ServerInstance | The name of the Dynamics Nav server instance | Yes | -| Tenant | The ID of the Dynamics Nav tenant | Yes +### Correlation configuration -### Remarks +The correlation configuration specifies which properties will be used to match an existing Empire account to a person in HelloID. -#### Dotsourcing of the _`NavAdminTools.ps1`_ file +| Setting | Value | +| ------------------------- | ---------- | +| Enable correlation | `True` | +| Person correlation field | `sAMAccountname` | +| Account correlation field | `Username` | -The `NavAdminTools.ps1` file is dotsourced in all lifecycle actions. Meaning that the functions defined within the _*.ps1_ file are imported into the current session. The original code was based on a _PSSession_ with `Import-Module` and `Invoke-Command`. Since the _*.ps1_ file is not a module, this seems to be unnecessary but requires further testing. +> [!TIP] +> _For more information on correlation, please refer to our correlation [documentation](https://docs.helloid.com/en/provisioning/target-systems/powershell-v2-target-systems/correlation.html) pages_. + +### Field mapping + +The field mapping can be imported by using the _fieldMapping.json_ file. + +### Account Reference + +The account reference is populated with the property `userSecurityID, FullName & Username` from Empire. +It is advised not to change the reference after creation or correlation due to issues in Authorizationbox when changing those fields. + +## Remarks + +- Service user that runs the agent must have sufficient rights within Empire to create and correlate accounts. +- This connector is usually linked to the AuthorizationBox connector, as Security ID is required there. + +### Dotsourcing of the `NavAdminTools.ps1` file + +The `NavAdminTools.ps1` file is dotsourced in all lifecycle actions. Functions defined within the file are imported into the current session. The original code was based on a PSSession with `Import-Module` and `Invoke-Command`. Since the file is not a module, this seems unnecessary but requires further testing. ```powershell - $s = New-PSSession -ComputerName 'applicationServer' - Invoke-Command -Session $s -ScriptBlock { - $ImportModule = Import-Module "D:\NavAdminTool.ps1" -ErrorAction SilentlyContinue - Invoke-Command -Session $s -ScriptBlock { - $CreateNavUser = New-NAVServerUser -ServerInstance $using:NavServerInstanceName -Tenant $using:tenant -WindowsAccount $using:SamAccountName -FullName $using:DisplayName -AuthenticationEmail $using:UserPrincipalName -Company $using:DefaultCompany -LanguageID $using:LanguageID -ContactEmail $using:EmailAddress -ErrorAction SilentlyContinue -Verbose - } - } +# Import module +$Import = Invoke-Command -Session $s -ScriptBlock { + param($ImportPath) + Import-Module $ImportPath -ErrorAction Stop +} -ArgumentList $PSPath ``` -## Setup the connector +## Development resources + +### API endpoints -> _How to setup the connector in HelloID._ Are special settings required. Like the _primary manager_ settings for a source connector. +This connector interacts with DynamicsEmpire via PowerShell functions from `NavAdminTools.ps1` rather than REST endpoints. + +### API documentation + + ## Getting help -> _For more information on how to configure a HelloID PowerShell connector, please refer to our [documentation](https://docs.helloid.com/hc/en-us/articles/360012558020-Configure-a-custom-PowerShell-target-system) pages_ +> [!TIP] +> For more information on configuring HelloID PowerShell connectors, refer to our documentation: https://docs.helloid.com/en/provisioning/target-systems/powershell-v2-target-systems.html + -> _If you need help, feel free to ask questions on our [forum](https://forum.helloid.com)_ ## HelloID docs diff --git a/update.ps1 b/update.ps1 deleted file mode 100644 index 481130e..0000000 --- a/update.ps1 +++ /dev/null @@ -1,128 +0,0 @@ -################################################ -# HelloID-Conn-Prov-Target-DynamicsEmpire-Update -# -# Version: 1.0.0 -################################################ -# Initialize default values -$config = $configuration | ConvertFrom-Json -$p = $person | ConvertFrom-Json -$aRef = $AccountReference | ConvertFrom-Json -$success = $false -$auditLogs = [System.Collections.Generic.List[PSCustomObject]]::new() - -# Default company -if($p.PrimaryContract.CostCenter.Code -eq ''){ - $defaultCompany = '' -} elseif($p.PrimaryContract.CostCenter.Code -eq ''){ - $defaultCompany = '' -} - -# FullName -if ($null -ne $p.custom.GewTussenvoegsel) { - $fullName = $firstName + " " + $middleName + " " + $lastName -} else { - $fullName = $firstName + " " + $lastName -} - -# Account mapping -$account = [PSCustomObject]@{ - displayName = $fullName - firstName = $p.Name.NickName - middleName = $p.custom.GewTussenvoegsel - lastName = $p.custom.GewAchternaam - emailAddress = $p.accounts.MicrosoftActiveDirectory.mail - samAccountName = $p.Accounts.MicrosoftActiveDirectory.sAMAccountName - userPrincipalName = $p.Accounts.MicrosoftActiveDirectory.userPrincipalName - defaultCompany = $defaultCompany - languageID = '' - permissionSetId = '' -} - -# Set debug logging -switch ($($config.IsDebug)) { - $true { $VerbosePreference = 'Continue' } - $false { $VerbosePreference = 'SilentlyContinue' } -} - -try { - if(-not(Test-Path $($config.NavAdminToolsFile))){ - throw [System.IO.FileNotFoundException]("The file: [$($config.NavAdminToolsFile)] cannot be found") - } else { - # Dotsource the *.ps1 file to make sure its function are imported in the current session - .$($config.NavAdminToolsFile) - } - - Write-Verbose "Verifying if a DynamicsEmpire account for [$($p.DisplayName)] exists" - $splatGetNavUserParams = @{ - ServerInstance = $($config.ServerInstance) - Tenant = $($config.Tenant) - } - $navUser = Get-NAVServerUser @splatGetNavUserParams -ErrorAction SilentlyContinue -Verbose | Where-Object {$_.UserName -eq $($account.samAccountName)} - - if ($null -ne $navUser) { - $action = 'Update' - $dryRunMessage = "DynamicsEmpire account for: [$($p.DisplayName)] will be updated." - } - elseif ($null -eq $navUser) { - $action = 'NotFound' - $dryRunMessage = "DynamicsEmpire account for: [$($p.DisplayName)] not found. Possibly deleted." - } - - # Add a warning message showing what will happen during enforcement - if ($dryRun -eq $true) { - Write-Information "[DryRun] $dryRunMessage" - } - - # Process - if (-not($dryRun -eq $true)) { - switch ($action) { - 'Update' { - Write-Verbose "Updating DynamicsEmpire account with accountReference: [$aRef]" - $splatSetNavUserParams = @{ - ServerInstance = $($config.ServerInstance) - Tenant = $($config.Tenant) - WindowsAccount = $($account.samAccountName) - FullName = $($account.displayName) - AuthenticationEmail = $($account.userPrincipalName) - Company = $($account.defaultCompany) - LanguageID = $($account.languageID) - ContactEmail = $($account.emailAddress) - } - - $null = Set-NAVServerUser @splatSetNavUserParams -ErrorAction Stop -Verbose -Force - - $success = $true - $auditLogs.Add([PSCustomObject]@{ - Message = 'Update DynamicsEmpire account was successful' - IsError = $false - }) - break - } - - 'NotFound' { - $success = $false - $auditLogs.Add([PSCustomObject]@{ - Message = "DynamicsEmpire account for: [$($p.DisplayName)] not found. Possibly deleted" - IsError = $true - }) - break - } - } - } -} catch { - $success = $false - $ex = $PSItem - $auditMessage = "Could not update DynamicsEmpire account. Error: $($ex.Exception.Message)" - Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)" - $auditLogs.Add([PSCustomObject]@{ - Message = $auditMessage - IsError = $true - }) -} finally { - $result = [PSCustomObject]@{ - Success = $success - Account = $account - Auditlogs = $auditLogs - } - Write-Output $result | ConvertTo-Json -Depth 10 -}