Active Directory, Powershell

Parse netlogon.log using PowerShell to find missing Subnets

[TL,DR: I’ve written a script to retrieve IP Addresses from subnets not defined in Active Directory. It is published on Technet Gallery: Get-MissingSubnets]

Active Directory uses Sites to define where in the topology a computer resides and thereby determining which server that should be used for certain services. For example sites are used to determine which Domain Controller a client should use as a logon server and if one of my clients log on at a site somewhere in Australia I probably don’t want it to process Group Policies from a Domain Controller in the US.

For this reason we define sites in Active Directory where each site is associated with one or more subnets and life is good. But what happens if we forget to define a certain subnet or a new subnet suddenly is used? If a computer connects to Active Directory without being assigned a site it might result in unexpected behavior.

For more details on this process there is an excellent blog post about it at this link: http://jorgequestforknowledge.wordpress.com/2011/01/27/dc-locator-what-does-quot-no-client-site-quot-mean-in-netlogon-log/

A great way of making computers from any undefined subnet use a default site is using “catch-all” subnets. If a computers ip address matches more than one subnet, the most specific match wins, meaning that I can assign 10.1.1.0/24 to Site1 and 10.1.2.0/24 to Site2 and then assign 10.1.0.0/16 to Site1 making any address within this scope that doesn’t match any other defined subnet will be associated with Site1.

This post however, is not about why, if or how subnets should be defined. It is about finding which subnets that are used by clients without being defined in Active Directory. To find these subnets (from now on referred to as “missing subnets”) we need to examine the netlogon.log on each Domain Controller and look for the error message NO_CLIENT_SITE .

The first part of the the row will be a timestamp (if not timestamping is disabled) and name and IP-address of the client computer will be listed last on the row. This might be a quite cumbersome task in large environments so I’ve written a script to gather the last part of the netlogon.log logs from several domain controllers and searching for NO_CLIENT_SITE. It will also extract the timestamp, computer name and IP Address from each row and return it as a custom PSObject for easy sorting and/or filtering. If timestamping of netlogon.log for any reason is disabled on any of your domain controllers, set the parameter –IncludeTimestamp to $false). The script is quite simple, I enumerate all domain controllers in current domain as a default value for –DomainControllers with the following code:

Then I wrote two functions, the first one uses Get-Content with the parameter –Tail to retrieve the last (n) number of rows from the netlogon.log log file from each domain controller and sends each row to the pipeline.

The second function accepts stings from the pipeline. In this function I first I defined a regular expression in the Begin{}-block depending on the parameter IncludeTimestamp. This pattern is then matched to each row analyzed. To extract the information that I’m interested in from each row that matches my pattern I use something called Named Groups. In short, anything enclosed in parenthesis in a regular expression is considered a group and will be populated in the automatic variable $matches when using the –match operator in PowerShell.

Each group can then be accessed by using $matches[index] where index is the index-number of the group starting at 1. $Matches[0] will always contain the whole matched string. However, it quickly gets quite frustrating to keep track of each groups index, specially if you have nested groups. There is a great solution to that problem called Named Groups.

Simply enter ‘?<groupname>’ right after the first parenthesis and the group can be extracted using groupname as index. Let’s take an example.

A group of people are given the task to present themselves in five sentences. We assume that everyone will write their name and probably prefixed with “name is”. Let’s match each sentence with a pattern for that.

This will search for “name is “ followed by w, which is regexp for any alphanumeric character or underscore, and +, meaning that the alphanumeric character (or underscore) will appear one or more times. The part of our match within the parenthesis is a group named “name”.

Now matching a sentence could look like this:

This will return True and populate the automatic variable $matches. Examining $matches by piping it to Get-Member will show that $matches is a hashtable. To extract the named group from the matched sentence we can simply get the value of the key ‘name’ from the hashtable:

Once a match is done, returning the result is done by creating a PSCustomObject which gets written to the output.