As long as Microsoft Active Directory (AD) has been around, there have been roaming clients. Roaming clients are those domain-joined machines that aren't assigned to an AD site. These computers don't have an Active Directory subnet defined to a site. They have no way to know what site they're in.

These computers are problematic because they randomly choose a domain controller to authenticate to. Authentication requests could take much longer than expected if the client decides to select a domain controller (DC) across the globe.

It's important to recognize these roaming clients and to remediate them whenever possible.

Search the netlogon.log File

The process of hunting down these clients is pretty simple. You need to query a log file on each domain controller in your AD forest. This log file contains lines with the string NO_CLIENT_SITE in them. You can be sure if you see an instance of this line you've got a client that's gone roaming.

You could check this log file yourself across your DCs, but that's not too fun. Let's automate this task with PowerShell!

You're looking for for a log file called netlogon.log on each DC. If any clients start roaming, the DC that they authenticate to will record that activity in this file. This file is located in the C:\Windows\Debug folder of each domain controller. This file is where we'll look for that NO_CLIENT_SITE reference.

Enumerating all DCs in a Forest

To account for all clients across the domain, you'll need to find all DCs in the forest. To do that, use both the Get-ADForest and Get-AdDomainController PowerShell cmdlets.

Get-AdForest returns a property called Domains that will show all domains in the forest. Once you have all the domains in the forest, you can then find all domain controllers in each of those domains with Get-ADDomainController.

$dcs = ((Get-ADForest).Domains | foreach {(Get-ADDomainController-Server $_ -Filter *) }).HostName

In the above example, I'm only outputting the HostName, which is the FQDN of each domain controller.

Automating Text File Searching on a DC

Now that you have all the DCs in the forest, you'll need to develop some code to query each of them. As good practice, I always prepare the code against one first. Once you have this, it's easy to expand to all domain controllers.

One way to find search text files with PowerShell is to use the Select-String cmdlet. Select-String is a cmdlet that allows you to specify a regular expression as a pattern to search for. Select-String can search for patterns inside of a string or a file with the Path parameter.

In the example below, I'm searching for all lines in the netlogon.log file with the string NO_CLIENT_SITE that look like this:

12/25 19:36:41 CHILD: NO_CLIENT_SITE: MYCLIENT 192.168.0.10

If there's a match, pull the name (MYCLIENT) out of that line. To do that, use the regular expression NO_CLIENT_SITE: (.*) \d. Then, pass that regular expression and the path to the netlogon.log file to Select-String.

Select-String -Pattern 'NO_CLIENT_SITE: (.*) \d' -Path "\\MYDOMAINCONTROLLER\c$\windows\debug\netlogon.log"

This output is great, but I only want to see client names. To do this, look at each of the objects that Select-String outputs. Then find the value that came from that regular expression we used.

That value can be found buried inside of the second Groups object inside of the Matches property for each line that was matched.

$_.Matches.Groups[1].Value

You now have code that looks something like this:

Select-String -Pattern 'NO_CLIENT_SITE: (.*) \d' -Path '\\MYDOMAINCONTROLLER\c$\windows\debug\netlogon.log' | foreach {
    $_.Matches.Groups[1].Value
}

You're still not done, however. You'll find that if a client has been roaming for a while, it will be recorded in the log many times.

To remove all the duplicates, pipe the objects from Select-String to the Group-Object cmdlet. This process will remove all duplicates and give you a way to find unique client names.

Select-String -Pattern 'NO_CLIENT_SITE: (.*) \d' -Path '\\MYDOMAINCONTROLLER\c$\windows\debug\netlogon.log' | foreach {
    $_.Matches.Groups[1].Value
} | Group-Object

The final step is to output only the client names from the output of Group-Object.

$clients = Select-String -Pattern 'NO_CLIENT_SITE: (.*) \d' -Path '\\MYDOMAINCONTROLLER\c$\windows\debug\netlogon.log' | foreach {
    $_.Matches.Groups[1].Value
} | Group-Object

$clients | foreach {
    $_.Name
}

Expanding to all DCs

You now have the code to do a single DC. Querying all DCs, at this point, is a piece of cake with a foreach loop.

Below is the whole code block you can use.

$dcs = ((Get-ADForest).Domains | foreach { (Get-ADDomainController -Server $_ -Filter *) }).HostName

foreach ($d in $dcs) {
    $output = @{'DomainController' = $d}
    $clients = Select-String -Pattern 'NO_CLIENT_SITE: (.*) \d' -Path "\\$d\c`$\windows\debug\netlogon.log" | foreach {
        $_.Matches.Groups[1].Value
    } | Group-Object

    if ($clients) {
        $clients | foreach {
            $output.Client = $_.Name
            [pscustomobject]$output
        }
    }
}

Summary

With a little knowledge of where roaming clients can be found in and some PowerShell, you can knock out this problem quickly. Build a script that will query each netlogon.log file in the entire domain, sit back and see how bad the problem is. Now actually fixing those issues is up to you!