Merge pull request #25648 from twangboy/fix_25352

Clarified functionality of reg module, fixed state to work with new module
This commit is contained in:
David Boucha 2015-07-27 13:30:33 -06:00
commit f05ae95f9c
3 changed files with 398 additions and 67 deletions

View file

@ -1,6 +1,23 @@
# -*- coding: utf-8 -*-
'''
Manage the registry on Windows
Manage the registry on Windows.
The read_key and set_key functions will be updated in Boron to reflect proper
registry usage. The registry has three main components. Hives, Keys, and Values.
### Hives
Hives are the main sections of the registry and all begin with the word HKEY.
- HKEY_LOCAL_MACHINE
- HKEY_CURRENT_USER
- HKEY_USER
### Keys
Keys are the folders in the registry. Keys can have many nested subkeys. Keys
can have a value assigned to them under the (Default)
### Values
Values are name/data pairs. There can be many values in a key. The (Default)
value corresponds to the Key, the rest are their own value pairs.
:depends: - winreg Python module
'''
@ -38,9 +55,12 @@ class Registry(object):
'''
def __init__(self):
self.hkeys = {
"HKEY_USERS": _winreg.HKEY_USERS,
"HKEY_CURRENT_USER": _winreg.HKEY_CURRENT_USER,
"HKEY_LOCAL_MACHINE": _winreg.HKEY_LOCAL_MACHINE,
"HKEY_USERS": _winreg.HKEY_USERS,
"HKCU": _winreg.HKEY_CURRENT_USER,
"HKLM": _winreg.HKEY_LOCAL_MACHINE,
"HKU": _winreg.HKEY_USERS,
}
self.reflection_mask = {
@ -48,6 +68,22 @@ class Registry(object):
False: _winreg.KEY_ALL_ACCESS | _winreg.KEY_WOW64_64KEY,
}
self.vtype = {
"REG_BINARY": _winreg.REG_BINARY,
"REG_DWORD": _winreg.REG_DWORD,
"REG_EXPAND_SZ": _winreg.REG_EXPAND_SZ,
"REG_MULTI_SZ": _winreg.REG_MULTI_SZ,
"REG_SZ": _winreg.REG_SZ
}
self.vtype_reverse = {
_winreg.REG_BINARY: "REG_BINARY",
_winreg.REG_DWORD: "REG_DWORD",
_winreg.REG_EXPAND_SZ: "REG_EXPAND_SZ",
_winreg.REG_MULTI_SZ: "REG_MULTI_SZ",
_winreg.REG_SZ: "REG_SZ"
}
def __getattr__(self, k):
try:
return self.hkeys[k]
@ -66,10 +102,29 @@ def __virtual__():
return False
def read_key(hkey, path, key, reflection=True):
def read_key(hkey, path, key=None):
'''
*** Incorrect Usage ***
The name of this function is misleading and will be changed to reflect
proper usage in the Boron release of Salt. The path option will be removed
and the key will be the actual key. See the following issue:
https://github.com/saltstack/salt/issues/25618
In order to not break existing state files this function will call the
read_value function if a key is passed. Key will be passed as the value
name. If key is not passed, this function will return the default value for
the key.
In the Boron release this function will be removed in favor of read_value.
***
Read registry key value
Returns the first unnamed value (Default) as a string.
Returns none if first unnamed value is empty.
Returns False if key not found.
CLI Example:
.. code-block:: bash
@ -77,19 +132,115 @@ def read_key(hkey, path, key, reflection=True):
salt '*' reg.read_key HKEY_LOCAL_MACHINE 'SOFTWARE\\Salt' 'version'
'''
ret = {'hive': hkey,
'key': path,
'vdata': None,
'success': True}
if key: # This if statement will be removed in Boron
salt.utils.warn_until('Boron', 'Use reg.read_value to read a registry'
'value. This functionality will be'
'removed in Salt Boron')
return read_value(hive=hkey,
key=path,
vname=key)
registry = Registry()
hkey2 = getattr(registry, hkey)
access_mask = registry.reflection_mask[reflection]
hive = registry.hkeys[hkey]
try:
handle = _winreg.OpenKeyEx(hkey2, path, 0, access_mask)
return _winreg.QueryValueEx(handle, key)[0]
except Exception:
return None
value = _winreg.QueryValue(hive, path)
if value:
ret['vdata'] = value
else:
ret['vdata'] = None
ret['comment'] = 'Empty Value'
except WindowsError as exc: # pylint: disable=E0602
log.debug(exc)
ret['comment'] = '{0}'.format(exc)
ret['success'] = False
return ret
def set_key(hkey, path, key, value, vtype='REG_DWORD', reflection=True):
def read_value(hive, key, vname=None):
r'''
Reads a registry value or the default value for a key.
:param hive: string
The name of the hive. Can be one of the following
- HKEY_LOCAL_MACHINE or HKLM
- HKEY_CURRENT_USER or HKCU
- HKEY_USER or HKU
:param key: string
The key (looks like a path) to the value name.
:param vname: string
The value name. These are the individual name/data pairs under the key. If
not passed, the key (Default) value will be returned
:return: dict
A dictionary containing the passed settings as well as the value_data if
successful. If unsuccessful, sets success to False
CLI Example:
.. code-block:: bash
salt '*' reg.read_value HKEY_LOCAL_MACHINE 'SOFTWARE\Salt' 'version'
'''
# Setup the return array
ret = {'hive': hive,
'key': key,
'vname': vname,
'vdata': None,
'success': True}
# If no name is passed, the default value of the key will be returned
# The value name is Default
if not vname:
ret['vname'] = '(Default)'
registry = Registry()
hive = registry.hkeys[hive]
try:
handle = _winreg.OpenKey(hive, key)
value, vtype = _winreg.QueryValueEx(handle, vname)
if value:
ret['vdata'] = value
ret['vtype'] = registry.vtype_reverse[vtype]
else:
ret['comment'] = 'Empty Value'
except WindowsError as exc: # pylint: disable=E0602
log.debug(exc)
ret['comment'] = '{0}'.format(exc)
ret['success'] = False
return ret
def set_key(hkey, path, value, key=None, vtype='REG_DWORD', reflection=True):
'''
*** Incorrect Usage ***
The name of this function is misleading and will be changed to reflect
proper usage in the Boron release of Salt. The path option will be removed
and the key will be the actual key. See the following issue:
https://github.com/saltstack/salt/issues/25618
In order to not break existing state files this function will call the
set_value function if a key is passed. Key will be passed as the value
name. If key is not passed, this function will return the default value for
the key.
In the Boron release this function will be removed in favor of set_value.
***
Set a registry key
vtype: http://docs.python.org/2/library/_winreg.html#value-types
CLI Example:
@ -98,29 +249,104 @@ def set_key(hkey, path, key, value, vtype='REG_DWORD', reflection=True):
salt '*' reg.set_key HKEY_CURRENT_USER 'SOFTWARE\\Salt' 'version' '0.97' REG_DWORD
'''
if key: # This if statement will be removed in Boron
salt.utils.warn_until('Boron', 'Use reg.set_value to set a registry'
'value. This functionality will be'
'removed in Salt Boron')
return set_value(hive=hkey,
key=path,
vname=key,
vdata=value,
vtype=vtype)
registry = Registry()
hkey2 = getattr(registry, hkey)
hive = registry.hkeys[hkey]
vtype = registry.vtype['REG_SZ']
try:
_winreg.SetValue(hive, path, vtype, value)
return True
except WindowsError as exc: # pylint: disable=E0602
log.error(exc)
return False
def set_value(hive, key, vname=None, vdata=None, vtype='REG_SZ', reflection=True):
'''
Sets a registry value.
:param hive: string
The name of the hive. Can be one of the following
- HKEY_LOCAL_MACHINE or HKLM
- HKEY_CURRENT_USER or HKCU
- HKEY_USER or HKU
:param key: string
The key (looks like a path) to the value name.
:param vname: string
The value name. These are the individual name/data pairs under the key. If
not passed, the key (Default) value will be set.
:param vdata: string
The value data to be set.
:param vtype: string
The value type. Can be one of the following:
- REG_BINARY
- REG_DWORD
- REG_EXPAND_SZ
- REG_MULTI_SZ
- REG_SZ
:param reflection: boolean
A boolean value indicating that the value should also be set in the
Wow6432Node portion of the registry. Only applies to 64 bit Windows. This
setting is ignored for 32 bit Windows.
:return: boolean
Returns True if successful, False if not
CLI Example:
.. code-block:: bash
salt '*' reg.set_value HKEY_LOCAL_MACHINE 'SOFTWARE\\Salt' 'version' '2015.5.2'
'''
registry = Registry()
hive = registry.hkeys[hive]
vtype = registry.vtype[vtype]
access_mask = registry.reflection_mask[reflection]
try:
_type = getattr(_winreg, vtype)
except AttributeError:
return False
try:
handle = _winreg.OpenKey(hkey2, path, 0, access_mask)
_winreg.SetValueEx(handle, key, 0, _type, value)
handle = _winreg.CreateKeyEx(hive, key, 0, access_mask)
_winreg.SetValueEx(handle, vname, 0, vtype, vdata)
_winreg.CloseKey(handle)
return True
except Exception:
handle = _winreg.CreateKeyEx(hkey2, path, 0, access_mask)
_winreg.SetValueEx(handle, key, 0, _type, value)
_winreg.CloseKey(handle)
return True
except WindowsError as exc: # pylint: disable=E0602
log.error(exc)
return False
def create_key(hkey, path, key, value=None, reflection=True):
def create_key(hkey, path, key=None, value=None, reflection=True):
'''
*** Incorrect Usage ***
The name of this function is misleading and will be changed to reflect
proper usage in the Boron release of Salt. The path option will be removed
and the key will be the actual key. See the following issue:
https://github.com/saltstack/salt/issues/25618
In order to not break existing state files this function will call the
set_value function if key is passed. Key will be passed as the value name.
If key is not passed, this function will return the default value for the
key.
In the Boron release path will be removed and key will be the path. You will
not pass value.
***
Create a registry key
CLI Example:
@ -129,24 +355,48 @@ def create_key(hkey, path, key, value=None, reflection=True):
salt '*' reg.create_key HKEY_CURRENT_USER 'SOFTWARE\\Salt' 'version' '0.97'
'''
if key: # This if statement will be removed in Boron
salt.utils.warn_until('Boron', 'Use reg.set_value to set a registry'
'value. This functionality will be'
'removed in Salt Boron')
return set_value(hive=hkey,
key=path,
vname=key,
vdata=value,
vtype='REG_SZ')
registry = Registry()
hkey2 = getattr(registry, hkey)
hive = registry.hkeys[hkey]
key = path
access_mask = registry.reflection_mask[reflection]
try:
handle = _winreg.OpenKey(hkey2, path, 0, access_mask)
handle = _winreg.CreateKeyEx(hive, key, 0, access_mask)
_winreg.CloseKey(handle)
return True
except Exception:
handle = _winreg.CreateKeyEx(hkey2, path, 0, access_mask)
if value:
_winreg.SetValueEx(handle, key, 0, _winreg.REG_DWORD, value)
_winreg.CloseKey(handle)
return True
except WindowsError as exc: # pylint: disable=E0602
log.error(exc)
return False
def delete_key(hkey, path, key, reflection=True):
def delete_key(hkey, path, key=None, reflection=True):
'''
*** Incorrect Usage ***
The name of this function is misleading and will be changed to reflect
proper usage in the Boron release of Salt. The path option will be removed
and the key will be the actual key. See the following issue:
https://github.com/saltstack/salt/issues/25618
In order to not break existing state files this function will call the
delete_value function if a key is passed. Key will be passed as the value
name. If key is not passed, this function will return the default value for
the key.
In the Boron release path will be removed and key will be the path.
reflection will also be removed.
***
Delete a registry key
Note: This cannot delete a key with subkeys
@ -155,24 +405,71 @@ def delete_key(hkey, path, key, reflection=True):
.. code-block:: bash
salt '*' reg.delete_key HKEY_CURRENT_USER 'SOFTWARE\\Salt' 'version'
salt '*' reg.delete_key HKEY_CURRENT_USER 'SOFTWARE\\Salt'
'''
if key: # This if statement will be removed in Boron
salt.utils.warn_until('Boron', 'Use reg.set_value to set a registry'
'value. This functionality will be'
'removed in Salt Boron')
return delete_value(hive=hkey,
key=path,
vname=key,
reflection=reflection)
registry = Registry()
hive = registry.hkeys[hkey]
key = path
try:
_winreg.DeleteKey(hive, key)
return True
except WindowsError as exc: # pylint: disable=E0602
log.error(exc)
return False
def delete_value(hive, key, vname=None, reflection=True):
'''
Deletes a registry value.
:param hive: string
The name of the hive. Can be one of the following
- HKEY_LOCAL_MACHINE or HKLM
- HKEY_CURRENT_USER or HKCU
- HKEY_USER or HKU
:param key: string
The key (looks like a path) to the value name.
:param vname: string
The value name. These are the individual name/data pairs under the key. If
not passed, the key (Default) value will be deleted.
:param reflection: boolean
A boolean value indicating that the value should also be set in the
Wow6432Node portion of the registry. Only applies to 64 bit Windows. This
setting is ignored for 32 bit Windows.
:return: boolean
Returns True if successful, False if not
CLI Example:
.. code-block:: bash
salt '*' reg.delete_value HKEY_CURRENT_USER 'SOFTWARE\\Salt' 'version'
'''
registry = Registry()
hkey2 = getattr(registry, hkey)
hive = registry.hkeys[hive]
access_mask = registry.reflection_mask[reflection]
try:
handle = _winreg.OpenKey(hkey2, path, 0, access_mask)
_winreg.DeleteKeyEx(handle, key)
handle = _winreg.OpenKey(hive, key, 0, access_mask)
_winreg.DeleteValue(handle, vname)
_winreg.CloseKey(handle)
return True
except Exception:
pass
try:
_winreg.DeleteValue(handle, key)
_winreg.CloseKey(handle)
return True
except Exception:
except WindowsError as exc: # pylint: disable=E0602
_winreg.CloseKey(handle)
log.error(exc)
return False

View file

@ -2,6 +2,9 @@
'''
Manage the registry on Windows
'''
import logging
log = logging.getLogger(__name__)
def __virtual__():
@ -11,20 +14,30 @@ def __virtual__():
return 'reg' if 'reg.read_key' in __salt__ else False
def _parse_key(key):
def _parse_key_value(key):
'''
split the full path in the registry to the key and the rest
'''
splt = key.split("\\")
hive = splt.pop(0)
key = splt.pop(-1)
path = r'\\'.join(splt)
return hive, path, key
vname = splt.pop(-1)
key = r'\\'.join(splt)
return hive, key, vname
def present(name, value, vtype='REG_DWORD', reflection=True):
def _parse_key(key):
'''
Set a registry entry
split the hive from the key
'''
splt = key.split("\\")
hive = splt.pop(0)
key = r'\\'.join(splt)
return hive, key
def present(name, value, vtype='REG_SZ', reflection=True):
'''
Set a registry value
Optionally set ``reflection`` to ``False`` to disable reflection.
``reflection`` has no effect on a 32-bit OS.
@ -42,27 +55,37 @@ def present(name, value, vtype='REG_DWORD', reflection=True):
- value: 0.15.3
- vtype: REG_SZ
- reflection: False
In the above example the path is interpreted as follows:
- ``HKEY_CURRENT_USER`` is the hive
- ``SOFTWARE\\Salt`` is the key
- ``version`` is the value name
So ``version`` will be created in the ``SOFTWARE\\Salt`` key in the
``HKEY_CURRENT_USER`` hive and given the ``REG_SZ`` value of ``0.15.3``.
'''
ret = {'name': name,
'result': True,
'changes': {},
'comment': ''}
# determine what to do
hive, path, key = _parse_key(name)
if value == __salt__['reg.read_key'](hive, path, key, reflection):
hive, key, vname = _parse_key_value(name)
# Determine what to do
if value == __salt__['reg.read_value'](hive, key, vname)['vdata']:
ret['comment'] = '{0} is already configured'.format(name)
return ret
else:
ret['changes'] = {'reg': 'configured to {0}'.format(value)}
# Check for test option
if __opts__['test']:
ret['result'] = None
return ret
# configure the key
ret['result'] = __salt__['reg.set_key'](hive, path, key, value, vtype,
reflection)
# Configure the value
ret['result'] = __salt__['reg.set_value'](hive, key, vname, value, vtype,
reflection)
if not ret:
ret['changes'] = {}
ret['comment'] = 'could not configure the registry key'
@ -72,30 +95,41 @@ def present(name, value, vtype='REG_DWORD', reflection=True):
def absent(name):
'''
Remove a registry key
Remove a registry value
Example::
'HKEY_CURRENT_USER\\SOFTWARE\\Salt\\version':
reg.absent
In the above example the path is interpreted as follows:
- ``HKEY_CURRENT_USER`` is the hive
- ``SOFTWARE\\Salt`` is the key
- ``version`` is the value name
So the value ``version`` will be deleted from the ``SOFTWARE\\Salt`` key in
the ``HKEY_CURRENT_USER`` hive.
'''
ret = {'name': name,
'result': True,
'changes': {},
'comment': ''}
hive, path, key = _parse_key(name)
if not __salt__['reg.read_key'](hive, path, key):
hive, key, vname = _parse_key_value(name)
# Determine what to do
if not __salt__['reg.read_value'](hive, key, vname)['success']:
ret['comment'] = '{0} is already absent'.format(name)
return ret
else:
ret['changes'] = {'reg': 'Removed {0}'.format(name)}
# Check for test option
if __opts__['test']:
ret['result'] = None
return ret
ret['result'] = __salt__['reg.delete_key'](hive, path, key)
# Delete the value
ret['result'] = __salt__['reg.delete_value'](hive, key, vname)
if not ret['result']:
ret['changes'] = {}
ret['comment'] = 'failed to remove registry key {0}'.format(name)

View file

@ -44,10 +44,10 @@ class RegTestCase(TestCase):
'result': True,
'comment': '{0} is already configured'.format(name)}
mock = MagicMock(side_effect=[value, 'a', 'a'])
mock = MagicMock(side_effect=[{'vdata': value}, {'vdata': 'a'}, {'vdata': 'a'}])
mock_t = MagicMock(return_value=True)
with patch.dict(reg.__salt__, {'reg.read_key': mock,
'reg.set_key': mock_t}):
with patch.dict(reg.__salt__, {'reg.read_value': mock,
'reg.set_value': mock_t}):
self.assertDictEqual(reg.present(name, value), ret)
with patch.dict(reg.__opts__, {'test': True}):
@ -72,10 +72,10 @@ class RegTestCase(TestCase):
'result': True,
'comment': '{0} is already absent'.format(name)}
mock = MagicMock(side_effect=[False, True, True])
mock = MagicMock(side_effect=[{'success': False}, {'success': True}, {'success': True}])
mock_t = MagicMock(return_value=True)
with patch.dict(reg.__salt__, {'reg.read_key': mock,
'reg.delete_key': mock_t}):
with patch.dict(reg.__salt__, {'reg.read_value': mock,
'reg.delete_value': mock_t}):
self.assertDictEqual(reg.absent(name), ret)
with patch.dict(reg.__opts__, {'test': True}):