I'm having a problem with the windows service I work on currently. Basically I store some values in HKCU registry (from a GUI tool run as administrator) and from within that GUI I am starting a service. The service uses SYSTEM account to run and I believe that's my problem - I can't access registry keys stored with my GUI tool inside the service, as it points to a different HKCU!
How can I "redirect" the service to use the HKCU of the user it was stored with? (Actually I can pass a user name to the service or SID if someone will point me how to retrieve it in my GUI, but I don't know what should I use to "change" the user to point to the correct one)
#EDIT
I use a static class to access registry, it is used by both GUI and Service and the function to retrieve the base key is (rootKey is string variable holding the subkey name):
private static RegistryKey GetBaseKey(bool writable = false)
{
try
{
RegistryKey reg = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64);
RegistryKey rk = reg?.OpenSubKey("SOFTWARE", writable)?.OpenSubKey(rootKey, writable);
return rk;
}
catch (Exception ex)
{
// handle exceptions later
}
return null;
}
I have found WindowsIdentity class which can provide a handle (AccessToken) for current user, should I pass it as an argument to my service and use this handle to impersonate inside the service?
#EDIT2
I have done some stuff but it doesn't work. What I tried:
CurrentUserToken = WindowsIdentity.GetCurrent().Token; // to get current identity token
then with ServiceController.Start I added CurrentUserToken.ToString()
as an argument. Within my service I initialized RegistryUserToken (IntPtr) with the passed value and I'm stuck at:
WindowsIdentity RegUser = new WindowsIdentity(RegistryUserToken)
throwing exception
Invalid token for impersonation - it cannot be duplicated
I tried the same with AccessToken of current instance of WindowsIdentity - same exception thrown
Can I at all go that way? Or should I try something different??
I can give you two options: impersonate that user if you have their credentials or use idea that HKCU is a symbolic link for one of the keys under HKEY_USERS.
For impersonating you can see this link.
If you know the SID of that user, then you can find it in there. You can get the SID as so:
var account = new NTAccount("usernameThatYouNeed");
var identifier = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
var sid = identifier.Value;
I prefer impersonate. The second option is for case if you don't know that user's credentials.
I dislike second option because it requires administrative rights to write in someone else's account.
I needed to read registry of another user from a service. This is how I got it working, but in this case I know exact user name. Other answers here helped a lot. Username and path are whatever you need.
NTAccount f = new NTAccount("yourUserName");
SecurityIdentifier s = (SecurityIdentifier)f.Translate(typeof(SecurityIdentifier));
String sidString = s.ToString();
Microsoft.Win32.RegistryKey OurKey = Microsoft.Win32.Registry.Users;
OurKey = OurKey.OpenSubKey(sidString + "\\SOFTWARE\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppContainer\\Mappings", true);
if (OurKey != null) {
foreach (string Keyname in OurKey.GetSubKeyNames())
{
Alright, I've managed to solve it going a bit different way. I've added SID variable to my Registry class and if it's not null then I open Users Registry Hive instead of HKCU. First I retrieve current's user SID (within my GUI application) using:
WindowsIdentity.GetCurrent().User.ToString();
which I pass as an argument to my service and set it for Registry class.
Related
Using WPF & C#, I can set all the attributes in Active Directory, but can't do the following :
1) Can't Set User Password
2) Can't Enable User
However, I can do the same thing manually!
Approach Tried:
1.
DirectoryEntry directoryEntry=
directoryEntry.Invoke("SetPassword", new object[] {myPass#x6712}); // To set password
directoryEntry.Properties["userAcountControl"].Value=0x0200; //To Enable User
2.
DirectoryEntry uEntry = new DirectoryEntry(userDn);
uEntry.Invoke("SetPassword", new object[] { password });
uEntry.Properties["LockOutTime"].Value = 0; //unlock account
3.
using (var context = new PrincipalContext( ContextType.Domain ))
{
using (var user = UserPrincipal.FindByIdentity( context, IdentityType.SamAccountName, userName ))
{
user.SetPassword( "newpassword" );
// or
user.ChangePassword( "oldPassword", "newpassword" );
user.Save();
}
}
ERROR ON PASSWORD SET: Exception has been thrown by the target invocation.
ERROR ON ENABLE USER: Access is denied.
NOTE: I'm using a Domain Admin User.
The program gives the exception in these above lines.
Please, Advice! Thanks in Advance !!
Maybe this is just a mistake in your question, but the code you show in your first example wouldn't compile because the password is not in quotes. It should be:
directoryEntry.Invoke("SetPassword", new object[] {"myPass#x6712"});
That code invokes IADsUser.SetPassword. The 'Remarks' in the documentation point to some prerequisites for it to work, namely, that it must be a secure connection. So it may have failed in setting up a secure connection. It would usually try Kerberos to do that, so something might have gone wrong there.
You can try specifically connecting via LDAPS (LDAP over SSL) by pointing it at port 636 (new DirectoryEntry("LDAP://example.com:636/CN=whatever,DC=example,DC=com")), but that requires that you trust the certificate that is served up. Sometimes it's a self-signed cert, so you would need to add the cert to the trusted certs on whichever computer you run this from.
Or, the account you are running it with does not have the 'Reset Password' permission on the account.
For enabling, the userAccountControl attribute is a bit flag, so you don't want to set it to 2, mostly because 2 (or more accurately, the second bit) means that it's disabled. So you want to unset the second bit. You would do that like this:
directoryEntry.Properties["userAcountControl"].Value =
(int) directoryEntry.Properties["userAcountControl"].Value & ~2;
Most of the time that will result in a value of 512 (NORMAL_ACCOUNT), but not necessarily. The account could have other bits set that you don't want to inadvertently unset.
You also need to call .CommitChanges() for the changes to userAcountControl to take effect:
directoryEntry.CommitChanges();
In a UWP app, I have enabled the User Account Information capability.
I need to get the username and the domain name (each of them separately) of the currently logged on user (The users are logged on with an in-premise Active Directory account - Not Azure AD).
For example, the user would log in to the Active Directory domain domain1 using the username user1. i.e. domain1\user1.
I am using the following code to try to get the required details:
IReadOnlyList<User> users = await User.FindAllAsync();
var user = users.FirstOrDefault();
// get domain
var data1 = await user.GetPropertyAsync(KnownUserProperties.DomainName);
string strDomainName = (string)data1;
// get username
var data2 = await user.GetPropertyAsync(KnownUserProperties.AccountName);
string strUserName = (string)data2;
Issues:
strDomainName returns domain1.com\user1. Why does this include the .com part for all our domains? On c# winforms applications we can easily get domain1\user1 without any issue.
strUserName returns an empty string. i.e. "". Why does this not return any value?
I also checked the following:
KnownUserProperties.FirstName returns an empty string. i.e. ""
KnownUserProperties.LastName returns an empty string. i.e. ""
KnownUserProperties.PrincipalName returns an empty string. i.e. ""
KnownUserProperties.ProviderName returns an empty string. i.e. ""
KnownUserProperties.GuestHost returns an empty string. i.e. ""
Is there anything else I need to enable similar to the User Account Information capability? Or are there any other permissions that need to be granted to the app to get this information?
I understand that I can get the value of strDomainName and perform string functions to get what I need. But I want to know if there is any way to get this information directly. Also curious why KnownUserProperties.AccountName and other properties listed above such as FirstName, LastName etc. just returns an empty string.
I am running the following version of Windows:
I have the following set as the Target version and Min Version:
To verify, I also tested with the UserInfo sample project by Microsoft from GitHub and I got the following output:
The following was automatically enabled in Settings > Privacy > Account Info.
TestApp is the app I tried with and User Info C# Sample is the sample app from GitHub:
Update:
After also enabling the Enterprise Authentication capability, KnownUserProperties.PrincipalName does return the expected value. i.e. user1#domain1.com.
However, other properties listed above such as FirstName, LastName etc. just returns an empty string and I am still unable to find any property that returns domain1\user1 (without the .com part)
The Information you are trying to access are not reliable, as they (as you mentioned) do not have to be set and also they can be restricted access to via privacy settings in general.
I had a similar problem and would advise you to use the UWP OneDrive API
using Microsoft.OneDrive.Sdk;
and then request wl.basic scope. This scope contains at least a reliable username.
public static async Task<bool> GetAuthenticatedClient()
{
string oneDriveConsumerBaseUrl = "https://api.onedrive.com/v1.0";
var scopes = new List<string>
{
"wl.signin",
"wl.basic",
};
Task authTask;
var onlineIdAuthProvider = new OnlineIdAuthenticationProvider(scopes.ToArray());
authTask = onlineIdAuthProvider.RestoreMostRecentFromCacheOrAuthenticateUserAsync();
oneDriveClient = new OneDriveClient(oneDriveConsumerBaseUrl, onlineIdAuthProvider);
AuthProvider = onlineIdAuthProvider;
try
{
await authTask;
if (!AuthProvider.IsAuthenticated)
{
return false;
}
}
catch (ServiceException exception)
{
// Swallow the auth exception but write message for debugging.
//Debug.WriteLine(exception.Error.Message);
return false;
}
return true;
}
As for the domain, I'm not sure, but you could try to access it via Environment.UserDomainName like described on MSDN or with Windows.Networking.Connectivity.NetworkInformation.GetHostNames() like described here.
I found another possible solution to this. If you are still debugging this locally or the app was already installed on the target machine, I could enable the capabality User Account Information but this was not propagated to the actual installed app:
when your search for your app name in the start menu and then right click the entry and select App settings you get something like this:
As soon as I enabled the highlighted option (basically it says 'Account information') it worked.
I'm working on a project where the client wants to restrict some content to only Active Directory users . Is there any way to identify that a SPUser is an AD user short of parsing the username string for the domain (or something along those lines). Something like SPUser.IsADUser would be awesome.
Edit
This seems to work, but I'm not sure if this is reliable enough? For this use case, identifying that a user is a windows user is enough (there are no local system accounts)
SPUser user = SPContext.Current.Web.CurrentUser;
string userName = user.LoginName.Substring(user.LoginName.IndexOf('|') + 1);
SPPrincipalInfo info = SPUtility.ResolveWindowsPrincipal(SPContext.Current.Site.WebApplication, userName, SPPrincipalType.User, false);
if(info != null){
//THIS IS A WINDOWS ACCOUNT
}
In my experience it is much better to use audiences for this purpose. You then can easily trim any web part using "Audience" property. You can read about audiences here. Of course it will only work if you have user profile synchronization configured.
I want to run this function, or at least the bit that deletes the machine account from AD with different credentials:
public static void DeleteMachineAccount(String MachineName)
{
String MachineLdapPath = LdapPath(MachineName);
String OuLdapPath = MachineLdapPath.Replace("CN=" + MachineName + ",", "");
Console.WriteLine(MachineLdapPath);
Console.WriteLine(OuLdapPath);
if (DirectoryEntry.Exists(MachineLdapPath))
{
try
{
DirectoryEntry MachineOu = new DirectoryEntry(OuLdapPath);
DirectoryEntry MachineToDelete = new DirectoryEntry(MachineLdapPath);
MachineOu.Children.Remove(MachineToDelete);
MachineToDelete.CommitChanges();
}
catch (Exception e)
{
Console.WriteLine(e.Message.ToString());
}
}
}
(The LdapPath function just returns an LDAP path for the machine name specified.)
How / where do I specify some different credentials to allow this to run? At the moment I get access denied, as the account I am using will not have permission to do this.
Thanks,
Ben
You can use the overload of the DirectoryEntry class that provides authentication. This will cause your LDAP query to be run from the DirectoryServices with this particular user's permission. A word of caution, in order to do this you'll need to pass credentials (which would need to be stored or entered by the user), so be careful in how you handle them. Storing them in plain text may cause system security problems.
New DirectoryEntry(ldapRoot, _activeDirectoryUsername, _activeDirectoryPassword);
You need to use impersonation. The easiest way to do this is to actually "borrow" the permission of whoever called this method. e.g., if this is invoked from a named pipe or WCF call, there are built-in ways to impersonate the caller and do this on their behalf.
How do I detect if my program runs in an Active Directory environment?
I'm using C# and .Net 2.0
Try getting Environment.UserDomainName and comparing it to Environment.MachineName. If the two are the same then it's likely that the user does not have a domain. If they are not the same then the user is logged into a domain which must have a directory server.
This code will check if the Computer itself is a member of a domain
using System.DirectoryServices.ActiveDirectory;
bool isDomain = false;
try
{
Domain.GetComputerDomain();
isDomain = true;
}
catch (ActiveDirectoryObjectNotFoundException)
{
}
However the computer can be in a domain, but the currently logged in user may be a local user account. If you want to check for this use the Domain.GetCurrentDomain() function
One way might be to query the LOGONSERVER environmental variable. That'll give the server name of your AD controller... Which, as far as I know, will be blank (or match current workstation? Not sure) if it isn't currently logged into a domain.
Example Usage:
string ADServer = Environment.GetEnvironmentVariable("LOGONSERVER");
I found something that works:
using System.Net.NetworkInformation;
IPGlobalProperties.GetIPGlobalProperties().DomainName;
Works with a local user and a domain user.
From http://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentry.path.aspx
To bind to the current domain using LDAP, use the path "LDAP://RootDSE", then get the default naming context and rebind the entry.
So without a domain the binding to "LDAP://RootDSE" should either fail or return nothing. I didn't try it for myself.
use System.DirectoryServices; // add reference to system.directoryservices.dll
...
DirectoryEntry ent = new DirectoryEntry("LDAP://RootDSE");
String str = ent.Properties["defaultNamingContext"][0];
DirectoryEntry domain = new DirectoryEntry("LDAP://" + str);
This is definitely a cleaner way of checking for an Active Directory than relying on an environment variable (which the user could delete or add to spoof the program).