Public/New-SingleFieldEnrichment.ps1

function New-SingleFieldEnrichment {
    <#
    .SYNOPSIS
        Builds a single-field Nexthink enrichment payload.
 
    .DESCRIPTION
        Creates a properly structured enrichment request object suitable for
        Invoke-EnrichmentRequest. This function:
 
        - Accepts a friendly object selector (ObjectName), such as "device.uid"
        - Accepts a friendly field selector (FieldName), such as "user.ad.city"
            or "device.#custom_field"
        - Maps both to the corresponding full Nexthink enrichment paths, e.g.:
                device.uid -> device/device/uid
                user.ad.city -> user/user/ad/city
                user.#cost_center -> user/user/#cost_center
        - Builds one enrichment entry per key/value pair in ObjectValues
 
        The resulting object can be passed directly into Invoke-EnrichmentRequest.
 
    .PARAMETER FieldName
        The enrichment field to update, specified using a short, friendly syntax.
 
        Fixed device virtualization fields (examples):
            device.configuration_tag
            device.virtualization.desktop_broker
            device.virtualization.desktop_pool
            device.virtualization.disk_image
            device.virtualization.environment_name
            device.virtualization.hostname
            device.virtualization.hypervisor_name
            device.virtualization.instance_size
            device.virtualization.last_update
            device.virtualization.region
            device.virtualization.type
 
        Fixed user AD fields (examples):
            user.ad.city
            user.ad.country_code
            user.ad.department
            user.ad.distinguished_name
            user.ad.email_address
            user.ad.full_name
            user.ad.job_title
            user.ad.last_update
            user.ad.office
            user.ad.organizational_unit
            user.ad.username
 
        Supported custom field patterns:
            device.#<custom_field_name> -> device/device/#<custom_field_name>
            user.#<custom_field_name> -> user/user/#<custom_field_name>
            binary.#<custom_field_name> -> binary/binary/#<custom_field_name>
            package.#<custom_field_name> -> package/package/#<custom_field_name>
            user.organization.#<custom_field_name> -> user/user/organization/#<custom_field_name>
 
        Examples:
            device.virtualization.region
            user.ad.department
            device.#image_channel
            user.#cost_center
            user.organization.#region
 
    .PARAMETER ObjectName
        A friendly shorthand identifier for the Nexthink object you want to enrich.
        This is automatically mapped to the correct full object path.
 
        Valid values:
            device.name -> device/device/name
            device.uid -> device/device/uid
            user.sid -> user/user/sid
            user.uid -> user/user/uid
            user.upn -> user/user/upn
            binary.uid -> binary/binary/uid
            package.uid -> package/package/uid
 
        Examples:
            device.uid
            user.upn
            package.uid
 
    .PARAMETER ObjectValues
        A hashtable where each key is the identifier for the target object
        (for example: device UID, user SID, user UPN, package UID) and each value
        is the enrichment value to assign to the specified FieldName.
 
        One enrichment entry is created for each key/value pair.
 
        Examples (for ObjectName 'user.sid'):
            @{
                'S-1-5-21-1234567890-1234567890-1234567890-1001' = 'IT'
                'S-1-5-21-1234567890-1234567890-1234567890-1002' = 'Finance'
            }
 
        Examples (for ObjectName 'device.uid'):
            @{
                '3fa85f64-5717-4562-b3fc-2c963f66afa6' = 'VDI-Pool-01'
                '0e8a9a54-9fd8-4e9a-83f4-4d611f9d1234' = 'VDI-Pool-02'
            }
 
    .OUTPUTS
        [PSCustomObject]
 
        Returns a PSCustomObject with the shape:
 
            @{
                enrichments = <array of enrichment items>
                domain = 'ps_custom_fields'
            }
 
        This object is designed to be passed directly to Invoke-EnrichmentRequest
        as the -Enrichment parameter.
 
    .NOTES
        This function does not call the Nexthink API by itself.
        Use Invoke-EnrichmentRequest to send the generated payload to the
        Nexthink Enrichment API endpoint.
 
    .EXAMPLE
        $values = @{
            '3fa85f64-5717-4562-b3fc-2c963f66afa6' = 'Region-East'
            '0e8a9a54-9fd8-4e9a-83f4-4d611f9d1234' = 'Region-West'
        }
 
        $payload = New-SingleFieldEnrichment `
            -FieldName 'device.virtualization.region' `
            -ObjectName 'device.uid' `
            -ObjectValues $values
 
        Invoke-EnrichmentRequest -Enrichment $payload
 
    .EXAMPLE
        $values = @{
            'S-1-5-21-1234567890-1234567890-1234567890-1001' = 'IT'
            'S-1-5-21-1234567890-1234567890-1234567890-1002' = 'Finance'
        }
 
        $payload = New-SingleFieldEnrichment `
            -FieldName 'user.ad.department' `
            -ObjectName 'user.sid' `
            -ObjectValues $values
 
        Invoke-EnrichmentRequest -Enrichment $payload
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param (
        # Field name (friendly shorthand) – mapped to the full enrichment path.
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$FieldName,

        # Object name (friendly shorthand) – we map this to the full enrichment ID
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('device.name','device.uid','user.sid','user.uid','user.upn','binary.uid','package.uid')]
        [string]$ObjectName,

        # Hashtable of object identifier -> field value
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
                if ($_.Count -eq 0) { throw "ObjectValues must contain at least one key/value pair."}
                $true
            })]
        [hashtable]$ObjectValues
    )

    # Convert the enrichment ID and field name mappings to hashtables
    [hashtable]$objectMap = @{}
    $Script:MAIN.EnrichmentIDMap.PSObject.Properties | ForEach-Object {
        $objectMap[$_.Name] = $_.Value }
    [hashtable]$fixedFieldMap = @{}
    $Script:MAIN.EnrichmentFieldMap.PSObject.Properties | ForEach-Object {
        $fixedFieldMap[$_.Name] = $_.Value }

    # Resolve object ID from the friendly ObjectName
    $objectId = $objectMap[$ObjectName]
    if ([string]::IsNullOrWhiteSpace($objectId)) {
        $validObjects = ($objectMap.Keys -join ', ')
        $message = "Invalid ObjectName '$ObjectName'. Valid values are: $validObjects"
        Write-CustomLog -Message $message -Severity 'ERROR'
        throw $message
    }

    # Resolve FieldName to full field path
    $fullFieldName = $null

    if ($fixedFieldMap.ContainsKey($FieldName)) {
        $fullFieldName = $fixedFieldMap[$FieldName]
    }
    else {
        # Handle custom field patterns
        # device.#custom, user.#custom, binary.#custom, package.#custom, user.organization.#custom
        if ($FieldName -match '^device\.#([A-Za-z0-9_]+)$') { $fullFieldName = "device/device/#$($matches[1])" }
        elseif ($FieldName -match '^user\.#([A-Za-z0-9_]+)$') { $fullFieldName = "user/user/#$($matches[1])" }
        elseif ($FieldName -match '^binary\.#([A-Za-z0-9_]+)$') { $fullFieldName = "binary/binary/#$($matches[1])" }
        elseif ($FieldName -match '^package\.#([A-Za-z0-9_]+)$') { $fullFieldName = "package/package/#$($matches[1])" }
        elseif ($FieldName -match '^user\.organization\.#([A-Za-z0-9_]+)$') { $fullFieldName = "user/user/organization/#$($matches[1])" }
        else {
            $validFixed = $fixedFieldMap.Keys | Sort-Object
            $validPatterns = @(
                'device.#<custom_field_name>',
                'user.#<custom_field_name>',
                'binary.#<custom_field_name>',
                'package.#<custom_field_name>',
                'user.organization.#<custom_field_name>',
                'user.ad.<field_name>'
            )

            $message = @()
            $message += "Invalid FieldName '$FieldName'."
            $message += "Accepted fixed values include (examples):"
            $message += " " + ($validFixed -join ", ")
            $message += "Supported patterns:"
            foreach ($p in $validPatterns) { $message += " $p"}

            $final = $message -join [Environment]::NewLine
            Write-CustomLog -Message $final -Severity 'ERROR'
            throw $final
        }
    }

    Write-CustomLog -Message (
        "Enriching field '{0}' (path: '{1}') of object '{2}' with {3} value(s)" -f `
            $FieldName, $fullFieldName, $objectId, $ObjectValues.Count
    ) -Severity 'DEBUG'

    # Use a strongly-typed list for enrichment items
    $enrichments = [System.Collections.Generic.List[object]]::new()

    foreach ($objectKey in $ObjectValues.Keys) {
        $objectValue = $ObjectValues[$objectKey]
        Write-CustomLog -Message ("Adding {0}: {1}" -f $objectKey, $objectValue) -Severity 'DEBUG'

        $identification = [PSCustomObject]@{
            name  = $objectId
            value = $objectKey
        }
        $fields = [PSCustomObject]@{
            name  = $fullFieldName
            value = $objectValue
        }
        $enrichments.Add(
            [PSCustomObject]@{
                identification = @($identification)
                fields         = @($fields)
            }
        ) | Out-Null
    }

    return [PSCustomObject]@{
        enrichments = $enrichments
        domain      = 'ps_custom_fields'
    }
}