OrganizationalUnit CRUD

Introduction

I hope to write a series of posts documenting my progress with LinqToLdap. I thought I’d start with organizationalUnits because they’re comparatively simple objects.

This article will show you how to Create, Read, Update and Delete organizationalUnits in Active Directory. The actual operations are known as Add, Query, Update and Delete in LinqToLdap but CRUD is a well-known acronym so I’ll stick to it.

First, in Setup, I’ll walk you through the initial work to make LinqToLdap available to your programs.  Then I’ll introduce the class OrganizationalUnitObject which will be used throughout the code samples to represent organizationalUnits in your directory.  After that, I’ll introduce OrganizationalUnitObjectMap which is used to relate organizationalUnits to OrganizationalUnitObjects.  I’m using simplied versions of both for the sake of this introductory article.  Then I’ll tackle the actual CRUD operations in the sections entitled Read, Create, Update and Delete.

Setup

For this, I’m using Microsoft Visual C# 2010 Express which is already installed.  I tend to use Professional but I wanted to show that you can do the same with the free version.

Download the files

If you haven’t already done it, download the LinqToLdap assembly and unzip it to a local folder (see here).

You can also get the full source code but this will do for this article.  Now that you have the necessary files, you can start the project.

Create the project

Fire up Visual Studio and create a new  Console Application project (see here).  I’m calling mine BlogOuTest1.

You’ll be presented with a window open on Program.cs.

Add a Reference to LinqToLdap

Next, you’ll need to add a reference to LinqToLdap.dll (see here).

I prefer to keep classes in separate files – it makes it easier to find classes in large projects and to copy classes between projects.

Create the OrganizationalUnitObject class

The first class we need is to represent an organizationalUnit.  I’m calling it OrganizationalUnitObject. This is to make it easier to tell when I’m talking about the OrganizationalUnitObject class in my program and when I’m talking about the organizationalUnit class in ActiveDirectory.  I hope that’s clear.  Good.  Please note that this is a simplified version of this object.  I introduce a more valid version in this article and explain why this version is only valid in this case.

Add a new class file called OrganizationalUnitObject.cs (see here).

Replace its contents with the following:

using System;
using LinqToLdap.Mapping;

namespace BlogOuTest1
{
    public class OrganizationalUnitObject
    {
        public Guid objectGuid { get; set; }
        public String distinguishedName { get; set; }
        public String organizationalUnitName { get; set; }
        public String description { get; set; }
        public DateTime whenCreated { get; set; }
        public DateTime whenChanged { get; set; }
    }
}

This is a simple class with a set of properties used to hold data from your directory.

The next class we need is used to map the OrganizationalUnitObject class onto the Active Directory organizationalUnit class.

Create the OrganizationalUnitObjectMap class

As above, please note that this is a simplified version of this object.  I introduce a more valid version in this article and explain why this version is only valid in this case.

Repeat the above process to create a class called OrganizationalUnitObjectMap.cs (see here).  Replace its contents with this:

using System;
using LinqToLdap.Mapping;

namespace BlogOuTest1
{
    public class OrganizationalUnitObjectMap : ClassMap<OrganizationalUnitObject>
    {
        public OrganizationalUnitObjectMap()
        {
            NamingContext("DC=big,DC=wooden,DC=badger");        // Replace with your domain's defaultNamingContext
            ObjectClass("organizationalUnit");
            ObjectCategory("organizationalUnit");
            DistinguishedName(x => x.distinguishedName);

            Map(x => x.organizationalUnitName).Named("ou").StoreGenerated();
            Map(x => x.description);
            Map(x => x.objectGuid).StoreGenerated();
            Map(x => x.whenChanged).StoreGenerated();
            Map(x => x.whenCreated).StoreGenerated();
        }
    }
}

For the NamingContext, you have a choice: you can set it to be the defaultNamingContext of your domain (as I’ve done, above) or you can narrow down the search by using the distinguishedName of a particular OU.

Now, your solution should look like this:

Once you’ve done this you’re ready to begin.

Read

To retrieve and display a list of OUs, replace the contents of Program.cs with this:

using System;
using System.Linq;
using LinqToLdap;

namespace BlogOuTest1
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new LdapConfiguration()
                .AddMapping(new OrganizationalUnitObjectMap());
            config.ConfigureFactory("dc1");

            var context = new DirectoryContext();

            var ous = context.Query<OrganizationalUnitObject>()
                .Where(o => o.organizationalUnitName.StartsWith("CS"));

            foreach (var ou in ous)
            {
                Console.WriteLine("Ou: {0}",
                ou.organizationalUnitName);
            }

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

Then run the program  using F5 or Debug > Start Debugging.  You should see a console window appear and, after a few seconds, a list of OUs.

So, what’s happening, here?

The first three lines create a new LdapConfiguration object.  This holds our class mappings and connection factory configuration and, optionally, our logging configuration and maximum page size.  As well as class mappings local to our program, LdapConfiguration can also retrieve them from another assembly.  There’s a standard connection factory and a pooled connection factory.  The connection factory holds the server (or domain) name, protocol version, port, whether or not to use SSL, and so on.

Next, we create a DirectoryContext.  This is used to carry out all operations against the directory.  In this form, it uses the static configuration and connection factory already created.

Next, we define our LINQ query.  Hopefully, in this format, known as Extension Method syntax, you can guess what it’ll return.

The query could also have been written like this:

            var ous = from o in context.Query()
                      where o.organizationalUnitName.StartsWith("CS")
                      select o;

You’ll have to decide which you prefer.

Finally, we iterate over the results and display them.  In fact, the act of iteration is what runs the query – until the program starts running the for loop, the query hasn’t been run.

Create

To create a new OU, replace the contents of Program.cs with this:

using System;
using System.Linq;
using LinqToLdap;

namespace BlogOuTest1
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new LdapConfiguration()
                .AddMapping(new OrganizationalUnitObjectMap());
            config.ConfigureFactory("dc1");

            var context = new DirectoryContext();

            var ouPath = "OU=TestOu1,OU=Tests,OU=Camelot,OU=Castles,DC=big,DC=wooden,DC=badger";
            var newOu = new OrganizationalUnitObject()
            {
                distinguishedName = ouPath
            };

            var createdOu = context.Add(newOu);

            Console.WriteLine("objectGuid = {0}",
                createdOu.objectGuid);

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

This time, we define a string called ouPath to hold the distinguishedName of our new OU, then create an instance of OrganizationalUnitObject using the ouPath.  Finally, we use the context to Add the object to the directory.  The Add method returns a new instance of OrganizationalUnitObject and I’ve used this to display the new OU’s objectGuid.

Update

Updating attributes on the object is quite easy: you get the object, set the properties and commit it back to AD. Replace the contents of Program.cs with the following (I’ve highlighted the lines that’ve changed from the Create step):

using System;
using System.Linq;
using LinqToLdap;

namespace BlogOuTest1
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new LdapConfiguration()
                .AddMapping(new OrganizationalUnitObjectMap());
            config.ConfigureFactory("mcjs-dc1");

            var context = new DirectoryContext();

            var createdOu = context.Query<OrganizationalUnitObject>()
                .Where(o => o.organizationalUnitName == "TestOu1").FirstOrDefault();
            if (createdOu == null)
            {
                Console.WriteLine("Object 'TestOu1' not found");
                return;
            }

            createdOu.description = "Test Ou for LinqToLdap blog post";
            var updatedOu = context.Update(createdOu);

            Console.WriteLine("Description = {0}",
                updatedOu.description);

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

This time, we’re updating the entry we just created.  First we read the OU from AD.  This time, you’ll notice that we’re using .FirstOrDefault().  Unlike the Read above, .FirstOrDefault causes the query to be executed immediately and returns a single object or null.  The next five lines check for a null object and abort the execution if necessary.

Assuming the OU is found, its description is updated and then the update is committed to the directory by calling Update on the context object.  As with Add, Update takes an instance of OrganizationalUnitObject and returns a new instance of OrganizationalUnitObject with the new values.

There are two operations that are not covered by the Update method: renaming the object within an OU and moving the object to another OU.  Because these operations are universal (that is, they don’t require mapped classes such as OrganizationalUnitObject with its associated OrganizationalUnitObjectMap) I’ll leave them to a later post (see here).

Delete

Deleting objects is also quite easy. Replace the contents of Program.cs with the following (I’ve highlighted the line that’s changed from the Update step):

using System;
using System.Linq;
using LinqToLdap;

namespace BlogOuTest1
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new LdapConfiguration()
                .AddMapping(new OrganizationalUnitObjectMap());
            config.ConfigureFactory("dc1");

            var context = new DirectoryContext();

            var createdOu = context.Query<OrganizationalUnitObject>()
                .Where(o => o.organizationalUnitName == "TestOu1").FirstOrDefault();
            if (createdOu == null)
            {
                Console.WriteLine("Object 'TestOu1' not found");
                return;
            }

            context.Delete(createdOu.distinguishedName);

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

Unlike the other modification methods shown here, Delete takes a distinguishedName and doesn’t return anything.  I’ve used the query to save a bit of hard-coding anything into my program.  Of course, because Delete uses a distinguishedName, as with moves and renames, it’s also universal.  But I couldn’t do CRUD without Delete, could I?

Closing the project

Visual Studio combines projects into solutions.  Even if you’ve only got one project, you still get a solution.  When you close the solution in Visual Studio, or close VS itself, you’ll be asked if you want to save it:

Click Save and you’ll be presented with a dialog allowing you to name the solution with the default naming being the same as the first project:

Accept the suggestion (or not) and click Save.

Summary

Hopefully, this article has shown you how easy it is to manipulate OUs using LinqToLdap.  You can download a copy from the link found on the Downloads page.

Querying AD using LinqToLdap

There are several ways to return results from LinqToLdap (documented here).  I prefer the ClassMap approach because it gives me POCOs which I can use with WCF RIA Services.  Since I’ll probably be using them for most of my posts, I’ll start with a ClassMap here.

All you need to make this work is a console project and a reference to LinqToLdap.dll from Version 2.0.2 of LinqToLdap, the name of your Domain Controller or the dns name of your domain in this line:

config.ConfigureFactory("dc1");

and the defaultNamingContext of your domain (or an OU distinguishedName, if you want to restrict the search) in this line:

NamingContext("DC=big,DC=wooden,DC=badger");

at least, it will if you’ve an account with a common name starting “John”.


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

namespace ConsoleTest1
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new LdapConfiguration()
               .AddMapping(new UserObjectMap());
            config.ConfigureFactory("dc1");

            var context = new DirectoryContext();

            var users = context.Query<UserObject>()
                .Where(u => u.commonName.StartsWith("John"));

            foreach (var user in users)
            {
                Console.WriteLine("User ID: {0,-12}\t\tCN: {1}",
                    user.sAMAccountName,
                    user.commonName);
            }

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

    public class UserObjectMap : ClassMap
    {
        public UserObjectMap()
        {
            NamingContext("DC=big,DC=wooden,DC=badger");
            ObjectClass("user");
            ObjectCategory("person");
            DistinguishedName(x => x.distinguishedName);
            Map(x => x.commonName).Named("cn");
            Map(x => x.description);
            Map(x => x.displayName);
            Map(x => x.employeeID);
            Map(x => x.objectGuid).StoreGenerated();
            Map(x => x.otherTelephone);
            Map(x => x.sAMAccountName);
            Map(x => x.userAccountControl);
            Map(x => x.whenChanged).StoreGenerated();
            Map(x => x.whenCreated).StoreGenerated();
        }
    }

    public class UserObject
    {
        public Guid objectGuid { get; set; }
        public String commonName { get; set; }
        public String distinguishedName { get; set; }
        public String employeeID { get; set; }
        public String sAMAccountName { get; set; }
        public String description { get; set; }
        public String displayName { get; set; }
        public Collection<String> otherTelephone { get; set; }
        public Int32 userAccountControl { get; set; }
        public DateTime whenCreated { get; set; }
        public DateTime whenChanged { get; set; }
    }
}

Good luck.