InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Searching the Global Address List - C# Edition

A long time ago, I wrote an article on How to get the Global Address List programatically. The theory behind that article is still valid, but it only features a VBScript example. Since someone from the Microsoft Exchange Forum had trouble converting it to C#, I fired up Visual Studio and hacked something together. The result is somewhat more complete than the VBScript example, because it allows access to all address lists, not just the default global address list (GAL).

Here we go:

using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;

namespace AddressListSample
{
    public class ActiveDirectoryConnection
    {
        public DirectoryEntry GetLdapDirectoryEntry(string path)
        {
            return GetDirectoryEntry(path, "LDAP");
        }

        public DirectoryEntry GetGCDirectoryEntry(string path)
        {
            return GetDirectoryEntry(path, "GC");
        }

        private DirectoryEntry GetDirectoryEntry(string path, string protocol)
        {
            var ldapPath = string.IsNullOrEmpty(path) ? string.Format("{0}:", protocol) : string.Format("{0}://{1}", protocol, path);
            return new DirectoryEntry(ldapPath);
        }
    }

    public class ExchangeAddressListService
    {
        private readonly ActiveDirectoryConnection _Connection;

        public ExchangeAddressListService(ActiveDirectoryConnection connection)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            _Connection = connection;
        }

        public IEnumerable<AddressList> GetGlobalAddressLists()
        {
            return GetAddressLists("CN=All Global Address Lists");
        }

        public IEnumerable<AddressList> GetAllAddressLists()
        {
            return GetAddressLists("CN=All Address Lists");
        } 
        public IEnumerable<AddressList> GetSystemAddressLists()
        {
            return GetAddressLists("CN=All System Address Lists");
        }

        private IEnumerable<AddressList> GetAddressLists(string containerName)
        {
            string exchangeRootPath;
            using (var root = _Connection.GetLdapDirectoryEntry("RootDSE"))
            {
                exchangeRootPath = string.Format("CN=Microsoft Exchange, CN=Services, {0}", root.Properties["configurationNamingContext"].Value);
            }
            string companyRoot;
            using (var exchangeRoot = _Connection.GetLdapDirectoryEntry(exchangeRootPath))
            using (var searcher = new DirectorySearcher(exchangeRoot, "(objectclass=msExchOrganizationContainer)"))
            {
                companyRoot = (string) searcher.FindOne().Properties["distinguishedName"][0];
            }

            var globalAddressListPath = string.Format(containerName + ",CN=Address Lists Container, {0}", companyRoot);
            var addressListContainer = _Connection.GetLdapDirectoryEntry(globalAddressListPath);

            using (var searcher = new DirectorySearcher(addressListContainer, "(objectClass=addressBookContainer)"))
            {
                searcher.SearchScope = SearchScope.OneLevel;
                using (var searchResultCollection = searcher.FindAll())
                {
                    foreach (SearchResult addressBook in searchResultCollection)
                    {
                        yield return
                            new AddressList((string) addressBook.Properties["distinguishedName"][0], _Connection);
                    }
                }
            }
        }
    }

    public class AddressList
    {
        private readonly ActiveDirectoryConnection _Connection;
        private readonly string _Path;

        private DirectoryEntry _DirectoryEntry;

        internal AddressList(string path, ActiveDirectoryConnection connection)
        {
            _Path = path;
            _Connection = connection;
        }

        private DirectoryEntry DirectoryEntry
        {
            get
            {
                if (_DirectoryEntry == null)
                {
                    _DirectoryEntry = _Connection.GetLdapDirectoryEntry(_Path);
                }
                return _DirectoryEntry;
            }
        }

        public string Name
        {
            get { return (string) DirectoryEntry.Properties["name"].Value; }
        }

        public IEnumerable<SearchResult> GetMembers(params string[] propertiesToLoad)
        {
            var rootDse = _Connection.GetGCDirectoryEntry(string.Empty);
            var searchRoot = rootDse.Children.Cast<DirectoryEntry>().First();
            using (var searcher = new DirectorySearcher(searchRoot, string.Format("(showInAddressBook={0})", _Path)))
            {
                if (propertiesToLoad != null)
                {
                    searcher.PropertiesToLoad.AddRange(propertiesToLoad);
                }
                searcher.SearchScope = SearchScope.Subtree;
                searcher.PageSize = 512;
                do
                {
                    using (var result = searcher.FindAll())
                    {
                        foreach (SearchResult searchResult in result)
                        {
                            yield return searchResult;
                        }
                        if (result.Count < 512) break;
                    }
                } while (true);
            }
        }
    }

    internal class Program
    {
        private static void Main()
        {
            var connection = new ActiveDirectoryConnection();
            var service = new ExchangeAddressListService(connection);
            foreach (var addressList in service.GetGlobalAddressLists())
            {
                Console.Out.WriteLine("addressList.Name = {0}", addressList.Name);
                foreach (var searchResult in addressList.GetMembers())
                {
                    Console.Out.WriteLine("\t{0}", searchResult.Properties["name"][0]);
                }
            }

            foreach (var addressList in service.GetAllAddressLists())
            {
                Console.Out.WriteLine("addressList.Name = {0}", addressList.Name);
                foreach (var searchResult in addressList.GetMembers())
                {
                    Console.Out.WriteLine("\t{0}", searchResult.Properties["name"][0]);
                }
            }

            foreach (var addressList in service.GetSystemAddressLists())
            {
                Console.Out.WriteLine("addressList.Name = {0}", addressList.Name);
                foreach (var searchResult in addressList.GetMembers())
                {
                    Console.Out.WriteLine("\t{0}", searchResult.Properties["name"][0]);
                }
            }
        }
    }
}

The sample wraps the whole logic up into two classes: ExchangeAddressListService and AddressList. The first serves as some kind of entry point and the latter allows access to the members of a list.

Hope this helps…


Posted by Henning Krause on Tuesday, October 25, 2011 8:22 PM, last modified on Tuesday, October 25, 2011 8:22 PM
Permalink | Post RSSRSS comment feed

SmartMemoryManagement

Posted under .NET Tools | Comments (0)

Description

The .NET Runtime has four different heaps where it stores objects. There are many good articles on how the runtime manages object on these heaps so I won’t into great details about the first three. Here are a two good links to blogs with more information on this topic:

The Large Object Heap

Most of the time, the GC does a well job managing the memory of your programs.
But there are situations, where it performs sub-optimally. One of these situations are so-called Large Objects. All objects with a size greater than 85000 bytes fall within this category. These objects are stored on a separate heap, called Large Object Heap. This heap will neither be defragmented, nor is it collected often, as the GC processes this heap along with the Generation-2 heap.
To reproduce the problem, try to read a large file (say 70MB or so) from a file stream and store it in a MemoryStream. You will see that your memory usage will climb to about 250 MB. Now, destroy your Memorystream and read the file again into another MemoryStream. Now, depending on your memory pressure, the GC will do a Gen-2 Collection (remember.. this is expensive) and reclaim that memory, or you end up with about 500MB of consumed memory.
Now you will probably argue that 250MB are way to much because you could preallocate one large buffer of 70MB for the file via MemoryStream.SetLength(), so you’ll end up with a smaller memory consumption. But there are two things with this argument:
  1. On the second read of the file you memory usage will increase anyway, albeit not to 500 MB but perhaps to about 150MB.
  2. You might not always know in advance how much memory you will need. Think of a server application that reads data over a TCP connection, there might be no hint on how large the amount of received data is. So you end up with increasing your buffer every now and then.

The Solution

The classes you can download below will give you the ability to reuse previously allocated chunks of memory. And once you are done with the memory, you can return it to the pool.
Note that I used weak references within the pool, so the GC can reclaim the buffers within the pool if memory pressure comes up.
Additionally, I have developed a new MemoryStream that uses this object pooling technique.

Downloads


Posted by Henning Krause on Saturday, February 12, 2005 12:26 PM, last modified on Saturday, February 12, 2005 1:25 PM
Permalink | Post RSSRSS comment feed