Get closest Domain Controller in current AD site without hard coding information
Asked Answered
G

5

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

Glazier answered 12/6, 2012 at 15:22 Comment(1)
Did you have the chance to take a look at the update I added to my answer?Miosis
M
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;

    }
Miosis answered 13/7, 2012 at 1:55 Comment(6)
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.Glazier
@Glazier - Updated my answer to include an example that I think will work for you.Miosis
@Glazier - did you have the chance to try this out?Miosis
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 thenGlazier
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 oneEgression
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 acceptableEgression
E
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.

Epicurean answered 21/11, 2012 at 16:33 Comment(0)
G
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;
    }
Glazier answered 12/6, 2012 at 17:44 Comment(2)
Question to anyone that can answer: Is the client required to dispose of each DomainController instance?Glazier
Since the class implements iDisposable I would suggest it. These DirectoryEntry classes tend to leak memory.Miosis
K
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)",""
Kurzawa answered 12/10, 2017 at 16:7 Comment(0)
B
0

I know this is an old tread but I wanted to share some code I put together for a single domain lookup and a multi-domain a one-liner to find the closest DC from any windows device running PowerShell 3+. I use this to help balance Sites and Services.

This block will search a single domain for all DC's and the number of hops and latency to each DC

$domain="<DOMAIN FQDN>";$ip=(Get-NetIPAddress -InterfaceIndex (Get-NetRoute -DestinationPrefix "0.0.0.0/0").ifIndex);$results=@();$a=New-Object 'System.DirectoryServices.ActiveDirectory.DirectoryContext'("domain",$domain);[System.DirectoryServices.ActiveDirectory.DomainController]::FindAll($a)|%{ $temp=Test-NetConnection $_.name -TraceRoute; $results+=[pscustomobject]@{SourceIP="$($ip.IPAddress)/$($ip.PrefixLength)";LogonServer=$env:LOGONSERVER; DomainController=$_.name;IP=$_.IPaddress;Site=$_.sitename;OS=$_.OSVersion;ResponseTime=$temp.PingReplyDetails.RoundtripTime;HopCount=($temp.TraceRoute).count}}; $results | sort ResponseTime | Out-GridView -Title "DC Finder"`

Here's a multi-domain expanded version of the domain controller status script.

# define your domain(s)
$domains="<FQDN DOMAIN 1>","<FQDN DOMAIN 2>"
# example domain list. uncomment the below line and add in all the domains.
# $domains="corp.domain.com","subdomain.corp.domain.com"

# get the IP information of the local computer running this code block
$ip=(Get-NetIPAddress -InterfaceIndex (Get-NetRoute -DestinationPrefix "0.0.0.0/0").ifIndex)

# create an empty arraylist object
$results = New-Object System.Collections.ArrayList

$domains | ForEach-Object {
    $domain = ($_).toupper()
    $a=New-Object 'System.DirectoryServices.ActiveDirectory.DirectoryContext'("domain",$domain)
    [System.DirectoryServices.ActiveDirectory.DomainController]::FindAll($a) | `
        ForEach-Object { 
            $ping = Test-NetConnection $_.name -TraceRoute
            $tmpobj = New-Object -TypeName PSObject -Property ([ordered]@{
                domainFQDN = $domain
                sourceIP = "$($ip.IPAddress)/$($ip.PrefixLength)"
                logonServer = $env:LOGONSERVER
                domainController = $_.name
                domainControllerIP = $_.IPaddress
                siteName = $_.sitename
                domainControllerOS = $_.OSVersion
                responseTime=$ping.PingReplyDetails.RoundtripTime;
                HopCount=($ping.TraceRoute).count
            })
        }
        $null = $results.add($tmpobj)
}
            
$results | sort responseTime | Out-GridView -Title "Domain Controller Stats"

After the script completes it will pop up a PowerShell Gridview window with the results.

Bootblack answered 13/8, 2024 at 15:46 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.