Parse-DNSDebugLogFile

Happy Friday, nerds.

This is just going to be a quick post wherein I paste in a Powershell script I wrote about a year ago that parses a Windows Server DNS debug log file, and converts the log file from flat boring hard-to-read text, into lovely Powershell object output.  This makes sorting, analyzing and data mining your DNS log files with Powershell a breeze.

It's not my best work, but I wanted to get it posted on here because I forgot about it until today, and I just remembered it when I wanted to parse a DNS log file, and I tried searching for it here and realized that I never posted it.

#Requires -Version 2
Function Parse-DNSDebugLogFile
{
<#
.SYNOPSIS
    This function reads a Windows Server DNS debug log file, and turns it from
    a big flat file of text, into a useful collection of objects, with each
    object in the collection representing one send or receive packet processed
    by the DNS server.
.DESCRIPTION
    This function reads a Windows Server DNS debug log file, and turns it from
    a big flat file of text, into a useful collection of objects, with each
    object in the collection representing one send or receive packet processed
    by the DNS server. This function takes two parameters - DNSLog and ErrorLog.
    Defaults are used if no parameters are supplied.
.NOTES
    Written by Ryan Ries, June 2013.
#>
    Param([Parameter(Mandatory=$True, Position=1)]
            [ValidateScript({Test-Path $_ -PathType Leaf})]
            [String]$DNSLog,
          [Parameter()]
            [String]$ErrorLog = "$Env:USERPROFILE\Desktop\Parse-DNSDebugLogFile.Errors.log")

    # This is the collection of DNS Query objects that this function will eventually return.
    $DNSQueriesCollection = @()
    # Try to read in the DNS debug log file. If error occurs, log error and return null.
    Try
    {
        [System.Array]$DNSLogFile = Get-Content $DNSLog -ReadCount 0 -ErrorAction Stop
        If($DNSLogFile.Count -LT 30)
        {
            Throw "File was empty."
        }
    }
    Catch
    {
        "$(Get-Date) - $($_.Exception.Message)" | Out-File $ErrorLog -Append
        Write-Verbose $_.Exception.Message
        Return
    }
    
    # Cut off the header of the DNS debug log file. It's about 30 lines long and we don't want it.
    $DNSLogFile = $DNSLogFile[30..($DNSLogFile.Count - 1)]
    # Create a temporary buffer for storing only lines that look valid.
    $Buffer = @()
    # Loop through log file. If the line is not blank and it contains what looks like a numerical date,
    # then we can be reasonably sure that it's a valid log file line. If the line is valid,
    # then add it to the buffer.    
    Foreach($_ In $DNSLogFile)
    {
        If($_.Length -GT 0 -AND $_ -Match "\d+/\d+/")
        {
            $Buffer += $_
        }
    }
    # Dump the buffer back into the DNSLogFile variable, and clear the buffer.
    $DNSLogFile = $Buffer
    $Buffer     = $Null
    # Now we parse text and use it to assemble a Query object. If all goes well,
    # then we add the Query object to the Query object collection. This is nasty-looking
    # stuff that we have to do to turn a flat text file into a beutiful collection of
    # objects with members.
    Foreach($_ In $DNSLogFile)
    {
        If($_.Contains(" PACKET "))
        {
            $Query = New-Object System.Object
            $Query | Add-Member -Type NoteProperty -Name Date      -Value $_.Split(' ')[0]
            $Query | Add-Member -Type NoteProperty -Name Time      -Value $_.Split(' ')[1]
            $Query | Add-Member -Type NoteProperty -Name AMPM      -Value $_.Split(' ')[2]
            $Query | Add-Member -Type NoteProperty -Name ThreadID  -Value $_.Split(' ')[3]
            $Query | Add-Member -Type NoteProperty -Name PacketID  -Value $_.Split(' ')[6]
            $Query | Add-Member -Type NoteProperty -Name Protocol  -Value $_.Split(' ')[7]
            $Query | Add-Member -Type NoteProperty -Name Direction -Value $_.Split(' ')[8]
            $Query | Add-Member -Type NoteProperty -Name RemoteIP  -Value $_.Split(' ')[9]
            $BracketLeft  = $_.Split('[')[0]
            $BracketRight = $_.Split('[')[1]
            $Query | Add-Member -Type NoteProperty -Name XID       -Value $BracketLeft.Substring($BracketLeft.Length - 9, 4)
            If($BracketLeft.Substring($BracketLeft.Length - 4, 1) -EQ "R")
            {
                $Query | Add-Member -Type NoteProperty -Name Response -Value $True
            }
            Else
            {
                $Query | Add-Member -Type NoteProperty -Name Response -Value $False
            }
            $Query | Add-Member -Type NoteProperty -Name OpCode -Value $BracketLeft.Substring($BracketLeft.Length - 2, 1)
            $Query | Add-Member -Type NoteProperty -Name ResponseCode -Value $BracketRight.SubString(10, 8).Trim()
            $Query | Add-Member -Type NoteProperty -Name QuestionType -Value $_.Split(']')[1].Substring(1,5).Trim()
            $Query | Add-Member -Type NoteProperty -Name QuestionName -Value $_.Split(' ')[-1]
            
            # Just doing some more sanity checks here to make sure we parsed all that text correctly.
            # If something looks wrong, we'll just discard this whole line and continue on
            # with the next line in the log file.
            If($Query.QuestionName.Length -LT 1)
            {
                Continue
            }
            If($Query.XID.Length -NE 4)
            {
                "$(Get-Date) - XID Parse Error. The line was: $_" | Out-File $ErrorLog -Append
                Continue
            }
            If($Query.Protocol.ToUpper() -NE "UDP" -AND $Query.Protocol.ToUpper() -NE "TCP")
            {
                "$(Get-Date) - Protocol Parse Error. The line was: $_" | Out-File $ErrorLog -Append
                Continue
            }
            If($Query.Direction.ToUpper() -NE "SND" -AND $Query.Direction.ToUpper() -NE "RCV")
            {
                "$(Get-Date) - Direction Parse Error. The line was: $_" | Out-File $ErrorLog -Append
                Continue
            }
            If($Query.QuestionName.Length -LT 1)
            {
                Continue
            }
            
            # Let's change the query format back from RFC 1035 section 4.1.2 style, to the 
            # dot notation that we're used to. (8)computer(6)domain(3)com(0) = computer.domain.com.
            $Query.QuestionName = $($Query.QuestionName -Replace "\(\d*\)", ".").TrimStart('.')
            
            # Finally let's add one more property to the object; it might come in handy:
            $Query | Add-Member -Type NoteProperty -Name HostName -Value $Query.QuestionName.Split('.')[0].ToLower()
            
            # The line, which we've now converted to a query object, looks good, so let's
            # add it to the query objects collection.
            $DNSQueriesCollection += $Query
        }
    }
    $DNSLogFile = $Null
    
    # Clear the error log if it's too big.
    If(Test-Path $ErrorLog)
    {
        If((Get-ChildItem $ErrorLog).Length -GT 1MB)
        {
            Clear-Content $ErrorLog
        }
    }
    
    Return $DNSQueriesCollection
}
# END FUNCTION Parse-DNSDebugLogFile

And the output looks like this:

Date         : 6/17/2013
Time         : 11:51:41
AMPM         : AM
ThreadID     : 19BC
PacketID     : 0000000010146F10
Protocol     : UDP
Direction    : Snd
RemoteIP     : 10.173.16.82
XID          : e230
Response     : True
OpCode       : Q
ResponseCode : NOERROR
QuestionType : A
QuestionName : host01.domain.com.
HostName     : host01

Date         : 6/17/2013
Time         : 11:51:41
AMPM         : AM
ThreadID     : 19BC
PacketID     : 000000000811A6C0
Protocol     : UDP
Direction    : Snd
RemoteIP     : 10.173.16.82
XID          : 36df
Response     : True
OpCode       : Q
ResponseCode : NXDOMAIN
QuestionType : SRV
QuestionName : _kerberos-master._udp.domain.com.
HostName     : _kerberos-master

Comments (1) -

Very nice script !
You can also convert the Microsoft Windows DNS Debug Log file into a CSV table format, which will make the logs more readable and easy to understand and analyse using the free tool available at www.networkstr.com/dnscentric/dns_log_converter    

Comments are closed