Winreg is making persistent changes I can test, but they aren't in my Windows Registry. They're in some other dimension

I have this fully documented at: windows - Python winreg says it opened a key that doesn't exist in my registry - Stack Overflow

But the bottom line is that I am using winreg to read, write, and change the Windows registry. My program flips a lot of different settings to fix things that annoy me about the windows defaults. When I run it, it seems to work. The changes even persist between Flask runs, even after restarting Windows Explorer, and the computer.

But every time I check the registry, the changes aren’t there. I already verified that everything is 64bit. I am running VScode as admin (and running flask through the terminal there). Just in case, I ran it in an admin terminal outside of vscode too.

No matter what I try, it tells me it’s changing the registry when it actually isn’t. It’s completely punking me.

Code is:

import winreg

def hive_name(hive):
    if hive == winreg.HKEY_CURRENT_USER:
        return "HKEY_CURRENT_USER"
    elif hive == winreg.HKEY_LOCAL_MACHINE:
        return "HKEY_LOCAL_MACHINE"
    elif hive == winreg.HKEY_CLASSES_ROOT:
        return "HKEY_CLASSES_ROOT"
    elif hive == winreg.HKEY_USERS:
        return "HKEY_USERS"
    elif hive == winreg.HKEY_PERFORMANCE_DATA:
        return "HKEY_PERFORMANCE_DATA"
    elif hive == winreg.HKEY_CURRENT_CONFIG:
        return "HKEY_CURRENT_CONFIG"
    else:
        return "UNKNOWN_HIVE"

def open_or_create_key(hive, path):
    try:
        # Open the registry key for reading and writing in 64-bit view
        key = winreg.OpenKey(hive, path, 0, winreg.KEY_READ | winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY)
        print(f"Key opened: {hive_name(hive)}\\{path}")
    except FileNotFoundError:
        # Handle if the key doesn't exist
        print(f"Creating key: {hive_name(hive)}\\{path}")
        key = winreg.CreateKeyEx(hive, path, 0, winreg.KEY_READ | winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY)
    except PermissionError:
        # Handle if there are permission issues
        print(f"Permission denied while accessing the key: {hive_name(hive)}\\{path}")
        key = None
    except Exception as e:
        # Handle any other exceptions
        print(f"An error occurred: {e}")
        key = None
    return key

def get_value(key,which):
    try:
        value, _ = winreg.QueryValueEx(key, which)
        print(f"Current value: {value}")
    except FileNotFoundError:
        print("Current value: <not set>")
    except Exception as e:
        print(f"An error occurred while querying the value: {e}")

def set_value(key,which,what):
    try:
        winreg.SetValueEx(key, which, 0, winreg.REG_DWORD, what)
        print (which, "was set to", what)
    except FileNotFoundError:
        print (which, "could not be set to", what)

def close_key(key):
    if key:
        winreg.CloseKey(key)
        print("Key closed.")

# Test the open_or_create_key function
if __name__ == "__main__":

    print("# This key does exist on my system and has tons of values")
    print("# Expected Output: Key opened: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced")
    key = open_or_create_key(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced")
    print("# Value name DisallowShaking is NOT in my registry.")
    print("# Expected Output: Current value: <not set> ")
    get_value(key,"DisallowShaking")
    print("# Value name HideFileExt IS in my registry.")
    print("# Expected Output: HideFileExt set to X (where X is the value set in the code) - needs to be checked in the registry to see if it changed between runs")
    set_value(key,"HideFileExt",1)
    close_key(key)

    print("# Neither {86ca1aa0-34aa-4e8b-a509-50c905bae2a2} nor InprocServer32 exist in my registry.")
    print("# Expected Output: Creating Key: Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32")
    key = open_or_create_key(winreg.HKEY_CURRENT_USER, r"Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32")
    close_key(key)

    print("# The Blocked key does not exist in my registry.")
    print("# Expected Output: Creating Key: SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Blocked")
    key = open_or_create_key(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Blocked")
    print("# If the key were created, then I can test for value {e2bf9676-5f8f-435c-97eb-11607a5bedf7} which should not exist yet.")
    print("# Expected Output: An error occurred while querying the value:  ")
    get_value(key,"{e2bf9676-5f8f-435c-97eb-11607a5bedf7}")
    close_key(key)

Are you checking for errors on all calls you make?

Do you have a small example that seems to work but the registry does not change?

The sample code is using try/catch so I should be catching everything. The link to the StackOverflow posting is where you’ll see what I’m doing. It has a runable test code that shows each type of problem and yes, the registry does not change in any way. However the changes are going SOMEWHERE… I can test for them and the changes are persistent

Please post code here so that answers and code are all in one place.

Code added to the post above.

Thank you. I was hoping for short example of the problem that I could run and test
on my system. It should not for example have UUIDs that can be specific to your installation.

Here’s a shorter one. As before, there’s nothing unique to my system, but i picked the most basic of changes possible - whether extensions are hidden on the computer or not:

import winreg

def open_or_create_key(hive, path):
	try:
		# Open the registry key for reading and writing in 64-bit view
		key = winreg.OpenKeyEx(hive, path, 0, winreg.KEY_READ | winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY)
		print(f"Key opened: {path}")
	except FileNotFoundError:
		# Handle if the key doesn't exist
		print(f"Creating key: {path}")
		key = winreg.CreateKeyEx(hive, path, 0, winreg.KEY_READ | winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY)
	except PermissionError:
		# Handle if there are permission issues
		print(f"Permission denied while accessing the key: {path}")
		key = None
	except Exception as e:
		# Handle any other exceptions
		print(f"An error occurred: {e}")
		key = None
	return key

def set_value(key,which,what):
	try:
		winreg.SetValueEx(key, which, 0, winreg.REG_DWORD, what)
		print (which, "was set to", what)
	except FileNotFoundError:
		print (which, "could not be set to", what)

def close_key(key):
	if key:
		winreg.CloseKey(key)
		print("Key closed.")

# Test the open_or_create_key function
if __name__ == "__main__":

	key = open_or_create_key(winreg.HKEY_CURRENT_USER, r"python")
	print("# Value name HideFileExt IS in my registry.")
	print("# Expected Output: HideFileExt set to X (where X is the value set in the code) - needs to be checked in the registry to see if it changed between runs")
	set_value(key,"HideFileExt",1)
	close_key(key)

This setting can be changed in the windows explorer options as well

I’m engaging with the problem itself on issue 121022. But here on the forum I’d like to make a few points about registry hives, predefined handles, and the design of the registry API. These points do not pertain to the issue. It’s just for general education on this topic.


What your script calls a “hive” is actually a predefined key handle, which in all but one of those cases (well, 1.5 cases) is not actually mounted by a registry hive.

On NT systems, the Configuration Manager creates Key objects dynamically in memory, starting at “\Registry”, which is the root Key in the system object namespace. However, much of the registry is meant to be persistent, so the Configuration Manager supports mounting a Key by what’s called a hive file. The API supports working with hive files via RegLoadKeyW(), RegLoadAppKeyW(), RegUnLoadKeyW(), RegRestoreKeyW(), RegReplaceKeyW(), RegSaveKeyW(), and RegSaveKeyExW().

Going all the way back to the first version of the registry in Windows 3.1 (1992), the API has always been designed around predefined Key handles. Initially in Windows 3.1 there was only one predefined handle, HKEY_CLASSES_ROOT, which referenced the root Key in the registry (it’s not actually the root key in later implementations of the registry). I suppose this design simplified the API a bit. Possibly the registry in Windows 3.1 was based on NT, which had been under development at Microsoft since 1989.

On NT systems (1993+), Key objects are usually created as subkeys of “\Registry”, the root Key in the object namespace. However, from the perspective of Windows applications, this root object path is an internal implementation detail. Since the opened path of an NT object can be relative to a parent object that contains it, the registry API on NT hides the internal root path by requiring Key objects to be opened relative to a handle for a parent Key, starting from a set of predefined handles.

This design is also convenient for remote registry access. WinAPI RegConnectRegistryW() connects to a remote machine via RPC (remote procedure call) to open a predefined handle on the machine. It returns a specially flagged handle that references the RPC connection and the actual Key handle on the remote machine. Opening a subkey relative to the latter handle also references the RPC connection. It’s a convenient way to reference and reuse a single RPC connection.

Some predefined handles:

  • HKEY_LOCAL_MACHINE references “\Registry\Machine” in the object namespace. Several hives are mounted on its subkeys, for which the hive files are found in the directory “%SystemRoot%\System32\config”. This includes the hive files “DRIVERS”, “SYSTEM”, “SECURITY”, “SAM”, and “SOFTWARE”.

  • HKEY_USERS references “\Registry\User” in the object namespace. The system mounts user profile hives on its subkeys.

    • The default user profile, “\Registry\User\.DEFAULT”, is mounted by a hive that’s loaded from the file “%SystemRoot%\System32\config\DEFAULT”. Any logon that doesn’t have its own profile use the default one. This applies to the SYSTEM logon.
    • A user profile hive gets loaded from the file “NTUSER.DAT” in the user profile directory, and mounted on the subkey “\Registry\User\{SSID}”, where SSID is the string representation of the user SID (security identifier). The hive file “NTUSER.DAT” roams with a user across machines if roaming profiles are enabled.
    • A user’s software classes hive gets loaded from the file “AppData\Local\Microsoft\Windows\UsrClass.dat”, relative to the user profile directory. It gets mounted at “\Registry\User\{SSID}_Classes”. A symbolic link at “\Registry\User\{SSID}\Software\Classes” resolves to the mount point. The hive file “UsrClass.dat” is always local to the machine.
  • HKEY_CURRENT_USER references the current user profile hive. The mapping to the real Key handle gets cached in the process on first use. Take care to instead use RegOpenCurrentUser() if the current thread is impersonation another user.

  • HKEY_CLASSES_ROOT is implemented as a merged view. The API first checks in the current user’s software classes. If the subkey doesn’t exist there, it checks the machine’s software classes at “\Registry\Machine\Software\Classes”. To get a merged view for another user, or while impersonating, use RegOpenUserClassesRoot(). Note that creating keys and storing values via the merged view has unreliable results. Instead, explicitly target the software classes of either the user or the machine.

  • HKEY_CURRENT_CONFIG references “\Registry\System\CurrentControlSet\Hardware Profiles\Current”. This path contains two symbolic links. The “CurrentControlSet” Key is a symlink to the control-set key that’s in current use, such as “\Registry\System\ControlSet001”. The “Current” Key is a symlink to the hardware profile that’s in current use, such as “\Registry\Machine\System\ControlSet001\Hardware Profiles\0001”.

  • HKEY_PERFORMANCE_DATA is implemented dynamically by the registry API. It does not correspond to a Key object in the kernel.

1 Like

I am not near a windows system for a few days and hope to try your code later.

1 Like

Someone suggested reinstalling python so I did. I upgraded from 3.10 to 3.12 and rebuilt my venv with new flask install and it works!

1 Like