16 September 2011

Python WMI, services and UAC

In this day and age, security-conscious Windows users "enjoy" the protection of User Access Control (UAC), a feature introduced with Vista and now a stalwart of the Microsoft world in Windows Server 2008 and Windows 7. This is why you get prompted to "allow this program to make changes to your computer" every time you install a program, or if you run a lot of "legacy" Win32 applications.

Under UAC, even when you are a local administrator, the programs you launch will not, by default, enjoy all system privileges you would expect. Until you get prompted by UAC to allow for system access, processes will run under a basic user profile.

This is a problem when you try to interoperate with Windows from Python. As soon as you try to manipulate system objects, for example to start/stop services, you'll get a lot of "Access Denied" return codes.

One solution, obviously, is to use the "Run as Administrator" option to launch python.exe, so that you get prompted by UAC and the resulting process will effectively run under elevated privileges. Note that it's not enough to launch Command Prompt (cmd.exe) with "run as an administrator" and to then call python.exe from there. You must explicitly hunt down python.exe in explorer and right-click on it, nothing less will do.

This is fine and dandy for the basic REPL interpreter, but what if you need to do it from a script? How do you elevate the process from inside a running python.exe?

The answer is to use ShellExecute with the "runas" verb, like this:

import win32api
win32api.ShellExecute( 0, # parent window
    "runas", # need this to force UAC to act
    "C:\\python27\\python.exe", 
    "c:\\path\\to\\script.py", 
    "C:\\python27", # base dir
    1 ) # window visibility - 1: visible, 0: background
UAC will then prompt the user and elevate the process. For an example script that you can only run under elevated privileges, check this:
import wmi
c = wmi.WMI()
serviceToStart = 'aspnet_state' # example
for service in c.Win32_Service(Name=serviceToStart): 
    service.StartService()

Note: I haven't tested this with py2exe yet, but at worst you should be able to right-click on the py2exe-generated binary and select to Run as Administrator anyway.