Welcome to MSDN Blogs Sign in | Join | Help

Syndication

MIIS/ILM Code Experiment: ILM Fantasy Football

Hello Everyone!

I am creating a series of posts crossing this MSDN blog and my TechNet blog http://blogs.technet.com/shawnrab where we will be build our own Fantasy Football engine utilizing Identity Lifecycle Manager with Feature Pack 1 and writing Visual Basic and Visual C# Rules Extensions

The initial rules of engagement are posted here:  http://blogs.technet.com/shawnrab/archive/2008/08/12/miis-ilm-tricks-ilm-fantasy-football.aspx

Have a good day!

--Shawn

 This posting is provided "AS IS" with no warranties, and confers no rights.

 

Posted Tuesday, August 12, 2008 8:46 PM by therabournidentity | 1 Comments

MIIS/ILM Code Experiment: Populating the Manager field in Active Directory

Hello again.  This post focuses on the manager attribute in Active Directory and using ILM to populate the field.  The manager attribute is a reference value.  So simply populating the name of the manager or some unique identifier is not enough.  Also, how do we know what manager goes with what person?  Well, without manual population I'd hope that ERP or a similar data source would provide the employeeID or an attribute like managerID that was a pointer back to the employeeID of the employee's manager. 

Why is the manager field important?  In Active Directory Users and Computers, it helps with the Organization tab on the user properties.  In populating a user's manager field, you also indirectly populate the manager's Direct Reports field.  This also helps enhance the Exchange GAL. 

This sample relies on having an employeeID that is unique for the enterprise and a managerID coming from somewhere.  I've also seen an attribute named similar to reportsTo used instead of managerID.   Two metaverse attributes need to be created managerID and adDN.  The managerID attribute would be flowed in from the ERP source and the adDN attribute would be a 1-1 mapping of the DN attribute in Active Directory.  We would set this in the MapAttributesForExport() function.  It relies on the manager having a correct, unique employee ID and looks back at the metaverse to see if the manager has an adDN attribute.  If the manager doesn't exist yet, the logic below will quietly fail.  The following attribute flows are:

ERP<----->MV<----->AD

ERP.person:managerID---(1-1 mapping)-->MV.person:managerID

MV.person:adDN<---(1-1 mapping)---AD.user:<dn>

MV.person:adDN(of the manager),managerID--->(rules extension mapping)--->AD.user:manager

Here is the rules extension of the Export Flow rule for populating AD.user:manager

 

                case "cd.user:manager<-mv.person:adDN,managerID":

                    if (mventry["managerID"].IsPresent)

                    {

                        MVEntry[] mveManager;

                        mveManager = Utils.FindMVEntries("employeeID", mventry["managerID"].Value); //performs a search

                        //on the managerID or reportsTo field coming in from ERP for entries with that corresponding employeeID

                         if (mveManager.Length == 1)//if we get only one return (which we should)

                        {

                            if (mveManager[0]["adDN"].IsPresent)//if there is the DN on that return

                            {

                                csentry["manager"].Value = mveManager[0]["adDN"].Value; //set the DN as the manager

                                break;

                            }

                            else

                            {

                                break;//dn may not be populated yet - it will occur on the next run

                            }

                        }

                        else

                        {

                            break;//should never happen (employeeID is unique)- we'll fall through if it does

                        }

                     }

                    else

                    {

                        break;

                    }

 

This method also works with a Secretary attribute.  You just need a secretaryID or similar and we reuse the adDN attribute.  WARNING!! FindMVEntries() does a search of the metaverse.  Make sure the attribute you are searching for is indexed in the metaverse.  In this case employeeID would need to be indexed. 

What if there is not a unique ID like employee ID?  You'll have to replace the trivial FindMVEntries backward lookup with logic similar to join rules for a manager.   Hopefully your ERP data provides enough to show who a person's manager is. 

Have a good day!

 

--Shawn

This posting is provided "AS IS" with no warranties, and confers no rights.

And Just in case...

This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code.

Posted Tuesday, April 29, 2008 7:52 PM by therabournidentity | 1 Comments

MIIS/ILM Code Experiment: Generic AD/ADAM Provisioning Function

Here is a fairly simple function to provision a user in Active Directory or ADAM and can be used interchangably so long as ADAM uses the object class "user" 

 

This function is making the assumption that your design will determine the RDN, the LDAP container/OU that the object will sit and the string name of the management agent you are provisioning to. 

 

/*

* ProvisionLDAPUser - Generic Provisioning function

* mventry is the affected metaverse entry

* MAName is the name of the Destination Management Agent

* rdn is the formatted relative name (ex: CN=John Smith or CN=Smith, John)

* container is the formatted destination container (ex: OU=Sales,OU=East,DC=northamerica,DC=fabrikam,DC=com)

*

*/

private void ProvisionLDAPUser(MVEntry mventry, string MAName, string rdn, string container)

{

        ConnectedMA ldapMA = mventry.ConnectedMAs[MAName];

        if (ldapMA.Connectors.Count == 0)//make sure we don't have any pre-existing connectors

        {

                CSEntry cseLDAP = ldapMA.Connectors.StartNewConnector("user");//generically start a new user

                cseLDAP.DN = ldapMA.EscapeDNComponent(rdn).Concat(container);//Concatenate the RDN and Container, ensuring proper formatting

                cseLDAP.CommitNewConnector();//Commit the new connector

        }

}

 

 If you wanted to, you could bring the predetermination of the user object type out of the function, in case you wanted to bring the logic for the determination of object (ex: user vs. contact)

private void ProvisionLDAP(MVEntry mventry, string MAName, string rdn, string container, string objectType)

{

        ConnectedMA ldapMA = mventry.ConnectedMAs[MAName];

        if (ldapMA.Connectors.Count == 0)//make sure we don't have any pre-existing connectors

        {

                CSEntry cseLDAP = ldapMA.Connectors.StartNewConnector(objectType);//generically start a new object

                cseLDAP.DN = ldapMA.EscapeDNComponent(rdn).Concat(container);//Concatenate the RDN and Container, ensuring proper formatting

                cseLDAP.CommitNewConnector();//Commit the new connector

        }

}

 There isn't any error handling on CommitNewConnector, for handling duplicates or incorrect/missing container (OU).  I like to handle outside of this function as I predetermine the rdn variable and container and if there is a caught error, I can handle it accordingly

 

try

{

        ProvisionLDAPUser(mventry, "Test Active Directory", String.Concat("CN=", mventry["displayName"].Value), "OU=Exchange Users,DC=test,DC=fabrikam,DC=com" );

 

 }

catch (ObjectAlreadyExistsException)

...

 

}

 

This is helpful if there is a conflict on naming.  Notice in my case, the RDN is the string literal "CN=" and the displayName attribute.  I can ensure some uniqueness in the creation of that displayName attribute and not have to worry about ObjectAlreadyExistsException.  If I don't, I can append an integer value or maybe use another attribute to append to the constructed RDN to ensure uniqueness. 

 More to come on this concept.

--Shawn

This posting is provided "AS IS" with no warranties, and confers no rights.

And Just in case...

This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code.

Posted Monday, February 11, 2008 10:50 PM by therabournidentity | 1 Comments

MIIS/ILM Code Experiment: XML-based MIIS/ILM Metaverse Router (part 1)

 

(formerly posted at http://blogs.technet.com/shawnrab/archive/2007/06/28/xml-based-miis-ilm-metaverse-router-part-1.aspx ) 

So one of the complaints of people who use MIIS/IIFP/ILM for GALSync is that there is only one field for a metaverse rules extension.  If you wanted to add provisioning in addition and seperate from the logic in the provision() block in the GALSync DLL, you would have to extend the GALSync.vb code which is subject to change (and be replaced in cumulative updates and service packs). There is decent solution out there called the MVRouter which looks in the MIIS extensions directory and looks for any DLL that starts with "mv" or "MVExtension" in the Extensions directory underneath the MIIS installation folder(technically it has a global called PREFIX that you can set).  It will call MV_Galsync.Provision() or MV_Extension.Provision() within MVRouter.Provision() if your PREFIX is "MV_".  The sample is located at the MIIS developer reference  (http://msdn2.microsoft.com/en-us/library/ms698364.aspx) and more targeted at http://msdn2.microsoft.com/en-us/library/ms696018.aspx

In sporadic posts, including this one we will build upon the sample at http://msdn2.microsoft.com/en-us/library/ms696018.aspx to create an XML-based MVRouter and we will use that to try some fairly cool things with MIIS.

I warn all of you.  This is just a sample.  We're going to grow this sample as time passes.  Also, suggestions are always appreciated.  There will always be a better way to write code. 

 

----snip----


using
System;
using Microsoft.MetadirectoryServices;
using System.Xml;

namespace
Miis_Metaverse
{
    public class MVExtensionObject : IMVSynchronization
    {
        IMVSynchronization[] myMVDlls;
//global array to pop the Metaverse Sync objects
        string[] MVsToRun; //global array for string literal DLL's
        XmlDocument doc = new XmlDocument(); //global XML document - we may want to bring this local later


        void IMVSynchronization.Initialize()
        {

            //the variable "dlls" is a number resulting from the private function popInitializeArr which will grab the 
            //metaverse dll files from the XML file
            short dlls = popInitializeArr();

 

            // Create the array the size of the number of files we found.
            myMVDlls = new IMVSynchronization[dlls];
            string fileName; // local variable for the file name
            Assembly assem; //local variable to use to load the DLL 
            Type[] types; //local variable array to get type declarations from the DLL
            Object objLoaded = null;

            // Load the extension files into the array.
           for(int i = 0; i < dlls; i++) //run this for the number of dll's that we have.

            //We could simplify and do a foreach if we wanted to 
            //ex: foreach (string mvtorun in mvstorun)
            {

                // Load the assembly.
                fileName = MVsToRun[i];

                assem = Assembly.LoadFrom(Utils.ExtensionsDirectory +
@"\" + fileName); //this checks for the file name/presence
 

                //note: we don't check to ensure that it is a DLL file here - a definite future improvement, not do we handle the 
                //lack of presence - we're assuming the file stated in XML actually exists
                types = assem.GetExportedTypes();

                foreach (Type type in types)
                {
                    if (type.GetInterface("Microsoft.MetadirectoryServices.IMVSynchronization") != null)
                    {

                       // Create an instance of the MV extension object type.
                        objLoaded = assem.CreateInstance(type.FullName);
                        break;

                    }

                }
//end foreach
 

                // If an object type within could not be found,
                // or if an instance could not be created, throw an exception.

                if (null == objLoaded)
                {

                    throw new UnexpectedDataException("Object type not found or instance not created");

                }
                // Add this MV extension object to our array of objects.
                myMVDlls[i] = (IMVSynchronization)objLoaded;

                // Call the Initialize() method on each MV extension object.
                myMVDlls[i].Initialize();

            }
        }

        

 

 

        void IMVSynchronization.Terminate()
        {

            // Call the Terminate() method on each MV extension object.
            foreach (IMVSynchronization mvextension in myMVDlls)
            {

                mvextension.Terminate();

            }

        }

 

 

        void IMVSynchronization.Provision(MVEntry mventry)
        {
            // Call the Provision() method on each MV extension object.
            foreach (IMVSynchronization mvextension in myMVDlls)
            {

                mvextension.Provision(mventry);

            }

        }

        

 

 

        bool IMVSynchronization.ShouldDeleteFromMV(CSEntry csentry, MVEntry mventry)
        {

            bool shouldDelete = false;
            // Call the ShouldDeleteFromMV() method on each MV extension object.
            foreach (IMVSynchronization mvextension in myMVDlls)
            {

                if (mvextension.ShouldDeleteFromMV(csentry, mventry))
                {

                    shouldDelete =
true;
                    break;

                }// end if
            }// end for
            return (shouldDelete);

        }

        

 

 

        /* popInitializeArr is a function created to populate a DLL array and return a number exemplifying the number of dll's in the array*/
        private short popInitializeArr()
        {

            short numParticipatingDLLs;
            numParticipatingDLLs = 0;
//assume nothing is there


            //logic to dig in XML to find the number of participating dlls
            doc.Load(Utils.ExtensionsDirectory + @"\MIISXML.xml"); //Utils.ExtensionsDirectory grabs the Extensions folder under the 
            //MIIS install folder



            XmlNode node = doc.SelectSingleNode(
"rules-extension-properties/dllfiles");


            //so the base tag is <rules-extension-properties> and we have a single child <dllfiles> - we are initializing 
            //variable "node" as <dllfiles>
            if (node.HasChildNodes) //I love expressions that do what they say
            {

                //right off the bat let's assume that the child nodes all correspond to dll file names
                //we'll do some checks in making this code better that ensure the DLL is present

                numParticipatingDLLs = (
short)node.ChildNodes.Count;
                //initialize the str array with the number we're running - we can concat later
                MVsToRun = new String[numParticipatingDLLs];
                //and we'll whittle it down and 
                int index = 0;

                foreach (XmlNode child in node.ChildNodes)
                {

                    //we probably need to check for the text ".dll" or append it later /check for indexOf ".dll" later...
                    MVsToRun.SetValue(child.Name, index);
                    index++;

                }
//end foreach

            }
// end if


  



            return numParticipatingDLLs;

        }
// end popInitializeArr

    }
//end class

}
//end namespace


 

----end snip----

 

So our requirements need to be that the file name has to correspond exactly with the XML tag and must exist in the extensions directory.  We should probably fix that later.  We also have some other homework to do and I have ideas to show the potential of this thing. 

Right now the XML tagging would look like this:

 

---snip----

<rules-extension-properties>

    <dllfiles>

        <examplemvdllname.dll></examplemvdllname.dll>

        <samplemvdllname2.dll></samplemvdllname2.dll>

        ...

    </dllfiles>

</rules-extension-properties>

----end snip----

 

Think about the potential here - we could use the tags and innertext to our advantage.  In the future, I will show some optimization I think we need and if anyone adds insight, I can add that.  Also I have an advancement that I want to put in place that may (or may not) work. 

 

--Shawn

 

 

 

 

This posting is provided "AS IS" with no warranties, and confers no rights.

And Just in case...

This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code.

Posted Tuesday, December 11, 2007 7:12 PM by therabournidentity | 1 Comments

MIIS/ILM Code Experiment: Joining using name tables for abbreviated and similar names

 

(formerly posted at http://blogs.technet.com/shawnrab/archive/2007/11/07/miis-ilm-rocket-science-joining-using-name-tables-for-abbreviated-and-similar-names.aspx ) 

I am tasked this week with performing joins.  We have an Active Directory environment and a Lotus Notes environment and we're simply trying to get the similar accounts joined.  Biggest problem is that we don't have anything consistent between the two.  No Employee ID and the Notes ShortName is not consistently the same as samAccountName in Active Directory.  Most of the identities have a first name, middle initial and a last name.  Some of them do not have a middle initial.

 We have around 10000 total identities and we were able to get all but 1700 joined with these three mappings:

1.) Lotus Notes (FirstName and MiddleInitial and LastName) ---direct mapping---> MV via Active Directory (givenName and initials and sn)

2.) LN(FirstName and LastName) ---direct mapping---> MV via AD (givenName and sn) with LN(ShortName) ---rules extension mapping--->MV via AD(samAccountName)

3.) LN(FirstName and LastName) ---direct mapping---> MV via AD (givenName and sn) with LN(ShortName) ---rules extension mapping--->MV via AD(first eight letters of the samAccountName)

 Where one of the entries of ShortName was "close" to samAccountName, however ShortName is multivalued

of the 1700 left, a large chunk is test accounts, service accounts and other fun items, but we found around 450 "potential" joins where we had LastName and one of the ShortName-samAccountName mappings correct however the first name was not correct.  I found a pattern where we would have a successful join if we could correlate similar or abbreviated names.  Example (names have been faked to protect the innocent):

 Similar/Misspelled Name-->Phillip D. Jones (pdjones) vs. Philip D. Jones (pdjones)

Abbreviated Name-->Richard J. Smith (rjsmith) vs. Rich J. Smith (rjsmith)

I created a file with entries comma seperated where these associations were made. Here are some example lines:

 Andrew,Andy,Drew

Edward,Eduard,Eddy,Eddie,Ed,Edwin

Jennifer,Jenifer,Jen,Jenny,Jeni,Jennie

Katherine,Kathleen,Kathrine,Catherine,Cathleen,Cathy,Kathy,Kate,Cate,Cathrine

Timothy,Tim,Timmy

...etc...

 

We were able to come up with over 100 names that could be abbreviated, spelled differently or easily misspelled.  I then created a multi-valued Metaverse Attribute called AltFirstName and flowed in these names.  To do so, I saved my flat file to the Extensions Directory (typically C:\Program Files\Microsoft Identity Integration Server\Extensions) and added the following logic to my Management Agent rules extensions (NOTE: the Notes Extension is below, the Active Directory Extension is similar, yet the attribute name givenName is used where in LN, FirstName is used.)

Public Class MAExtensionObject

Implements IMASynchronization

 

    Dim AltNameArr As String() ' global array to populate similar names

    Public Sub Initialize() Implements IMASynchronization.Initialize

        AltNameArr = File.ReadAllLines(Utils.ExtensionsDirectory + "\names.txt") ' single call to push the lines of the file to

        ' a String Array - each string represents the possible alternate names

    End Sub

 

...

    Public Sub MapAttributesForImport(ByVal FlowRuleName As String, _

    ByVal csentry As CSEntry, ByVal mventry As MVEntry) Implements _

    IMASynchronization.MapAttributesForImport

 

        Select Case FlowRuleName

            Case "AltFirstName"

                If Not mventry("AltFirstName").IsPresent Then ' this is rather expensive so only do it once

                    For Each nameArr As String In Me.AltNameArr ' for each line

                        If nameArr.IndexOf(csentry("FirstName").Value + ",", StringComparison.CurrentCulture) > -1 Then ' check to see if the name is present

                        ' I added the comma so "Jo" short for "Joanne" didn't return an index for "Joe" and the case sensitivity

                        ' StringComparison.CurrentCulture so "Ian" doesn't get confused with "Brian"

                            Dim AltFNValues As String() ' Initialize a single string array for each possibility

                            AltFNValues = nameArr.Split(",") ' Populate that array

                            For Each altFN As String In AltFNValues ' for each entry in the array

                                mventry("altFirstName").Values.Add(altFN) ' add a new value to the AltFirstName metaverse entry

                            Next

                        End If

                    Next

                End If

...

 

I then created these join mappings 

1.) LN(FirstName and LastName) ---direct mapping---> MV via AD (AltFirstName and sn) with LN(ShortName) ---rules extension mapping--->MV via AD(samAccountName)

2.) LN(FirstName and LastName) ---direct mapping---> MV via AD (AltFirstName and sn) with LN(ShortName) ---rules extension mapping--->MV via AD(first eight letters of the samAccountName)

 

Now, I am sure there is a more efficient way to do this, using SQL or XML rather than a flat file to import the name associations, or using a two-dimensional array instead of looping the file into one array and splitting to get a second array, but as a first test, we were rather successful with zero false positives.  We were able to recover 425 of the 450 "potential" joins this way. 

I'd like to mention that I received a lot of help from the customer on creating the large list of names (over 400 names) - Christine M. and my shadow this week, Raffe who also came up with a few as well.  Both were also very good voices of reason ensuring me that I wasn't unreasonable trying to get "Brian" to map to "Brien" when the last name was Smiswartszkoether (completely made-up last name intended to sound unique) on both sides. 

I am sure I am not the first person to do this, but if I can help someone out there out with their joins by posting this, I've done exactly what I've set out to do. 

Have fun, and good luck

--Shawn

UPDATE:  11/28 - For those people running SQL 2005, it appears that Alex Tcherniakhovski found a way to do this using functionality built in to SQL 2005 SSIS - see http://blogs.gotdotnet.com/alextch/archive/2005/07/15/init-record-linking-via-ssis.aspx

or

http://blogs.msdn.com/alextch/archive/2005/08/09/miis-and-ssis.aspx

to find out more. 

 

This posting is provided "AS IS" with no warranties, and confers no rights.

And Just in case...

This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code.

 

Posted Tuesday, December 11, 2007 7:10 PM by therabournidentity | 1 Comments

Shawn's MIIS/ILM Code Experiment: Obligatory First Post...

 

Hello! 

Welcome to my MIIS/ILM Code Experiment!  My name is Shawn Rabourn and I am in my seventh year at Microsoft and I currently work for Application Consulting and Engineering (ACE) Services as a Security Consultant.  This Blog is in response to a lot of the feedback I was receiving from my Technet Blog, http://blogs.technet.com/shawnrab - I have been experimenting with a lot of different coding concepts in Microsoft Identity Integration Server and Identity Lifecycle Manager and much of it belongs outside of the TechNet space and more in the MSDN space.  So, today I will be posting a few posts from my Technet Blog, making sure it is in the correct area.  I also have a lot of scripts and samples that I have stored away for a rainy day that I will post here in the future. 

 

 --Shawn

This posting is provided "AS IS" with no warranties, and confers no rights.

And Just in case...

This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code.

Posted Tuesday, December 11, 2007 7:00 PM by therabournidentity | 1 Comments

Filed under:

Page view tracker