Source code for registry

# -*- coding: utf-8 -*-
"""
The registry consists of a series of roots from each of which descends
a tree of keys and values. Each key has an anonymous (default) value
and optionally a set of named values, each of which has a particular
type (string, number etc.).

For convenience in accessing registry keys and values, the module
implements the idea of a registry moniker which has the form:
:const:`[\\\\computer\\]HKEY[\\subkey path][:[value]]`. For example::

    from winsys import registry

    #
    # The value of the Version item in the HKLM\Software\Python key on SVR01
    #
    registry.registry(r"\\SVR01\HKEY_LOCAL_MACHINE\Software\Python:Version")

    #
    # The Control Panel\Desktop key in HKEY_CURRENT_USER on the current machine
    #
    registry.registry(r"HKCU\Control Panel\Desktop")

The key function here is :func:`registry` which is a factory
returning a :class:`Registry` object which contains
most of the useful functionality in the module. However, the same
functionality is replicated at module level in many cases for
convenience.
"""
from __future__ import unicode_literals

import os, sys
import operator
import re

import winerror
import win32api
import win32con
import pywintypes

from winsys._compat import *
from winsys import constants, core, exc, security, utils

class RegistryConstants(constants.Constants):

    def name_from_value(self, value, default=core.UNSET, patterns=["*"]):
        """Find the longest name in the set of constants (optionally qualified by pattern)
        which matches value.
        """
        try:
            return max(
                (name for name in self.names(patterns) if self[name] == value),
                key=len
            )
        except ValueError:
            if default is core.UNSET:
                raise KeyError("No constant matching name %s and value %s" % (patterns, value))
            else:
                return default

REGISTRY_HIVE = RegistryConstants.from_list([
    "HKEY_CLASSES_ROOT",
    "HKEY_CURRENT_CONFIG",
    "HKEY_CURRENT_USER",
    "HKEY_DYN_DATA",
    "HKEY_LOCAL_MACHINE",
    "HKEY_PERFORMANCE_DATA",
    "HKEY_PERFORMANCE_NLSTEXT",
    "HKEY_PERFORMANCE_TEXT",
    "HKEY_USERS",
], namespace=win32con)
REGISTRY_HIVE.update(dict(
    HKLM = REGISTRY_HIVE.HKEY_LOCAL_MACHINE,
    HKCU = REGISTRY_HIVE.HKEY_CURRENT_USER,
    HKCR = REGISTRY_HIVE.HKEY_CLASSES_ROOT,
    HKU = REGISTRY_HIVE.HKEY_USERS
))
REGISTRY_HIVE.doc("Registry hives (root keys) including common aliases (HKCU, HKLM, etc.)")
REGISTRY_ACCESS = constants.Constants.from_list([
    "KEY_ALL_ACCESS",
    "KEY_CREATE_LINK",
    "KEY_CREATE_SUB_KEY",
    "KEY_ENUMERATE_SUB_KEYS",
    "KEY_EXECUTE",
    "KEY_NOTIFY",
    "KEY_QUERY_VALUE",
    "KEY_READ",
    "KEY_SET_VALUE",
    "KEY_WOW64_32KEY",
    "KEY_WOW64_64KEY",
    "KEY_WRITE",
], namespace=win32con)
REGISTRY_ACCESS.doc("Registry-specific access rights")
REGISTRY_VALUE_TYPE = constants.Constants.from_list([
    "REG_BINARY",
    "REG_DWORD",
    "REG_DWORD_LITTLE_ENDIAN",
    "REG_DWORD_BIG_ENDIAN",
    "REG_EXPAND_SZ",
    "REG_LINK",
    "REG_MULTI_SZ",
    "REG_NONE",
    "REG_QWORD",
    "REG_QWORD_LITTLE_ENDIAN",
    "REG_SZ",
], namespace=win32con)
REGISTRY_VALUE_TYPE.doc("Registry value data types")

PyHANDLE = pywintypes.HANDLEType

[docs]class x_registry(exc.x_winsys): "Base exception for all registry exceptions"
[docs]class x_moniker(x_registry): "Base exception for problems with monikers"
[docs]class x_moniker_ill_formed(x_moniker): "Raised when a moniker does not match the correct format"
[docs]class x_moniker_no_root(x_moniker): "Raised when a moniker has no Hive in the first or second position"
sep = "\\" WINERROR_MAP = { winerror.ERROR_PATH_NOT_FOUND : exc.x_not_found, winerror.ERROR_FILE_NOT_FOUND : exc.x_not_found, winerror.ERROR_NO_MORE_ITEMS : StopIteration, winerror.ERROR_ACCESS_DENIED : exc.x_access_denied, winerror.ERROR_INVALID_HANDLE : exc.x_invalid_handle, } wrapped = exc.wrapper(WINERROR_MAP, x_registry) def _parse_moniker(moniker, accept_value=True): r"""Take a registry moniker and return the computer, root key, subkey path and value label. NB: neither the computer nor the registry key need exist; they need simply to be of the right format. The slashes must be backslashes (since registry key names can contain forward slashes). accept_value is mostly used internally to indicate that a key search is going on, where a colon is to be considered part of the key name; if it is True, then the colon is to be considered a value separator. The moniker must be of the form: [\\computer\]HKEY[\subkey path][:value] Valid monikers are: \\SVR01\HKEY_LOCAL_MACHINE\Software\Python:Version -> "SVR01", 0x80000002, "Software\Python", "Version" HKEY_CURRENT_USER\Software -> "", 0x80000001, "Software", None HKEY_CURRENT_USER\Software\Python: -> "", 0x80000001, "Software\Python", "" """ if accept_value: moniker_parser = re.compile(r"(?:\\\\([^\\]+)\\)?([^:]+)(:?)(.*)", re.UNICODE) else: moniker_parser = re.compile(r"(?:\\\\([^\\]+)\\)?(.*)", re.UNICODE) matcher = moniker_parser.match(moniker) if not matcher: raise x_moniker_ill_formed(errctx="_parse_moniker", errmsg="Ill-formed moniker: %s" % moniker) if accept_value: computer, keypath, colon, value = matcher.groups() else: computer, keypath = matcher.groups() colon = value = None keys = keypath.split(sep) root = path = None key0 = keys.pop(0) try: root = REGISTRY_HIVE[key0.upper()] except KeyError: root = None if root is None and keys: key1 = keys.pop(0) try: root = REGISTRY_HIVE[key1.upper()] except KeyError: root = None if root is None: raise x_moniker_no_root(errctx="_parse_moniker", errmsg="A registry hive must be the first or second segment in moniker") path = sep.join(keys) # # If a value is indicated (by a colon) but none is supplied, # use "" to indicate that the default value is requested. # Otherwise use None to indicate that no value is requested # if value == "" and not colon: value = None return computer, root, path, value
[docs]def create_moniker(computer, root, path, value=None): """Return a valid registry moniker from component parts. Computer is optional but root and path must be specified. :param computer: (optional) name of a remote computer or "." or :const:`None` :param root: name or value from REGISTRY_HIVE :param path: backslash-separated registry path :param value: name of a value on that path. An empty string refers to the default value. :returns: a valid moniker string """ if root not in REGISTRY_HIVE: root = REGISTRY_HIVE.name_from_value(root) fullpath = sep.join([root] + path.split(sep)) if computer: moniker = r"\\%s\%s" % (computer, fullpath) else: moniker = fullpath if value: return moniker + ":" + value else: return moniker
[docs]class Registry(core._WinSysObject): """ Represent a registry key (including one of the roots) giving access to its subkeys and values as well as its security and walking its subtrees. The key is True if it exists, False otherwise. When access rights are supplied to any function, they can either be an integer representing a bitmask, or one or more letters corresponding to the :attr:`ACCESS` mapping. The default access is "F" indicating Full Control. Note that addition and attribute / item getting and setting are overridden for convenience in accessing subkeys and values as follows: * Adding a string to a :class:`Registry` object will result in a new object representing the subkey of that name:: from winsys import registry software = registry.registry(r"HKLM\Software") software + r"Python\Pythoncore" == registry.registry (r"HKLM\Software\Python\Pythoncore") * Getting an attribute of a :class:`Registry` object will return a value if one exists, a key otherwise (or will raise AttributeError). For a key name this is the same as calling :meth:`get_key` or adding the key name as above. For a value name, this is the same as calling :meth:`get_value` * Setting an attribute always writes a value, even if a key of that name already exists. This is equivalent to calling :meth:`set_value`. * Deleting an attribute will attempts to delete a value if one exists, otherwise it will delete a key of that name. This is equivalent to calling :meth:`del_value` or :meth:`delete`. :: from winsys import registry python = registry.registry(r"HKLM\Software\Python") python.testing = 4 python.get_value("testing") == 4 python.testing == python['testing'] del python.testing * To create a key, call :meth:`create` or the module-level :func:`create` function. * To delete a key, call its :meth:`delete` method or the module level :func:`delete` function. :: from winsys import registry winsys = registry.registry(r"hklm\software\winsys").create() winsys.test_value = 4 registry.registry(r"hklm\software\winsys:test_value") == 4 registry.delete(winsys) # or winsys.delete() # or registry.registry(r"hklm\software").delete("winsys") """ ACCESS = { "Q" : REGISTRY_ACCESS.KEY_QUERY_VALUE, "D" : constants.ACCESS.DELETE, "R" : REGISTRY_ACCESS.KEY_READ, "W" : REGISTRY_ACCESS.KEY_WRITE, "C" : REGISTRY_ACCESS.KEY_READ | REGISTRY_ACCESS.KEY_WRITE, "F" : REGISTRY_ACCESS.KEY_ALL_ACCESS, "S" : constants.ACCESS.READ_CONTROL | constants.ACCESS.WRITE_DAC, } """Mapping between characters and access rights: * Q - Query * D - Delete * R - Read * W - Write * C - Change (R|W) * F - Full Control * S - Security """ DEFAULT_ACCESS = "R" def __init__(self, moniker, access=DEFAULT_ACCESS): core._WinSysObject.__init__(self) utils._set(self, "hKey", None) utils._set(self, "moniker", unicode(moniker)) utils._set(self, "id", _parse_moniker(self.moniker.lower(), accept_value=False)) utils._set(self, "access", self._access(access)) utils._set(self, "name", moniker.split(sep)[-1] if moniker else "") @classmethod def _access(cls, access): """Conversion function which returns an integer representing a security access bit pattern. Uses the class's ACCESS map to translate letters to integers. """ if access is None: return None try: return int(access) except ValueError: return reduce(operator.or_, (cls.ACCESS[a] for a in access.upper()), 0) def __eq__(self, other): return self.id == other.id and self.access == other.access def __hash__(self): return hash((self.id, self.access))
[docs] def __add__(self, path): """Allow a key to be added to an existing moniker. :param path: can be a simple name or a backslash-separated relative path :returns: a :class:`Registry` object for the new path """ if path: return self.__class__(self.moniker + sep + path) else: # # path is unlikely to be empty for a specific call, # but this functionality is used by the create and # delete methods which refer to the existing class # by default. # return self
[docs] def pyobject(self): """Lazily return an internal registry key handle according to the instance's access requirements. :raises: :exc:`x_not_found` if the registry path the key refers to does not exist """ if self.hKey is None: hKey, moniker, _ = self._from_string(self.moniker, access=self.access, accept_value=False) utils._set(self, "hKey", hKey) if self.hKey is None: raise exc.x_not_found(errctx="Registry.pyobject", errmsg="Registry path %s not found" % moniker) return self.hKey
def as_string(self): return self.moniker
[docs] def security(self, options=security.Security.DEFAULT_OPTIONS): """For a security request, hand off to the :meth:`~security.Security.from_object` method of the :class:`security.Security` object, specifying a registry key as the object type. Most commonly used as a context manager for security operations:: from winsys import registry with registry.registry(r"hklm\software\python").security() as s: print s.owner """ return security.Security.from_object( self.pyobject(), object_type=security.SE_OBJECT_TYPE.REGISTRY_KEY, options=options )
def __nonzero__(self): """Determine whether the registry key exists or not. """ hKey, _, _ = self._from_string(self.moniker, accept_value=False) return bool(hKey) __bool__ = __nonzero__ def dumped(self, level=0): output = [] output.append(self.as_string()) output.append("access: %s" % self.access) if bool(self): output.append("keys:\n%s" % utils.dumped_list((key.name for key in self.iterkeys(ignore_access_errors=True)), level)) output.append("values:\n%s" % utils.dumped_dict(dict((name or "(Default)", repr(value)) for (name, value) in self.itervalues(ignore_access_errors=True)), level)) output.append("security:\n%s" % utils.dumped(self.security().dumped(level), level)) return utils.dumped("\n".join(output), level) def __getattr__(self, attr): """Allow attribute access (key.value) by trying for a value first and then falling back to a key and finally raising AttributeError """ try: return self.get_value(attr) except exc.x_not_found: try: return self.get_key(attr) except exc.x_not_found: raise AttributeError __getitem__ = __getattr__ def __setattr__(self, attr, value): """Allow attribute assignment by assigning a value. """ self.set_value(attr, value) __setitem__ = __setattr__ def __delattr__(self, attr): try: self.del_value(attr) except exc.x_not_found: try: self.delete(attr) except exc.x_not_found: raise AttributeError __delitem__ = __delattr__
[docs] def get_value(self, name): """Return the key's value corresponding to name """ value, type = wrapped(win32api.RegQueryValueEx, self.pyobject(), name) return value
[docs] def get_value_type(self, name): """Return the type of the key's value corresponding to name """ value, type = wrapped(win32api.RegQueryValueEx, self.pyobject(), name) return type
[docs] def get_key(self, name): """Return a Registry instance corresponding to the key's subkey name :param name: a registry relative path (may be a single name or a backslash-separated path """ return self + name
[docs] def set_value(self, label, value, type=None): """Attempt to set one of the key's named values. If type is None, then if the value already exists under the key its current type is assumed; otherwise a guess is made at the datatype as follows: * If the value is an int, use DWORD * If the value is a list, use MULTI_SZ * If the value has an even number of percent signs, use EXPAND_SZ * Otherwise, use REG_SZ This is a very naive approach, and will falter if, for example, a string is passed which can be converted into a number, or a string with 2 percent signs which don't refer to an env var. """ def _guess_type(value): try: int(value) except (ValueError, TypeError): pass else: return REGISTRY_VALUE_TYPE.REG_DWORD if isinstance(value, list): return REGISTRY_VALUE_TYPE.REG_MULTI_SZ value = unicode(value) if "%" in value and value.count("%") % 2 == 0: return REGISTRY_VALUE_TYPE.REG_EXPAND_SZ return REGISTRY_VALUE_TYPE.REG_SZ type = REGISTRY_VALUE_TYPE.constant(type) if type is None: try: _, type = wrapped(win32api.RegQueryValueEx, self.pyobject(), label) except exc.x_not_found: type = _guess_type(value) wrapped(win32api.RegSetValueEx, self.pyobject(), label, 0, type, value)
[docs] def del_value(self, label): """Removed the value identified by label from this registry key""" return wrapped(win32api.RegDeleteValue, self.pyobject(), label)
@classmethod def _from_string(cls, string, access=DEFAULT_ACCESS, accept_value=True): """Treat the string param as a moniker and return the corresponding registry key and value name. """ computer, root, path, value = _parse_moniker(string, accept_value=accept_value) moniker = REGISTRY_HIVE.name_from_value(root) + ((sep + path) if path else "") if computer: hRoot = wrapped(win32api.RegConnectRegistry, None if computer == "." else computer, root) else: hRoot = root try: if computer: moniker = r"\\%s\%s" % (computer, moniker) return wrapped(win32api.RegOpenKeyEx, hRoot, path, 0, cls._access(access)), moniker, value except exc.x_not_found: return None, moniker, value @classmethod
[docs] def from_string(cls, string, access=DEFAULT_ACCESS, accept_value=True): """Treat the string param as a moniker return either a key or a value. This is mostly used via the :func:`registry` function. """ hKey, moniker, value = cls._from_string(string, access, accept_value) if value is None: return cls(moniker, access) else: return cls(moniker, access).get_value(value)
[docs]def registry(root, access=Registry.DEFAULT_ACCESS, accept_value=True): """Factory function for the Registry class. :param root: any of None, a Registry instance, or a moniker string :param access: an integer bitmask or an :data:`Registry.ACCESS` string :returns: a :class:`Registry` object """ if root is None: return None elif isinstance(root, Registry): return root elif isinstance(root, basestring): return Registry.from_string(root, access=access, accept_value=accept_value) else: raise x_registry(errctx="registry", errmsg="root must be None, an existing key or a moniker")
[docs]def values(root, ignore_access_errors=False, _want_types=False): """Yield the values of a registry key as (name, value). This is convenient for, eg, populating a dictionary:: from winsys import registry com3 = registry.registry(r"HKLM\Software\Microsoft\Com3") com3_values = dict(com3.itervalues()) :param root: anything accepted by :func:`registry` :param ignore_access_errors: if True, will keep on iterating even if access denied :param _want_types: (internal) used when, eg, copying keys exactly :returns: yields (name, value) for every value under :const:`root` """ root = registry(root, accept_value=False) try: hKey = root.pyobject() except exc.x_access_denied: if ignore_access_errors: raise StopIteration else: raise values = [] i = 0 while True: try: name, value, type = wrapped(win32api.RegEnumValue, hKey, i) if _want_types: yield name, value, type else: yield name, value except exc.x_access_denied: if ignore_access_errors: raise StopIteration else: raise i += 1
itervalues = values
[docs]def keys(root, ignore_access_errors=False): """Yield the subkeys of a registry key as :class:`Registry` objects :param root: anything accepted by :func:`registry` :param ignore_access_errors: if True, will keep on iterating even if access denied :returns: yield :class:`Registry` objects for each key under `root` """ root = registry(root, accept_value=False) try: hRoot = root.pyobject() except exc.x_access_denied: if ignore_access_errors: raise StopIteration else: raise try: for subname, reserved, subclass, written_at in wrapped(win32api.RegEnumKeyExW, hRoot): yield registry(root.moniker + sep + subname, accept_value=False) except exc.x_access_denied: if ignore_access_errors: raise StopIteration else: raise
iterkeys = keys
[docs]def copy(from_key, to_key, use_access="F"): """Copy one registry key to another, returning the target. If the target doesn't already exist it will be created. :param from_key: anything accepted by :func:`registry` :param to_key: anything accepted by :func:`registry` :returns: a :class:`Registry` object for `to_key` """ source = registry(from_key, accept_value=False) target = registry(to_key, access=use_access, accept_value=False) if not target: target.create() for root, subkeys, subvalues in walk(source, _want_types=True): target_root = registry( target.moniker + utils.relative_to(root.moniker, source.moniker), access=use_access, accept_value=False ) for k in subkeys: target_key = registry( target.moniker + utils.relative_to(k.moniker, source.moniker), accept_value=False, access=use_access ) target_key.create() for name, value, type in subvalues: target_root.set_value(name, value, type) return target
[docs]def delete(root, subkey=""): """Delete a registry key and all its subkeys The common use for this will be to delete a key itself. The optional subkey param is useful when this is invoked as a method of a :class:`Registry` object and it's convenient to remove one of its subkeys:: from winsys import registry ws = registry.registry(r"hklm\software\winsys") ws.create() for subkey in ["winsys1", "winsys2", "winsys3"]: ws.create(subkey) for key in ws.iterkeys(): print key for subkey in ["winsys1", "winsys2", "winsys3"]: ws.delete(subkey) ws.delete() :param root: anything accepted by :func:`registry` :param subkey: anything accepted by :meth:`Registry.get_key` :returns: a :class:`Registry` object for `root` """ key = registry(root, accept_value=False).get_key(subkey) for k in key.iterkeys(): k.delete() win32api.RegDeleteKey(key.parent().pyobject(), key.name) return key
[docs]def create(root, subkey="", sec=None): """Create a key and apply specific security to it, returning the key created. Note that a colon in the key name is treated as part of the name not as a value indicator. Any parts of the path not already existing will be created as needed:: from winsys import registry, security sec = security.Security(dacl=[("", "F", "ALLOW")]) registry.create(r"hklm\software\winsys\test", sec=sec) registry.registry(r"hklm\software\winsys\test").dump() :param root: anything accepted by :func:`registry` :param subkey: anything accepted by :meth:`Registry.get_key` :param sec: a :class:`security.Security` instance or None :returns: a :class:`Registry` object for `root` """ key = registry(root, accept_value=False).get_key(subkey) computer0, root0, path0, value0 = _parse_moniker(key.moniker, accept_value=False) parts = path0.split(sep) for i, part in enumerate(parts): computer, root, path, value = _parse_moniker(create_moniker(computer0, root0, sep.join(parts[:i+1])), accept_value=False) if computer: hRoot = wrapped(win32api.RegConnectRegistry, computer, root) else: hRoot = root wrapped( win32api.RegCreateKeyEx, Key=hRoot, SubKey=path, samDesired=Registry._access(Registry.DEFAULT_ACCESS), SecurityAttributes=sec.pyobject() if sec else None ) return key
[docs]def walk(root, ignore_access_errors=False, _want_types=False): """Mimic the os.walk functionality for the registry, starting at root and yielding (key, subkeys, values) for each key visited. subkeys and values are themselves generators. :param root: anything accepted by :func:`registry` :param ignore_access_errors: if True, will keep on iterating even if access denied :param _want_types: (internal) indicates whether value types are returned as well as values :returns: yields `key`, `subkeys`, `values` recursively for every key under `root` """ root = registry(root, accept_value=False) yield ( root, root.iterkeys(ignore_access_errors=ignore_access_errors), root.itervalues(ignore_access_errors=ignore_access_errors, _want_types=_want_types) ) for subkey in root.iterkeys(ignore_access_errors=ignore_access_errors): for result in walk(subkey, ignore_access_errors=ignore_access_errors, _want_types=_want_types): yield result
[docs]def flat(root, ignore_access_errors=False): """Yield a flattened version the tree rooted at root. :param root: anything accepted by :func:`registry` :param ignore_access_errors: if True, will keep on iterating even if access denied :returns: yields `key` and then `value` items for each key under `root` """ for key, subkeys, values in walk(root, ignore_access_errors): yield key for value in values: yield value
[docs]def parent(key): """Return a registry key's parent key if it exists :param key: anything accepted by :func:`registry` :returns: a :class:`Registry` object representing the parent of key :raises: :exc:`x_registry` if no parent exists (eg for a hive) """ key = registry(key, accept_value=False) computer, root, path, value = _parse_moniker(key.moniker, accept_value=False) parent_moniker = create_moniker(computer, root, sep.join(path.split(sep)[:-1])) pcomputer, proot, ppath, pvalue = _parse_moniker(parent_moniker, accept_value=False) if ppath: return registry(parent_moniker, key.access, accept_value=False) else: raise x_registry(errctx="parent", errmsg="%s has no parent" % key.moniker)
def hklm(): return registry("hklm") def hkcu(): return registry("hkcu") Registry.values = values Registry.itervalues = itervalues Registry.keys = keys Registry.iterkeys = iterkeys Registry.__iter__ = keys Registry.delete = delete Registry.create = create Registry.walk = walk Registry.flat = flat Registry.copy = copy Registry.parent = parent