Index: PC/_winreg.c =================================================================== --- PC/_winreg.c (revision 78986) +++ PC/_winreg.c (working copy) @@ -89,7 +89,7 @@ "key is the predefined handle to connect to.\n" "\n" "The return value is the handle of the opened key.\n" -"If the function fails, an EnvironmentError exception is raised."); +"If the function fails, a WindowsError exception is raised."); PyDoc_STRVAR(CreateKey_doc, "key = CreateKey(key, sub_key) - Creates or opens the specified key.\n" @@ -104,6 +104,21 @@ "The return value is the handle of the opened key.\n" "If the function fails, an exception is raised."); +PyDoc_STRVAR(CreateKeyEx_doc, +"key = CreateKeyEx(key, sub_key, res, sam) - Creates or opens the specified key.\n" +"\n" +"key is an already open key, or one of the predefined HKEY_* constants\n" +"sub_key is a string that names the key this method opens or creates.\n" +"res is a reserved integer, and must be zero. Default is zero.\n" +"sam is an integer that specifies an access mask that describes the desired\n" +" If key is one of the predefined keys, sub_key may be None. In that case,\n" +" the handle returned is the same key handle passed in to the function.\n" +"\n" +"If the key already exists, this function opens the existing key\n" +"\n" +"The return value is the handle of the opened key.\n" +"If the function fails, an exception is raised."); + PyDoc_STRVAR(DeleteKey_doc, "DeleteKey(key, sub_key) - Deletes the specified key.\n" "\n" @@ -114,8 +129,23 @@ "This method can not delete keys with subkeys.\n" "\n" "If the method succeeds, the entire key, including all of its values,\n" -"is removed. If the method fails, an EnvironmentError exception is raised."); +"is removed. If the method fails, a WindowsError exception is raised."); +PyDoc_STRVAR(DeleteKeyEx_doc, +"DeleteKeyEx(key, sub_key, sam, res) - Deletes the specified key.\n" +"\n" +"key is an already open key, or any one of the predefined HKEY_* constants.\n" +"sub_key is a string that must be a subkey of the key identified by the key parameter.\n" +"res is a reserved integer, and must be zero. Default is zero.\n" +"sam is an integer that specifies an access mask that describes the desired\n" +" This value must not be None, and the key may not have subkeys.\n" +"\n" +"This method can not delete keys with subkeys.\n" +"\n" +"If the method succeeds, the entire key, including all of its values,\n" +"is removed. If the method fails, a WindowsError exception is raised.\n" +"On unsupported Windows versions, NotImplementedError is raised."); + PyDoc_STRVAR(DeleteValue_doc, "DeleteValue(key, value) - Removes a named value from a registry key.\n" "\n" @@ -129,7 +159,7 @@ "index is an integer that identifies the index of the key to retrieve.\n" "\n" "The function retrieves the name of one subkey each time it is called.\n" -"It is typically called repeatedly until an EnvironmentError exception is\n" +"It is typically called repeatedly until a WindowsError exception is\n" "raised, indicating no more values are available."); PyDoc_STRVAR(EnumValue_doc, @@ -138,7 +168,7 @@ "index is an integer that identifies the index of the value to retrieve.\n" "\n" "The function retrieves the name of one subkey each time it is called.\n" -"It is typically called repeatedly, until an EnvironmentError exception\n" +"It is typically called repeatedly, until a WindowsError exception\n" "is raised, indicating no more values.\n" "\n" "The result is a tuple of 3 items:\n" @@ -192,7 +222,7 @@ " security access for the key. Default is KEY_READ\n" "\n" "The result is a new handle to the specified key\n" -"If the function fails, an EnvironmentError exception is raised."); +"If the function fails, a WindowsError exception is raised."); PyDoc_STRVAR(OpenKeyEx_doc, "See OpenKey()"); @@ -1014,6 +1044,29 @@ } static PyObject * +PyCreateKeyEx(PyObject *self, PyObject *args) +{ + HKEY hKey; + PyObject *obKey; + char *subKey; + HKEY retKey; + int res = 0; + REGSAM sam = KEY_WRITE; + long rc; + if (!PyArg_ParseTuple(args, "Oz|ii:CreateKeyEx", &obKey, &subKey, + &res, &sam)) + return NULL; + if (!PyHKEY_AsHKEY(obKey, &hKey, FALSE)) + return NULL; + + rc = RegCreateKeyEx(hKey, subKey, res, NULL, (DWORD)NULL, + sam, NULL, &retKey, NULL); + if (rc != ERROR_SUCCESS) + return PyErr_SetFromWindowsErrWithFunction(rc, "CreateKeyEx"); + return PyHKEY_FromHKEY(retKey); +} + +static PyObject * PyDeleteKey(PyObject *self, PyObject *args) { HKEY hKey; @@ -1032,6 +1085,46 @@ } static PyObject * +PyDeleteKeyEx(PyObject *self, PyObject *args) +{ + HKEY hKey; + PyObject *obKey; + HMODULE hMod; + typedef LONG (WINAPI *RDKEFunc)(HKEY, const char*, REGSAM, int); + RDKEFunc pfn = NULL; + char *subKey; + long rc; + int res = 0; + REGSAM sam = KEY_WOW64_64KEY; + + if (!PyArg_ParseTuple(args, "Os|ii:DeleteKeyEx", + &obKey, &subKey, &sam, &res)) + return NULL; + if (!PyHKEY_AsHKEY(obKey, &hKey, FALSE)) + return NULL; + + /* Only available on 64bit platforms, so we must load it + dynamically. */ + hMod = GetModuleHandle("advapi32.dll"); + if (hMod) + pfn = (RDKEFunc)GetProcAddress(hMod, + "RegDeleteKeyExA"); + if (!pfn) { + PyErr_SetString(PyExc_NotImplementedError, + "not implemented on this platform"); + return NULL; + } + Py_BEGIN_ALLOW_THREADS + rc = (*pfn)(hKey, subKey, sam, res); + Py_END_ALLOW_THREADS + + if (rc != ERROR_SUCCESS) + return PyErr_SetFromWindowsErrWithFunction(rc, "RegDeleteKeyEx"); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * PyDeleteValue(PyObject *self, PyObject *args) { HKEY hKey; @@ -1479,8 +1572,8 @@ if (!PyHKEY_AsHKEY(obKey, &hKey, FALSE)) return NULL; - // Only available on 64bit platforms, so we must load it - // dynamically. + /* Only available on 64bit platforms, so we must load it + dynamically. */ hMod = GetModuleHandle("advapi32.dll"); if (hMod) pfn = (RDRKFunc)GetProcAddress(hMod, @@ -1515,8 +1608,8 @@ if (!PyHKEY_AsHKEY(obKey, &hKey, FALSE)) return NULL; - // Only available on 64bit platforms, so we must load it - // dynamically. + /* Only available on 64bit platforms, so we must load it + dynamically. */ hMod = GetModuleHandle("advapi32.dll"); if (hMod) pfn = (RERKFunc)GetProcAddress(hMod, @@ -1552,8 +1645,8 @@ if (!PyHKEY_AsHKEY(obKey, &hKey, FALSE)) return NULL; - // Only available on 64bit platforms, so we must load it - // dynamically. + /* Only available on 64bit platforms, so we must load it + dynamically. */ hMod = GetModuleHandle("advapi32.dll"); if (hMod) pfn = (RQRKFunc)GetProcAddress(hMod, @@ -1569,14 +1662,16 @@ if (rc != ERROR_SUCCESS) return PyErr_SetFromWindowsErrWithFunction(rc, "RegQueryReflectionKey"); - return PyBool_FromLong(rc); + return PyBool_FromLong(result); } static struct PyMethodDef winreg_methods[] = { {"CloseKey", PyCloseKey, METH_VARARGS, CloseKey_doc}, {"ConnectRegistry", PyConnectRegistry, METH_VARARGS, ConnectRegistry_doc}, {"CreateKey", PyCreateKey, METH_VARARGS, CreateKey_doc}, + {"CreateKeyEx", PyCreateKeyEx, METH_VARARGS, CreateKeyEx_doc}, {"DeleteKey", PyDeleteKey, METH_VARARGS, DeleteKey_doc}, + {"DeleteKeyEx", PyDeleteKeyEx, METH_VARARGS, DeleteKeyEx_doc}, {"DeleteValue", PyDeleteValue, METH_VARARGS, DeleteValue_doc}, {"DisableReflectionKey", PyDisableReflectionKey, METH_VARARGS, DisableReflectionKey_doc}, {"EnableReflectionKey", PyEnableReflectionKey, METH_VARARGS, EnableReflectionKey_doc}, Index: Doc/library/_winreg.rst =================================================================== --- Doc/library/_winreg.rst (revision 78986) +++ Doc/library/_winreg.rst (working copy) @@ -68,15 +68,40 @@ :exc:`WindowsError` exception is raised. +.. function:: CreateKeyEx(key, sub_key[, res=0[, sam=KEY_ALL_ACCESS]]) + + Creates or opens the specified key, returning a :dfn:`handle object` + + *key* is an already open key, or one of the predefined :const:`HKEY_\*` + constants. + + *sub_key* is a string that names the key this method opens or creates. + + *res* is a reserved integer, and must be zero. The default is zero. + + *sam* is an integer that specifies an access mask that describes the desired + security access for the key. Default is :const:`KEY_ALL_ACCESS` + + If *key* is one of the predefined keys, *sub_key* may be ``None``. In that + case, the handle returned is the same key handle passed in to the function. + + If the key already exists, this function opens the existing key. + + The return value is the handle of the opened key. If the function fails, a + :exc:`WindowsError` exception is raised. + +.. versionadded:: 2.7 + + .. function:: DeleteKey(key, sub_key) Deletes the specified key. - *key* is an already open key, or any one of the predefined :const:`HKEY_\*` + *key* is an already open key, or any one of the predefined :const:`HKEY_\*` constants. - *sub_key* is a string that must be a subkey of the key identified by the *key* - parameter. This value must not be ``None``, and the key may not have subkeys. + *sub_key* is a string that must be a subkey of the key identified by the *key* + parameter. This value must not be ``None``, and the key may not have subkeys. *This method can not delete keys with subkeys.* @@ -84,6 +109,37 @@ If the method fails, a :exc:`WindowsError` exception is raised. +.. function:: DeleteKeyEx(key, sub_key[, sam=KEY_WOW64_64KEY[, res=0]]) + + Deletes the specified key. + + .. note:: + The :func:`DeleteKeyEx` function is implemented with the RegDeleteKeyEx + Windows API function, which is specific to 64-bit versions of Windows. + See http://msdn.microsoft.com/en-us/library/ms724847%28VS.85%29.aspx + + *key* is an already open key, or any one of the predefined :const:`HKEY_\*` + constants. + + *sub_key* is a string that must be a subkey of the key identified by the + *key* parameter. This value must not be ``None``, and the key may not have + subkeys. + + *res* is a reserved integer, and must be zero. The default is zero. + + *sam* is an integer that specifies an access mask that describes the + desired security access for the key. Default is :const:`KEY_WOW64_64KEY` + + *This method can not delete keys with subkeys.* + + If the method succeeds, the entire key, including all of its values, is + removed. If the method fails, a :exc:`WindowsError` exception is raised. + + On unsupported Windows versions, :exc:`NotImplementedError` is raised. + +.. versionadded:: 2.7 + + .. function:: DeleteValue(key, value) Removes a named value from a registry key. @@ -381,8 +437,50 @@ Value lengths are limited by available memory. Long values (more than 2048 bytes) should be stored as files with the filenames stored in the configuration registry. This helps the registry perform efficiently. + + +.. function:: DisableReflectionKey(key) + + Disables registry reflection for 32-bit processes running on a 64-bit + Operating System. + + *key* is an already open key, or one of the predefined :const:`HKEY_\*` + constants. + + Will generally raise :exc:`NotImplemented` if executed on a 32-bit + Operating System. + If the key is not on the reflection list, the function succeeds but has no + effect. Disabling reflection for a key does not affect reflection of any + subkeys. + +.. function:: EnableReflectionKey(key) + + Restores registry reflection for the specified disabled key. + + *key* is an already open key, or one of the predefined :const:`HKEY_\*` + constants. + + Will generally raise :exc:`NotImplemented` if executed on a 32-bit + Operating System. + + Restoring reflection for a key does not affect reflection of any subkeys. + + +.. function:: QueryReflectionKey(key) + + Determines the reflection state for the specified key. + + *key* is an already open key, or one of the predefined :const:`HKEY_\*` + constants. + + Returns ``True`` if reflection is disabled. + + Will generally raise :exc:`NotImplemented` if executed on a 32-bit + Operating System. + + .. _handle-object: Registry Handle Objects Index: Lib/test/test_winreg.py =================================================================== --- Lib/test/test_winreg.py (revision 78986) +++ Lib/test/test_winreg.py (working copy) @@ -4,13 +4,32 @@ import os, sys import unittest from test import test_support +from platform import machine # Do this first so test will be skipped if module doesn't exist test_support.import_module('_winreg') # Now import everything from _winreg import * +try: + REMOTE_NAME = sys.argv[sys.argv.index("--remote")+1] +except (IndexError, ValueError): + REMOTE_NAME = None + +# tuple of (major, minor) +WIN_VER = sys.getwindowsversion()[:2] +# Some tests should only run on 64-bit architectures where WOW64 will be. +WIN64_MACHINE = True if machine() == "AMD64" else False + +# Starting with Windows 7 and Windows Server 2008 R2, WOW64 no longer uses +# registry reflection and formerly reflected keys are shared instead. +# Windows 7 and Windows Server 2008 R2 are version 6.1. Due to this, some +# tests are only valid up until 6.1 +HAS_REFLECTION = True if WIN_VER < (6, 1) else False + test_key_name = "SOFTWARE\\Python Registry Test Key - Delete Me" +# On OS'es that support reflection we should test with a reflected key +test_reflect_key_name = "SOFTWARE\\Classes\\Python Test Key - Delete Me" test_data = [ ("Int Value", 45, REG_DWORD), @@ -32,8 +51,7 @@ "values"], REG_MULTI_SZ), ] -class WinregTests(unittest.TestCase): - remote_name = None +class BaseWinregTests(unittest.TestCase): def setUp(self): # Make sure that the test key is absent when the test @@ -56,7 +74,7 @@ CloseKey(hkey) DeleteKey(root, subkey) - def WriteTestData(self, root_key): + def _write_test_data(self, root_key, CreateKey=CreateKey): # Set the default value for this key. SetValue(root_key, test_key_name, REG_SZ, "Default value") key = CreateKey(root_key, test_key_name) @@ -96,7 +114,7 @@ except EnvironmentError: pass - def ReadTestData(self, root_key): + def _read_test_data(self, root_key, OpenKey=OpenKey): # Check we can get default value for this key. val = QueryValue(root_key, test_key_name) self.assertEquals(val, "Default value", @@ -136,7 +154,7 @@ key.Close() - def DeleteTestData(self, root_key): + def _delete_test_data(self, root_key): key = OpenKey(root_key, test_key_name, 0, KEY_ALL_ACCESS) sub_key = OpenKey(key, "sub_key", 0, KEY_ALL_ACCESS) # It is not necessary to delete the values before deleting @@ -166,38 +184,175 @@ except WindowsError: # Use this error name this time pass - def TestAll(self, root_key): - self.WriteTestData(root_key) - self.ReadTestData(root_key) - self.DeleteTestData(root_key) + def _test_all(self, root_key): + self._write_test_data(root_key) + self._read_test_data(root_key) + self._delete_test_data(root_key) + +class LocalWinregTests(BaseWinregTests): - def testLocalMachineRegistryWorks(self): - self.TestAll(HKEY_CURRENT_USER) + def test_registry_works(self): + self._test_all(HKEY_CURRENT_USER) + + def test_registry_works_extended_functions(self): + # Substitute the regular CreateKey and OpenKey calls with their + # extended counterparts. + # Note: DeleteKeyEx is not used here because it is platform dependent + cke = lambda key, sub_key: CreateKeyEx(key, sub_key, 0, KEY_ALL_ACCESS) + self._write_test_data(HKEY_CURRENT_USER, cke) + + oke = lambda key, sub_key: OpenKeyEx(key, sub_key, 0, KEY_READ) + self._read_test_data(HKEY_CURRENT_USER, oke) + + self._delete_test_data(HKEY_CURRENT_USER) - def testConnectRegistryToLocalMachineWorks(self): + def test_connect_registry_to_local_machine_works(self): # perform minimal ConnectRegistry test which just invokes it h = ConnectRegistry(None, HKEY_LOCAL_MACHINE) + self.assertNotEqual(h.handle, 0) h.Close() + self.assertEqual(h.handle, 0) + + def test_inexistant_remote_registry(self): + connect = lambda: ConnectRegistry("abcdefghijkl", HKEY_CURRENT_USER) + self.assertRaises(WindowsError, connect) - def testRemoteMachineRegistryWorks(self): - if not self.remote_name: - return # remote machine name not specified - remote_key = ConnectRegistry(self.remote_name, HKEY_CURRENT_USER) - self.TestAll(remote_key) - - def testExpandEnvironmentStrings(self): + def test_expand_environment_strings(self): r = ExpandEnvironmentStrings(u"%windir%\\test") self.assertEqual(type(r), unicode) self.assertEqual(r, os.environ["windir"] + "\\test") + + def test_context_manager(self): + # ensure that the handle is closed if an exception occurs + try: + with ConnectRegistry(None, HKEY_LOCAL_MACHINE) as h: + self.assertNotEqual(h.handle, 0) + raise WindowsError + except WindowsError: + self.assertEqual(h.handle, 0) + # Reflection requires XP x64/Vista at a minimum. XP doesn't have this stuff + # or DeleteKeyEx so make sure their use raises NotImplementedError + @unittest.skipUnless(WIN_VER < (5, 2), "Requires Windows XP") + def test_reflection_unsupported(self): + try: + with CreateKey(HKEY_CURRENT_USER, test_key_name) as ck: + self.assertNotEqual(ck.handle, 0) + + key = OpenKey(HKEY_CURRENT_USER, test_key_name) + self.assertNotEqual(key.handle, 0) + + self.assertRaises(NotImplementedError, DisableReflectionKey(key)) + self.assertRaises(NotImplementedError, EnableReflectionKey(key)) + self.assertRaises(NotImplementedError, QueryReflectionKey(key)) + self.assertRaises(NotImplementedError, + DeleteKeyEx(HKEY_CURRENT_USER, test_key_name)) + finally: + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + +@unittest.skipUnless(REMOTE_NAME, "Skipping remote registry tests") +class RemoteWinregTests(BaseWinregTests): + + def test_remote_registry_works(self): + remote_key = ConnectRegistry(REMOTE_NAME, HKEY_CURRENT_USER) + self._test_all(remote_key) + + +@unittest.skipUnless(WIN64_MACHINE, "x64 specific registry tests") +class Win64WinregTests(BaseWinregTests): + + def test_reflection_functions(self): + # Test that we can call the query, enable, and disable functions + # on a key which isn't on the reflection list with no consequences. + with OpenKey(HKEY_LOCAL_MACHINE, "Software") as key: + # HKLM\Software is redirected but not reflected in all OSes + self.assertTrue(QueryReflectionKey(key)) + self.assertEquals(None, EnableReflectionKey(key)) + self.assertEquals(None, DisableReflectionKey(key)) + self.assertTrue(QueryReflectionKey(key)) + + @unittest.skipUnless(HAS_REFLECTION, "OS doesn't support reflection") + def test_reflection(self): + # Test that we can create, open, and delete keys in the 32-bit + # area. Because we are doing this in a key which gets reflected, + # test the differences of 32 and 64-bit keys before and after the + # reflection occurs (ie. when the created key is closed). + try: + with CreateKeyEx(HKEY_CURRENT_USER, test_reflect_key_name, 0, + KEY_ALL_ACCESS | KEY_WOW64_32KEY) as created_key: + self.assertNotEqual(created_key.handle, 0) + + # The key should now be available in the 32-bit area + with OpenKey(HKEY_CURRENT_USER, test_reflect_key_name, 0, + KEY_ALL_ACCESS | KEY_WOW64_32KEY) as key: + self.assertNotEqual(key.handle, 0) + + # Write a value to what currently is only in the 32-bit area + SetValueEx(created_key, "", 0, REG_SZ, "32KEY") + + # The key is not reflected until created_key is closed. + # The 64-bit version of the key should not be available yet. + open_fail = lambda: OpenKey(HKEY_CURRENT_USER, + test_reflect_key_name, 0, + KEY_READ | KEY_WOW64_64KEY) + self.assertRaises(WindowsError, open_fail) + + # Now explicitly open the 64-bit version of the key + with OpenKey(HKEY_CURRENT_USER, test_reflect_key_name, 0, + KEY_ALL_ACCESS | KEY_WOW64_64KEY) as key: + self.assertNotEqual(key.handle, 0) + # Make sure the original value we set is there + self.assertEqual("32KEY", QueryValue(key, "")) + # Set a new value, which will get reflected to 32-bit + SetValueEx(key, "", 0, REG_SZ, "64KEY") + + # Reflection uses a "last-writer wins policy, so the value we set + # on the 64-bit key should be the same on 32-bit + with OpenKey(HKEY_CURRENT_USER, test_reflect_key_name, 0, + KEY_READ | KEY_WOW64_32KEY) as key: + self.assertEqual("64KEY", QueryValue(key, "")) + finally: + DeleteKeyEx(HKEY_CURRENT_USER, test_reflect_key_name, + KEY_WOW64_32KEY, 0) + + @unittest.skipUnless(HAS_REFLECTION, "OS doesn't support reflection") + def test_disable_reflection(self): + # Make use of a key which gets redirected and reflected + try: + with CreateKeyEx(HKEY_CURRENT_USER, test_reflect_key_name, 0, + KEY_ALL_ACCESS | KEY_WOW64_32KEY) as created_key: + # QueryReflectionKey returns whether or not the key is disabled + disabled = QueryReflectionKey(created_key) + self.assertEqual(type(disabled), bool) + # HKCU\Software\Classes is reflected by default + self.assertFalse(disabled) + + DisableReflectionKey(created_key) + self.assertTrue(QueryReflectionKey(created_key)) + + # The key is now closed and would normally be reflected to the + # 64-bit area, but let's make sure that didn't happen. + open_fail = lambda: OpenKeyEx(HKEY_CURRENT_USER, + test_reflect_key_name, 0, + KEY_READ | KEY_WOW64_64KEY) + self.assertRaises(WindowsError, open_fail) + + # Make sure the 32-bit key is actually there + with OpenKeyEx(HKEY_CURRENT_USER, test_reflect_key_name, 0, + KEY_READ | KEY_WOW64_32KEY) as key: + self.assertNotEqual(key.handle, 0) + finally: + DeleteKeyEx(HKEY_CURRENT_USER, test_reflect_key_name, + KEY_WOW64_32KEY, 0) + + def test_main(): - test_support.run_unittest(WinregTests) + test_support.run_unittest(LocalWinregTests, RemoteWinregTests, + Win64WinregTests) if __name__ == "__main__": - try: - WinregTests.remote_name = sys.argv[sys.argv.index("--remote")+1] - except (IndexError, ValueError): + if not REMOTE_NAME: print "Remote registry calls can be tested using", print "'test_winreg.py --remote \\\\machine_name'" - WinregTests.remote_name = None test_main()