Let’s start with the basic problem of how to provision users in ADAM that exist in Active Directory.  The solution lies in MIIS rule extensions.  Rule extensions are .NET DLLs with classes that implement either one of two interfaces defined in the MIIS API: IMVSynchronization, and IMASynchronization.  By implementing an interface in a class, one is of course guaranteeing that one’s class implements certain methods.  Therefore, if one’s DLL contains a class implementing either one of the two interfaces that MIIS knows about, MIIS knows that it can invoke certain methods of the class whenever certain events occur.  The MV in the name of the IMVSynchronization interface refers to the meta-verse, while the MA in the name of the IMASynchronization interface stands for management agent.  So, a class that implements IMVSynchronization will have its methods invoked by MIIS when events occur in the meta-verse, while a class that implements IMASynchronization will have its methods invoked when events occur in the connector space of a given management agent.  The Provision method of a class implementing the IMVSynchronization interface is invoked when an object is projected into the meta-verse, which provides us with the opportunity we need to execute some code to provision in ADAM the user that is being projected from Active Directory into the meta-verse. 

 

We’ll go step-by-step through the process of building the rule extension that we need.  First, we recognize that since a rule extension implementing IMVSynchronization is invoked by MIIS as events occur in the meta-verse, the rule extension will be associated not with any particular management agent, but with the meta-verse itself.  The process of creating rule extensions for the meta-verse begins by going to the MIIS Identity Manager Tools menu and choosing Configure Extensions.  A dialog appears on which one can designate a rule extension to handle events occurring in the meta-verse.  Note that there is a check-box specifically to enable calls for provisioning events.  The dialog provides a button for generating a Visual Studio.NET project for programming the rule extension in either Visual Basic.NET or C#.  Naturally, that project will include a class that implements IMVSynchronization, but it is also important to know that MIIS expects to find rule extension DLLs within a particular subfolder of its program folder, and consequently, the project that MIIS generates for you is configured so as to compile its output into that subfolder. 

 

Now we can look at the code for the rule extension that provisions users from Active Directory into ADAM.  

 

using System;

using Microsoft.MetadirectoryServices;

using Microsoft.DeveloperAndPlatformEvangelism.Demonstrations.TaskVisionII.Utility;

 

namespace Mms_Metaverse

{

       //Rules extension projects must be configured so as to compile into the \Program Files\Microsoft Identity   

       // Integration Server\Extensions

       //When a rules extension project is created using MIIS, it is automatically configured in that way. 

 

       /// <summary>

       /// Provisions users in the TaskVisionII application directory partition of ADAM.

       /// </summary>

    public class MVExtensionObject: CLoggingRuleExtension, IMVSynchronization

    {

              private const string c_sFile_Log = @"Provision_ADAM_TaskVisionII.log";

              private const byte c_byThreshold_Log = 10;

 

              private const string c_stAgent_Contributing = @"ActiveDirectory";

              private const string c_stAgent_Target = @"ADAM_TaskVisionII";

 

              private const string c_stContainer_Target = @"CN=Users,O=TaskVisionII,C=US";

 

             

        public MVExtensionObject()

        {

                     base.sFile_Log = MVExtensionObject.c_sFile_Log;

                     base.byThreshold_Log = MVExtensionObject.c_byThreshold_Log;

              }

 

        void IMVSynchronization.Initialize ()

        {

            //

            // TODO: Add initialization logic here

            //

        }

 

        void IMVSynchronization.Terminate ()

        {

            //

            // TODO: Add termination logic here

            //

        }

 

              void IMVSynchronization.Provision (MVEntry rCurrentConnector)

              {

                     try

                     {

                           /*

                            *     In \Program Files\Microsoft Identity Integration Server\Extensions, there is a library

                            *  assembly called, "Logging.DLL" that can be used to output debug messages. 

                            *

                            *  In this class, methods have been programmed to wrap those of the logging assembly. 

                            *

                            *  For some reason, if one removes the reference to the Logging assembly, then the assembly itself

                            *  gets deleted from the disk.  Happily, the source code is in

                            *  \Program Files\Microsoft Identity Integration Server\SourceCode\Logging, so one can compile

                            *  it to get a new copy. 

                            *

                            *  By default, when one creates a rules extension project from the MIIS user interface, a reference

                            *  to the assembly, \Program Files\Microsoft Identity Integration

                            *  Server\bin\assemblies\Microsoft.MetadirectoryServices.dll. 

                            *  That assembly exposes the classes that one will need to write a rules extension: in particular,

                            *  it contains the types passed to the rules extension as parameters.  However, the Logging assembly

                            *  exposes the same namespace of classes, so if one will be doing any logging, then that is the

                            *  only assembly one requires. 

                            * 

                            */

                           this.Log(@"Provisioning ... ");

                           /*

                            * Visual Studio.NET, cannot interogate the types exposed from the MIIS assemblies properly, so

                            * their methods do not show up either in Intellisense or in the Object Browser.  However, the

                            * documentation in the MIIS developer's reference, identifying properties and methods that cannot

                            * be seen are accurate, and correct references to those properties and methods will compile. 

                            *

                            */

                           this.Log(string.Concat("Object type: ",rCurrentConnector.ObjectType));

                          

 

                                                     

                           switch(rCurrentConnector.ObjectType)

                           {

                                  case @"person":

                                  case @"group":

                                         break;

                                  default:

                                         return;

                           }

                            

                           try

                           {

                                  string sName = rCurrentConnector[@"cn"].Value;

                           }

                           catch(Exception)

                           {

                                  return;

                           }

 

                           this.Log(string.Concat("Last Contributing Agent: ",rCurrentConnector[@"cn"].LastContributingMA.Name));

                            if(string.Compare(rCurrentConnector[@"cn"].LastContributingMA.Name,MVExtensionObject.c_stAgent_Contributing,true)!=0)

                           {

                                  return;

                           }

                          

 

                           ConnectedMA rAgent = rCurrentConnector.ConnectedMAs[MVExtensionObject.c_stAgent_Target];

                           this.Log(string.Concat(@"Agent: ",rAgent.Name));

                           int cConnectors = rAgent.Connectors.Count;

 

                          

 

                           string sRelativeDistinguishedName = null;

                           try

                           {

                                  sRelativeDistinguishedName = string.Concat(@"CN=",rCurrentConnector[@"cn"].Value);

                           }

                           catch(Exception rException)

                           {

                                  this.Log(string.Concat(@"Exception: ",rException.Message));

                                  return;

                           }

                           this.Log(string.Concat(@"Provisioning ",rCurrentConnector[@"cn"].Value));

 

                           ReferenceValue rDistinguishedName = rAgent.EscapeDNComponent(sRelativeDistinguishedName).Concat

                                 (MVExtensionObject.c_stContainer_Target);

                           this.Log(string.Concat(@"DN: ",rDistinguishedName.ToString()));

 

                           CSEntry rProvisioned = null;

                           switch(cConnectors)

                           {

                                  case 0:

                                         this.Log(@"Starting new connector.");

                                        

                                         string sObjectType = null;

 

                                         switch(rCurrentConnector.ObjectType)

                                         {

                                                case @"person":

                                                       sObjectType = "user";

                                                       break;

                                                case @"group":

                                                       sObjectType = "group";

                                                       break;

                                                default:

                                                       throw new UnexpectedDataException("Unexpected object type!");

                                         }

                                        

                                         rProvisioned = rAgent.Connectors.StartNewConnector(sObjectType);

                                        

                                         rProvisioned.DN = rDistinguishedName;

                                         rProvisioned.CommitNewConnector();

                                         break;

                                  case 1:

                                         this.Log(@"Modifying existing connector.");

                                         rProvisioned = rAgent.Connectors.ByIndex[0];

                                         rProvisioned.DN = rDistinguishedName;

                                         break;

                                  default:

                                         throw new UnexpectedDataException("Multiple Connectors on Management Agent!");

                           }

                     }

                     catch(Exception rException)

                     {

                           //All exceptions are caught and displayed by the MIIS user interface,

                           //so this block is merely for the sake of centralizing control over the exception. 

                           this.Log(((rException.Message == null)||(rException.Message == string.Empty))?@"Unknown exception.":string.Concat("Exception: ",rException.Message));

                           throw rException;

 

 

                     }

             

                    

 

        }    

 

        bool IMVSynchronization.ShouldDeleteFromMV (CSEntry csentry, MVEntry mventry)

        {

            //

            // TODO: Add MV deletion logic here

            //

        }

    }

}

 

 

Our rule extension inherits from CLoggingRuleExtension, which is defined thus:

 

using System;

 

namespace Microsoft.DeveloperAndPlatformEvangelism.Demonstrations.TaskVisionII.Utility

{

       public class CLoggingRuleExtension

       {

              protected string sFile_Log;

              protected byte byThreshold_Log;

              protected bool fLogConfigured = false;

 

              public CLoggingRuleExtension()

              {

                     //

                     // TODO: Add constructor logic here

                     //

              }

 

              protected void Log(string sEntry, byte byLevel)

              {

                     if(!(this.fLogConfigured))

                     {

                           //The log file goes into the folder, D:\Program Files\Microsoft Identity Integration Server\MaData

                            Microsoft.MetadirectoryServices.Logging.Logging.SetupLogFile(this.sFile_Log,this.byThreshold_Log);

                           this.fLogConfigured = true;

                     }

 

                     //For an entry to be added to the log file, the logging level for the entry must be equal to or below the threshold

                     //specified when the file was set up. 

                     Microsoft.MetadirectoryServices.Logging.Logging.Log(sEntry,true,byLevel);

              }

 

              protected void Log(string sEntry)

              {

                     this.Log(sEntry,0);

              }

       }

}

 

In the code for the rule extension, you will note, to start with, that the Provision handler takes a parameter identifying the object in the connector space that has been projected into the meta-verse.  You will recall that on the dialog for configuring the meta-verse rule extension, one can only select a single rule extension for the meta-verse.  From the parameter identifying the current connector, one can determine which management agent projected the object into the connector space using the LastContributingConnector property to tailor the behaviour of the rule extension according to the currently-executing management agent. 

 

The first couple of lines of code do nothing other than write out to a log.  That rules extensions can write to a log file is key to one being able to debug their behaviour.  MIIS provides a library, in the extensions folder, called Logging.DLL, that provides methods for writing out to a log file with a specified name.  The log files for meta-verse rule extensions get written into the MAData subfolder of the MIIS program folder.  Log files for management agents get written into subfolders of the MAData subfolder named for the management agent in question.  Logging.DLL exposes the methods, SetUpLogFile, which is used to specify the name for the log file, and the Log method for actually writing entries to it.  While those methods are utterly invaluable for debugging a rule extension, there is one curiosity to which I should alert you.  If one deletes one’s reference to the Logging.DLL from a rule extension project, then you may find that the DLL itself has been deleted, which can be disconcerting.  Happily, the MIIS installation provides the source code for the Logging library in the form of a Visual Studio.NET project that one can simply open and compile in order to restore the DLL. 

 

The remainder of the code for the rule extension determines the type of object that has been projected into the meta-verse.  We are only interested in groups and users, so we exit the routine if the object is of any other type.  Note that the class that is called users in Active Directory typically maps to the person class in MIIS.  That person class is intended to cover all of the various classes that may be defined to accommodate users in the schemas of different directory services.  Anyway, in the projection rules for the Active Directory management agent, we say that users in the Active Directory connector space are projected as persons into the meta-verse, and, consequently, in our handler for the provision event, we see the object that has been projected into the meta-verse as a person object rather than as a user object. 

 

Next, the code checks whether the management agent that caused a person or group object to be projected into the meta-verse was the management agent for Active Directory.  That is the only case we are interested in, so, again, we exit from the routine if that condition is not met. 

 

The next step is to use the ConnectedMAs collection of the parameter representing the project object to get a reference to the management agent for ADAM.  Of course, our objective is to have the object that was projected into the meta-verse provisioned in ADAM, and the MIIS management agent for ADAM has been configured to be able to talk to ADAM, to know what port to use, and to provide a valid set of credentials.  After retrieving the attributes of the object that was projected into the meta-verse, we use the StartNewConnector method of the ADAM management agent to create a new object in the connector space for ADAM corresponding to the object that was projected into the meta-verse from Active Directory. 

 

So, to recap, our meta-verse rule extension has been programmed to respond to the projection of objects into the MIIS meta-verse.  For group and user objects projected into the meta-verse from ActiveDirectory, the code has the management agent for ADAM create corresponding objects in the ADAM connector space.