Microsoft DNS & stale records

This post explores how identification of stale records in a dynamically updated Microsoft DNS zone.

The time stamp taken from a DNS record represents the numbers of hours since 01/01/1601 00:00. By default, all times are reported and tested in UTC.

A stale record is a record where both the No-Refresh Interval and Refresh Interval have passed without the time stamp updating. Ordinarily stale records would be removed by a Scavenging process.

Listing stale records with VbScript

This script uses a WMI query to return all A records for a domain, then it sorts through each record, echoing when the time stamp is older than our pre-defined maximum age. The script will work best when run with cscript.

' No-Refresh + Refresh (in Days)
Const MAXIMUM_AGE = 4

' Connect to the MicrosoftDNS Namespace
Set objWMIService = _
    GetObject("winmgmts:\\dc01.domain.example\root\MicrosoftDNS")

' Query A records with MicrosoftDNS_AType class where the record is not static
Set colItems = objWMIService.ExecQuery("SELECT * FROM MicrosoftDNS_AType " & _
    " WHERE ContainerName='domain.example' AND TimeStamp<>0")

For Each objItem In colItems
    ' Convert the timestamp into a date and time
    dtmTimeStamp = DateAdd("h", objItem.TimeStamp, "01/01/1601 00:00:00")
    ' Compare the date and time with MAXIMUM_AGE
    If dtmTimeStamp <= (Date() - MAXIMUM_AGE) Then
        ' Echo the record details if it is older than the MAXIMUM_AGE
        WScript.Echo objItem.OwnerName & VbTab & objItem.IPAddress & _
            VbTab & dtmTimeStamp
    End If
Next

Listing stale records with PowerShell

This snippet uses Get-WMIObject and a improved query to return only stale records rather than sorting after returning all dynamic records.

A timespan value is generated to represent the minimum value of TimeStamp for valid records.

# No-Refresh + Refresh (in Days)
$TotalAgingInterval = 4

$params = @{
    Filter       = 'ContainerName="domain.example" AND TimeStamp<>0' 
    Class        = 'MicrosoftDNS_AType'
    Namespace    = 'root\MicrosoftDNS'
    ComputerName = 'dc01.domain.example'
}
Get-WmiObject @params |
    Select-Object *, @{
        Name       = 'TimeStamp'
        Expression = {
            (Get-Date "01/01/1601").AddHours($_.TimeStamp)
        }
    } |
    Where-Object { $_.TimeStamp -lt (Get-Date).AddDays($TotalAgingInterval) }

Reading Aging intervals with PowerShell

The Aging intervals and the date the zone can be scavenged set on a zone can be read using WMI using the MicrosoftDNS_Zone class. As with the TimeStamp the .AddHours method must be used to return a date.

$params = @{
    Filter       = 'ContainerName="domain.example"' 
    Class        = 'MicrosoftDNS_Zone'
    Namespace    = 'root\MicrosoftDNS'
    ComputerName = 'dc01.domain.example'
}
Get-WmiObject @params |
    Select-Object NoRefreshInterval, RefreshInterval, @{
        Name       = 'AvailForScavengeTime'
        Expression = { (Get-Date "01/01/1601").AddHours($_.AvailForScavengeTime) }
    }

Localisation

As mentioned at the beginning of this post, all times are reported in UTC by default. By calling a the ToLocalTime method the date returned can be converted to local time, using the time zone configured on the system executing the query.

# No-Refresh + Refresh (in Days)
$TotalAgingInterval = 4

$params = @{
    Filter       = 'ContainerName="domain.example" AND TimeStamp<>0' 
    Class        = 'MicrosoftDNS_AType'
    Namespace    = 'root\MicrosoftDNS'
    ComputerName = 'dc01.domain.example'
}
Get-WmiObject @params |
    Select-Object *, @{
        Name       = 'TimeStamp'
        Expression = {
            (Get-Date "01/01/1601").AddHours($_.TimeStamp).ToLocalTime()
        }
    }