Active Directory

Export AD group membership to a file

When I put in a network license server for AutoCAD a couple of years the challenge was not the installation but enabling the management to be done by others. Autodesk use a FLEXlm which is lightweight and uses a text file to define the options. Users who are able to use the software or a feature are listed by username in the text file. However logging on to a server and editing text files is not something you want a Service Desk doing.

As we already use Active Directory for user management why not use it to control who can log on and borrow a license. All you need is an automated way to export members of a group to a (correctly structured) text file. There are lots of ways of doing this but after a couple of years I have settled on the following.

To export the members of a group I have used the function walk method in the active_directory module. This works the same way as os.walk in that it recursively walks through the groups from the given root group. We can then recursively process the list of users returned in the tuple to get the username (or sAMAccountName as it is referred to in AD). As it is plausible the same user may appear more than once I have used a set to store the users; if the same user appears in groups further down the tree they will be silently ignored.

Just writing this out in a file is not enough, the file must be structured in a way the license manager will understand. For this I will use a template file and the sub method from the regular expression (re) module. I’ve created a separate blog post on how this works. This will replace any occurrence of AD{group_name} with the members of that group.

Finally you need to be able to tell the license manager that the options have been changed. This will be dependent on the license manager; FLEXlm allows you to do this by running lmutils from the command prompt. To do this in Python you can use the subprocess module which I’ll cover in the next post.

If you are still awake after readying that, try out the example program. If you are in a domain just change the group name to one that exists and you will see the results. In reality the template file would be a text file read by the script but in the example file I’ve embedded it in a variable. I’ve used three groups; one for people who can run the program, another for a list of users who can loan a license and a group for IT so they can test the software runs.

Network WMI queries

I have already covered searching AD and running a WMI query on remote computers. It is easy to combine the two for a flexible tool that can run the same query on all computers that meet a certain criteria.

To achieve this I wrote the function adwmiquery. It does a lot considering it is only really 4 lines of code.

I originally wanted to see the amount of free space of all the drive on all of the servers. This is still the default behavior if you just run the netquery.py directly. As an added bonus you can run any wmi query against all servers simply by entering the select statement on the command line as so.

python netquery.py Select Name,DriverName,PortName From Win32_Printer

The function takes 3 optional arguments as follows
WMI Query: self explanatory (defaults to drive space)
AD Query: specifies the computers to run the WMI Query on (defaults to servers)
Filter Function: a function which gets the result of the WMI query

The last argument is a function that accepts two arguments, the name of the computer and the result of the WMI query as a list. This can be used to format the output or store the result. You can also use this to add additional filtering, maybe you only want to see drives with less than 10% free space.

As an example the following lists all printers on the server (so used the default AD query) and writes them to a CSV file.

import csv,sys

# if netquery.py is not in you path you need the following line
# sys.path.append(r"C:\Path\To\File")
from netquery import adwmiquery

with open(r"C:\Temp\printers.csv","w") as csvfile:
    out = csv.writer(csvfile)
    out.writerow(['Server','Printer Name','Driver (Make & Model','Port'])

    def prnwrite ( compname , prnlist ):
        "writes the printer details to the CSV file"
        for prn in prnlist:
            out.writerow([compname,prn.Name,prn.DriverName,prn.PortName])

    # run the wmi query on all servers (default) using above fn to output to csv
    adwmiquery(wql = "Select Name,DriverName,PortName From Win32_Printer",
               filterfn = prnwrite)

Creating a Remote Desktop MMC

I was shown a useful custom MMC this week, a list of servers in an RDP container. You could RDP on to any server just by clicking it. Only problem was only had a few servers in as each had to be added manually. A repetitive task is perfect for automation I though. Nothing however is quite that simple.

Creating a new MMC is easy enough using the MMC20.Application COM interface. It is even fairly well documented. You may not have the Remote Desktop snap-in available. You need the Microsoft Remote Server Administration Tools installed and configured on your computer. This article here shows you how to do it on Windows 7.

So I can create the MMC and add Remote Desktops. Getting the list of servers from Active Directory can be done by finding all AD objects with the operatingSystem property containing the word server. However trying enter the server name through the COM interface alluded me. After a couple of hours going around in circles I gave up on this approach.

Instead I used the following fiddle; filling the keyboard buffer with the commands and server names needed and letting the MMC application read this in. This works but you cannot use the computer while it is populating the MMC (the period between the MMC being displayed on the screen and the python script finishing).

The finished script mmc.py adds about 4 servers a second so it shouldn’t take more than a few minutes to complete.

Tim Golden’s active_directory wrapper in Python 3

Tim Golden wrote a useful wrapper for accessing active directory. It is was written for Python 2 but 2to3.py is able to fully translate it. Once downloaded (and updated to 3 if applicable) run python setup.py install to compile and copy the library ready to be used in your programs.

For example to display the user logon name (ID) for all accounts with a name beginning with quackajack (notice the asterisk to do the wildcard search)

import active_directory
adusers = active_directory.search("displayName='quackajack*'", objectCategory='Person', objectClass='User')
for adu in adusers:
    print adu.sAMAccountName

If using sAMAccountName seems confusing don’t worry. I struggle to remember all but the most common ones preferring to look them up when needed by listing all attributes then picking the ones I need. If you want to have a look a full list of user attributes is available here.

Those who have used dsquery before from the command line may be aware of the using -attr * to display all attributes of the object. You can do the same in your python script with the dump function. Hence changing the print line above to print adu.dump() will print out a key,value list of all attributes. Be warned its a long list.