How to change registry entries of different users? - c#

I am working on a C# desktop application and i require so store some settings individually for each user in the HKCU section of the registry for each user. the requirements are such that these settings cannot be stored in a file or the database and registry is the best solution if possible..
Is it not possible to use impersonate method as we might not know passwords of all the users. however we will have the administrator access when running the exe.
is there a way that with administrator access we can access HKCU section for each user and populate the settings there

According to your scenario the first option was to impersonate the user, but as you are not willing to adopt it, now we have another option, obtain the user's SID, and then write the data under the KEY_USERS registry because this path contains all users in the machine.
Following is the utility function to write a value to specific user's registry key, obviously you need to know his/her username.
public static bool AddUserData(string userName, string key, string value)
{
try
{
//Gets the SID of the desired user
NTAccount f = new NTAccount(userName);
SecurityIdentifier s = (SecurityIdentifier)f.Translate(typeof(SecurityIdentifier));
string sid = s.ToString();
//Define the path to the subkey
string path = #"SOFTWARE\Console\DefaultConfig";
//try to open the path
var reg = Registry.Users.OpenSubKey(string.Format("{0}\\{1}", sid, path), true);
if (reg == null)
{
//if not exists create that path for that user
reg = Registry.Users.CreateSubKey(string.Format("{0}\\{1}", sid, path));
}
//set value against key
reg.SetValue(key, value);
return true;
}
catch
{
return false;
}
}
feel free to check documentation of the Registry class so that you will learn better to read the value back, this is just starting direction for you, Also I have not tested the code but I am pretty sure this will help you a lot. The admin access for the program is must for this code to work, otherwise you are left with impersonation.

Related

Registry get directory owner programmatically using C#

I want to read programmatically the owner of a directory (and its subdirectories) of the windows registry using C#.
For example, assume my registry contains the directory HKEY_CURRENT_USER\Software\Microsoft which is owned by the user SYSTEM. A code example (leaving out the recursion over sub-directories of dir) how I intend to use it would be:
string dir = #"HKEY_CURRENT_USER\Software\Microsoft";
string owner = ReadRegOwner(dir); // owner is "SYSTEM"
However, I am not sure how to implement ReadRegOwner in C#. I have already found the RegistrySecurity class, but I am not sure how to use it to get the owner of a registry directory. It has the GetOwner member function, but that function requires an argument of type Type and I am not sure what to pass there.
Does anyone know how to implement this?
So, an implementation could look like:
string ReadRegOwner(string dir)
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(dir, false);
RegistrySecurity rs = key.GetAccessControl();
IdentityReference owner = rs.GetOwner(typeof(System.Security.Principal.NTAccount));
return owner.ToString();
}
Example:
string dir = #"Software\Microsoft";
string owner = ReadRegOwner(dir); // Looks in HKEY_CURRENT_USER
Of course, CurrentUser could be also replaced if a different base key than HKEY_CURRENT_USER is desired.

C# WPF - Registry Access not Allowed, even with Administrator Privileges?

I've recently been working on a very nice Registry Editor.
However, certain Registry keys, pointed out below in Regedit, will not show up in my program, as they raise an error of insufficient privileges when opened, and so are caught by error handling and skipped:
Regedit:
My program:
As you can see, the SECURITY key is missing, and the SAM key is not expandable, even though I am running the program with administrator privileges.
This can obviously be fixed by making fake keys and putting them there, and just displaying an empty default value for them, however that isn't a concrete solution, just a way to make it seem to the user as if the issue is solved.
I was wondering if there is a way to fix the issue in a concrete way, or in other words, to receive registry access to those keys?
All they display is an empty default value any way, including the expandable SAM key - it just has a subkey named 'SAM' with an empty default value as well.
However, to the user, it's much better if the program displays exactly as in Regedit, as it means that it's a fully functional piece of software.
Thanks for the help.
Edit (code included):
public static void TreeViewItemExpanded(TreeViewItem sender)
{
if (sender.Items[0] is string)
{
sender.Items.Clear();
RegistryKey expandedKey = (RegistryKey)sender.Tag;
foreach (string key in expandedKey.GetSubKeyNames().OrderBy(x => x)) try { sender.Items.Add(CreateTreeViewItem(expandedKey.OpenSubKey(key))); } catch { }
}
}
private static TreeViewItem CreateTreeViewItem(RegistryKey key)
{
TreeViewItem treeViewItem = new TreeViewItem() { Header = new RegistryEditor_RegistryStructure_TreeView() { Name = Path.GetFileName(key.ToString()) }, Tag = key };
try { if (key.SubKeyCount > 0) treeViewItem.Items.Add("Loading..."); } catch { }
return treeViewItem;
}
You did not supply sample code to your routine, but I have a suspision that you are using a default registry security descriptor.
You can specify a security descriptor for a registry key when you call the RegCreateKeyEx or RegSetKeySecurity function.
When you call the RegOpenKeyEx function, the system checks the requested access rights against the key's security descriptor. If the user does not have the correct access to the registry key, the open operation fails. If an administrator needs access to the key, the solution is to enable the SE_TAKE_OWNERSHIP_NAME privilege and open the registry key with WRITE_OWNER access.
This information is taken from: MSDN:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms724878(v=vs.85).aspx
In C# You should be using the Registery Permission Class
https://msdn.microsoft.com/en-us/library/system.security.permissions.registrypermission(v=vs.110).aspx
A good example of how to handle Registry Permissions can be found here:
https://msdn.microsoft.com/en-us/library/microsoft.win32.registrykey.setaccesscontrol(v=vs.110).aspx
you need enable SE_RESTORE_PRIVILEGE and SE_BACKUP_PRIVILEGE and use RegOpenKeyEx or ZwOpenKeyEx with REG_OPTION_BACKUP_RESTORE flag (but this will be work only begin from Windows 7 and later versions of Windows)
If this flag is set, the function ignores the samDesired parameter and
attempts to open the key with the access required to backup or restore
the key. If the calling thread has the SE_BACKUP_NAME privilege
enabled, the key is opened with the ACCESS_SYSTEM_SECURITY and
KEY_READ access rights. If the calling thread has the SE_RESTORE_NAME
privilege enabled, beginning with Windows Vista, the key is opened
with the ACCESS_SYSTEM_SECURITY, DELETE and KEY_WRITE access rights.
If both privileges are enabled, the key has the combined access rights
for both privileges.
for example
#define LAA(se) {{se},SE_PRIVILEGE_ENABLED|SE_PRIVILEGE_ENABLED_BY_DEFAULT}
#define BEGIN_PRIVILEGES(tp, n) static const struct {ULONG PrivilegeCount;LUID_AND_ATTRIBUTES Privileges[n];} tp = {n,{
#define END_PRIVILEGES }};
ULONG AdjustBackupRestore()
{
HANDLE hToken;
if (OpenProcessToken(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
BEGIN_PRIVILEGES(tp, 2)
LAA(SE_RESTORE_PRIVILEGE),
LAA(SE_BACKUP_PRIVILEGE),
END_PRIVILEGES
AdjustTokenPrivileges(hToken, FALSE, (::PTOKEN_PRIVILEGES)&tp, 0, 0, 0);
ULONG err = GetLastError();
CloseHandle(hToken);
return err;
}
return GetLastError();
}
if (!AdjustBackupRestore())//called once on startup
{
HKEY hKey;
if (!RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SECURITY\\SAM", REG_OPTION_BACKUP_RESTORE|REG_OPTION_OPEN_LINK, 0, &hKey))
{
RegCloseKey(hKey);
}
}
however for get full power for registry editor/viewer I be use native api

How can I set this registry value for my User from my installer

The problem from
https://stackoverflow.com/a/37859812/4878558
I need to set Registry value for current user, who launch the install up. Since install going for system mode - I don't know anything about current user
Also my code giving 'System.UnauthorizedAccessException'
SecurityIdentifier sID = WindowsIdentity.GetCurrent().User;
var subKey = Registry.Users.OpenSubKey(sID + "\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run");
subKey.SetValue("test", "test");
enter code here
As Ripple and I have both commented, there's no need for code. Go to the Registry view in the setup project, right-click on Software under HKEY_CURRENT_USER and add the key Microsoft, then Windows, the CurrentVersion, then Run, adding each key.
Then in the Run key view, right-click in the Name, View pane on the right and add new string value, the name being your name. The value, I assume, is the path to your exe, and (assuming it's in the Application folder) make the value [TARGETDIR]my.exe.
If your install is an "Everyone" install then there is a perfectly good reason why it cannot work. This is nothing to do with the code. In an Everyone install that custom action code is running with the System account (NOT the installing user) so you are trying to create a run key for the system account.
Here is how to write autostartup options:
const string AutorunRegistryKey = #"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run";
Registry.SetValue(AutorunRegistryKey, <AppName>, <PathToApplication>);
If you want to remove it from autostartup:
const string AutorunRelativePath = #"Software\Microsoft\Windows\CurrentVersion\Run\";
var key = Registry.CurrentUser.OpenSubKey(AutorunRelativePath, true);
if (key != null)
{
key.DeleteValue(<AppName>, false);
key.Close();
}

Permission For writing to LOCAL_MACHINE

I made an app that allows windows users to spoof Mac Address .
It works by adding "NetworkAdapter": "00ff00ff00ff" key/value pair to registry of the users selected nic.
The problem is that every time the app tries to make changes to windows registry Windows pop's up a warning dialog, e.g.:
but clicking continue will add the registry values successfully and the app functions normally.
What can i do/or add changes in my code to make the dialog box disappear or can i do it in a better way?
The app requires Admin Privileges
here's the git repo of the app
here's the method:
public void SetMac(string macAddress)
{
const string Name = #"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002bE10318}";
using (RegistryKey key0 = Registry.LocalMachine.OpenSubKey(Name, RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.FullControl))
{
string[] x = key0.GetSubKeyNames();
foreach (string name in x)
{
var var1 = Registry.LocalMachine.OpenSubKey(Name,RegistryKeyPermissionCheck.ReadWriteSubTree,RegistryRights.FullControl);
var v = var1.OpenSubKey(name, RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.FullControl);
var z = v.GetValue("DriverDesc");
if (comboBox1.Text == z.ToString() )
{
v.SetValue("NetworkAddress",comboBox2.Text);
MessageBox.Show(z.ToString());
}
v.Close();
var1.Close();
}
key0.Close();
}
}
You need to run your app under elevated privileges, see Requested registry access is not allowed.
The problem here is that the user does not have permission to open the target key for writing. As abatishchev has already suggested, you need to run the application elevated so that the user actually has Administrators group membership when the code is executed.
The reason that this looks like a CAS permission error is a design flaw in the RegistryKey.OpenSubKey method. It ought to throw an UnauthorizedAccessException when the target key cannot be opened for writing due to inadequate user permissions, but it actually throws a SecurityException instead. The problem ends up appearing to be due to insuffience CAS permissions when it is really the user, not the code, that lacks permissions to edit the key.

How can I convert from a SID to an account name in C#

I have a C# application that scans a directory and gathers some information. I would like to display the account name for each file. I can do this on the local system by getting the SID for the FileInfo object, and then doing:
string GetNameFromSID( SecurityIdentifier sid )
{
NTAccount ntAccount = (NTAccount)sid.Translate( typeof( NTAccount ) );
return ntAccount.ToString();
}
However, this does not work for files on a network, presumably because the Translate() function only works with local user accounts. I thought maybe I could do an LDAP lookup on the SID, so I tried the following:
string GetNameFromSID( SecurityIdentifier sid )
{
string str = "LDAP://<SID=" + sid.Value + ">";
DirectoryEntry dirEntry = new DirectoryEntry( str );
return dirEntry.Name;
}
This seems like it will work, in that the access to "dirEntry.Name" hangs for a few seconds, as if it is going off and querying the network, but then it throws a System.Runtime.InteropServices.COMException
Does anyone know how I can get the account name of an arbitrary file or SID? I don't know much about networking or LDAP or anything. There's a class called DirectorySearcher that maybe I'm supposed to use, but it wants a domain name, and I don't know how to get that either - all I have is the path to the directory I'm scanning.
See here for a good answer:
The best way to resolve display username by SID?
The gist of it is this bit:
string sid="S-1-5-21-789336058-507921405-854245398-9938";
string account = new System.Security.Principal.SecurityIdentifier(sid).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
This approach works for me for non-local SID's over the active directory.
The SecurityReference object's Translate method does work on non-local SIDs but only for domain accounts. For accounts local to another machine or in a non-domain setup you would need to PInvoke the function LookupAccountSid specifying the specific machine name on which the look up needs to be performed.
System.DirectoryServices.AccountManagement.UserPrincipal class (msdn link) has a static function FindByIdentity to convert an SID to a User object. It should be able to work both against the local machine or an LDAP/Active Directory server. I have only used it against active directory.
Here is an example that I have used in IIS:
// Set the search context to a specific domain in active directory
var searchContext = new PrincipalContext(ContextType.Domain, "YOURDOMAIN", "OU=SomeOU,DC=YourCompany,DC=com");
// get the currently logged in user from IIS
MembershipUser aspUser = Membership.GetUser();
// get the SID of the user (stored in the SecurityIdentifier class)
var sid = aspUser.ProviderUserKey as System.Security.Principal.SecurityIdentifier;
// get the ActiveDirectory user object using the SID (sid.Value returns the SID in string form)
var adUser = UserPrincipal.FindByIdentity(searchContext, IdentityType.Sid, sid.Value);
// do stuff to user, look up group membership, etc.
In C#, get the user SID and assign it to a string variable through:
string strUser = System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString();
You will need to use string because the ability to resolve to the UserName supports string. In other words, using var varUser will result in a namespace error.
string strUserName = new System.Security.Principal.SecurityIdentifier(strUser).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
You can also get account name of special accounts like "Everyone" with code like this that will work regardless of user's language settings:
SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
string everyone = everyoneSid.Translate(typeof(System.Security.Principal.NTAccount)).ToString();
Ooh, then it's possible that the LDAP call is not working because you might not be in an Active Directory environment. If this is the case, then each of your machines is responsible for its own identity store. And your first code sample is not working across the network because the machine on which you are executing your code does not know how to resolve the SID that only makes sense on the remote machine.
You really should check if your machines are a part of an Active Directory. You would know this during the logon process. Or you can check by right clicking on "My Computer", select "Properties", the "Computer Name" tab, then see if your computer is part of a domain.
Great. I cribbed some LookupAccountSid() code from here:
http://www.pinvoke.net/default.aspx/advapi32.LookupAccountSid
And that worked, though I had to provide the host name myself. In the case of a UNC path I can just take the first component of it. When it's a mapped drive, I use this code to convert the path to a UNC one:
http://www.wiredprairie.us/blog/index.php/archives/22
It seems to work, so that's how I'll do it, unless someone comes up with a situation in which the first component of a UNC path isn't the host name...
Thank you all for your help.
This one is a stumper. You are in an Active Directory environment right? Just checking:)
Anyhow, instead of binding with sid.Value,
string str = "LDAP://<SID=" + sid.Value + ">";
I would try converting the SID's byte array to an Octet String and bind with that instead.
There is a sweet example here on page 78. This will get you closer. To be honest, I've not tried binding with a SID before. But I've had success binding with a user's GUID though :)
Good luck and let me know how it goes.
Get the current domain:
System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain();
Get a directory entry from ldap and the domain name:
DirectoryEntry de = new DirectoryEntry(string.Format("LDAP://{0}", domain));
Get the sid from an ActiveDirectoryMembershipProvider ActiveDirectoryMembershipUser:
ActiveDirectoryMembershipUser user = (ActiveDirectoryMembershipUser)Membership.GetUser();
var sid = (SecurityIdentifier)user.ProviderUserKey;
Get the username from the SecurityIdentifier:
(NTAccount)sid.Translate(typeof(NTAccount));
Get directory search done on an activedirectory with the domain directory entry and username:
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = string.Format("(SAMAccountName={0})", username);
search.PropertiesToLoad.Add("Name");
search.PropertiesToLoad.Add("displayName");
search.PropertiesToLoad.Add("company");
search.PropertiesToLoad.Add("homePhone");
search.PropertiesToLoad.Add("mail");
search.PropertiesToLoad.Add("givenName");
search.PropertiesToLoad.Add("lastLogon");
search.PropertiesToLoad.Add("userPrincipalName");
search.PropertiesToLoad.Add("st");
search.PropertiesToLoad.Add("sn");
search.PropertiesToLoad.Add("telephoneNumber");
search.PropertiesToLoad.Add("postalCode");
SearchResult result = search.FindOne();
if (result != null)
{
foreach (string key in result.Properties.PropertyNames)
{
// Each property contains a collection of its own
// that may contain multiple values
foreach (Object propValue in result.Properties[key])
{
outputString += key + " = " + propValue + ".<br/>";
}
}
}
Depending on the data in your active directory, you will get a varied response in the output.
Here is a site that has all the user properties I needed:
For all the Windows developers, the answer is LookupAccountSid
LookupAccountSid(null, Sid, username, userSize, domainName, domainSize, sidType);
I am quite sure you will be able to use the accepted answer from here: Determine the LocalSystem account name using C#
Basically, you can translate an instance of the SecurityIdentifier class to type NTAccount, from which you can get the user name. In code:
using System.Security.Principal;
SecurityIdentifier sid = new SecurityIdentifier("S-1-5-18");
NTAccount acct = (NTAccount)sid.Translate(typeof(NTAccount));
Console.WriteLine(acct.Value);

Categories

Resources