Contents

WinSys - Python tools for the Windows Administrator

We read MSDN so you don’t have to

Introduction

WinSys is a Python package which wraps aspects of the Windows API to make them more Pythonic and usable by Windows administrators directly from the interpreter or as part of a wider set of applications. It targets recent versions of Python and reasonably recent versions of Windows although it’s not yet up to speed on Vista/W7 & x64.

It is unashamedly platform-specific: no hint of a concession towards more Unix-like operating systems. You can read about the design philosophy and decisions in About WinSys. If you want to see some examples, have a look in the Cookbook.

WinSys is developed as an Open Source project and the project home, together with issues list and browseable source code is at:

If you’re interested in helping with the project let me know and I’ll add you to the project members list.

Example

Copy a registry key from HKLM to HKCU and set its permissions so that only the current user has change access while everyone else gets read. Then dump the details of the new top-level key, including its security.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from __future__ import with_statement
from winsys import registry, accounts

new_key = registry.copy(r"HKLM\Software\Python", r"HKLM\Software\WinsysPython")

try:
    with new_key.security() as sec:
        sec.break_inheritance(copy_first=False)
        sec.dacl += [
           ("Users", "R", "ALLOW"),
           (accounts.me(), "F", "ALLOW"),
        ]
        sec.dump()

finally:
    print "***"
    new_key.security().dump()

This example makes use of the registry, accounts and security modules. You can see discussion of this example and more in the Cookbook.

Download

  • easy_install:

    FIXME: NOT YET -- easy_install winsys
    
  • MSI installers & Zipped archives

    Visit http://timgolden.me.uk/python/downloads/winsys/ and then:

    winsys-x.y.z.msi
    

    or:

    unzip winsys-x.y.z.zip
    python setup.py install
    
  • Git master:

    git clone git://github.com/tjguk/winsys.git
    

    and then either add that directory into sys.path (I use a .pth file but whatever works for you) or run setup.py install from there. I don’t do setuptools, so you can’t do setup.py develop unless you tweak the setup.py code.

About WinSys

The Windows API offers the would-be system administrator a plethora of low-level tools to secure files, log changes, investigate problems and do whatever else might be needed to a Windows system. The pywin32 extensions for Windows offer an enormous amount of that functionality to the Python user. And the builtin ctypes library allows you to fill in the gaps.

But it’s all quite low-level and the examples are mostly in C. And all you want to do is take ownership of a bunch of files, move them to an archive area and compress them there. And you want to do that for the home folder of every user who’s left the company. And you want to do that every Sunday morning.

Enter WinSys: the Python Windows Administrator’s Toolkit. A collection of modules with a consistent approach, wrapping the Windows API calls already exposed by pywin32, adding some with ctypes, and giving them all a pythonic feel. There’s nothing here you couldn’t do yourself with 10 minutes and a copy of the pywin32 and SDK docs. But it’s all here already, and with a more Pythonish feel about it.

Design Principles

The following principles have directed the design of the modules and packages wherever possible:

  • Make ad-hoc use possible and easy (and even attractive)
  • Provide sensible defaults for the common cases, always allowing for more complex scenarios
  • Put all constants into one place, grouping them according to their API usage
  • Use the pywin32 functionality where available, supplementing it with ctypes where needed
  • Assume a recent version of Python (tested on 2.6, 2.7, 3.2 and 3.3)
  • Make good use of context managers (with-statements)
  • Have each object able to dump its contents and that of its children cleanly
  • Maintain an object approach, but provide convenient module-level functions
  • Have useful factory functions for classes, robustly accepting string or object parameters

Common Features

Most of the objects in winsys subclass the core._WinSysObject class which offers sensible defaults and defines common functionality such as a dump function. In addition, the following features are common to many of the modules:

Pythonic Naming

This is mildly contentious, but the same naming convention has been used throughout, following the lowercase_with_underscores convention widely adopted in the Python community. The most widespread exception to this is in the constants module, where Windows constants retain their UPPERCASE_WITH_UNDERSCORES names.

Module-level Functions

While a lot of use has been made of Python classes to wrap the function-driven Windows API, a lot of the functionality has been exposed as module-level convenience functions. So, for example, in the fs module, the fs.File class offers a fs.File.copy() method, but the same functionality is exposed at the module level as fs.copy(). That way, you don’t have to instantiate one or more objects simply for the purpose of a single operation.

Factory Functions

Most of the classes have a corresponding factory function (usually with the same name in lower case) which tries to be more accepting in what its parameters are and to convert them to what’s needed by the class’s own __init__ method. So, for example, the Principal class whose initialiser expects a PySID structure has a corresponding principal() function which will take a Sid or a user or group name or None or an existing Principal object.

Dump

Each object derived from core._WinSysObject has a dump method which is intended to display its internal structures, possibly recursively where some of the structures are themselves WinSys objects. This is intended more for ad-hoc use in the interpreter where it’s convenient to see, eg, the security structure which has been loaded from a file.

Iterators & Generators

Where possible and meaningful, lazy iterators have been used, often implemented by generators. This started in the fs module where thousands of files were being queried for information, but the approach has generally been adopted across the package.

Context Managers

Where it makes sense, context managers have been used, either by means of the contextlib contextmanager decorator or by defining an object as its own context manager by means of __enter__ and __exit__ methods. Examples of context-managed objects include the ipc.Mailslot and security.Security objects. Examples of decorated functions include the security.change_privileges() and security.impersonate() functions.

ToDo

Obviously, there’s loads to do. The Windows API is vast; even the amount of it exposed by pywin32 far exceeds my immediate needs and the time at my disposal. The implementation of this package has been driven largely by the very specific needs of our Windows sysadmins in their day-to-day work. My intention is to carry on wrapping Windows functionality in a similar way, but if anyone has particular needs, or can provide functionality to add in, let’s hear about it.

Modules

accounts

View and manage local and user groups. Acts mainly as a support module for

dialogs

Create simple autosized data-entry dialogs with defaults. Create modeless dialogs with callbacks. Create info dialogs for displaying moderate quantities of text. Drag and drop from Explorer into entry fields.

fs

Access the file system using Win32 API and exposing Win32-specific attributes and functionality. List the files and directories in one directory or in an entire tree. Zip directories or files. Query or set security on file system entries, including taking ownership of an entire tree. Compress and encrypt files and directories.

security

Construct and apply Windows security objects. Add allow and deny aces to an acl. Break inheritance on existing security. Impersonate another account. Enable privileges for the duration of a process.

core – Core objects

Introduction

The core module is not intended to be used by user code but establishes base objects for all other WinSys modules.

Exceptions

exception core.x_winsys

Base of all exceptions in the package

exception core.x_access_denied

Common exception corresponding to an access denied error

exception core.x_not_found

Common exception corresponding to a file or path not found error

exception core.x_invalid_handle

Common exception corresponding to a handle not found error

Classes

class core._WinSysObject

Not intended to be used by instantiated by itself, this class forms the basis for most of the classes in the WinSys package. It provides useful defaults for common functionality, including filling in comparison functions (__ne__(), __le__() and so on) so that total ordering can be assumed for all derived classes.

as_string()

By default, the name of the class; usually overriden in derived classes to be some meaningful name. Forms the basis for the __str__() and __unicode__() methods.

dumped()

By default, simply dump the result of as_string() to give a usable if unhelpful default. Intended to be overriden by subclasses, generally to stack attribute values into a list of strings which is then passed to utils.dumped() to fill in the curly brackets.

dump()

Calls the underlying dumped() implementation to provide a convenient representation of the object’s data.

Logging Functions

The core module establishes a simple logger under the name “winsys”, logging to filename winsys.log. The logger’s debug, log, info, warn and error methods are imported directly into the module’s namespace.

constants

Classes

Constants

References

See also

Using the constants module
Cookbook examples of using the constants module

exc – Exceptions

Exceptions

Functions

accounts – Users, Groups, SIDs &c.

Functions

Classes

Constants

Exceptions

References

See also

Using the accounts module
Cookbook examples of using the accounts module

To Do

  • LSA & Credentials functionality

dialogs – Dialog boxes

Functions

Classes

Constants

Exceptions

References

See also

Using the dialogs module
Cookbook examples of using the accounts module

event_logs – Event Logs

Each Windows machine comes equipped with an expandable set of event logs for tracking system- or application-level event information. This module offers a Pythonic interface to event logs, including iterating over them, checking their length and accessing them by means of easy monikers, regardless of what machine they’re on.

Each Windows system comes with predefined Event Logs called (in the English-language versions): Application, System, Security. Certain Microsoft applications create extra ones, but most applications create an event source against the Applications log.

In principal, event sources are key to the way in which event logs work. An event source represents a DLL and a resource file containing messages, possibly in multiple languages, possibly containing placeholders for the calling application to fill in with the name of a file or a user or whatever. It’s linked to one of the event logs (Application, System, etc). When you log an event, you do it via an event source handle.

In reality, it’s perfectly possible to log an event against an event source which doesn’t exist. You’ll get a bit of boilerplate text in the event message saying that something couldn’t be found, but the event will log. This module allows creation of simple event sources, via the EventSource.create() method and at present forces an event source to exist before a record can be logged against it.

Functions

Of these functions, the two you’re most likely to need are: event_log(), which returns an EventLog corresponding to the named log, which you can then iterate over; and log_event(), which logs an event against a named source.

Classes

Exceptions

Constants

References

See also

Event Logs
Documentation on microsoft.com for event logs
Using the event_logs module
Cookbook examples of using the eventlogs module

To Do

  • New Vista / 2008 Event Logs mechanism
  • Some way of incorporating DLLs of messages
  • Using EVENTLOG_SEEK_READ for better random access

fs – Files, Directories, &c.

Introduction

The fs module makes it easy to work with files, directories, drives, volumes and paths within the Windows filesystems. The most common entry-point is to use entry() to return a File or Dir object, although you can use file() or dir() directly. Instances of these classes need not exist on any filesystem – in fact they equate to True or False according to the existence or not of a corresponding filesystem object. But they can be the source or target of all the usual filesystem operations. In common with other modules in this package, functionality is provided at the module level as well as at the class level, so you can, eg, call File.copy() or copy() to copy a file to another location.

An important part of the module is the FilePath class which eases manipulation of filesystem paths and is at the same time a subclass of unicode, so is accepted in system calls where strings are expected.

Functions

Factories

stdlib Extras

Several functions are either convenient or superior replacements to equivalent stdlib functionality.

Helpers

Additional Filesystem Operations

Classes

The Drive class

The Volume class

The FilePath class

The Entry class

The File class

The Dir class

Constants

fs Constants

Exceptions

References

See also

Using the fs module
Cookbook examples of using the fs module

ipc – Interprocess Communication

Introduction

The IPC module offers an interface to the various forms of interprocess communication available under windows: mailslots, events, named pipes, mutexes, sempahores and waitable timers. At least, that’s the plan. At the time of writing, only mailslots and events are in there. But the rest are definitely on the way.

Functions

Factories

Helpers

Classes

The Mailslot class

The Event class

The Mutex class

The Pipe classes

Constants

Exceptions

References

See also

Synchronisation
Documentation on microsoft.com for synchronisation objects
Using the ipc module
Cookbook examples of using the ipc module

To Do

  • Named Pipes
  • Waitable Timers
  • Waits

registry – Registry

Functions

Classes

Constants

Exceptions

References

See also

Using the registry module
Cookbook examples of using the registry module

To Do

  • New Vista / 2008 Registry funcs (transactions etc.)
  • Export-a-like functionality to create a human-readable export function

security

Functions

Classes

Constants

Exceptions

References

See also

Using the security module
Cookbook examples of using the security module

Miscellaneous

Introduction

Various of the modules offered by winsys don’t fit neatly into any one area. These include the logging_handlers, based on the Mailslot class.

Sections

logging_handlers – Additional Logging Handlers

Introduction

Python’s builtin logging module offers the concept of handlers each of which takes the logged message and passes to an output channel. There are handlers provided for screen, files, NT Event Logs and so on. This module offers two handlers to output to a named Windows mailslot. It makes use of the ipc.Mailslot class and can write to a one-time mailslot or to a mailslot which is running permanently.

The sort of situation where this might be useful is where you have a number of system routines running on and off. If they all log to the same mailslot, say on an administrator’s desktop, then he can see the progress of all in one place. Better still, if they log to all the mailslots of the same name in the domain, then everyone can have a logging screen running on their desktop which they can start up and close down at will to see the current progress.

Classes
class logging_handlers.MailslotHandler(mailslot_name : string)

Set up a handler going to the named mailslot. Note that the usual possibilities obtain for mailslot names: an unqualified name must exist on the local machine; a computer or domain-qualified name will broadcast to that machine or that domain without checking existence; a * will just broadcast, full-stop.

The close function sends a None to the receiving mailslot, intended to act as a prompt to shut down. (Although that’s obviously in the hands of the receiver).

class logging_handlers.PermanentMailslotHandler(mailslot_name : string)

Subclass the MailslotHandler and override the close function so that it doesn’t send the shutdown token to the receiving mailslot(s).

environment – Environment Block

Functions
Classes
References

See also

Using the environment module
Cookbook examples of using the environment module

Cookbook

Using the accounts module

The examples here all refer to the accounts module.

Get the logged-on user’s account

Get the account for the logged-on user.

from winsys import accounts

print accounts.me()
Discussion

This is so common a need that there is a module-level convenience function for the very purpose.

Act as another user

Temporarily switch the current process to use another user’s identity. Create a file as that other user and then check that the file was created with that owner. (Assumes the existence of a “python” user).

import uuid
from winsys import accounts, security, fs

username = filename = str(uuid.uuid1())[:8]
user = accounts.User.create(username, "password")
f = fs.file(filename)
assert(not f)

assert accounts.me() != user
try:
    with user:
        assert accounts.me() == user
        f.touch()
        assert f.security().owner == user
        f.delete()
finally:
    user.delete()
Discussion

There can be a lot of subtlety involved in switching users. The winsys code at present takes a very straightforward approach which should work for several cases.

Since we’re using the Principal class itself as a context manager, no password can be passed in, so the program automatically pops up a standard Windows password UI with the username filled in. To pass a password in automatically, use the Principal.impersonate() method which accepts a password and a logon type.

Note

This hasn’t been tested at all on Vista or W7 where the security model is much tighter, particularly with respect to raising privs.

Create a local user

Create a new local user with minimum permissions and a password.

from winsys import accounts

python = accounts.User.create("python", "Pa55w0rd")
print python
Discussion

The new user will have no logon script, no home directory and will not be in the Users group. (This last means that it will not show up on the quick-logon screen in XP etc.). The same approach can be used for creating a local group, except that the Group.create() method should be called on the Group class instead.

Delete a local user

Delete an existing local user

from winsys import accounts

accounts.principal("python").delete()
Discussion

The same approach can be used for deleting a local group.

Using the asyncio module

Using the constants module

Create constants set for file attributes

File attributes are represented by a bitmask set of constants, all starting with FILE_ATTRIBUTES. Many of these are defined in the win32file module. Some were added more recently and have be added specifically.

import win32file
from winsys import constants

#
# Pull all file attributes known to win32file
#
FILE_ATTRIBUTE = constants.Constants.from_pattern(u"FILE_ATTRIBUTE_*", namespace=win32file)
FILE_ATTRIBUTE.dump()

#
# Add extra file attributes added since win32file
#
extras = dict(
    SPARSE_FILE = 0x00000200,
    REPARSE_POINT = 0x00000400
)
FILE_ATTRIBUTE.update(extras)
FILE_ATTRIBUTE.dump()
Discussion

The most common way of initialising a Constants structure is by pulling in all constants following a naming pattern from one of the pywin32 modules. In this case, this leaves some useful constants undefined so we add them in by creating a dictionary with their values, and then passing that to the original object’s Constants.update() function which accepts any dictionary-like object.

Like all winsys objects, the constants objects have a core._WinSysObject.dump() method which provides a readable display of its values.

Show file access flags

File access is a wide bitmask of flags in each ACE in the filesystem DACL. It represents a set of access flags, named in the fs.FILE_ACCESS constants. Look at each ACE in turn and indicate the trustee and the set of operations permtted.

import sys
from winsys import fs, security

for dace in security.security(sys.executable).dacl:
    print dace.trustee, fs.FILE_ACCESS.names_from_value(dace.access)
Discussion

The Constants.names_from_value() method returns the list of constant names corresponding to the single value by comparing their bitmasks. No check is made to see whether any bits are left “unclaimed” nor whether any flags overlap partly or wholly (ie are synonyms).

Using the dialogs module

The examples here all refer to the dialogs module.

Ask the user for input

This is the simplest kind of dialog box: a one-line edit control with [Ok] and [Cancel].

from winsys import dialogs

name = dialogs.dialog("What is your name?", ("Name", ""))

Prompt for a filename to open

Ask the user for a filename, check that the file exists, and then use os.startfile to open the file. Allow the user to type the filename in, drag-and-drop a file from Explorer, or select from a dialog box.

import os
from winsys import dialogs, fs

DEFAULT = "temp.csv"
filename, = dialogs.dialog(
    "Open a filename",
   ("Filename", DEFAULT, dialogs.get_filename)
)

if not fs.file(filename):
    raise RuntimeError("%s does not exist" % filename)
else:
    os.startfile(filename)
Discussion

All edit controls accept files drag/dropped from Explorer. In addition, by specifying a third field for the “Filename” input, we can call out to another dialog box to select the file. Here we use the one supplied by winsys, but the only requirement for the callback is that it return a string or None to indicate no change.

A fs.Entry object evaluates to True if the file it represents exists on the file system.

Using the environment module

The examples here all refer to the environment module.

Dump the current process environment

from winsys import environment

environment.Process().dump()

Remove all Python installations from the PATH

Prior, say, to installing a new version Python, make sure that no existing Python installations remain on the PATH. PATH is made up of the system PATH env var plus the user PATH.

from winsys import registry, environment

def munge_path(env, python_paths):
    #env['PATH'] =
    print ";".join(
        p for p in env['PATH'].split(";") if not any(
            p.lower().startswith(py) for py in python_paths
        )
    )

py = registry.registry(r"hklm\software\python\pythoncore")
py_paths = set(version.InstallPath.get_value("").rstrip("\\").lower() for version in py)
py = registry.registry(r"hkcu\software\python\pythoncore")
py_paths.update(version.InstallPath.get_value("").rstrip("\\").lower() for version in py)

munge_path(environment.user(), py_paths)
munge_path(environment.system(), py_paths)

The Python installations are listed in the registry under the SoftwarePython key in HKLM and HKCU. That key has one subkey for each version installed and the subkey holds its installation directory in the default value of the InstallPath key.

We collect the unique list of installation directories and filter the user and system PATH env vars in turn by including only those paths which are not part of a Python installation.

We have to allow for case differences between the PATH and the installation directories, and for the fact that some of the install dirs have a trailing backslash while some don’t.

Note

For the purposes of not endangering your PATH, the critical line which actually updates the PATH is commented out and the would-be result is shown instead.

Using the event_logs module

The examples here all refer to the event_logs module.

Log an event against an event source

Log an information event against a WinSys event source, creating it if does not already exist. Once the event is written, remove the event source.

from winsys import event_logs

try:
    source = event_logs.event_source("WinSys")
except event_logs.x_not_found:
    source = event_logs.EventSource.create("WinSys")

try:
    source.log_event(type="information", message="Testing")
finally:
    source.delete()
Discussion

Events are always logged against a specific event source. We can create a simple event source which will render single-string messages. For the purposes of demonstration, we attempt to pick up the event source if it already exists and to create it if it does not. We use the class-level convenience function to log the event against the source, which simply hands off to the module-level function of the same name.

An event source can be deleted at any time: its records simply become “orphans” in that the system will no longer be able to format their messages since it won’t know the location of the DLL which contains the corresponding strings.

Note

The logging library in Python’s stdlib already has an NTEventLog handler, so if all you want to do is to log events in a standard manner, then it’s probably best to use that.

List the 10 most recent records in each event log

Go through each event log on the system and list the 10 most recent events.

from winsys import event_logs

for log in event_logs.event_logs():
    print log
    for n_event, event in enumerate(reversed(log)):
        if n_event == 10: break
        print event.time_generated, event

    print
Discussion

By default, iterating over an event log starts oldest first. To pick up the most recent records, we reverse the iterator. This makes use of the EventLog.__reversed__() magic method which starts a reverse iterator. Without that, the implementation would fall back to a __getitem__-based sequence solution, asking for [-1] and then [-2] and so on. Since our EventLog.__getitem__() implementation actually iterates anyway, this would be a less-than-optimal solution.

The standard __str__ for an event log record shows the record number, the source and the event type. We add here the record’s timestamp.

Write to CSV selected events from a remote event log

Go through the a remote System event log and write to CSV the record number, id and message for records matching a particular event source and type.

import os
import csv
from winsys import event_logs, dialogs

def remote_events(computer, event_source, event_type_id):
    for event in event_logs.event_log(r"\\%s\system" % computer):
        if event.source_name.upper() == event_source and event.event_type == event_type_id:
            yield event

computer, event_source,(event_type_name, event_type_id) = dialogs.dialog(
    "Computer",
    ("Computer Name", "."),
    ("Event Source", "DHCP"),
    ("Event Type", event_logs.EVENTLOG_TYPE.items())
)

csv.writer(open("dhcp.csv", "wb")).writerows(
   (event.record_number, event.time_generated, event.event_id, event.message) for
        event in remote_events(computer, event_source.upper(), event_type_id)
)

os.startfile("dhcp.csv")
Discussion

We use the winsys dialogs.dialog module convenience function to request the name of the remote computer and the source and event type from the user. It would be possible to pick out the list of event sources for a computer (using the event_sources() function), but there’s no mechanism within the dialogs module for updating one field on the basis of another, so we could only pre-populate with the valid list for one particular computer.

The module-level convenience function event_log() returns an instance of an EventLog matching the computer and event log name in the UNC-style moniker. The standard iterator goes from the oldest first forwards. If we wanted to see the records in reverse chronological order, we’d use the reversed builtin to invoke the class’s EventLog.__reversed__() method.

Using the fs module

Take ownership of a file

Take ownership of a file to which you have no other access.

from __future__ import with_statement
from winsys import security, fs

f = fs.file("c:/temp/tim.txt")
with security.change_privileges([security.PRIVILEGE.TAKE_OWNERSHIP]):
    f.take_ownership()

assert f.security("O").owner == security.me()

The Entry.take_ownership() method assumes that you have no existing access to the object, so does not attempt to read its security at all since this would probably fail. If you do not even have permission to set the owner entry in the security descriptor you need to enable the SeTakeOwnership privilege in your token. Best practice is to enable privileges for only as long as you need them, so the security.changed_privileges() is a context manager which reverses the privilege changes once it exits.

Find the sizes of top-level directories

For a given root directory, find the total size taken by all the files in each of its top-level subdirectories, and display them in descending order of size.

import operator
from winsys import dialogs, fs, utils

[root] = dialogs.dialog(
    "Find top-level sizes",
    ("Start from", "", dialogs.get_folder)
)

sizes = dict(
   (d, sum(f.size for f in d.flat()))
        for d in fs.dir(root).dirs()
)
for d, size in sorted(sizes.items(), key=operator.itemgetter(1), reverse=True):
    print d.name, "=>", utils.size_as_mb(size)

The dialogs.dialog() function sets up a simple dialog box requesting the name of the root directory (offering a push-button for a standard selection dialog). Then the Dir.dirs() method iterates over all subdirectories of its directory, and Dir.flat() iterates over all the files underneath a directory from which we can fetch their (compressed) size and sum them all up.

The rest is mostly standard Python gimmickry with sorting dictionaries etc. utils.size_as_mb() provides a more human-readable version of a number of bytes.

Using the ipc module

Writing to a network logger

Sending log entries to a logging mailslot which may or may not be running without needing to know which server is hosting it.

import threading

from winsys import ipc

def logger():
    with ipc.mailslot("logger") as l:
        while True:
            word = l.get()
            if word == "STOP":
                break
            else:
                print word

threading.Thread(target=logger).start()

with ipc.mailslot(r"\\*\mailslot\logger") as logging_mailslot:
    for word in "the quick brown fox".split():
        logging_mailslot.put(word)
    logging_mailslot.put("STOP")

This is where mailslots really score over named pipes, sockets, etc. You can send messages to a named mailslot without knowing where it is or if it’s even running. Furthermore, there may be several active mailslots with the same name, all of which will receive the data sent.

The obvious application is for centralised or distributed logging but it could also be used as a form of pub-sub mechanism, at least for small pieces of data. The maximum message size across the network is about 400 bytes.

The mailslot() function is the quickest way to get hold of a mailslot for reading or writing. It provides useful defaults especially for local mailslots where the full path can be determined. Since the underlying Mailslot can be context-managed, I’ve enclosed the activity of each mailslot in a “with” block.

For simplicity I’ve run a reading mailslot inside a thread. For a clearer demonstration you could run the same code in a separate process, ideally on a separate machine within the same domain.

Read and write a local mailslot

Read and write to local named mailslot, interleaving reads and writes.

import random

from winsys import ipc

reader = ipc.mailslot("reader")
writer = ipc.mailslot("reader")

message = list(reversed("the quick brown fox jumps over the lazy dog".split()))

while True:
    try:
        data = reader.get(block=False, timeout_ms=1000)
        if data == "STOP":
            break
    except ipc.x_mailslot_empty:
        pass
    else:
        print data

    if random.randint(1, 100) > 95:
        try:
            writer.put(message.pop())
        except IndexError:
            writer.put("STOP")

Although the most likely application of mailslots is from separate threads or processes on separate machines even, it’s quite possible for the same thread to read from and write to the same mailslot. The caveat is that one object must be obtained for reading and another for writing. They are linked by passing the same name to mailslot().

We make use of the fact that the Mailslot objects are mimicking Python’s Queue objects. This includes a Mailslot.get() method which has a timeout option. Using this, we can check for active messages and pass on by if none is present. We then randomly put one word from our message into the mailslot from the writer’s end and go round again. Finally, we send our STOP sentinel so that both ends release their respective mailslot handles.

Mailslot as an iterable

Iterate over the contents of a mailslot

from winsys import ipc

Using the registry module

The examples here all refer to the registry module.

Copy one registry key to another

Copy an existing registry key to a new one and set the new key’s security so that only the current user has change rights and all other users have read-only. Finally, display the details of the new top-level key, including its security.

from __future__ import with_statement
from winsys import registry, accounts

new_key = registry.copy(r"HKLM\Software\Python", r"HKLM\Software\WinsysPython")

try:
    with new_key.security() as sec:
        sec.break_inheritance(copy_first=False)
        sec.dacl += [
           ("Users", "R", "ALLOW"),
           (accounts.me(), "F", "ALLOW"),
        ]
        sec.dump()

finally:
    print "***"
    new_key.security().dump()
Discussion

The functions in the registry module hand off any references to a registry key to the registry() function which is as accepting as possible. Here, we’re referring to the local machine (there’s no server-style \\ prefix in either moniker) and using the HKLM/HKCU shortcut styles. In fact the code would work equally well if a remote machine were to specified on either side, assuming that the necessary permissions were in place.

The Registry.security() method acts as a context manager, allowing a series of changes to the registry key’s security descriptor. Here we are breaking the inheritance which the new key has inherited automatically, without copying the existing permissions over first. Then we add a new DACL with just two permissions: allowing the logged-on user full control; and allowing all authenticated users read access. The 3-tuples are passed to the security.ace() function.

Finally, to demonstrate that the security has been applied, we call the registry key’s Registry.dump() method which produces useful information about the key and its security in a readable format.

Find a string in the registry

Search the registry under a particular root and find a value which contains the searched-for string. Output the registry key, the value name and the value itself.

from winsys import dialogs, registry

root, term = dialogs.dialog(
    "Search the Registry",
    ("Root", registry.REGISTRY_HIVE.keys()),
    ("Term", "")
)

root = registry.registry(root)
term = term.lower()
for key, subkeys, values in root.walk(ignore_access_errors=True):
    for name, value in values:
        if term in str(value).lower():
            print key.moniker.encode("utf8")
            print name.encode("utf8") or "(Default)"
            print unicode(value).encode("utf8")
            print
Discussion

We use dialogs.dialog() to select the root key and the string to search for. We then walk the registry from that key downwards, skipping over any keys or values to which we do not have access. When we find a value which matches our search term, we output the key name, value label and the value which matched.

Using the security module

The examples here all refer to the security module.

Dump the security on a specific file

List details of the security which applies to a specific file. This is most interesting in an ad-hoc situation when you’re trying to assess things using the interpreter.

from winsys import dialogs, security

[filename] = dialogs.dialog("Choose file",("Filename", ""))
security.security(filename).dump()

ChangeLog

Version 0.5

  • Did stuff
  • Did more stuff