Showing posts with label MA Rules Extensions. Show all posts
Showing posts with label MA Rules Extensions. Show all posts

Tuesday, February 19, 2013

The Last FIM Management Agent Rules Extension You Will Ever Need

For the next in the Last FIM series, I extended the concept introduced in The Last FIM Workflow You Will Ever Need to the Synchronization Engine.  In this case, this Management Agent Extension will take the Attribute Flow Name provided, compile it and run it like code.  So as an example, if you were creating an import flow to displayName and wanted to calculate it by concatenating  firstName + " " + lastName from the connector space, you could do the following as the Flow rule name: mventry[“displayName”].Value = csentry[“FirstName”].Value + " " + csentry[“LastName”].Value:

image

In order to make this work, download and install the CodePlex project from http://fimma.codeplex.com, then reference the Insight.FIM.CodelessSync.dll on the MA Extensions tab:

image

A few things to note, currently the code that you can place in the Flow rule name is limited to a single line, so only fairly simple calculations can be performed, but it may keep you from having to go to the Portal for "codeless" sync rules.  Only C# syntax is currently accepted, although a VB version could be written.  There may also be some value in writing a version that uses its own simplified syntax.  Only reference the .Value property of any mventry or csentry attribute.  The extension will inspect the data type to determine how to handle it from there.  Also, this can be nicely combined with an existing MA Extension code you might have, simply use it as your default call on your switch statement for import or export flow code:

 

public void MapAttributesForImport(string FlowRuleName, CSEntry csentry, MVEntry mventry)
       {
           switch (FlowRuleName)
           {
               case "existingFlowName":
                   //existing code
                   break;
               default:
                   runCommand(FlowRuleName, mventry, csentry, "mventry");
                   break;
           }           
       }

The extension is fairly new, so may still need some tweaking, but I wanted to get it out to the community for your feedback and contributions.  Let me know what you think!

Also, be on the watch for the next in the Last FIM series, The Last FIM Metaverse Extension You Will Ever Need.

Monday, November 16, 2009

The Missing ILM Extension

While recently browsing the Microsoft.Metadirectory services namespace for ILM I discovered a new extension that I had not seen before. In addition to the 6 that I was familiar with:
  • IMAExtensibleCallExport
  • IMAExtensibleFileExport
  • IMAExtensibleFileImport
  • IMAPasswordManagement
  • IMASynchronization
  • IMVSynchronization

There was a 7th that I had never hear of, the IMACalloutExtension. Curious, I began to investigate. The Extension implements the following methods:

public void BeginExportToCd(string connectTo, string user, string domain, string password)
{
}

public void EndExportToCd()
{
}

public void BeforeExportEntryToCd(string deltaEntryXml, string[] changedAttributes)
{
}

public void AfterExportEntryToCd(byte[] origAnchor, string origDN, string origDeltaEntryXml, byte[] newAnchor, string newDN, string failedDeltaEntryXml, string errorMessage)
{
}

The only documentation that I could find was within the interface definition itself. It appears that this extension was to be implemented using a new Management Agent of type "Callout" (or something similar) and gives the developer the ability to take action both before and after each entry export action, which could be extremely helpful. I haven't been able to find a roadmap for this feature, but it appears to be something we can look forward to in the future!

Monday, October 20, 2008

Hit or MIIS

While working out at a client site on their MIIS implementation, occasionally during a sync on one of their MAs, the Identity Manager Application would become almost completely unresponsive. I couldn't start or stop the running of an MA, execute a Preview, complete an MV Search, perform any manual Joins or Disconnections. It was all very frustrating, I would have to wait a few minutes until MIIS came back from the abyss to continue my work. The only other information that I had was from performing a SQL Profiler Trace. MIIS was definitely working on something, but what and why? <spoiler>The Answer: it has to do with the Joining activity that MIIS does while sync'ing an object.</spoiler> So, here's a little more background...

While configuring a Management Agent, you are given the ability to define rules that MIIS can use to determine if the object that its currently looking at should be linked to user data that it already has from other systems. This can be done by simply going to the Configure Join and Projection Rules in the Metaverse Designer and clicking on the New Join Rule button at the bottom:

In its simplest form, a Join Rule can be designed that directly correlates data in the source with data in MIIS. For example, if the First and Last Name are the same, join the identities together:

On the backend, MIIS is executing a query against the Metaverse table to get all of the object_ids of user's with data matching the join criteria:
select distinct [mms_metaverse].object_id from [mms_metaverse] with (holdlock) where (([<MVAttributeName>] =N'<CSAttributeValue>'))
So if we are sync'ing an individual named Joe Smith, we get a query like (and, yes, there really are both sets of parentheses around each expression):
select distinct [mms_metaverse].object_id from [mms_metaverse] with (holdlock) where (([givenName] =N'Joe')) and (([sn] =N'Smith))
It will then run the following series of queries to build a portfolio about every object_id returned:
exec mms_getmvsvall_xlock @mvobjid = '<object_id>';
exec mms_getmvmulti @mvobjid = '<object_id>';
exec mms_getmvrefasobjid @mvobjid = '<object_id>';
Once MIIS has all of this data, it can join with an existing identity for Joe Smith, if one is found. In this scenario, if more than one Joe Smith is found, MIIS will consider this a Non-match and move onto the next join rule. It will continue to execute the join rules one after the other, in turn, until either a join is made or it reaches the end of the list. If no join is made, MIIS will then move onto projection, if this option has been defined.

Now, In an actual deployment these join scenarios are usually a little more complicated. To resolve a more complicated search/match, we usually have to go out to our rules extension code. There are two places you can inject code during the join procedure. The first of these is done by using the Rules extension mapping type. This will allow you to add some additional logic around the attribute equivalency search. Here is what I mean... you are trying to join based on Social Security Number and the source system has SSNs stored as 123-456-789 but MIIS has it stored as 123456789, while these two are not technically equivalent if doing a direct evaluation, we can add code to ensure that evaluation is done "correctly" via code executed in the MapAttributesForJoin Method.

The second place we can inject code is to pick the proper join out of the X number of candidates found by the Join search. So continuing from our example above, if we have defined a join based on SSN, but our data tends to be pretty dirty and multiple people have the same SSN. We have ensured that our SSN matching is performed properly above in the MapAttributesForJoin by reformatting the data, but the match/search logic finds 5 people that could potentially match my source SSN of 123-456-789. We can add additional code in the ResolveJoinSearch method to determine, based on other data elements, say last name, that number 3 of the 5 people found is the right individual to join with.

So, here is a more real world example. What if we want to join people base on their birth date with a last name or previous last name match? In this case we will have to go out to code to determine whether a join candidate can be found. We would start by creating a join rule based on birth date:

We have decided to use a Mapping Type of Rules extension to allow us to go out to code to reformat the birth date to ensure that it matches the format in MIIS. In our rules extension, MIIS would be looking for an entry point of "cd.person#1:mccdDOB->mccdDOB" in the MapAttributesForJoinMethod:
switch (FlowRuleName)
{

     case "cd.person#1:mccdDOB->mccdDOB":

          if ( csentry["mccdDOB"].IsPresent )
          {

               //reformat the dob with style used in the MV
               values.Add( System.Convert.ToDateTime(csentry["mccdDOB"].StringValue).ToString("yyyy-MM-dd") );

          }

          break;

     default:
          throw new EntryPointNotImplementedException();

}
We have also selected the Use rules extension to resolve option. This will allow us to add code to check current and previous last names against those individuals found with the same birth date. This is done using the following code in the ResolveJoinSearch Method:
//set index of final join candidate to -1 to indicate no join found
imventry = -1;

//create a variable to keep our place in the loop
int curIndex = 0;

switch (joinCriteriaName)
{

     case "cd.student#1":

          //loop through each person to check current/prev last name
          foreach (MVEntry mventry in rgmventry)
          {

               if ( csentry["sn"].IsPresent && mventry["sn"].IsPresent)
               {

                    //try to join on last name
                    if ( csentry["sn"].StringValue == mventry["sn"].StringValue )
                    {

                         //its a match, return the index of this individual
                         imventry = curIndex;

                    }

               }
               else if ( csentry["prevSn"].IsPresent && mventry["sn"].IsPresent)
               {

                    //try to join on previous last name
                    if ( csentry["prevSn"].StringValue == mventry["sn"].StringValue )
                    {

                         //its a match, return the index of this individual
                         imventry = curIndex;

                    }

               }

               //we are moving to the next object, increment the counter
               curIndex++;

          }

          break;

     default:
          throw new EntryPointNotImplementedException();

}

//return index of individual found
return imventry;
So, now that we have all of the basics of joining down, we can get back to the problem. Did you catch it? When MIIS is performing the join search, it executes a set of stored procedures to get all of the data about the matching Metaverse objects using an exclusive lock, locking up our Metaverse table from any other reads or updates until the search is complete. This usually isn't a problem because this process happens so quickly you usually won't notice it. With that said, be very careful not to create inefficient join rules that return more than a few results. If more than a few records need to be returned and evaluated, not only will being to see a degradation in the UI response of Identity Manager, it will also take exponentially longer for the MA to complete.