COM

IE as a UI

While I still believe Microsoft’s decision to include a browser in the OS is a bad one it does open up an interesting use case; using IE as the user interface for Python. Like a lot of MS applications, IE can be controlled through a COM interface so with few lines we can start IE and point it at whatever URL we need.

import win32com
ie = win32com.Dispatch("InternetExplorer.Application")
ie.Navigate("https://quackajack.wordpress.com/")
ie.AddressBar = ie.MenuBar = ie.StatusBar = ie.ToolBar = False
ie.visible = True

As wonderful as this website is, it isn’t a user interface. Using a micro web framework you can create a local webserver and then navigate IE to this. Use a template language to make life easier and suddenly you’ve got a flexible UI for a few minutes work.

Advertisements

Calling AutoIt using COM

AutoIt is a scripting language that came about from the requirement to automate Windows GUI actions. It is commonly used to solve installation issues but that is not of much interest to us. What is a little more interesting is some of the functionality of AutoIt is available to external programs through AutoItX.

This comes as a DLL (well two, a 32- and 64-bit version) which you can call a function from or register the DLL to get a COM server. In this article I will use the COM interface which is documented nicely in the accompanying help file.

If you installed the whole AutoIt package it should register the DLL for you. If not change into the directory with the DLL and type in regsvr32.exe AutoItX3.dll (or AutoItX3_x64.dll for a 64-bit OS).

To check whether everything is set up correctly try the following example code. It just moves the currently active window to the top left hand corner of the screen. Gimmicky but fairly short.

import win32com.client
aitcom = win32com.client.Dispatch("AutoItX3.Control")

# get currently active window title and its x & y position
wintitle = aitcom.WinGetTitle("")
startx = aitcom.WinGetPosX(wintitle)
starty = aitcom.WinGetPosY(wintitle)

# tool tip - stays on the screen until cleared
aitcom.ToolTip("Currently active window: "+wintitle,startx+25,starty+25)

# move window to top left hand corner in steps
steps = 10
x = range(startx,0,-startx//steps)
y = range(starty,0,-starty//steps)
for i in range(1,steps):
  aitcom.WinMove(wintitle,"",x[i],y[i])
aitcom.WinMove(wintitle,"",0,0)
aitcom.ToolTip("") # clear tool tip

Look through the documentation for more functionality and experiment.

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.

Log Parser

There are some amazing utilities available which never seem to get much recognition. One of these in my opinion is Microsoft’s Log Parser; you can download v2.2 from here and find an introduction here. It gives you the ability to use SQL-like queries on common log files. While it is presented as a command line program most of the work is done by the accompanying DLL so would it be possible to access it from Python.

Looking in the Log Parser help file it documents a COM interface which gives us our way in. Before you can use the COM interface you must register the DLL with the following command

regsvr32 LogParser.dll

All the examples are in VBScript unsurprisingly but we can use them with a bit of trial and error. The first item of interest is the name of the COM object, MSUtil.LogQuery. Now we know what to pass to the dispatch method. If you have used a library that confirms to the Python database API think of this as the connect method.

Next comes the two ways of working, batch or interactive. Interactive seems the most natural place to start so we need to use the Execute method. This takes the SQL-like query and optionally the input format. We’ll leave it to guess the input format for this test.

The execute method returns a LogRecordSet. Here is where the VBScript example helps. This is an interface to a set a records (obviously) and allows us to find out the columns and get the records. At a stretch you can think of it as cursor. Putting all this together we can query the System event log to find out all the times the computer was switched on (Event ID 12) with the following code

import win32com.client
lp = win32com.client.Dispatch("MSUtil.LogQuery")
sql = "SELECT * FROM System WHERE EventID = 12"
lrs = lp.Execute(sql)
for col in range(rs.getColumnCount()):
    print col, rs.getColumnName(col)

Now we have the result of query in the record set we need to get at the values. We can get each record sequentially using the record set interface. This returns a LogRecord object which basically just allows you to get each value by specifying the index you want. The example above gives us two columns we could use, I’ll use the TimeGenerated one. So lets display them

# continuing on from above
while not lrs.atEnd():
    lr = lrs.getRecord()
    print "Computer started at",lr.getValue(2)
    lrs.moveNext()

With a working COM interface, I wrote the following program to allow the SQL statements to be run from the command line in an interactive environment. The output formatting is poor but it makes a good example program.

Accessing COM objects

One powerful feature of Windows which can get overlooked is the COM method of accessing other programs. This defines the standard application binary interface so that programs can communicate and use each other almost as you would a library. The interface is language neutral and can be used from Python just as well as any other language.

COM objects use the client-server model. The program being accessed, or library in the above analogy, is the server and the program doing the calling is the client. Most scripts are clients which use various COM servers already installed on the computer (or other computers on the network). To get a reference to the COM server you use the Dispatch method.

For example I can use COM to start Microsoft Word (assuming I have Word installed on the computer) and open a document with the following script

import win32com.client
app = win32com.client.Dispatch("Word.Application")
doc = app.Documents.Open("file.doc")
app.Visible = True

Four lines of code and I have a word processor! The strength of COM is also its weakness, it only defines how the programs talk; it doesn’t define the methods or properties, what is called the application programming interface. That is left up to the server.

So how did I know about the Documents.Open method or the Visible property? In an ideal world the API would be fully documented and available. In reality there is little or no documents or it is not available or it is difficult to make sense of. If the COM server has been registered in a standard way you can use a COM browser to gain information.

A basic browser comes with pywin32. A more powerful browser, oleview, can be downloaded from Microsoft as part of the Windows Server 2003 Resource Kit Tools. If working with Microsoft Office COM objects, my preferred browser is actually the Object Browser that comes with VBA. Just press Alt + F11 from Word and then F2. I find this has the cleanest interface.

The last way of making a start with a COM interface is to simply dump everything that can be found out about the API into a file and use that as a starting point. You can then use this information with the interactive prompt to query the COM object and see what it returns. The program do this is makepy.py which can be found in the win32client\client directory. I’ll hopefully cover using makepy in another article.