5

For instances when Active Directory takes too long to replicate data between sites, I need to ensure that the local AD replica contains the most up to date information.

  • How can I get a list of DomainControllers for the current site?

I haven't found anything on Codeproject or on StackOverflow

Community
  • 1
  • 1
makerofthings7
  • 60,103
  • 53
  • 215
  • 448

4 Answers4

9

Going to all this trouble is probably wasted effort. Unless you are experiencing issues with the built in logic for finding a domain controller you should just go with the built in method that returns one. According to Microsoft it automatically tries to find the closes one: http://technet.microsoft.com/en-us/library/cc978016.aspx.

Just use the static DomainController.FindOne method and pass in your directorycontext.

Update
Alright, try the code below, let me know how it works for you. It pings each, returns the roundtrip time, if -1 (no connection) it skips it. Flags PDC status if present. Orders by PDC status, followed by ping round trip.

    static void Main(string[] args)
    {
        var dcsInOrder = (from DomainController c in Domain.GetCurrentDomain().DomainControllers
                          let responseTime = Pinger(c.Name)
                          where responseTime >=0
                          let pdcStatus = c.Roles.Contains(ActiveDirectoryRole.PdcRole)
                          orderby pdcStatus, responseTime
                          select new {DC = c, ResponseTime = responseTime} 
                          ).ToList();

        foreach (var dc in dcsInOrder)
        {
            System.Console.WriteLine(dc.DC.Name + " - " + dc.ResponseTime);
        }

        System.Console.ReadLine();
    }

    private static int Pinger(string address)
    {
        Ping p = new Ping();
        try
        {
            PingReply reply = p.Send(address, 3000);
            if (reply.Status == IPStatus.Success) return (int)reply.RoundtripTime;
        }
        catch { }

        return -1;

    }
Peter
  • 9,643
  • 6
  • 61
  • 108
  • The problem is that I don't have confidence that FindOne works in the way technet describes. Running code seems to indicate the server is chosen randomly. – makerofthings7 Jul 13 '12 at 15:15
  • @makerofthings7 - Updated my answer to include an example that I think will work for you. – Peter Jul 13 '12 at 18:30
  • @makerofthings7 - did you have the chance to try this out? – Peter Jul 18 '12 at 17:36
  • Thanks, I haven't tried it yet, but think it may work. I'll be back in the office next week and can try it out then – makerofthings7 Jul 20 '12 at 16:54
  • DC locator returns the closest one via DNS request which uses round robin. The problem is that the closest one may have low channel throughput. Ping solution is a good one – oldovets Oct 15 '17 at 11:18
  • The best solution would be to use a combination of locator an ping. Locator returns you a closest DC, then you ping it. If ping suits you then you use this DC. Otherwise you request another DC with force relocating option. Finally, you will connect to a closest DC with lowest ping or ping that is acceptable – oldovets Oct 15 '17 at 11:35
2

First, I'll answer the question that you actually asked:

System.DirectoryServices.ActiveDirectory.ActiveDirectorySite.GetComputerSite().Servers

But it seems like you're asking how to make sure that you're talking to the closest domain controller possible. Windows doesn't exactly provide this functionality, the best it will do is give you a domain controller in the same site that the code is running from.

I think the first thing to check is that you have your sites and subnets configured correctly. Run Active Directory Sites and Services, and make sure that subnets and domain controllers are assigned to the correct sites.

This MSDN page (and the Technet article in Peter's answer) says that you must be searching by the DNS name for the DC Locator to attempt to find a DC in the current site. I don't know if the Name property of the Domain class is the DNS domain name.

I have to assume that DomainController.FindOne is a wrapper for DsGetDcName. At that link, you can find how to turn on tracing for that function. You can use this if you still have problems, or maybe you should just PInvoke this function.

Sean Hall
  • 7,629
  • 2
  • 29
  • 44
1

Here is a code sample that has no hard coding of DCs. Comments and criticism are welcome.

    /// <summary>
    /// For best results ensure all hosts are pingable, and turned on.  
    /// </summary>
    /// <returns>An ordered list of DCs with the PDCE first</returns>
    static LinkedList<DomainController> GetNearbyDCs()
    {
        LinkedList<DomainController> preferredDCs = new LinkedList<DomainController>();
        List<string> TestedDCs = new List<string>();

        using (var mysite = ActiveDirectorySite.GetComputerSite())
        {
            using (var currentDomain = Domain.GetCurrentDomain())
            {
                DirectoryContext dctx = new DirectoryContext(DirectoryContextType.Domain, currentDomain.Name);
                var listOfDCs = DomainController.FindAll(dctx, mysite.Name);

                foreach (DomainController item in listOfDCs)
                {
                    Console.WriteLine(item.Name );
                    if (IsConnected(item.IPAddress))
                    {
                        // Enumerating "Roles" will cause the object to bind to the server
                        ActiveDirectoryRoleCollection rollColl = item.Roles;
                        if (rollColl.Count > 0)
                        {
                            foreach (ActiveDirectoryRole roleItem in rollColl)
                            {
                                if (!TestedDCs.Contains(item.Name))
                                {
                                    TestedDCs.Add(item.Name);
                                    if (roleItem == ActiveDirectoryRole.PdcRole)
                                    {
                                        preferredDCs.AddFirst(item);
                                        break;
                                    }
                                    else
                                    {

                                        if (preferredDCs.Count > 0)
                                        {
                                            var tmp = preferredDCs.First;
                                            preferredDCs.AddBefore(tmp, item);
                                        }
                                        else
                                        {
                                            preferredDCs.AddFirst(item);
                                        }
                                        break;
                                    }
                                } 

                            }
                        }
                        else
                        {
                            // The DC exists but has no roles
                            TestedDCs.Add(item.Name);
                            if (preferredDCs.Count > 0)
                            {
                                var tmp = preferredDCs.First;
                                preferredDCs.AddBefore(tmp, item);
                            }
                            else
                            {
                                preferredDCs.AddFirst(item);
                            }
                        }
                    }
                    else
                    {
                        preferredDCs.AddLast(item);
                    }
                }
            }
        }
        return preferredDCs;
    }
    static bool IsConnected(string hostToPing)
    {
        string pingurl = string.Format("{0}", hostToPing);
        string host = pingurl;
        bool result = false;
        Ping p = new Ping();
        try
        {
            PingReply reply = p.Send(host, 3000);
            if (reply.Status == IPStatus.Success)
                return true;
        }
        catch { }
        return result;
    }
makerofthings7
  • 60,103
  • 53
  • 215
  • 448
  • Question to anyone that can answer: Is the client required to dispose of each `DomainController` instance? – makerofthings7 Jun 12 '12 at 17:46
  • Since the class implements iDisposable I would suggest it. These DirectoryEntry classes tend to leak memory. – Peter Jul 13 '12 at 01:48
0

Here's my approach using powershell but I'm sure it's a simple implementation in c#, etc. If DHCP is setup correctly, the Primary DNS server in your subnet should be the closest Domain Controller. So the following code should grab the first DNS IP and resolve it to the hostname of the closest DC. This doesn't require RSAT or credentials and contains no specific properties of the current domain.

$NetItems = @(Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = 'True'" -ComputerName $env:COMPUTERNAME)
foreach ($objItem in $NetItems)
{
    if ($objItem.{DNSServerSearchOrder}.Count -ge 1)
    {
        $PrimaryDNS = $objItem.DNSServerSearchOrder[0]
        $domain = $objItem.DNSDomain
        break
    }
}
[System.Net.Dns]::GetHostbyAddress($PrimaryDNS).hostname -replace ".$($domain)",""
Fred B
  • 175
  • 1
  • 3
  • 11