1

I just discovered that the reason my code for detecting and changing key values wasn't working, was because it WAS working - just on a different user. Because my winform app will need elevated access for some operations, I have the following in my manifest per many guides saying this is necessary:

  <requestedExecutionLevel level="highestAvailable" uiAccess="false" />

Meanwhile, I have code for detecting and changing a value in the registry as follows:


            using (var hklm = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64))
            using (var F1key = hklm.OpenSubKey(@"SOFTWARE\Classes\TypeLib\{8cec5860-07a1-11d9-b15e-000d56bfe6ee}\1.0\0\win64"))
            {
                // EGADS! It's active!
                if (F1key != null)
                {
                    fckF1Status.Text = "F1 Help is on. Turning off";
                    F1key.SetValue("", "", RegistryValueKind.String);
                }
                else
                {
                    fckF1Status.Text = "F1 Help is off. Turning on";
                    F1key.SetValue("", "c:\windows\helppane.exe", RegistryValueKind.String);
                }
            }

The problem is that the changes only show up when I'm looking at regedit as an admin and it appears to be loading it into the "current user" branch of the admin and not the logged in user. How can I make sure registry changes to Registry.CurrentUser are for the logged in user and not the admin/elevated account?

not_a_generic_user
  • 1,906
  • 2
  • 19
  • 34
  • 1
    The `HKEY_CURRENT_USER` key is actually a "virtual" link to the subkey of `HKEY_USERS` that corresponds to that user. If you change a value via `HKEY_CURRENT_USER` it should also change in the corresponding subkey of `HKEY_USERS` because it actually points to the same part of the registry. However, when you write to the registry with elevated permissions, it's actually going to use the admin user. – Matthew Watson Sep 14 '21 at 13:47
  • Understood. I've looked around and been able to find the SID of the correct user to change in HKEY_USERS and now I have to find out how to change that registry entry directly. – not_a_generic_user Sep 14 '21 at 13:50
  • Have a look at the (old) answers on this other site; it might help: https://superuser.com/questions/1218413/write-to-current-users-registry-through-a-different-admin-account – Matthew Watson Sep 14 '21 at 13:52
  • It's probably easier to request elevation after starting, rather than trying to downgrade privilege. But you could use [`CreateRestrictedToken`](https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-createrestrictedtoken) then [`WindowsIdentity.RunImpersonated`](https://learn.microsoft.com/en-us/dotnet/api/system.security.principal.windowsidentity.runimpersonated?view=netframework-4.8) using that token – Charlieface Sep 14 '21 at 14:00

1 Answers1

0

The answer was that HKCU is just an alias and you need to work with Registry.Users instead. To do that, you have to determine who the current user actually is which was done like so (from How to get current windows username from windows service in multiuser environment using .NET):


        private string loggedInUser;
        private SecurityIdentifier loggedInSID;
        private string loggedInSIDStr;


        [DllImport("Wtsapi32.dll")]
        private static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WtsInfoClass wtsInfoClass, out IntPtr ppBuffer, out int pBytesReturned);
        [DllImport("Wtsapi32.dll")]
        private static extern void WTSFreeMemory(IntPtr pointer);

        private enum WtsInfoClass
        {
            WTSUserName = 5,
            WTSDomainName = 7,
        }

        private static string GetUsername(int sessionId, bool prependDomain = true)
        {
            IntPtr buffer;
            int strLen;
            string username = "SYSTEM";
            if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, WtsInfoClass.WTSUserName, out buffer, out strLen) && strLen > 1)
            {
                username = Marshal.PtrToStringAnsi(buffer);
                WTSFreeMemory(buffer);
                if (prependDomain)
                {
                    if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, WtsInfoClass.WTSDomainName, out buffer, out strLen) && strLen > 1)
                    {
                        username = Marshal.PtrToStringAnsi(buffer) + "\\" + username;
                        WTSFreeMemory(buffer);
                    }
                }
            }
            return username;
        }

        private void YOUR_CONSTRUCTOR(object sender, EventArgs e)
        {
            loggedInUser = GetUsername(Process.GetCurrentProcess().SessionId);
            NTAccount f = new NTAccount(loggedInUser);
            loggedInSID = (SecurityIdentifier)f.Translate(typeof(SecurityIdentifier));
            loggedInSIDStr = loggedInSID.ToString();

        }

The only modificaations I made from the linked answer was to to set some variables in my class and assign them in the constructor. Later, when I wanted to set the values for the user, I used this:

            using (var hklm = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Registry64))
            {
                using (hklm.CreateSubKey(loggedInSIDStr + @"\SOFTWARE\Classes\Typelib\{8cec5860-07a1-11d9-b15e-000d56bfe6ee}\1.0\0\win32")) { };

                using (hklm.CreateSubKey(loggedInSIDStr + @"\SOFTWARE\Classes\Typelib\{8cec5860-07a1-11d9-b15e-000d56bfe6ee}\1.0\0\win64")) { };
            }

This seems to do the job. You still won't see the changes in HKCU if you opened Regedit as admin because it will point to the admin's users branch of the registry, but it will show up in the proper user's branch.

not_a_generic_user
  • 1,906
  • 2
  • 19
  • 34