Public/Expressions/Test-JIMExpression.ps1
|
# Copyright (c) Tetron Limited. All rights reserved. # Licensed under the Tetron Commercial License. See LICENSE file in the project root. function Test-JIMExpression { <# .SYNOPSIS Tests an expression with sample attribute data. .DESCRIPTION Tests a JIM expression with sample Metaverse and Connected System attribute values to verify syntax and see the expected output before deploying to a Synchronisation Rule mapping. Expressions use DynamicExpresso syntax with mv["AttributeName"] and cs["AttributeName"] for attribute access. With -SyncRuleId and -MappingId, the expression is tested as that Attribute Flow, by the same code a synchronisation runs: each sample value is converted to its attribute's type, then the Attribute Flow's Missing Input Behaviour, the expression, value processing and conversion to the target attribute's type apply in turn. Outcome then says whether the target would be given values, no value, or an error, and ValueLines gives the values it would hold. An Attribute Flow that generates its value (a Generated Value) is tested as far as its base expression and stops before generation, as a synchronisation does: generation needs the database to know which values are taken. GeneratedValue is then $true, and BaseOutcome and BaseValue say what generation would be given in place of Outcome and ValueLines. .PARAMETER Expression The expression to test. Uses DynamicExpresso syntax with mv["AttributeName"] and cs["AttributeName"] for attribute access. It can be left out only for a Generated Value with no base expression, which generates its value from a sequence or random token alone. .PARAMETER MvAttributes A hashtable of sample Metaverse attribute values. Keys are attribute names, values are the attribute values. $null is no value and an array gives the attribute several values; with an Attribute Flow, empty text is a value distinct from $null. .PARAMETER CsAttributes A hashtable of sample Connected System attribute values. Keys are attribute names, values are the attribute values. $null is no value and an array gives the attribute several values; with an Attribute Flow, empty text is a value distinct from $null. .PARAMETER SyncRuleId The ID of the Synchronisation Rule whose Attribute Flow to test the expression as. Give it with MappingId. .PARAMETER MappingId The ID of the Attribute Flow to test the expression as. The expression tested is Expression, so an edit can be tried before it is saved. .PARAMETER ConnectedSystemObjectId The ID of a Connected System Object whose values to test with, for an import Attribute Flow. Its values for the attributes the expression reads are the inputs, and a value in CsAttributes replaces the object's value for that attribute. An expression that also reads mv["..."] takes those inputs from the Metaverse Object it is joined to. .PARAMETER MetaverseObjectId The ID of a Metaverse Object whose values to test with, for an export Attribute Flow. A value in MvAttributes replaces the object's value for that attribute. .PARAMETER AsOf The instant to test as of: what Now() and Today() read. A time without a time zone is read as UTC. .OUTPUTS The result of evaluating the expression against the sample values. ResultLines gives it as the portal's tester shows it, one value per line. With an Attribute Flow, Outcome, OutcomeDescription and ValueLines say what a synchronisation would give the target attribute, Inputs lists the values each input was given, and IfInputHasNoValue says what each input's absence would do. For a Generated Value, BaseOutcome and BaseValue take the place of Outcome and ValueLines. .EXAMPLE Test-JIMExpression -Expression '"Hello, " + mv["Display Name"] + "!"' -MvAttributes @{ "Display Name" = "John Doe" } Tests a simple string concatenation expression with a sample display name. .EXAMPLE Test-JIMExpression -Expression '"CN=" + EscapeDN(mv["Display Name"]) + ",OU=Users,DC=domain,DC=local"' -MvAttributes @{ "Display Name" = "John, Doe" } Tests a DN construction expression with proper escaping. .EXAMPLE Test-JIMExpression -Expression 'Lower(cs["FirstName"]) + "." + Lower(cs["LastName"]) + "@company.com"' -CsAttributes @{ "FirstName" = "John"; "LastName" = "DOE" } Tests an email address construction expression. .EXAMPLE Test-JIMExpression -Expression 'IIF(mv["Department"] == "IT", "tech-" + mv["AccountName"], mv["AccountName"])' -MvAttributes @{ "Department" = "IT"; "AccountName" = "jdoe" } Tests a conditional expression that prefixes account names for IT users. .EXAMPLE Test-JIMExpression -Expression 'FormatDate((DateTime)cs["hireDate"], "yyyy-MM-dd")' -SyncRuleId 12 -MappingId 40 -CsAttributes @{ "hireDate" = "2026-03-02" } Tests an expression as Attribute Flow 40 on Synchronisation Rule 12: the sample is converted to a date, as the Connected System Object would hold it, and the result is converted for the Attribute Flow's target attribute. .EXAMPLE Test-JIMExpression -Expression 'Lower(cs["givenName"]) + "." + Lower(cs["sn"])' -SyncRuleId 12 -MappingId 41 -ConnectedSystemObjectId '0b0e1c5a-6d3a-4c7e-9a43-6a7b5d2f1e01' Tests an expression as Attribute Flow 41 with a real Connected System Object's values, then shows what each input's absence would do through IfInputHasNoValue. .EXAMPLE Test-JIMExpression -Expression 'IIF((DateTime)cs["leaveDate"] < Today(), "Leaver", "Active")' -SyncRuleId 12 -MappingId 42 -CsAttributes @{ "leaveDate" = "2026-12-31" } -AsOf '2027-01-15' Tests what Attribute Flow 42 would give an object on 15 January 2027, when its leave date has passed. .EXAMPLE Test-JIMExpression -Expression 'Left(cs["givenName"], 1) + cs["sn"]' -SyncRuleId 12 -MappingId 43 -CsAttributes @{ "givenName" = "John"; "sn" = "Smith" } Tests the base expression of Attribute Flow 43, a Generated Value: BaseValue is the base generation would add its uniqueness token to, after the Attribute Flow's value processing. Which value is free is decided when a synchronisation runs. .EXAMPLE Test-JIMExpression -Expression 'RandomPassword(16, true)' Tests random password generation (result will vary on each run). .EXAMPLE Test-JIMExpression -Expression 'RandomPassphrase(3, "-")' Tests random passphrase generation (e.g., "purple-boat-ladder"). .LINK New-JIMSyncRuleMapping Get-JIMSyncRuleMapping #> [CmdletBinding(DefaultParameterSetName = 'Expression')] [OutputType('JIM.TestExpressionResponse')] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'Expression')] [Parameter(Position = 0, ParameterSetName = 'AttributeFlow')] [string]$Expression, [Parameter()] [hashtable]$MvAttributes, [Parameter()] [hashtable]$CsAttributes, [Parameter(Mandatory, ParameterSetName = 'AttributeFlow')] [int]$SyncRuleId, [Parameter(Mandatory, ParameterSetName = 'AttributeFlow')] [int]$MappingId, [Parameter(ParameterSetName = 'AttributeFlow')] [guid]$ConnectedSystemObjectId, [Parameter(ParameterSetName = 'AttributeFlow')] [guid]$MetaverseObjectId, [Parameter()] [datetime]$AsOf ) process { # Check connection first if (-not $script:JIMConnection) { Write-Error "You are not connected to JIM. Run Connect-JIM -Url <your JIM URL> to authenticate, then try again." return } # Build request body. A Generated Value with no base expression is tested without one. $body = @{} if ($PSBoundParameters.ContainsKey('Expression')) { $body.expression = $Expression } if ($MvAttributes) { $body.mvAttributes = $MvAttributes } if ($CsAttributes) { $body.csAttributes = $CsAttributes } if ($PSCmdlet.ParameterSetName -eq 'AttributeFlow') { $body.syncRuleId = $SyncRuleId $body.mappingId = $MappingId if ($PSBoundParameters.ContainsKey('ConnectedSystemObjectId')) { $body.connectedSystemObjectId = $ConnectedSystemObjectId } if ($PSBoundParameters.ContainsKey('MetaverseObjectId')) { $body.metaverseObjectId = $MetaverseObjectId } } if ($PSBoundParameters.ContainsKey('AsOf')) { # ISO 8601 with no offset unless one was given, so the API reads a bare date or time as UTC. $body.asOf = if ($AsOf.Kind -eq [DateTimeKind]::Unspecified) { $AsOf.ToString('yyyy-MM-ddTHH:mm:ss') } else { $AsOf.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') } } Write-Verbose "Testing expression: $Expression" try { $result = Invoke-JIMApi -Endpoint "/api/v1/synchronisation/test-expression" -Method 'POST' -Body $body if ($result.isValid) { Write-Verbose "Expression is valid. Result type: $($result.resultType)" } else { Write-Verbose "Expression is invalid: $($result.errorMessage)" } # The API's own object, so every property it reports reaches the caller. $result } catch { Write-Error "Failed to test expression: $_" } } } |