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

Iterating through all mailboxes in an Exchange 2000/2003 organization

Description [Updated]

Updates

  • 2008-07-23: Corrected intra-site links.

One question that came up lately in the newsgroups is: How can I find messages containing certain keywords in the mailboxes of all users in my organization?

This scenario is not directly supported by Exchange. All one can do is to search each mailbox individually.

You must follow these steps to do the search over all mailboxes:

  1. Enumerate the users which have a mailbox. Essentially, these are the users appearing on the global address list. See How to get the Global Address List programatically for more information on how to do this.
  2. Build the mailbox url which can be used to access the mailbox via WebDAV or ExOleDB. See Get the WebDAV url for an Exchange 2000/2003 mailbox on how to do this. If you are using the ExOleDB provider or want to use the administrative virtual root instead, see the remarks section for more information.
  3. Once you have the url for the mailbox you can start accessing it. If you must support different languages, see Getting Well-Known Mailbox Folder URLs on MSDN to get the url of the default folders.
  4. If you are using WebDAV and have Form-based-authentication enabled on your server, you must do a manual logon to the mailbox. See Access the Exchange store via WebDAV with Form-Based-Authentication turned on.

Remarks

Permissions

Depending on how you want to access the mailboxes, you need different permissions:

  • If you are using the normal urls (e.g. http://myserver/exchange/username), you need access permissions to all mailboxes on the MAPI level. See HOWTO: Grant access to all mailboxes on a mailbox store to a special account on how to do this. If you have more than one mailbox store, you should grant the necessary permissions on each mailbox store. To simplify this process, you could also grant the "Send as" and "Receive as" permission on the Administrative Groups container via ADSIEdit.msc instead of each mailbox store.
  • You can also use the administrative virtual root. This method is used by the Exchange Systems manager, and it is available via WebDAV and ExOleDB. The normal MAPI permissions are completely ignored when using this method, but an administrative account is required to use this method (See Working with Store Permissions in Microsoft Exchange 2000 and 2003 on Technet for more information on this topic).

Mailbox urls

  • If you are using WebDAV to access the store, you can simply build the mailbox url based on the article Get the WebDAV url for an Exchange 2000/2003 mailbox. To use the administrative root instead, modify the url from http://myserver/exchange/mailboxname to http://myserver/exadmin/admin/<dsndomainname>/mbx/<mailboxname>. You must replace the <dnsdomainname> with the primary smtp domain name of your organization.
  • If you are using ExOleDB, you must modify the address from http://myserver/exchange/mailboxname to file://./backofficestorage/<dnsdomainname>/mbx/<mailboxname>. To use the administrative virtual root, change this url to file://./backofficestorage/admin/<dnsdomainname>/mbx/<mailboxname>.

Posted by Henning Krause on Thursday, April 13, 2006 12:00 AM, last modified on Wednesday, July 23, 2008 10:59 PM
Permalink | Post RSSRSS comment feed