Wednesday, 17 February 2010

The Collectible Edition of Our Book

Occasionally, I will check online to see the reviews for our book.  It is now almost 4 years old and a bit outdated with respect to the changes that came with .NET 3.5.  The website and forums however has been trucking along (thanks Joe!) these years.  We never sold a ton of books about this particular niche topic, but for the audience size, it wasn't too shabby.  Joe and I are not about to retire on our royalties, however.

I was a bit surprised to see this online however at Amazon:

image

Now, it is a bit silly for two reasons, a.) our book as a collectible, really?  Who would actually collect it?  and b.) It says it was signed by both authors.  Now, there is a slight chance it is authentic as I think Joe and I signed maybe 2 books together ever.  However, the odds of it being authentic are exceedingly slim.  Who knows. it's funny regardless.

ADAM on Windows 7

One of my most popular posts to this day is the hack to install ADAM (now called AD LDS) on Vista.  It is the source of numerous emails as well.  So today, I am happy to say that AD LDS (aka ADAM) is available for Windows 7 officially - no hacks needed.

Download it here

Now, I fully realize this doesn't help those that are using Vista today, but there are quite a few folks that are using Windows 7 or upgrading from Vista or XP to 7.  LDAP directories are fabulous tools and AD LDS is a great one.  Enjoy.

Thursday, 05 June 2008

Paged Asynchronous LDAP Searches Revisited

A member in the book's forum mentioned some code I had originally posted here in the blog for asynchronous, paged searches in System.DirectoryServices.Protocols (SDS.P).  He questioned whether or not it was thread safe.  I honestly don't know - it might not be as I didn't test it extensively.

Regardless, I had actually moved on from that code and started using anonymous delegates for callbacks instead of events.  I liked this pattern a bit better because it also got rid of the shared resources.

After reading Stephen Toub's article on asynchronous stream processing, I learned about the AsyncOperationManager which was something I was missing in my implementation.  I have been doing a lot lately with .NET 3.5, LINQ, and lambda expressions, so I also decided to rewrite the anonymous delegates to lambda expressions.  That is not as big a change, but it is more concise.

I actively investigated using async iterators, but ultimately I decided closures seemed to be more intuitive for me.  I might revisit this at some time and change my mind.  Here is my outcome:

public class AsyncSearcher
{
    LdapConnection _connect;

    public AsyncSearcher(LdapConnection connection)
    {
        this._connect = connection;
        this._connect.AutoBind = true; //will bind on first search
    }

    public void BeginPagedSearch(
            string baseDN,
            string filter,
            string[] attribs,
            int pageSize,
            Action<SearchResponse> page,
            Action<Exception> completed                
            )
    {
        if (page == null)
            throw new ArgumentNullException("page");

        AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);

        Action<Exception> done = e =>
            {
                if (completed != null) asyncOp.Post(delegate
                {
                    completed(e);
                }, null);
            };

        SearchRequest request = new SearchRequest(
            baseDN,
            filter,
            System.DirectoryServices.Protocols.SearchScope.Subtree,
            attribs
            );

        PageResultRequestControl prc = new PageResultRequestControl(pageSize);

        //add the paging control
        request.Controls.Add(prc);

        AsyncCallback rc = null;

        rc = readResult =>
            {
                try
                {
                    var response = (SearchResponse)_connect.EndSendRequest(readResult);
                    
                    //let current thread handle results
                    asyncOp.Post(delegate
                    {
                        page(response);
                    }, null);

                    var cookie = response.Controls
                        .Where(c => c is PageResultResponseControl)
                        .Select(s => ((PageResultResponseControl)s).Cookie)
                        .Single();

                    if (cookie != null && cookie.Length != 0)
                    {
                        prc.Cookie = cookie;
                        _connect.BeginSendRequest(
                            request,
                            PartialResultProcessing.NoPartialResultSupport,
                            rc,
                            null
                            );
                    }
                    else done(null); //signal complete
                }
                catch (Exception ex) { done(ex); }
            };


        //kick off async
        try
        {
            _connect.BeginSendRequest(
                request,
                PartialResultProcessing.NoPartialResultSupport,
                rc,
                null
                );
        }
        catch (Exception ex) { done(ex); }
    }

}

It can be consumed very easily using something like this:

class Program
{
    static ManualResetEvent _resetEvent = new ManualResetEvent(false);
    
     static void Main(string[] args)
    {
        //set these to your environment
        string servername = "server.yourdomain.com";
        string baseDN = "dc=yourdomain,dc=com";

        using (LdapConnection connection = CreateConnection(servername))
        {
            AsyncSearcher searcher = new AsyncSearcher(connection);

            searcher.BeginPagedSearch(
                baseDN,
                "(sn=Dunn)",
                null,
                100,
                f => //runs per page
                {
                    foreach (var item in f.Entries)
                    {
                        var entry = item as SearchResultEntry;

                        if (entry != null)
                        {
                            Console.WriteLine(entry.DistinguishedName);
                        }
                    }

                },
                c => //runs on error or when done
                {
                    if (c != null) Console.WriteLine(c.ToString());
                    Console.WriteLine("Done");
                    _resetEvent.Set();
                }
            );

            _resetEvent.WaitOne();
            
        }

        Console.WriteLine();
        Console.WriteLine("Finished.... Press Enter to Continue.");
        Console.ReadLine();
    }

    static LdapConnection CreateConnection(string server)
    {
        LdapConnection connect = new LdapConnection(
            new LdapDirectoryIdentifier(server),
            null,
            AuthType.Negotiate
            );

        connect.SessionOptions.ProtocolVersion = 3;
        connect.SessionOptions.ReferralChasing = ReferralChasingOptions.None;

        connect.SessionOptions.Sealing = true;
        connect.SessionOptions.Signing = true;

        return connect;
    }
}

 

The important thing to note is that because everything is running asynchronously, it is totally possible for the end delegate to be invoked before the paging delegate has a chance to finish processing results (depending on how complicated your code is).  You would need to compensate for this yourself.

This client is a console application, so I am using a ManualResetEvent just to prevent it from closing before finishing.  You wouldn't need to do this in a WinForms or WPF app.

I am sure there are other optimizations you could make to pass in parameters or even other directory controls.  However, the general pattern should apply.

Tuesday, 30 October 2007

Implementing Change Notifications in .NET

There are three ways of figuring out things that have changed in Active Directory (or ADAM).  These have been documented for some time over at MSDN in the aptly titled "Overview of Change Tracking Techniques".  In summary:

  1. Polling for Changes using uSNChanged. This technique checks the 'highestCommittedUSN' value to start and then performs searches for 'uSNChanged' values that are higher subsequently.  The 'uSNChanged' attribute is not replicated between domain controllers, so you must go back to the same domain controller each time for consistency.  Essentially, you perform a search looking for the highest 'uSNChanged' value + 1 and then read in the results tracking them in any way you wish.
    • Benefits
      • This is the most compatible way.  All languages and all versions of .NET support this way since it is a simple search.
    • Disadvantages
      • There is a lot here for the developer to take care of.  You get the entire object back, and you must determine what has changed on the object (and if you care about that change).
      • Dealing with deleted objects is a pain.
      • This is a polling technique, so it is only as real-time as how often you query.  This can be a good thing depending on the application. Note, intermediate values are not tracked here either.
  2. Polling for Changes Using the DirSync Control.  This technique uses the ADS_SEARCHPREF_DIRSYNC option in ADSI and the LDAP_SERVER_DIRSYNC_OID control under the covers.  Simply make an initial search, store the cookie, and then later search again and send the cookie.  It will return only the objects that have changed.
    • Benefits
      • This is an easy model to follow.  Both System.DirectoryServices and System.DirectoryServices.Protocols support this option.
      • Filtering can reduce what you need to bother with.  As an example, if my initial search is for all users "(objectClass=user)", I can subsequently filter on polling with "(sn=dunn)" and only get back the combination of both filters, instead of having to deal with everything from the intial filter.
      • Windows 2003+ option removes the administrative limitation for using this option (object security).
      • Windows 2003+ option will also give you the ability to return only the incremental values that have changed in large multi-valued attributes.  This is a really nice feature.
      • Deals well with deleted objects.
    • Disadvantages
      • This is .NET 2.0+ or later only option.  Users of .NET 1.1 will need to use uSNChanged Tracking.  Scripting languages cannot use this method.
      • You can only scope the search to a partition.  If you want to track only a particular OU or object, you must sort out those results yourself later.
      • Using this with non-Windows 2003 mode domains comes with the restriction that you must have replication get changes permissions (default only admin) to use.
      • This is a polling technique.  It does not track intermediate values either.  So, if an object you want to track changes between the searches multiple times, you will only get the last change.  This can be an advantage depending on the application.
  3. Change Notifications in Active Directory.  This technique registers a search on a separate thread that will receive notifications when any object changes that matches the filter.  You can register up to 5 notifications per async connection.
    • Benefits
      • Instant notification.  The other techniques require polling.
      • Because this is a notification, you will get all changes, even the intermediate ones that would have been lost in the other two techniques.
    • Disadvantages
      • Relatively resource intensive.  You don't want to do a whole ton of these as it could cause scalability issues with your controller.
      • This only tells you if the object has changed, but it does not tell you what the change was.  You need to figure out if the attribute you care about has changed or not.  That being said, it is pretty easy to tell if the object has been deleted (easier than uSNChanged polling at least).
      • You can only do this in unmanaged code or with System.DirectoryServices.Protocols.

For the most part, I have found that DirSync has fit the bill for me in virtually every situation.  I never bothered to try any of the other techniques.  However, a reader asked if there was a way to do the change notifications in .NET.  I figured it was possible using SDS.P, but had never tried it.  Turns out, it is possible and actually not too hard to do.

My first thought on writing this was to use the sample code found on MSDN (and referenced from option #3) and simply convert this to System.DirectoryServices.Protocols.  This turned out to be a dead end.  The way you do it in SDS.P and the way the sample code works are different enough that it is of no help.  Here is the solution I came up with:

public class ChangeNotifier : IDisposable
{
    LdapConnection _connection;
    HashSet<IAsyncResult> _results = new HashSet<IAsyncResult>();
    
    public ChangeNotifier(LdapConnection connection)
    {
        _connection = connection;
        _connection.AutoBind = true;
    }
 
    public void Register(string dn, SearchScope scope)
    {
        SearchRequest request = new SearchRequest(
            dn, //root the search here
            "(objectClass=*)", //very inclusive
            scope, //any scope works
            null //we are interested in all attributes
            );
 
        //register our search
        request.Controls.Add(new DirectoryNotificationControl());
 
        //we will send this async and register our callback
        //note how we would like to have partial results
        IAsyncResult result = _connection.BeginSendRequest(
            request,
            TimeSpan.FromDays(1), //set timeout to a day...
            PartialResultProcessing.ReturnPartialResultsAndNotifyCallback,
            Notify,
            request
            );
 
        //store the hash for disposal later
        _results.Add(result);
    }
 
    private void Notify(IAsyncResult result)
    {
        //since our search is long running, we don't want to use EndSendRequest
        PartialResultsCollection prc = _connection.GetPartialResults(result);
 
        foreach (SearchResultEntry entry in prc)
        {
            OnObjectChanged(new ObjectChangedEventArgs(entry));
        }
    }
 
    private void OnObjectChanged(ObjectChangedEventArgs args)
    {
        if (ObjectChanged != null)
        {
            ObjectChanged(this, args);
        }
    }
 
    public event EventHandler<ObjectChangedEventArgs> ObjectChanged;
 
    #region IDisposable Members
 
    public void Dispose()
    {
        foreach (var result in _results)
        {
            //end each async search
            _connection.Abort(result);
        }
    }
 
    #endregion
}
 
public class ObjectChangedEventArgs : EventArgs
{
    public ObjectChangedEventArgs(SearchResultEntry entry)
    {
        Result = entry;
    }
 
    public SearchResultEntry Result { get; set;}
}

It is a relatively simple class that you can use to register searches.  The trick is using the GetPartialResults method in the callback method to get only the change that has just occurred.  I have also included the very simplified EventArgs class I am using to pass results back.  Note, I am not doing anything about threading here and I don't have any error handling (this is just a sample).  You can consume this class like so:

static void Main(string[] args)
{
    using (LdapConnection connect = CreateConnection("localhost"))
    {
        using (ChangeNotifier notifier = new ChangeNotifier(connect))
        {
            //register some objects for notifications (limit 5)
            notifier.Register("dc=dunnry,dc=net", SearchScope.OneLevel);
            notifier.Register("cn=testuser1,ou=users,dc=dunnry,dc=net", SearchScope.Base);
 
            notifier.ObjectChanged += new EventHandler<ObjectChangedEventArgs>(notifier_ObjectChanged);
 
            Console.WriteLine("Waiting for changes...");
            Console.WriteLine();
            Console.ReadLine();
        }
    }
}
 
static void notifier_ObjectChanged(object sender, ObjectChangedEventArgs e)
{
    Console.WriteLine(e.Result.DistinguishedName);
    foreach (string attrib in e.Result.Attributes.AttributeNames)
    {
        foreach (var item in e.Result.Attributes[attrib].GetValues(typeof(string)))
        {
            Console.WriteLine("\t{0}: {1}", attrib, item);
        }
    }
    Console.WriteLine();
    Console.WriteLine("====================");
    Console.WriteLine();
}

And there you have it... change notifications in .NET.  You can also download my project file for Visual Studio 2008.

Monday, 29 October 2007

Don't Sort on the Server in Active Directory

This is just a reminder that you should not use server-side sorting with your queries in Active Directory or ADAM.  This situation was reinforced when a reader asked me why a particular ASQ (attribute scope query) was failing with an error when querying a rather large group (more than 20,000 members).  The customer was getting a fairly nondescript error, "The server does not support the requested critical extension" about halfway through his results.

After checking the network trace, and handing the DSID error back to my buddy Eric - we (more he, than me) determined it was failing in the code path for sorting.  It turns out that sorting this much data on the server requires temp table space.  If you run out of space before the sorting is complete you get this type of error.  This is not particular to ASQ by any means, but all sorting.

The moral of story is don't sort on the server.  This is a very real example of why this is the case.  You can always easily sort once you have results on the client.

Friday, 10 August 2007

Range Retrieval using System.DirectoryServices.Protocols

Link pair attributes in Active Directory and ADAM can be quite big.  I don't know the official limit, but needless to say, for practical purposes you can assume they are quite large indeed.  By default, AD and ADAM will not return the entire attribute if it contains more than a certain number of values (1000 for Windows 2000 and 1500 for Windows 2003+ by default).  As such, if you truly want robust code, you need to always use what is called range retrieval for link value paired attributes.

Range retrieval is a process similar to paging in directory services, whereby you ask the directory for a certain range of particular attribute.  You know that you are using range retrieval when you see the attribute being requested in the following format:

"[attribute];range=[start]-[end]"

As an example, in the case of the 'member' attribute, you might ask for the first 1500 values like so:

"member;range=0-1499"

Notice, it is zero based so you need to take this into account.  The general algorithm as such is:

  1. Ask for as big as you can get.  This means use the "*" for the ending range to ask for it all.
  2. The directory will respond with either the actual max value (some integer), or with a "*" indicating you got everything.  If you got everything, you are done.
  3. If not, using the max value now as your step, repeatedly ask for larger and larger values inside a loop until the directory responds with a "*" as the end range.

We covered how to use range retrieval in SDS in our book, and you can download sample code that shows how from the book's website.  What we didn't cover was how to do it in SDS.P.

SDS.P is a layer closer the the metal than our ADSI based System.DirectoryServices (SDS).  As such, if you are expected to do range retrieval for SDS, you can be assured that you need to do it for SDS.P as well.  Adopting the code SDS, you get something like this (but modified to some extent):

static List<string> RangeRetrieve(

    LdapConnection connect, string dn, string attribute)

{

    int idx = 0;

    int step = 0;

    List<string> list = new List<string>();

 

    string range = String.Format(

       "{0};range={{0}}-{{1}}",

       attribute

       );

 

    string currentRange = String.Format(range, idx, "*");

 

    SearchRequest request = new SearchRequest(

        dn,

        String.Format("({0}=*)", attribute),

        SearchScope.Base,

        new string[] { currentRange }

        );

 

    SearchResultEntry entry = null;

    bool lastSearch = false;

 

    while (true)

    {

        SearchResponse response =

            (SearchResponse)connect.SendRequest(request);

 

        if (response.Entries.Count == 1) //should only be one

        {

            entry = response.Entries[0];

 

            //this might be optimized to find full step or just use 1000 for

            //compromise

            foreach (string attrib in entry.Attributes.AttributeNames)

            {

                currentRange = attrib;

                lastSearch = currentRange.IndexOf("*", 0) > 0;

                step = entry.Attributes[currentRange].Count;

            }

 

            foreach (string member in

                entry.Attributes[currentRange].GetValues(typeof(string)))

            {

                list.Add(member);

                idx++;

            }

 

            if (lastSearch)

                break;

 

            currentRange = String.Format(range, idx, (idx + step));

 

            request.Attributes.Clear();

            request.Attributes.Add(currentRange);

 

        }

        else

            break;

 

    }

    return list;

}

Happy coding... of course, if you are clever you will realize you can avoid all this range retrieval mess by using an attribute scope query (ASQ). :)

*edit: tried to fix the style for code to render in Google Reader correctly

Wednesday, 01 August 2007

Getting Active Directory Group Membership in .NET 3.5

I have previously covered pretty extensively the options for getting a user's group membership in Active Directory or ADAM (soon to be Active Directory LDS (Lightweight Directory Services)) here on the blog, in the forum, and in the book.  However, there is a new option for users of .NET 3.5 that should be of interest.

The Directory Services group at Microsoft has released in beta form a new API for dealing with a lot of the common things we need to do with users, groups, and computers in Active Directory, ADAM, and the local machine.  This API is called System.DirectoryServices.AccountManagement (or SDS.AM).  Here is a simple example of how to get a users groups (including nested, and primary):

static void Main(string[] args)
{
    PrincipalContext ctx = new PrincipalContext(ContextType.Domain);

    using (ctx)
    {
        Principal p = Principal.FindByIdentity(ctx, "ryandunn");

        using (p)
        {
            var groups = p.GetGroups();
            using (groups)
            {
                foreach (Principal group in groups)
                {
                    Console.WriteLine(group.SamAccountName + "-" + group.DisplayName);
                }
            }
        }

    }
    Console.ReadLine();
}

That's not too bad - in fact, it looks worse than it is because I am trying to make sure everything is wrapped in a 'using' statement where necessary.  The equivalent code to do this would be many times more (using DsCrackNames or LDAP searches) and would yield far less information being returned (just the DN in most cases).

Over the next few weeks and months, I intend to dig more deeply into this namespace and put some samples up here for everyone.  This is just a taste for now, but it should show you how powerful this namespace really is.

 *Updated to fix CSS renderings in Google Reader

Thursday, 28 June 2007

Directory Services Samples Milestone

I was checking on the book's website the other day and noticed that we broke the 54,000 mark for downloads of our sample code.  That really surprised me.  I just didn't think that there were that many people in the world working on these types of scenarios.

 Now, I happen to know that we did not sell that many copies of the book, so this means a lot of people are downloading just the samples.  For the record, that is perfectly fine and we encourage people to look at the samples.  Admittedly, some of the samples might be head-scratchers without the book for context, but hey, its free and so is our time on the forums.

Joe and I log a lot of time here and in other forums helping people and we love to hear your feedback on what works and what doesn't.  Give us a shout when samples don't make sense, or when a scenario seems to be overly painful.  Some people have asked how they can repay us for our time (I have had offers for beer, wine, and lodging so far).  A simple thanks is enough for both of us, however, and a perhaps a recommendation to others.  If you really feel the need to contribute beyond nice words - simple, just buy the book!

Friday, 01 June 2007

Working with Large Amounts of Data in Directory Services

I almost missed this one from Tomek, but he has a good analysis of what happens when you have many results to return from the directory and a nice comparison of how the different stacks (System.DirectoryServices vs. System.DirectoryServices.Protocols) handle it.  The moral of the story:  if you have a ton of results coming back, it might be in your interest to pursue using the Protocols stack.

Sunday, 15 April 2007

Introduction to System.DirectoryServices.Protocols (S.DS.P)

Fellow MVP, Ethan Wilansky has a new article on MSDN outlining the System.DirectoryServices.Protocols stack.  I haven't had a chance to read every last word in it yet (it's a huge article!), but it appears to show roughly 80% of everything you might want to do with SDS.P.  Check it out.

Link to Introduction to System.DirectoryServices.Protocols (S.DS.P)

Tuesday, 20 March 2007

Transitive Link Value Filter Evaluation

I was speaking with Eric Fleischman at the MVP summit this year and he told me about a neat feature you will find in Window Server 2003 SP2 and Longhorn server.  It is a new type of matching rule ID filter that allows for transitive link value evaluation.  This is one filter type that is incredibly useful and you will want to know about.

First some background: a matching rule ID filter is a special syntax filter that allows for an arbitrary search behavior as defined by the matching rule.  Active Directory and ADAM only shipped with two matching rules until recently: LDAP_MATCHING_RULE_BIT_AND (1.2.840.113556.1.4.803) and LDAP_MATCHING_RULE_BIT_OR (1.2.840.113556.1.4.804).  We commonly used these matching rules to check our bitwise flag values.  For instance, here is the key portion of the filter that specifies an account is disabled:

(userAccountControl:1.2.840.113556.1.4.803:=2)

Notice that we have the attribute name we are searching on, the rule OID we want to use (the AND rule), and the value to check (in decimal).  Pretty simple, right?

The new matching rule is called LDAP_MATCHING_RULE_IN_CHAIN and it has an OID of 1.2.840.113556.1.4.1941.  This new rule allows us to search across all DN-syntax attributes recursively and evaluate the entire tree of relationships (hence the transitive name).

Evaluating Group Membership

Where we will typically see this used is in group membership evaluation.  Specifically, it answers two questions that are constantly asked by developers:  What groups are my user a member of? and What users are in this group?

It is really simple to use this filter, so here we go.  The first question is, what groups is my user a member of?

(member:1.2.840.113556.1.4.1941:=CN=User1,OU=X,DC=domain,DC=com)

The next question, what users are in this group?

(memberOf:1.2.840.113556.1.4.1941:=CN=A Group,OU=Y,DC=domain,DC=com)

If we place base this search on the main partition and use a subtree search, it will return for us all the matches across the domain.  However, if we scope the second search to a specific user object and use a base search, it is a quick and dirty way of telling us if the user is a member of the group.  Hence, this would also work for a type of IsInRole() function:

public bool IsUserMember(DirectoryEntry user, string groupDN)
{
    string filter = String.Format(
        "(memberOf:1.2.840.113556.1.4.1941:={0})", groupDN);

    DirectorySearcher ds = new DirectorySearcher(
        user,
        filter,
        null,
        SearchScope.Base);

    return (ds.FindOne() != null);
}

Now, I want to also point out that this sort of code also makes me cringe a little bit thinking about the abuse that can occur...  Remember, it is performing a search each and every time you want to check group membership.  It is still a better idea to build the entire group membership of a user and store it in one of the IPrincipal classes and use the .IsInRole() functionality to keep network access to a minimum.

Creating an Org Chart

The other area where we will find this filter being pretty handy is when we want to find all the users that directly and indirectly report to a single person.  This is the typical situation when building org charts or trying to find the users for a mailing list.  Here is one such example:

public static void GetOrgChart(DirectoryEntry entry, string bossDN)
{
    string filter = String.Format(
        "(&(mail=*)(manager:1.2.840.113556.1.4.1941:={0}))",
        bossDN);

    DirectorySearcher ds = new DirectorySearcher(
        entry,
        filter
        );

    using (SearchResultCollection src = ds.FindAll())
    {
        foreach (SearchResult sr in src)
        {
            Console.WriteLine(sr.Properties["mail"][0]);
        }
    }
}
  

Performance

The next question we will want to answer for this new filter type is the performance relative to the code required to process this recursively.  I will use the code I presented here to test the recursive case and compare it to a simple filter of:

(memberOf:1.2.840.113556.1.4.1941:=CN=BigNestedGroup,OU=X,DC=Y)

So, to cut to the chase, how does the new filter compare to recursively chasing DN link pairs yourself?  Short answer... it doesn't.  The code I wrote for the book and here on the blog blows it away by a factor of 10.  I have to admit that before I ran the tests, I expected the new filter to run circles around my code and I was pretty shocked when the reverse was actually true.  I tested this using 3 nested groups each with 100 members and running both the recursive search and the transitive search 100 times and averaging the results.  To expand the lead group with 300 direct and indirect members took roughly half a second using a transitive filter (409ms) as compared to only (41ms) using the recursive search.  This would have a big impact on server apps serving many of these searches, but would probably not be a huge factor in client side apps or where a smallish number of these searches are performed.

Summary

The transitive filter is a great new addition and can greatly simplify the code that you have to write.  This new filter is not without pitfalls however.  Make sure you are cognizant of the performance tradeoffs that are inherent in choosing this filter as it is considerably slower to use. 

Friday, 22 July 2005

Useful System.DirectoryServices Resources

(Updated 12/11/06)

A common question often raised by new .NET developers is : what resources are available for me to learn how to program against Active Directory or other LDAP sources?

There are a number of resources available for .NET developers:

General Resources

  • Directory Programming .NET - contains all the sample code from DotNevDevGuide to DS Programming and useful tools.  This is probably your best bet for any .NET related questions.  Check out the forums where you can get in touch with both Joe and me.  Tons of sample code for .NET can found here as well in both C# and VB.NET.
  • microsoft.public.adsi.general – This is a great resource and well trafficked.  This is also where you should post your non-.NET related questions.  C++, scripting, and *blech* VB are all fair game here.  Newsgroups might not be your bag for posting… so read on.
  • ADSI Yahoo Group – This discussion group has slowed down quite a bit, but is still a good avenue to find some help for ADSI and LDAP related questions.  The focus tends to be on .NET, but 3rd party LDAP and other technologies are fair game.

Other Resources

Books

Tools

  • It takes a little getting used to, but ldp.exe is probably the most useful tool for working with AD or ADAM.  It is a no-frills and ugly tool, but definitely powerful.  You can find this on most Windows 2003 servers or with the AdminPak.msi.  Probably an even easier way to get it is to download ADAM and just install the tools.  I rely on this tool to test my LDAP queries and bind operations first.
  • Softerra makes a nice LDAP browser for free that is useful.  I have not tried the commercial version that allows you to edit things, so I can’t comment on that.  I wish it would support more types of binds so we can bind with our current credentials, but it works well otherwise.  It does not use ADSI at all and might not support paging correctly, but it has some slick features like the ability to export objects to LDIF files with a click.
  • Wireshark - formerly Ethereal - this is an awesome tool to use to sniff the underlying traffic when you just don't know what is going on.  It does a great job of decoding the Kerberos and LDAP traffic into human-readable format.  Highly recommended when other troubleshooting steps fail.
  • Microsoft’s Err.exe tool.  Used in conjunction with ldp.exe, you can very easily pinpoint the true error.  No more COMException: An unknown exception has occurred.
  • Beavertail – An open-source LDAP browser written in C# by Marc Scheuner.  A strange name perhaps for a browser, but definitely worth a look if you want to see C# and LDAP in action.
  • ADSI Browser– Another LDAP Browser, this time written in Delphi by Marc Scheuner.  This one has a few more features than the Beavertail offering, but it is not open-source.
  • Joe Richards has a number of free tools available that worth checking out.  In particular ADFind is a must see for the command line junkie in all of us.

Thursday, 02 June 2005

Inspecting your Trusts in .NET 2.0

One of the great things about .NET 2.0 is the new System.DirectoryServices.ActiveDirectory namespace.  It contains a plethora of previously either obscure or difficult things to do using Ds* or NetApi* API calls.  I was troubleshooting a trust relationship at a client and was digging how easy this was to do in 2.0:

    public static void ShowAllTrusts()
    {
        Domain domain = Domain.GetCurrentDomain();
        int i = 0;
 
        foreach (TrustRelationshipInformation tri in domain.GetAllTrustRelationships())
        {
            Console.WriteLine("Trust #{0}", ++i);
            Console.WriteLine("=============================================");
            Console.WriteLine(
                "Source Name: {0}",
                tri.SourceName
                );
 
            Console.WriteLine(
                "Target Name: {0}",
                tri.TargetName
                );
 
            Console.WriteLine(
                "Trust Direction: {0}",
                tri.TrustDirection
                );
 
            Console.WriteLine(
                "Trust Type: {0}",
                tri.TrustType
                );
 
 
            string verifiedMsg = "true";
 
            try
            {
                domain.VerifyTrustRelationship(
                    Domain.GetDomain(
                        new DirectoryContext(
                            DirectoryContextType.Domain,
                            tri.TargetName
                            )
                        ),
                    tri.TrustDirection
                    );
            }
            catch (Exception ex)
            {
                verifiedMsg = ex.Message;
            }
 
            Console.WriteLine(
                "Verified: {0}",
                verifiedMsg        
                );
 
            Console.WriteLine("=============================================");
            Console.WriteLine();
        }
    }

That took all of 5 minutest to code and test using SnippetCompiler.  Very cool.