Getting Windows System Information with Python

Another script I had to come up with for my employer dealt with getting various bits and pieces of information about each of our user’s physical machines. We wanted to keep track of the CPU speed, hard drive sizes and the amount of RAM they had (among other things) so we would know when it was time to upgrade their computer. Gathering all the pieces from the various places on the internet was a royal pain, so to save you the trouble, I’m going to post what I found. Note that a lot of this code was taken from various recipes on ActiveState or mailing lists. Most of the following can be found almost verbatim in this recipe.

For reasons I don’t understand, we wanted to know which Windows OS the user had on their machine. When I started, we had a mix of Windows 98 and Windows XP Professional, with the latter being over 90%. Anyway, here’s the script we use to figure out what the user is running:

 
def get_registry_value(key, subkey, value):
    import _winreg
    key = getattr(_winreg, key)
    handle = _winreg.OpenKey(key, subkey)
    (value, type) = _winreg.QueryValueEx(handle, value)
    return value

def os_version():
    def get(key):
        return get_registry_value(
            "HKEY_LOCAL_MACHINE", 
            "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
            key)
    os = get("ProductName")
    sp = get("CSDVersion")
    build = get("CurrentBuildNumber")
    return "%s %s (build %s)" % (os, sp, build)

The way to run this code properly is to call the os_version function. This will then call the nested get function twice with two different keys to get the OS and the Service Pack. The get_registry_value is called twice from the nested function and we also call it directly to get the build information. Finally, we use string substitution to put together a string that will represent the PC’s OS version.

This next code snippet is used to find out what processor is in the client PC:

def cpu():
    try:
        cputype = get_registry_value(
            "HKEY_LOCAL_MACHINE", 
            "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
            "ProcessorNameString")
    except:
        import wmi, pythoncom
        pythoncom.CoInitialize() 
        c = wmi.WMI()
        for i in c.Win32_Processor ():
            cputype = i.Name
        pythoncom.CoUninitialize()
    
    if cputype == 'AMD Athlon(tm)':
        c = wmi.WMI()
        for i in c.Win32_Processor ():
            cpuspeed = i.MaxClockSpeed
        cputype = 'AMD Athlon(tm) %.2f Ghz' % (cpuspeed / 1000.0)
    elif cputype == 'AMD Athlon(tm) Processor':
        import wmi
        c = wmi.WMI()
        for i in c.Win32_Processor ():
            cpuspeed = i.MaxClockSpeed
        cputype = 'AMD Athlon(tm) %s' % cpuspeed
    else:
        pass
    return cputype

Note that if you want to follow along, you’ll need to download Tim Golden’s WMI (Windows Management Instrumentation) module which I think is dependent on the PyWin32 package (even if it’s not, you’ll need that package for a later example). Notice that we first try to get the cpu type from the Windows Registry using the function we defined in the previous example. If that fails, then we use the WMI module to get it. The main reason for this is that it’s faster to look in the Registry than it is to make the WMI call. Also note that we have the WMI code wrapped in pythoncom methods. As I recall, the reason those are there is because we run this script in a thread and you need the CoInitialize stuff to keep WMI happy. Otherwise, you’ll end up with some goofy errors or crashes. If you don’t want to use threads, then I think you can remove those lines.

The next piece in this snippet is a conditional that checks what kind of cpu type is returned. If it’s an AMD processor, then we do another WMI call to get the processor’s clock speed and add that to the CPU string. Otherwise, we just return the string as is (which usually means that an Intel chip is installed).

We use VNC to connect to most of our machines, so we like to know the machine’s name. To get that, we run the following:

from platform import node

def compname():
    try:
        return get_registry_value(
            "HKEY_LOCAL_MACHINE",
            'SYSTEM\\ControlSet001\\Control\\ComputerName\\ComputerName',
            'ComputerName')
    except:
        compName = node
        return compName

Once again, we check the Windows Registry first to see if what we want is squirreled away in there. If it fails, than we use Python’s platform module. As you can see, we are using bare except statements, which is bad design on our part. I’ll have to fix that. In my defense, I made most of these in my first year as a Python programmer and didn’t really know any better. Anyway, there’s another way to get this information and that’s with WMI:

c = wmi.WMI()
for i in c.Win32_ComputerSystem():
    compname = i.Name

Now, wasn’t that simple? I really like this stuff, although I don’t know why we have to use a loop to get the information out of WMI. If you know, please drop me a line in the comments below.

The next topic is web browsers. At my work place, we install Mozilla Firefox on all our machines in addition to Internet Explorer. Oddly enough, we have a number of vendors that haven’t upgraded their software or websites to function properly with Internet Explorer 8, so we sometimes run into issues when Microsoft pushes out updates. This introduces us to our next snippet of code:

def firefox_version():
    try:
        version = get_registry_value(
            "HKEY_LOCAL_MACHINE", 
            "SOFTWARE\\Mozilla\\Mozilla Firefox",
            "CurrentVersion")
        version = (u"Mozilla Firefox", version)
    except WindowsError:
        version = None
    return version
    
def iexplore_version():
    try:
        version = get_registry_value(
            "HKEY_LOCAL_MACHINE", 
            "SOFTWARE\\Microsoft\\Internet Explorer",
            "Version")
        version = (u"Internet Explorer", version)
    except WindowsError:
        version = None
    return version
    
def browsers():
    browsers = []
    firefox = firefox_version()
    if firefox:
        browsers.append(firefox)
    iexplore = iexplore_version()
    if iexplore:
        browsers.append(iexplore)
        
    return browsers

The proper way to run this is to just call the browsers function. It will call the other two functions which continue to reuse our get_registry_value that grabs the required information from the Windows Registry. This piece is kind of boring, but easy to use.

Our next script will get the approximate RAM that is installed:

import ctypes

def ram():
    kernel32 = ctypes.windll.kernel32
    c_ulong = ctypes.c_ulong
    class MEMORYSTATUS(ctypes.Structure):
        _fields_ = [
            ('dwLength', c_ulong),
            ('dwMemoryLoad', c_ulong),
            ('dwTotalPhys', c_ulong),
            ('dwAvailPhys', c_ulong),
            ('dwTotalPageFile', c_ulong),
            ('dwAvailPageFile', c_ulong),
            ('dwTotalVirtual', c_ulong),
            ('dwAvailVirtual', c_ulong)
        ]
        
    memoryStatus = MEMORYSTATUS()
    memoryStatus.dwLength = ctypes.sizeof(MEMORYSTATUS)
    kernel32.GlobalMemoryStatus(ctypes.byref(memoryStatus))
    mem = memoryStatus.dwTotalPhys / (1024*1024)
    availRam = memoryStatus.dwAvailPhys / (1024*1024)
    if mem >= 1000:
        mem = mem/1000
        totalRam = str(mem) + ' GB'
    else:
#        mem = mem/1000000
        totalRam = str(mem) + ' MB'
    return (totalRam, availRam)

When I wrote this originally, I used the following WMI method:

c = wmi.WMI()
for i in c.Win32_ComputerSystem():
    mem = int(i.TotalPhysicalMemory)

The conditionals that follow it are mine, but they’re main use is to make the numbers returned more meaningful. I have yet to fully understand how ctypes works, but suffice it to say that the code above is getting the information on a lower level than the WMI method. It’s probably a little bit faster too.

The final piece is getting the hard drive space, which is done with the following:

    
def _disk_c(self):
    drive = unicode(os.getenv("SystemDrive"))
    freeuser = ctypes.c_int64()
    total = ctypes.c_int64()
    free = ctypes.c_int64()
    ctypes.windll.kernel32.GetDiskFreeSpaceExW(drive, 
                                    ctypes.byref(freeuser), 
                                    ctypes.byref(total), 
                                    ctypes.byref(free))
    return freeuser.value

This is another instance where the original recipe writer used ctypes. Since I don’t want to sound like an idiot, I won’t pretend to understand it well enough to explain it. However, Tim Golden has shown a similar recipe for finding the Free Space left on one’s hard drive using WMI that you might find interesting. Just go to his website to check it out.

I hope that all made sense. Let me know if you have any questions about it and I’ll do my best to answer.