Powershell Connector: Force multiple fields to be passed to a function

I am creating a custom PowerShell connector.  Within Identity Manager, we have the departments arranged in a hierarchy.  Level 1 departments is divisions in the target system, and level 2 departments are departments in the target system.  The target system stores the department and divisions through separate endpoints.  I initially created two PowerShell schemas for Division and Department but could not filter successfully to do what I want so looking at an alternative.

I am now trying another approach, is having a wrapper function that calls the respective backend function as required.  To do this, I am wanting Identity Manager to pass through the treelevel parameter on each call.  This is where I am failing.

My XML file is this:

<?xml version="1.0" encoding="utf-8"?>
<PowershellConnectorDefinition Id="XyzConnector" Description="Xyz connector" Version="1.0">
    <PluginAssemblies/>
    <ConnectionParameters>
        <ConnectionParameter Name="Module"   Description="Module"/>
        <ConnectionParameter Name="JsonFile" Description="Data file"/>
    </ConnectionParameters>
    <Initialization>
        <PredefinedCommands>
            <Command Name="Connect-Xyz"/>
            <Command Name="Disconnect-Xyz"/>
			<Command Name="Get-XyzDeptDivisions" />
			<Command Name="Get-XyzDeptDivision" />
			<Command Name="New-XyzDeptDivision" />
			<Command Name="Set-XyzDeptDivision" />
			<Command Name="Remove-XyzDeptDivision" />
         </PredefinedCommands>
        <CustomCommands>
            <CustomCommand Name="Import-ConnectorModule">
                <![CDATA[
                    param($Module)
                    Import-Module -Force $Module
                ]]>
            </CustomCommand>
            <CustomCommand Name="Connect-TargetSystem">
                <![CDATA[
                    param($JsonFile)
                    Connect-Xyz $JsonFile
                ]]>
            </CustomCommand>
            <CustomCommand Name="Disconnect-TargetSystem">
                <![CDATA[
                    Disconnect-Xyz
                ]]>
            </CustomCommand>
        </CustomCommands>
        <EnvironmentInitialization>
            <Connect>
                <CommandSequence>
                    <Item Command="Import-ConnectorModule" Order="1">
                        <SetParameter Param="Module" Source="ConnectionParameter" Value="Module"/>
                    </Item>
                    <Item Command="Connect-TargetSystem" Order="2">
                        <SetParameter Param="JsonFile" Source="ConnectionParameter" Value="JsonFile"/>
                    </Item>
                </CommandSequence>
            </Connect>
            <Disconnect>
                <CommandSequence>
                    <Item Command="Disconnect-TargetSystem" Order="1"/>
                </CommandSequence>
            </Disconnect>
        </EnvironmentInitialization>
    </Initialization>
    <Schema>
		<Class Name="XYZDeptDivisions">
				<Properties>
					<Property Name="ID" DataType="String" IsUniqueKey="true" IsMandatory="true" AccessConstraint="ReadOnly">							
						<ReturnBindings>
							<Bind CommandResultOf="Get-XyzDeptDivisions" Path="id" />
							<Bind CommandResultOf="Get-XyzDeptDivision" Path="id" />	
							<Bind CommandResultOf="New-XyzDeptDivision" Path="id" />							
						</ReturnBindings>
						<CommandMappings>
							<Map ToCommand="Get-XyzDeptDivision" 		 Parameter="id"/>
							<Map ToCommand="New-XyzDeptDivision"         Parameter="id"/>
							<Map ToCommand="Set-XyzDeptDivision"         Parameter="id"/>
							<Map ToCommand="Remove-XyzDeptDivision"      Parameter="id"/>
						</CommandMappings>							
					</Property>				
					<Property Name="Name" DataType="String" AccessConstraint="None">
						<ReturnBindings>
							<Bind CommandResultOf="Get-XyzDeptDivisions" Path="Name" />
							<Bind CommandResultOf="Get-XyzDeptDivision" Path="Name" />
						</ReturnBindings>
						<CommandMappings>
							<Map ToCommand="New-XyzDeptDivision"  Parameter="Name"/>
							<Map ToCommand="Set-XyzDeptDivision"  Parameter="Name"/>
						</CommandMappings>	
						<ModifiedBy>
							<ModBy Command="Set-XyzDeptDivision"/>
						</ModifiedBy>						
					</Property>			
					<Property Name="TreeLevel" DataType="Int" IsMandatory="true" AccessConstraint="None">
						<ReturnBindings>
							<Bind CommandResultOf="Get-XyzDeptDivisions" Path="treelevel" />
							<Bind CommandResultOf="Get-XyzDeptDivision" Path="treelevel" />
						</ReturnBindings>
						<CommandMappings>
							<Map ToCommand="Get-XyzDeptDivision" 		 Parameter="treelevel"/>
							<Map ToCommand="New-XyzDeptDivision"         Parameter="treelevel"/>
							<Map ToCommand="Set-XyzDeptDivision"         Parameter="treelevel"/>
							<Map ToCommand="Remove-XyzDeptDivision"      Parameter="treelevel"/>
						</CommandMappings>							
					</Property>					
				</Properties>
				<ReadConfiguration>
					<ListingCommand Command="Get-XyzDeptDivisions"/>
					<CommandSequence>
						<Item Command="Get-XyzDeptDivision" Order="1"/>
					</CommandSequence>
				</ReadConfiguration>
				<MethodConfiguration>
					<Method Name="Insert">
						<CommandSequence>
							<Item Command="New-XyzDeptDivision" Order="1"/>
						</CommandSequence>
					</Method>
					<Method Name="Update">
						<CommandSequence>
							<Item Command="Set-XyzDeptDivision" Order="1" Condition="ModificationExists">
								<SetParameter Param="con" Source="GlobalVariable" Value="con"/>
							</Item>
						</CommandSequence>
					</Method>
					<Method Name="Delete">
						<CommandSequence>
							<Item Command="Remove-XyzDeptDivision" Order="1"/>
						</CommandSequence>
					</Method>
				</MethodConfiguration>
			</Class>			
    </Schema>
</PowershellConnectorDefinition>

What I am wanting is for the TreeLevel and ID to passed through to every transaction.

An example of the PowerShell function that is called is thus:

function Set-XyzDeptDivision($id, $Name, $treelevel) {
    $Log.Info("Set-DeptDivision id=$id treelevel=$treelevel Name=$Name")
    $Log.Debug("  * parameters = {0}" -f ($PSBoundParameters | ConvertTo-Json -Compress))
	switch ($treelevel) {
		# Division
		$cDivisionLevel { 
			$res = Set-XyzDivision -id $id -Division_Name $Name
		}
		$cDepartmentLevel {
			$res = Set-XyzDepartment -id $id -Department_Name $Name
		}
		default {
			$log.Debug("Why are we even here - we should never get here!")
		}
		
	}
	
}

As can be seen, depending upon the value of tree level will determine what function will perform the work.

Looking at the log file I get this:

2024-01-12 15:54:46.8529 WARN (SystemConnector Posh-03EBEE Job 4d29d931-ee60-4e09-9a91-0845e4455b00) : Returned PSObject was $null or result was empty. If calling a custom cmdlet consider throwing an exception instead of returning $null. 
2024-01-12 15:54:46.8529 INFO (XyzConnectorLog  Job 4d29d931-ee60-4e09-9a91-0845e4455b00) : Set-DeptDivision id=9b13ad76-102e-4c4b-bcdd-10bfcc8aaf04 treelevel= Name=98765 - Security Division 
2024-01-12 15:54:46.8675 INFO (XyzConnectorLog  Job 4d29d931-ee60-4e09-9a91-0845e4455b00) : Get-DeptDivision id=9b13ad76-102e-4c4b-bcdd-10bfcc8aaf04  treelevel=

This is one using version 9.

Within mappings in Identity Manager I have treelevel from the department table mapped to the PowerShell schema. 

Is this possible, or what am I missing?

  • I think you are running into the following issue.
    The sync engine transfers ONLY the changed properties.
    If you turn on the logging you can see that the sync engine always perform a related Get-CustomObject before the Set-CustomObject
    and then removes all the unchanged properties even the ones that you defined as mandatory in the XML definition file.
    This behaviour is non-configurable. For more the details see:
    Powershell connector and ForceSyncOf parameter.

    As a work-around you have 2-options
    1 - Mark the mandatory properties as IsMandatory="true" and IsUniqueKey="true", there are no side-effects I'm aware of by doing this.
    2 - Warp the Get-CustomObject inside the Set-CustomObject and set the mandatory properties.

  • Thanks.  I went searching through the forum for an answer, but did not think that post was relevant.  Turns out it was.  Yes, this has resolved my issue and by using option 1 above, now get the treelevel parameter passed through.  Thanks for you quick response.

  • My previous reply has been removed by the automatic forum moderation, because of an included url?

    I think you are running into the following issue.
    The sync engine transfers ONLY the changed properties.
    If you turn on the logging you can see that the sync engine always perform a related Get-CustomObject before the Set-CustomObject
    and removes all the unchanged properties even the ones that you defined as mandatory in the XML definition file.
    This behaviour is non-configurable. For more the details search for this title in the forum
    "Powershell connector and ForceSyncOf parameter"

    As a work-around you have 2-options
    1 - Mark the mandatory properties as IsMandatory="true" and IsUniqueKey="true", there are no side-effects I'm aware of by doing this.
    2 - Warp the Get-CustomObject inside the Set-CustomObject and set the mandatory properties.

  • Thanks Niels

    I tried to reply to your message and caught in requiring a moderator to approve - so your message was there until I replied to your message.  Option1 worked a treat.  Option 2 is not an option for me, as I need treelevel parameter to determine what endpoint I need to speak to on the target system.  I did look through the forum, and skipped that thread as I did not think it applied to me.  Thank you for your prompt answer as well.  Looking at that thread you mention, it appears that while it is recognised as a "feature", there is no fix likely to come along, as it is still there in 9.1.1.  Be good if this was documented within the documentation somewhere.