How to query the Active Directory Schema using LinqToLdap

When creating ClassMaps for earlier articles, I’ve needed info from the AD Schema.  Earlier, I thought, “Why not do this using LinqToLdap?”  So I did.  In this article, I show you what’s needed so you can do the same.  I build on earlier articles, Creating a directory objects assembly for LinqToLdap, in particular.

Overview

First, I added a couple of properties to Top.cs: adminDescription and adminDisplayName. I’d already created a new subclass of Top, TopX, to hold attributes for other directory object classes I haven’t blogged about, yet. I’m not keen on the name, so it may change. Feel free to offer alternatives.

Then, I created new directory object class files called ClassSchema.cs and AttributeSchema.cs with matching ClassMap files, ClassSchemaMap.cs and AttributeSchemaMap.cs.

Finally, I used them in a series of queries.  I’ve included all of the files below.  Here are some examples of queries I’ve run.

The first example retrieves a particular classSchema entry, user, and displays selected properties.

    static void Main(string[] args)
    {
        var config = new LdapConfiguration()
            .AddMapping(new ilminator.LinqToLdap.DirectoryObjects.ClassMaps.AttributeSchemaMap())
            .AddMapping(new ilminator.LinqToLdap.DirectoryObjects.ClassMaps.ClassSchemaMap());

        var factory = config.ConfigureFactory("dc1");    // Insert your DC name or domain DNS name here

        var context = new DirectoryContext();

        // ----- Start of section to replace -----

        var query = context.Query<ClassSchema>()
            .Where(c => c.lDAPDisplayName.Equals("user"));

        foreach (var item in query)
        {
            Console.WriteLine("lDAPDisplayName = {0}", item.lDAPDisplayName);
            DisplaySingleValuedAttribute(item.subClassOf, "subClassOf");
            DisplayMultiValuedAttribute(item.systemAuxiliaryClass, "systemAuxiliaryClass");
            DisplayMultiValuedAttribute(item.auxiliaryClass, "auxiliaryClass");
            DisplayMultiValuedAttribute(item.systemMustContain, "systemMustContain");
            DisplayMultiValuedAttribute(item.mustContain, "mustContain");
            DisplayMultiValuedAttribute(item.systemMayContain, "systemMayContain");
            DisplayMultiValuedAttribute(item.mayContain, "mayContain");
        }

        // ----- End of section to replace -----

        Console.WriteLine("\r\nPress a key to continue...");
        Console.ReadKey(true);
    }

    private static void DisplaySingleValuedAttribute(Object attribute, string attributeName)
    {
        if (attribute != null)
        {
            Console.WriteLine("{0,-30}: {1}", attributeName, attribute);
        }
    }

    private static void DisplayMultiValuedAttribute(Collection<String> attributeValues, string attributeName)
    {
        if (attributeValues != null)
        {
            var list = new List<String>(attributeValues);
            list.Sort();

            Console.WriteLine("{0}:", attributeName);
            foreach (var item in list)
            {
                Console.WriteLine("    {0}", item);
            }
        }
    }

The second example retrieves all classSchema entries which have mailRecipient as an auxiliaryClass or systemAuxiliaryClass and displays their lDAPDisplayNames.

    // ----- Start of section to replace -----

    var query = context.Query<ClassSchema>()
        .Where(c => (c.auxiliaryClass.Contains("mailRecipient") || c.systemAuxiliaryClass.Contains("mailRecipient")));

    foreach (var item in query)
    {
        Console.WriteLine(item.lDAPDisplayName);
    }
            
    // ----- End of section to replace -----

The third example retrieves all classSchema entries which have mail as an attribute displays their lDAPDisplayNames.

    // ----- Start of section to replace -----

    var query = context.Query<ClassSchema>()
        .Where(c => (c.mustContain.Contains("mail")
            || c.systemMustContain.Contains("mail")
            || c.mayContain.Contains("mail")
            || c.systemMayContain.Contains("mail")));

    foreach (var item in query)
    {
        Console.WriteLine(item.lDAPDisplayName);
    }
            
    // ----- End of section to replace -----

The fourth example retrieves and displays all of the mapped attributes of all attributeSchema entries.

    // ----- Start of section to replace -----

    DisplayAllAttributes(context);
            
    // ----- End of section to replace -----

and

    private static void DisplayAllAttributes(DirectoryContext context)
    {
        var query = context.Query<AttributeSchema>();

        var attributes = from a in query
                            select a;

        foreach (var attribute in attributes)
        {
            DisplaySchemaAttribute(attribute);
            Console.WriteLine();
        }
    }

    private static void DisplaySchemaAttribute(AttributeSchema attribute)
    {
        DisplaySingleValuedAttribute(attribute.lDAPDisplayName, "lDAPDisplayName");
        DisplaySingleValuedAttribute(attribute.commonName, "commonName");
        DisplaySingleValuedAttribute(attribute.adminDescription, "adminDescription");
        DisplaySingleValuedAttribute(attribute.adminDescription, "adminDescription");
        DisplaySingleValuedAttribute(attribute.attributeID, "attributeID");
        DisplaySingleValuedAttribute(attribute.attributeSyntax, "attributeSyntax");
        DisplaySingleValuedAttribute(attribute.classDisplayName, "classDisplayName");
        DisplayMultiValuedAttribute(attribute.description, "description");
        DisplaySingleValuedAttribute(attribute.displayName, "displayName");
        DisplaySingleValuedAttribute(attribute.distinguishedName, "distinguishedName");
        DisplaySingleValuedAttribute(attribute.isDefunct, "isDefunct");
        DisplaySingleValuedAttribute(attribute.isMemberOfPartialAttributeSet, "isMemberOfPartialAttributeSet");
        DisplaySingleValuedAttribute(attribute.isSingleValued, "isSingleValued");
        DisplaySingleValuedAttribute(attribute.linkID, "linkID");
        DisplaySingleValuedAttribute(attribute.name, "name");
        DisplaySingleValuedAttribute(attribute.objectGuid, "objectGuid");
        DisplaySingleValuedAttribute(attribute.oMObjectClass, "oMObjectClass");
        DisplaySingleValuedAttribute(attribute.oMSyntax, "oMSyntax");
        DisplaySingleValuedAttribute(attribute.rangeLower, "rangeLower");
        DisplaySingleValuedAttribute(attribute.rangeUpper, "rangeUpper");
        DisplaySingleValuedAttribute(attribute.searchFlags, "searchFlags");
        DisplaySingleValuedAttribute(attribute.systemOnly, "systemOnly");
    }

The fifth example retrieves and displays all of the mapped attributes of all classSchema entries.

    // ----- Start of section to replace -----

    DisplayAllClasses(context);
            
    // ----- End of section to replace -----

and


    private static void DisplayAllClasses(DirectoryContext context)
    {
        var schemaQuery = context.Query<ClassSchema>();

        var schemaclasses = from a in schemaQuery
                            select a;

        foreach (var schemaClass in schemaclasses)
        {
            DisplaySchemaClass(schemaClass);
            Console.WriteLine();
        }
    }

    private static void DisplaySchemaClass(ClassSchema schemaClass)
    {
        DisplaySingleValuedAttribute(schemaClass.lDAPDisplayName, "lDAPDisplayName");
        DisplaySingleValuedAttribute(schemaClass.commonName, "commonName");
        DisplaySingleValuedAttribute(schemaClass.adminDescription, "adminDescription");
        DisplaySingleValuedAttribute(schemaClass.adminDisplayName, "adminDisplayName");
        DisplayMultiValuedAttribute(schemaClass.auxiliaryClass, "auxiliaryClass");
        DisplaySingleValuedAttribute(schemaClass.classDisplayName, "classDisplayName");
        DisplaySingleValuedAttribute(schemaClass.defaultHidingValue, "defaultHidingValue");
        DisplaySingleValuedAttribute(schemaClass.defaultObjectCategory, "defaultObjectCategory");
        DisplayMultiValuedAttribute(schemaClass.description, "description");
        DisplaySingleValuedAttribute(schemaClass.displayName, "displayName");
        DisplaySingleValuedAttribute(schemaClass.distinguishedName, "distinguishedName");
        DisplaySingleValuedAttribute(schemaClass.governsID, "governsID");
        DisplaySingleValuedAttribute(schemaClass.isDefunct, "isDefunct");
        DisplayMultiValuedAttribute(schemaClass.mayContain, "mayContain");
        DisplayMultiValuedAttribute(schemaClass.mustContain, "mustContain");
        DisplaySingleValuedAttribute(schemaClass.name, "name");
        DisplaySingleValuedAttribute(schemaClass.objectClassCategory, "objectClassCategory");
        DisplaySingleValuedAttribute(schemaClass.objectGuid, "objectGuid");
        DisplayMultiValuedAttribute(schemaClass.possSuperiors, "possSuperiors");
        DisplaySingleValuedAttribute(schemaClass.rDNAttID, "rDNAttID");
        DisplaySingleValuedAttribute(schemaClass.schemaIDGUID, "schemaIDGUID");
        DisplaySingleValuedAttribute(schemaClass.showInAdvancedViewOnly, "showInAdvancedViewOnly");
        DisplaySingleValuedAttribute(schemaClass.subClassOf, "subClassOf");
        DisplayMultiValuedAttribute(schemaClass.systemAuxiliaryClass, "systemAuxiliaryCLass");
        DisplayMultiValuedAttribute(schemaClass.systemMayContain, "systemMayContain");
        DisplayMultiValuedAttribute(schemaClass.systemMustContain, "systemMustContain");
        DisplaySingleValuedAttribute(schemaClass.systemOnly, "systemOnly");
        DisplayMultiValuedAttribute(schemaClass.systemPossSuperiors, "systemPossSuperiors");
    }

Top and TopX

This is what Top looks like, now. Note that I’ve added a namespace, AbstractClasses, and moved Top into it. The ‘Abstract’ part refers to classes that are abstract in the project rather than in Active Directory. I’ve also added an abstract class, TopX. It contains commonName which is actually part of top but is optional. It’s absent from organizationalUnit so it doesn’t make sense to have it in Top. It also contains description, in it’s multi-valued incarnation. See the class summaries for more info.

using System;
using System.Collections.ObjectModel;
using LinqToLdap.Mapping;

namespace ilminator.LinqToLdap.DirectoryObjects.AbstractClasses
{
    /// <summary>
    /// Classes should not normally inherit Top but should instead choose one of
    /// existing subclasses: <see cref="TopX"/>, <see cref="SecPrinMailRecipEtc"/> 
    /// or <see cref="OrganizationalUnitObject"/> as these have additional 
    /// attributes common to other classes.
    /// </summary>
    public abstract class Top : DirectoryObjectBase
    {
        /// <summary>
        /// The description displayed on admin screens.
        /// [From: http://msdn.microsoft.com/en-us/library/ms675213.aspx]
        /// RangeLower: 0
        /// RangeUpper: 1024
        /// </summary>
        public String adminDescription { get; set; }
        /// <summary>
        /// The name to be displayed on admin screens.
        /// [From: http://msdn.microsoft.com/en-us/library/ms675214.aspx]
        /// RangeLower: 1
        /// RangeUpper: 256
        /// </summary>
        public String adminDisplayName { get; set; }

        // description cannot be implemented here because it has different uses in 
        // Active Directory.  For SAM-managed objects, it is used as if it were
        // defined as single-valued.  For other objects, it is used as defined, 
        // which is as a multi-valued object.  For this reason, I think it makes
        // sense to implement it differently for SAM- and non-SAM-managed objects.
        // See the definitions of SecPrinMailRecipEtc and OrganizationalUnit for
        // examples.
        // Ref: http://msdn.microsoft.com/en-us/library/windows/desktop/ms675492.aspx.

        private string _displayName;
        /// <summary>
        /// Display-Name.  The display name for an object. This is usually 
        /// the combination of the users first name, middle initial, and last name.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms675514.aspx]
        /// </summary>
        public String displayName
        {
            get { return this._displayName; }
            set
            {
                if (value != this._displayName)
                {
                    this._displayName = value;
                    AttributeChanged<Top, String>(a => a.displayName);
                }
            }
        }

        /// <summary>
        /// Obj-Dist-Name.
        /// </summary>
        public String distinguishedName { get; set; }
        /// <summary>
        /// RDN.  The Relative Distinguished Name of an object.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms678697.aspx]
        /// </summary>
        public String name { get; set; }
        /// <summary>
        /// Object-Guid.  The unique identifier for an object.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms679021.aspx]
        /// </summary>
        public Guid objectGuid { get; set; }
        /// <summary>
        /// USN-Changed.  USN value assigned by the local directory for the 
        /// latest change, including creation.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680871.aspx]
        /// </summary>
        public Int64 uSNChanged { get; set; }
        /// <summary>
        /// USN-Created.  USN-Changed value assigned at object creation.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680874.aspx]
        /// </summary>
        public Int64 uSNCreated { get; set; }
        /// <summary>
        /// When-Changed.  The date when this object was last changed. This 
        /// value is not replicated and exists in the global catalog.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680921.aspx"]
        /// </summary>
        public DateTime whenChanged { get; set; }
        /// <summary>
        /// When-Created.  The date when this object was created. This value
        /// is replicated and is in the global catalog.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680924.aspx"]
        /// </summary>
        public DateTime whenCreated { get; set; }

        private string _wWWHomePage;
        /// <summary>
        /// WWW-HomePage.  The primary webpage.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680927.aspx]
        /// </summary>
        public String wWWHomePage
        {
            get { return this._wWWHomePage; }
            set
            {
                if (value != this._wWWHomePage)
                {
                    this._wWWHomePage = value;
                    AttributeChanged<Top, String>(a => a.wWWHomePage);
                }
            }
        }
    }
}

and TopX:

using System;
using System.Collections.ObjectModel;

namespace ilminator.LinqToLdap.DirectoryObjects.AbstractClasses
{
    /// <summary>
    /// Top, with additional attributes.  Non-SAM classes should
    /// normally use this class as their parent class.
    /// </summary>
    public abstract class TopX : Top
    {
        public String commonName { get; set; }  // Top (but optional)

        private Collection<string> _description;     // top (see note on Top)
        /// <summary>
        /// Description.  Contains the description to display for an object. In 
        /// Active Directory, this value is restricted as single-valued for 
        /// backward compatibility in some cases but is allowed to be multi-valued
        /// in others. See 'Remarks' on the follow link.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms675492.aspx]
        /// </summary>
        public Collection<String> description
        {
            get { return this._description; }
            set
            {
                if (value != this._description)
                {
                    this._description = value;
                    AttributeChanged<TopX, Collection<String>>(a => a.description);
                }
            }
        }
    }
}

ClassSchema and ClassSchemaMap

First, ClassSchema. Note that I’ve created a namespace, SchemaClasses, to hold the directory object classes for attributes and classes.

using System;
using System.Collections.ObjectModel;

namespace ilminator.LinqToLdap.DirectoryObjects.SchemaClasses
{
    public class ClassSchema : ilminator.LinqToLdap.DirectoryObjects.AbstractClasses.TopX
    {
        public Collection<String> auxiliaryClass { get; set; }
        public String classDisplayName { get; set; }
        public Boolean defaultHidingValue { get; set; }
        public String defaultObjectCategory { get; set; }
        public String governsID { get; set; }
        public Nullable<Boolean> isDefunct { get; set; }
        public String lDAPDisplayName { get; set; }
        public Collection<String> mayContain { get; set; }
        public Collection<String> mustContain { get; set; }
        public Int32 objectClassCategory { get; set; }
        public Collection<String> possSuperiors { get; set; }
        public String rDNAttID { get; set; }
        public Guid schemaIDGUID { get; set; }
        public Boolean showInAdvancedViewOnly { get; set; }
        public String subClassOf { get; set; }
        public Collection<String> systemAuxiliaryClass { get; set; }
        public Collection<String> systemMayContain { get; set; }
        public Collection<String> systemMustContain { get; set; }
        public Boolean systemOnly { get; set; }
        public Collection<String> systemPossSuperiors { get; set; }
    }
}

then, ClassSchemaMap

using System;
using LinqToLdap.Mapping;

namespace ilminator.LinqToLdap.DirectoryObjects.ClassMaps
{
    public class ClassSchemaMap : ClassMap<ilminator.LinqToLdap.DirectoryObjects.SchemaClasses.ClassSchema>
    {
        public ClassSchemaMap()
        {
            NamingContext("CN=Schema,CN=Configuration,DC=big,DC=wooden,DC=badger");   //   Replace with your defaultNamingContext
            ObjectClass("classSchema");
            ObjectCategory("classSchema");

            // From ClassSchema
            Map(x => x.auxiliaryClass).StoreGenerated();
            Map(x => x.classDisplayName).StoreGenerated();
            Map(x => x.defaultHidingValue).StoreGenerated();
            Map(x => x.defaultObjectCategory).StoreGenerated();
            Map(x => x.governsID).StoreGenerated();
            Map(x => x.isDefunct).StoreGenerated();
            Map(x => x.lDAPDisplayName).StoreGenerated();
            Map(x => x.mayContain).StoreGenerated();
            Map(x => x.mustContain).StoreGenerated();
            Map(x => x.objectClassCategory).StoreGenerated();
            Map(x => x.possSuperiors).StoreGenerated();
            Map(x => x.rDNAttID).StoreGenerated();
            Map(x => x.schemaIDGUID).StoreGenerated();
            Map(x => x.showInAdvancedViewOnly).StoreGenerated();
            Map(x => x.subClassOf).StoreGenerated();
            Map(x => x.systemAuxiliaryClass).StoreGenerated();
            Map(x => x.systemMayContain).StoreGenerated();
            Map(x => x.systemMustContain).StoreGenerated();
            Map(x => x.systemOnly).StoreGenerated();
            Map(x => x.systemPossSuperiors).StoreGenerated();

            // From TopX
            Map(x => x.commonName).Named("cn").StoreGenerated();
            Map(x => x.description).StoreGenerated();

            // From Top
            DistinguishedName(x => x.distinguishedName);

            Map(x => x.adminDescription).StoreGenerated();
            Map(x => x.adminDisplayName).StoreGenerated();
            Map(x => x.displayName).StoreGenerated();
            Map(x => x.name).StoreGenerated();
            Map(x => x.objectGuid).StoreGenerated();
            Map(x => x.uSNChanged).StoreGenerated();
            Map(x => x.uSNCreated).StoreGenerated();
            Map(x => x.whenChanged).StoreGenerated();
            Map(x => x.whenCreated).StoreGenerated();
            Map(x => x.wWWHomePage).StoreGenerated();
        }
    }
}

AttributeSchema and AttributeSchemaMap

Now, AttributeSchema.

using System;

namespace ilminator.LinqToLdap.DirectoryObjects.SchemaClasses
{
    public class AttributeSchema : ilminator.LinqToLdap.DirectoryObjects.AbstractClasses.TopX
    {
        public AttributeSchema() { }

        public String attributeID { get; set; }
        public String attributeSyntax { get; set; }
        public String classDisplayName { get; set; }
        public Nullable<Boolean> isDefunct { get; set; }
        public Nullable<Boolean> isMemberOfPartialAttributeSet { get; set; }
        public Boolean isSingleValued { get; set; }
        public String lDAPDisplayName { get; set; }
        public Nullable<Int32> linkID { get; set; }
        public Byte[] oMObjectClass { get; set; }
        public Int32 oMSyntax { get; set; }
        public Nullable<Int32> rangeLower { get; set; }
        public Nullable<Int32> rangeUpper { get; set; }
        public Int32 searchFlags { get; set; }
        public Boolean systemOnly { get; set; }
    }
}

then, AttributeSchemaMap:

using System;
using LinqToLdap.Mapping;

namespace ilminator.LinqToLdap.DirectoryObjects.ClassMaps
{
    public class AttributeSchemaMap : ClassMap<ilminator.LinqToLdap.DirectoryObjects.SchemaClasses.AttributeSchema>
    {
        public AttributeSchemaMap()
        {
            NamingContext("CN=Schema,CN=Configuration,DC=big,DC=wooden,DC=badger");   //   Replace with your defaultNamingContext
            ObjectClass("attributeSchema");
            ObjectCategory("attributeSchema");

            // From AttributeSchema

            Map(x => x.attributeID).StoreGenerated();
            Map(x => x.attributeSyntax).StoreGenerated();
            Map(x => x.classDisplayName).StoreGenerated();
            Map(x => x.isDefunct).StoreGenerated();
            Map(x => x.isMemberOfPartialAttributeSet).StoreGenerated();
            Map(x => x.isSingleValued).StoreGenerated();
            Map(x => x.lDAPDisplayName).StoreGenerated();
            Map(x => x.linkID).StoreGenerated();
            Map(x => x.oMObjectClass).StoreGenerated();
            Map(x => x.oMSyntax).StoreGenerated();
            Map(x => x.rangeLower).StoreGenerated();
            Map(x => x.rangeUpper).StoreGenerated();
            Map(x => x.searchFlags).StoreGenerated();
            Map(x => x.systemOnly).StoreGenerated();

            // From TopX
            Map(x => x.commonName).Named("cn").StoreGenerated();
            Map(x => x.description).StoreGenerated();

            // From Top
            DistinguishedName(x => x.distinguishedName);

            Map(x => x.adminDescription).StoreGenerated();
            Map(x => x.adminDisplayName).StoreGenerated();
            Map(x => x.displayName).StoreGenerated();
            Map(x => x.name).StoreGenerated();
            Map(x => x.objectGuid).StoreGenerated();
            Map(x => x.uSNChanged).StoreGenerated();
            Map(x => x.uSNCreated).StoreGenerated();
            Map(x => x.whenChanged).StoreGenerated();
            Map(x => x.whenCreated).StoreGenerated();
            Map(x => x.wWWHomePage).StoreGenerated();
        }
    }
}

Note that the NamingContexts on both ClassMaps have been set to the forest schemaNamingContext.

Summary

It only took me a few minutes to set these up and now I can query the schema the same way I run other queries.  I hope you find it as useful as I have.

Advertisements