Active Directory



Active Directory combines all sorts of domain-wide information into one spot. Using Microsoft's COM LDAP provider, python has good access to Active Directory.


To access active directory, it is easiest to put some boilerplate code into a functions for future use. This will be done w/python's COM access to Active Directory. If you use python's ldap library, you can do similar stuff. Unfortunately, Microsoft switched directions w/exchange. You used to be able to access a lot of exchange functionality with ldap which no longer exists. Now, COM is needed.
Here is a list of the functions

Later we'll add information about managing users and computers.

Getting Active Directory Objects


A tool that can be of a great help is ADSIedit which is in the Windows
2000 support tools on the Windows 2000 server cdrom. It gives you the
raw ldap view of active directory.

def discover():
    Here is a function that helps you determine the active directory ldap strings that you can actually use.
    #get base LDAP string
    #container where users are put it, may be like:

    Finding out about exchange, it is a bit more complicated.
    #typical exchange string
    ex_loc='CN=Microsoft Exchange,CN=Services,CN=Configuration'

    #look for your exchange sites
    for i in win32com.client.GetObject('LDAP://'+ex_loc+','+ldap_loc):
    if!='Active Directory Connections': ex_sites.append(

    #get an exchange server
    ex_admin_grps='CN=Administrative Groups,cn='+ex_site+','+ex_loc+','+ldap_loc

    for i in servers:
    print '\texchange servers',string.join(ex_servers)
    ex_first_store='CN=First Storage Group,CN=InformationStore,CN=%s,CN=Servers,CN=%s,%s'%(ex_servers[-1],admin_grp,ex_admin_grps)


    for i in win32com.client.GetObject('LDAP://'+ex_first_store):
    print '\tExchange stores:',string.join(ex_stores,"',")

Making the object

One such useful function is opends. Ultimately, you need some sort of "LDAP://" string. The opends function builds it for you, by going to a default container. It also automatically authenticates w/a specific account. If you just give the function an id like 'fred', it will wrap it with something like:

This then allows things like:
print 'The last name: ',opends('fred').sn #sn =surname
or to get the groups fred is a member of
print "groups=',opends('fred').memberOf

def opends(loc,server=''):
    '''automatically buoild ldap string and authenticate

    #if there is no ","  then they are not providing a full url
    #so append the standard url for it
    #if there is no '=', assume they want a cn
    if loc.find(',')==-1:
        if loc.find('=')==-1: loc='cn='+loc+','+Ad.ldap_main_loc
        else: loc=loc+','+Ad.ldap_main_loc
    if loc.find('LDAP://')==-1: loc='LDAP://'+loc
    return ldap.OpenDSObject(loc,Ad.ldap_auth,,1)

What does the object have?

Now that you can connection to objects, how do you determine what properties you can get from the object. The following function, ad_dict, converts the avilable properties of the object to a python dictionary, that is then easily inspected. To do so is a little tricky. It first determines what schema the object has and uses the schema, to grab mandatory and optional properties of the object. For any value that evaluates to true, it builds a dictionary. It also uses a conv_time fuction that deals with the way active directory saves time information and converts it to something compatible with python's time library.

You would use ad_dict like a normal python dictionary.
for i in ad_dict('fred').items(): print i
A lot of things convert to automatic python data types. For others, you'll get stuff like COMObject. You need to do specific processing, like the one done for pwdLastSet below, to extract data from them.

def ad_dict(ldapobj,attr_dict={},recurse=0,auth=1,filter=()):
    if ldapobj.find(',')==-1: ldapobj='cn='+ldapobj+','+Ad.ldap_main_loc
    if auth: #setup authenticated connections
        if debug: print 'auth'
        #if debug: print 'authenticated to',ldapobj    
        if debug: print 'connected to',ldapobj    

    if not(filter):
        #check for children
        for i in adobj:
            if debug: print '****at',,str(
            if recurse:
                #get children's attributes too
        if debug: print 'getting schema'
        for i in schema_obj.MandatoryProperties:
            if i =='nTSecurityDescriptor':continue #takes a long time, skip it
            if value: attr_dict[i]=value

        for i in schema_obj.OptionalProperties:
            if i == 'pwdLastSet':
                #convert com object to sec since 1970
            elif value: attr_dict[i]=value
        #only get data that is available
        for item in filter:
                if item == 'pwdLastSet':
                    #convert com object to sec since 1970
                else: attr_dict[item]=getattr(adobj,item)
    return attr_dict

The time property

Time in active directory is stored in a 64 bit integer that keeps track of the number of 100-nanosecond intervals which have passed since January 1, 1601. The 64-bit value uses 2 32 bit parts to store the time. The following is a function that returns the time in the typical format the python time libraries use (seconds since 1970). Here's an example where you'd use it: user=opends('fred') print 'time in seconds',conv_time(user.pwdLastSet.lowpart,user.pwdLastSet.highpart) user.pwdLastSet returns a com object, not a python data type.
def conv_time(l,h):
    #converts 64-bit integer specifying the number of 100-nanosecond
    #intervals which have passed since January 1, 1601.
    #This 64-bit value is split into the
    #two 32 bits  stored in the structure.
    d=116444736000000000L #diference between 1601 and 1970
    #we divide by 10million to convert to seconds
    return (((long(h)<< 32) + long(l))-d)/10000000    

Managing Users

User Basics

Adding Exchange Access

In Conclusion

So far we've only covered the basics of using Active Directory. Stay tuned for more.

Further Info


John Nielsen,
-- Have a great time with programming with python!


   ADSI, Exchange, and Python