Lock Down Windows with Python

Almost four years ago, I was tasked with converting a Kixtart script to Python. This particular script was used to lock down Windows XP machines so they could be used as kiosks. Obviously, you don’t need Python to do this. Any programming language that can access the Windows Registry will be able to do this or you could just use Group Policies. But this is a Python blog, so that’s what you’re going to get in this article!

Getting Started

All you need for this tutorial is the standard Python distribution and the PyWin32 package. The modules we’ll be using are _winreg and subprocess, which are built into the standard distribution and win32api and win32con from PyWin32.

We’ll look at the code in two halves. The first half will use the _winreg module:

import subprocess, win32con
from win32api import SetFileAttributes
from _winreg import *

# Note: 1 locks the machine, 0 opens the machine.      
UserPolicySetting = 1

# Connect to the correct Windows Registry key and path, then open the key
reg = ConnectRegistry(None, HKEY_CURRENT_USER)
regpath = r"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer"
key = OpenKey(reg, regpath, 0, KEY_WRITE)

# Edit registry key by adding new values (Lock-down the PC)
SetValueEx(key, "NoRecentDocsMenu", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoRun", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoFavoritesMenu", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoFind", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoSetFolders", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoSetTaskbar", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoSetActiveDesktop", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoWindowsUpdate", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoSMHelp", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoCloseDragDropBands", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoActiveDesktopChanges", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoMovingBands", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoViewContextMenu", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoChangeStartMenu", 0, REG_DWORD, UserPolicySetting)
SetValueEx(key, "NoTrayContextMenu", 0, REG_DWORD, UserPolicySetting)
CloseKey(key)

To start, we import the modules we need. Then we connect to the Windows Registry and open the following key for writing:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer

WARNING: Before continuing, it should be noted that messing with the Windows Registry can cause your PC harm if you do it incorrectly. If you do not know what you’re doing, back up your registry before doing anything or use a virtual machine that you can restore (like VirtualBox or VMWare). The script above will make your PC mostly unusable for anything other than a kiosk.

Anyway, after opening the key, we set over a dozen settings that hide the Run option, Favorites, Recent, and Find from the Start Menu. We also disable right-clicking on the desktop, Windows Update and all changes to various menus (among other things). This is all controlled by our UserPolicySetting variable. If it’s set to one (i.e. boolean True), it will lock down all these values; if it’s zero, then it enables all the settings again. At the end, we call CloseKey to apply the settings.

We should stop here a moment and consider that this is some truly ugly code (as pointed out in the comments below). I wrote this when I had only a month of so of programming experience and I was porting from a Kixtart script that looked as bad or worse than this one. Let’s try to make it cleaner:

from _winreg import *
        
UserPolicySetting = 1

# Connect to the correct Windows Registry key and path, then open the key
reg = ConnectRegistry(None, HKEY_CURRENT_USER)
regpath = r"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer"
key = OpenKey(reg, regpath, 0, KEY_WRITE)

sub_keys = ["NoRecentDocsMenu", "NoRun", "NoFavoritesMenu",
            "NoFind", "NoSetFolders", "NoSetTaskbar",
            "NoSetActiveDesktop", "NoWindowsUpdate",
            "NoSMHelp", "NoCloseDragDropBands",
            "NoActiveDesktopChanges", "NoMovingBands",
            "NoViewContextMenu", "NoChangeStartMenu",
            "NoTrayContextMenu"]

# Edit registry key by adding new values (Lock-down the PC)
for sub_key in sub_keys:
    SetValueEx(key, sub_key, 0, REG_DWORD, UserPolicySetting)

CloseKey(key)

The main difference here is that I took all the sub_key names and put them into a Python list that we can then iterate over, setting each to the right value. Now, if we had multiple differences in these lines of code, such as a mixture of REG_DWORD and REG_SZ, then we would need to do something different. For example, we’d need to iterate over a list of tuples instead or maybe create a function to pass the information to. If you want the most flexibility, you could create a class that does this for you in addition to error handing and the opening and closing of the keys. I’ll leave that as an exercise for the reader though.

The other half of our script will hide various icons and folders:

# Sets the "Hidden" attribute for specified files/folders.
if UserPolicySetting == 1:
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\Set Program Access and Defaults.lnk", win32con.FILE_ATTRIBUTE_HIDDEN)
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\Windows Catalog.lnk", win32con.FILE_ATTRIBUTE_HIDDEN)
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\Open Office Document.lnk", win32con.FILE_ATTRIBUTE_HIDDEN)
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\New Office Document.lnk", win32con.FILE_ATTRIBUTE_HIDDEN)
    # NOTE: Two backslashes are required before the last directory only
    subprocess.Popen('attrib +h "C:\Documents and Settings\All Users\Start Menu\Programs\\vnc"')
    subprocess.Popen('attrib +h "C:\Documents and Settings\All Users\Start Menu\Programs\\Outlook Express"')
    subprocess.Popen('attrib +h "C:\Documents and Settings\All Users\Start Menu\Programs\\Java Web Start"')
    subprocess.Popen('attrib +h "C:\Documents and Settings\All Users\Start Menu\Programs\\Microsoft Office"')
    subprocess.Popen('attrib +h "C:\Documents and Settings\All Users\Start Menu\Programs\\Microsoft SQL Server"')
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\Programs\Adobe Reader 6.0.lnk", win32con.FILE_ATTRIBUTE_HIDDEN)
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\Programs\Windows Media Player.lnk", win32con.FILE_ATTRIBUTE_HIDDEN)
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\Programs\Windows Movie Maker.lnk", win32con.FILE_ATTRIBUTE_HIDDEN)
else:
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\Set Program Access and Defaults.lnk", win32con.FILE_ATTRIBUTE_NORMAL)
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\Windows Catalog.lnk", win32con.FILE_ATTRIBUTE_NORMAL)
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\Open Office Document.lnk", win32con.FILE_ATTRIBUTE_NORMAL)
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\New Office Document.lnk", win32con.FILE_ATTRIBUTE_NORMAL)
    subprocess.Popen('attrib -h "C:\Documents and Settings\All Users\Start Menu\Programs\\vnc"')
    subprocess.Popen('attrib -h "C:\Documents and Settings\All Users\Start Menu\Programs\\Outlook Express"')
    subprocess.Popen('attrib -h "C:\Documents and Settings\All Users\Start Menu\Programs\\Java Web Start"')
    subprocess.Popen('attrib -h "C:\Documents and Settings\All Users\Start Menu\Programs\\Microsoft Office"')
    subprocess.Popen('attrib -h "C:\Documents and Settings\All Users\Start Menu\Programs\\Microsoft SQL Server"')
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\Programs\Adobe Reader 6.0.lnk", win32con.FILE_ATTRIBUTE_NORMAL)
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\Programs\Windows Media Player.lnk", win32con.FILE_ATTRIBUTE_NORMAL)
    SetFileAttributes("C:\Documents and Settings\All Users\Start Menu\Programs\Windows Movie Maker.lnk", win32con.FILE_ATTRIBUTE_NORMAL)

This snippet hides the following

  • Various links to Microsoft Office documents that usually appear in the Start Menu
  • Windows Catalog and the Set Program Access and Defaults shortcuts
  • Various folders in the Start Menu’s Programs sub-menu that we don’t want user’s to access, such as VNC, Office, the Microsoft SQL Connector, etc.
  • Other links in the Programs sub-menu

This is accomplished through a combination of win32api’s SetFileAttributes and win32con’s file attributes for the shortcuts. The folder’s hidden status are toggled using a subprocess call to Windows attrib command.

Unfortunately, this is another example of repetitious code. Let’s take a moment and try to refactor this. It looks like everything that we’re setting is in the Start Menu or its Programs subfolder. We can probably take those paths and put them into a loop like we did with the previous example. We can also use the flag to tell us when to hide or show the folders. We’ll put the whole thing into a function to make it easier to reuse too. Let’s see how that looks:

import os
import subprocess
import win32con
from win32api import SetFileAttributes

def toggleStartItems(flag=True):
    items = ["Set Program Access and Defaults.lnk", "Windows Catalog.lnk",
             "Open Office Document.lnk", "New Office Document.lnk",
             "Programs\Adobe Reader 6.0.lnk", "Programs\Windows Media Player.lnk",
             "Programs\Windows Movie Maker.lnk", "Programs\\vnc",
             "Programs\\Outlook Express", "Programs\\Java Web Start",
             "Programs\\Microsoft Office", "Programs\\Microsoft SQL Server"]
    path = r'C:\Documents and Settings\All Users\Start Menu'
    if flag:
        toggle = "+h"
    else:
        toggle = "-h"
        
    for item in items:
        p = os.path.join(path, item)
        if os.path.isdir(p):
            subprocess.Popen('attrib %s "%s"' % (toggle, p))
        elif flag:
            SetFileAttributes(p, win32con.FILE_ATTRIBUTE_HIDDEN)
        else:
            SetFileAttributes(p, win32con.FILE_ATTRIBUTE_NORMAL)

Now, doesn’t that look much nicer? It also makes adding and removing items easier. This leads for simpler maintenance and less headaches in the future.

Wrapping Up

Now you know how to create a kiosk of your own with Windows and Python. I hope you found this article helpful.

Note: These scripts are tested using Python 2.4+ on Windows XP

Further Reading