Month: September 2012

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)
Advertisements

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.

64bit v 32bit ODBC

Although Python is available in 64- and 32-bit versions, there are not many times when it makes a massive difference. One time it will is when you use ODBC. As we are talking about drivers here, if your app (or in our case Python build) is 32bit you will need to use a 32bit ODBC driver and if you app is 64bit you will need a 64bit driver.

Under a 64bit version of Windows, you will have two versions of the ODBC Administrator tool available. Naturally the 32bit version is used to configure the 32bit ODBC drivers and the 64bit version (the one available from the Control Panel) is for 64bit drivers. Confusingly both tools show 32bit and 64bit user DSN even through they can only configure one, see Microsoft article KB942976 for details. To get to the 32bit version run

%systemdrive%\Windows\SysWoW64\Odbcad32.exe

Still you probably use the ADODB COM interface and these are not 64-/32-bit dependent right? Unfortunately not. This still relies on the the same mechanism as ODBC, so you will need the correct drivers installed.

This caught me out when I tried to open an Access 2007 database and couldn’t understand the error message. Office 2007 is still a 32bit application and hence only installs 32bit ODBC drivers. It was not until 2010 that you had the option of a 64bit version. So what should have been a simple few lines to connect just produced an error.

# Worked under
import win32com.client
dbcom = win32com.client.Dispatch('ADODB.Connection')
connstr = "Driver={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=C:\\PathToDB\\dbfile.accdb;Uid=Admin;Pwd=;"
db = dbcom.Open(connstr)

It wasn’t until I dropped back to a 32bit build of Python and the same lines worked was I finally sure the problem was with the ODBC driver. Running this with a 64bit build produced the following error

Exception occurred. Provider cannot be found. It may not be properly installed

The correct fix is to install the correct drivers, although this might not be as easy as it should be. When I tried installing the 64bit Microsoft Access Database Engine 2010 Redistributable it refused to install because I the 32bit Office installed.

Another work around would be to use the COM interface to the Access application itself, although this will only work on computers where MS Access is installed.

Webserver part 1

Python has a lot of functionality in its libraries and is designed to enable network programming so it is perhaps not surprising to find a web server . Just six lines of code are enough to get a completely useless and standard breaking server running which we can then expand.

# Python 2 only
def mywebserver(environ, start_response):
    "No matter what is requested, return the path"
    start_response('200 OK',[('Content-type', 'text/html')])
    return ['You looked for %s' % (environ.get('PATH_INFO', ''))]

from wsgiref.simple_server import make_server
myserver = make_server('localhost',8081,mywebserver)
myserver.serve_forever()

simple_server really does live up to its name. It will handle the network sockets and convert the HTTP request into a dictionary object but all the logic to power the web server you have to write yourself. In order to get up and running my web server just returns the path of the object requested.

Even something as simple as this is hiding a problem. I’m hoping you all saw the Python 2 only comment; if you think of one of the major changes between 2 and 3 you may guess (or know) the problem. Unless informed otherwise, and you should really include the character set encoding, web clients nowadays will assume the information returned in UTF-8 encoded. Python 2 defaults to 8bit ASCII strings which is how we got away with the above but in Python 3 the strings are in Unicode.

Still we shouldn’t be relying on luck to keep the output correct so we should encode the output correctly using the appropriately named encode method. While we are doing this we should also include the encoding in the Content-type header line. Our fixed mywebserver function now works on both.

def mywebserver(environ, start_response):
    "No matter what is requested, return the path"
start_response('200 OK',[('Content-type', 'text/plain; charset=utf-8')])
response = 'You looked for %s' % (environ.get('PATH_INFO', ''))
return [ response.encode('utf-8') ]

Now lets make our web server do something. One thing we might like to call functions from out webserver. This would give us functionality similar to CGI but without the overhead. As this is only part one, my first webserver program uses a dictionary to map the functions I want to expose to a path.

Each function is passed the environment variables dictionary so it can work out anything it needs. You can have the same function available from different paths which is how I’ve handled / and /index.html. Best of all it is straight forward to add additional functions so start experimenting.