I just published the Python app for my day job that I mentioned I would be working on. It turned out quite a bit differently than I thought it would.

The goal: grab events from our Active Directory domain controllers from around the world and parse them against our network subnet information (specifically, what building a subnet is deployed in) to provide a list of IP subnets that should be added to Active Directory and assigned to a site.

The tools: one slightly dangerous CS grad, ActivePython, Winbatch, Sysinternal‘s PSLogList, and a W2K3 server.

The method:

  • Put PSLogList on each DC along with a shell script that is scheduled to run it weekly. The command used: psloglist -s -d 14 -i 5778 > c:\5778logs.csv outputs the last 14 days’ 5778 events with comma-separated values and puts them in a text file. PSLogList can remotely query servers, however its performance over WAN links is much slower than just running it locally and copying over the results.
  • Wrote a Winbatch script that gets a list of all the current AD DCs, and attempts to find and copy down their CSV files. After it’s done, it puts them all together into a single CSV file.
  • Wrote a Python script that reads in a CSV file with network subnet information (network address, subnet mask, and building info are used) and then iterates through each 5778 event, associating a network subnet to each referenced client’s IP address. I used a bit of brute force to calculate the correct subnet. Our network has a lot of variety, with plenty of subnetted Class B and C subnets, so this calculation takes awhile for the thousands of 5778 events that are being parsed. There are probably more elegant ways of doing this, so this part of the process will improve over time. (The Python script I wrote is in the extended entry.) The script outputs a formatted text file that includes information about every unassigned subnet.
  • Created a shell script to wrap this all up and run weekly after the runs of the DCs’ scheduled PSLogList jobs.

The Python module I wrote for this task:

"""Active Directory (AD) Subnet Discoverer
Alex Harden, WSTS, TEIS Americas (aharden@tycoelectronics.com)

This is designed to take in a list of messages from Event 5778's on
AD DC's and match the cited subnets in a list of network locations.
A text file of the results is provided to serve as a guide for adding
the subnets to appropriate AD sites, for increased logon efficiency.

The functions noted with a ** taken from the following ASPN recipe:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66517

The dictsort function was adapted from the following ASPN recipe:
http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52306

"""

import os, sys, socket, struct

def dottedQuadToNum(ip):
    "convert decimal dotted quad string to long integer (**)"
    return struct.unpack('!L',socket.inet_aton(ip))[0]

def numToDottedQuad(n):
    "convert long int to dotted quad string (**)"
    return socket.inet_ntoa(struct.pack('!L',n))

def makeMask(n):
    "return a mask of n bits as a long integer (**)"
    return (2L 23 and (ipaddr.split('.')[0:3] == network.split('.')[0:3] )) or \
           (mask > 15 and mask  7 and mask  '' :
        temp=x.split(',')
        subnet=convertMask(temp[0],temp[1])
        #subnet = '%s.%s.%s' % tuple(temp[0].split('.')[0:3]) # format 'xxx.xxx.xxx' no lead zeros
        self[subnet] = temp[3]
        x=infile.readline()
    return self

def MakeUnassignedDict(subnets,infile):
    "returns a dictionary of unassigned subnets, indexed by network/mask"
    self={}
    x=infile.readline()
    while x  '' :
        temp=x.split(',')
        #skip through crud
        if len(temp) == 9 and temp[6] == '5778' and len(temp[8].split("'")) > 3 :
            myname=temp[8].split("'")[1]
            myaddress=temp[8].split("'")[3]  #format xxx.xxx.xxx.xxx no lead zeros
            mysubnet=findSubnet(myaddress,subnets)
            if mysubnet not in self:
                if mysubnet in subnets :
                    self[mysubnet] = '%s::%s::%s::%s\n' % (mysubnet,subnets[mysubnet],myname,myaddress)
                else :
                    self[mysubnet] = "%s::%s::%s::no reference for this subnet\n" % (mysubnet,myname,myaddress)
        x=infile.readline()
    return self

def dictSort(d):
    """ returns a dictionary sorted by keys """
    our_list = d.items()
    our_list.sort()
    k = {}   
    for x in range(0,len(our_list)):
        k[our_list[x][0]] = our_list[x][1]
    return k

def WriteOutput(dict,outfile):
    "writes the contents of the dictionary to the output file"
    output=()
    dict=dictSort(dict)
    for x in dict :
        outfile.writelines(dict[x])

if __name__ == "__main__" :
    myinfile=open('c:\\jobs\\5778logs\\5778-logs.csv','r')  #event messages
    myinfile2=open('c:\\jobs\\5778logs\\subnet.txt','r')  #subnet/bldg info
    mysubnets=MakeNetDict(myinfile2)
    myinfile2.close()
    unassigned=MakeUnassignedDict(mysubnets,myinfile)
    myinfile.close()
    myoutfile=open('c:\\jobs\\5778logs\\unassigned.txt','w') #new file with unassigned subnet info
    WriteOutput(unassigned,myoutfile)
    myoutfile.close()