I need to get the Display Name of the current user, and cannot find a solution that always works. Just for clarity, I'm not looking for the username. I need the "John Doe". The value displayed on the Start Menu.
There are many posts about this question however none have solved my problem.
Get Windows User Display Name
How do I get the AD Display Name of the currently logged in user
These two posts lead me to:
PrincipalContext context = domain.Equals(Environment.MachineName, StringComparison.CurrentCultureIgnoreCase) ?
new PrincipalContext(ContextType.Machine) :
new PrincipalContext(ContextType.Domain, domain);
UserPrincipal userPrincipal = new UserPrincipal(context) { SamAccountName = username };
PrincipalSearcher searcher = new PrincipalSearcher(userPrincipal);
userPrincipal = searcher.FindOne() as UserPrincipal;
string displayName = userPrincipal.DisplayName;
And this code works for the most part. However if the user's has disabled/stopped the Server service on his/her computer I get an exception saying "The Server service is not started."
System.DirectoryServices.AccountManagement.UserPrincipal.Current.DisplayName
Same error.
How to get logged-in user's full name in windows?
StringBuilder name = new StringBuilder(1024);
uint userNameSize = (uint)name.Capacity;
const int NameDisplay = 3;
GetUserNameEx(NameDisplay, name, ref userNameSize)
Returns no error, but an empty string if the user is not on a domain.
How do you read the user's display (first and last) name on all versions of Windows reliably?
// get SAM compatible name <server/machine>\\<username>
if (0 != GetUserNameEx(2, username, ref userNameSize))
{
IntPtr bufPtr;
try
{
string domain = Regex.Replace(username.ToString(), #"(.+)\\.+", #"$1");
DirectoryContext context = new DirectoryContext(DirectoryContextType.Domain, domain);
DomainController dc = DomainController.FindOne(context);
if (0 == NetUserGetInfo(dc.IPAddress,
Regex.Replace(username.ToString(), #".+\\(.+)", "$1"),
10, out bufPtr))
{
var userInfo = (USER_INFO_10) Marshal.PtrToStructure(bufPtr, typeof (USER_INFO_10));
return Regex.Replace(userInfo.usri10_full_name, #"(\S+), (\S+)", "$2 $1");
}
}
finally
{
NetApiBufferFree(out bufPtr);
}
}
With the above I get an ActiveDirectoryObjectNotFoundException with message "Domain controller not found in the domain.." when DomainController.FindOne is called.
I haven't found a registry setting for the display name.
I'm don't know what else to try. Please help.
All of the above methods will only work if you are on a domain. If you are not, then you must rely on the local user account store. The following details how to retrieve this info: How can I get a list Local Windows Users (Only the Users that appear in the windows Logon Screen). In a domain situation though, the users account will not be in the local store.
If you are on a domain but not connected to the domain controller, the Display Name will not be readily available to you. This information is stored on the domain controller, not the local user's computer. If your users are on a domain, they really shouldn't be able to disable the Server service(use GPOs). Also, they lose much more than the ability to retrieve their user account by disabling that service.
I would check for domain availability before trying to get the display name. If it fails, display a message indicating the failure. There are potentially too many edges cases here to make it work accounting for all of them. Go with the scenario that you intend the program to be used under, and give an error message for the others.
Related
Question: Is it possible to determine whether an account is a service account in Active Directory using C# LDAP? If yes, how?
Context: I have a program that is retrieving all objects of schema class type USER, GROUP, COMPUTER, FOREIGN SECURITY PRINCIPAL, and CONTACT. Currently, a service account is identified by string parsing the canonical name for 'service account'. I do not like this solution because string parsing is dependent on a folder location in the hierarchy that literally says 'service account'. It seems possible that a service account could be created and then placed in a folder path that does not include the string 'service account'. Unfortunately, I cannot test this because I am not an AD admin.
I have browsed around online without any luck so I am not sure if it is even possible.
Update:
Per Microsoft, it appears that the service account is contained in objectClass msDS-ManagedServiceAccount. However, when I set the DirectoryEntry filter to msDS-ManagedServiceAccount, no results are returned.
directoryEntry = new DirectoryEntry(strActiveDirectoryHost, null, null, AuthenticationTypes.Secure);
string strDsFilter = "(objectClass=msDS-ManagedServiceAccount)";
DirectorySearcher directorySearcher = new DirectorySearcher(directoryEntry)
{
Filter = strDsFilter,
SearchScope = SearchScope.Subtree,
PageSize = intActiveDirectoryPageSize,
};
return searchResultCollection = directorySearcher.FindAll();
I have testing your code, and it does in fact return results in my environment. A few things to note:
Be sure that strActiveDirectoryHost is formatted correctly. The format should be LDAP://DC=contoso,DC=com
Check that you are searching from the root (or high enough to find the accounts you are looking for). MSAs are under the Managed Service Accounts container under the domain NC (i.e. LDAP://CN=Managed Service Accounts,DC=contoso,DC=com)
In my tests, I call new DirectoryEntry() with only the path. Not sure if passing AuthenticationTypes.Secure is causing an issue for you
The objectClass you have is correct.
So I am working on this to get the MSA as well as create them. I am able to get the MSA using the System.DirectoryServices.AccountManagement namespace, still working on creating it (unsure if this is really possible)
But for finding the accounts which are MSAs you can use the below code
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain, sDomain, sDefaultOU, ContextOptions.SimpleBind, sServiceUser, sServicePassword);
GroupPrincipal currentGroup = GroupPrincipal.FindByIdentity(oPrincipalContext, "YourGroupName");
foreach (Principal a_principal in currentGroup.GetMembers())
{
if (a_principal.StructuralObjectClass == "msDS-ManagedServiceAccount")
{
Console.Write(a_principal.SamAccountName); //To get the name
ComputerPrincipal oComputerPrincipal = ComputerPrincipal.FindByIdentity(oPrincipalContext, a_principal.Name); //creating a computerprincipal to get more details about the MSA
}
}
You can use the above logic and create a Principal for the user account and get the structural object class for that account to find out if it is MSA.
Something like this:
UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPrincipalContext, sUserName);
if (oUserPrincipal.StructuralObjectClass == "msDS-ManagedServiceAccount")
{
Console.Write(oUserPrincipal.SamAccountName); //To get the samaccountname
}
I try to authenticate users belonging to remote ActiveDirectory from my machine, which is not the same domain as the current machine or user domain. There will be no trust between my machine and remote ActiveDirectory machine.
Initial Try
I tried to authenticate a user(Input: sAMAccountName, machine's ipaddress, machine's domain username("Administrator") and machine's password(***). Able to get result that the user with 'sAMAccountName' do exist in ActiveDirectory.
My Requirement:
Imagine that already a user("qwerty") is created in ActiveDirectory
From my local machine, I will have the following information,
a. Remote ActiveDirectory ipaddress
b. Remote ActiveDirectory machine's username and password.
c. Username and password of User "qwerty"
I need to check whether User "qwerty" is present in remote ActiveDirectory's users list and validate whether the password entered is same in ActiveDirectory's Users list
Code I tried:
DirectoryEntry entry = new DirectoryEntry("LDAP://ipaddress/DC=dinesh,DC=com", name, password);
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "(sAMAccountName=" + name + ")";
try
{
SearchResult adsSearchResult = adsSearcher.FindOne();
isValid = true;
adsEntry.Close();
}
catch (Exception ex)
{
adsEntry.Close();
}
Do I need to create a trust between local machine and remote ActiveDirectory machine before validating Users in a remote ActiveDirectory? If yes please tell how it can be done;
After creating trust, how can I validate Users?
===========================================================================
I am able to use the solution suggested by Rainer, but with a new problem. When I create a new user via C# code from a different machine, then some properties do not set properly.
Does this need to be set compulsorily while creating user?
First some basics (independent of this question)
Authentication
The system checks if Bob is really Bob. In an Active Directory environment, this is usually done with a domain login from the workstation, Bob enters his username and password, and he gets a Kerberos ticket. Later, if he wants to access e.g. a file share on a remote fileserver, he does not need to login anymore, and can access the files without entering username/password.
Authorization
The system checks which resources Bob is allowed to access. Usually Bob is in domain groups, and a group is in the ACL (access control list) of the resource.
If there are multiple trusting domains, Bob needs to login in one domain, and can access resources in all other domains.
This is one of the main reasons using Active Directory: single sign on
Checking if user / password is valid
If you have a username and password and want to check if the password is valid, you have to do a login to the domain. There is no way of just “checking if the password is correct”.
Login means: if there is a security policy “lock account if more than 3 invalid logins”, the account will be locked out checking with wrong password, even if you “only want to check the user+password”.
Using .NET Directory Service functions
I assume here that the process is either run by a human account as a normal program, or the program is a Windows service or a scheduled task which runs under a domain “technical user” account. In this case, you do not need to provide credentials for using the AD functions. If accessing other trusting AD domains, this is also true.
If you want to login to a “foreign domain”, and there is no trust, you need to provide a username+password (as in your code).
"Manually" authenticating a user
Normally, this should not be needed. Example: ASP.NET intranet usage. The user access a web application on the current domain or trusting domain, the authentication is done “in the background” by browser and IIS (if integrated Windows authentication is on). So you never need to handle user passwords in the application.
I don’t see many use cases where a password is handled by code.
One may that your program is a helper tool for storing emergency user accounts/passwords. And you want to check periodically if these accounts are valid.
This is a simple way to check:
using System.DirectoryServices.AccountManagement;
...
PrincipalContext principalContext =
new PrincipalContext(ContextType.Domain, "192.168.1.1");
bool userValid = principalContext.ValidateCredentials(name, password);
One can also use the older, raw ADSI functions:
using System.DirectoryServices;
....
bool userOk = false;
string realName = string.Empty;
using (DirectoryEntry directoryEntry =
new DirectoryEntry"LDAP://192.168.1.1/DC=ad,DC=local", name, password))
{
using (DirectorySearcher searcher = new DirectorySearcher(directoryEntry))
{
searcher.Filter = "(samaccountname=" + name + ")";
searcher.PropertiesToLoad.Add("displayname");
SearchResult adsSearchResult = searcher.FindOne();
if (adsSearchResult != null)
{
if (adsSearchResult.Properties["displayname"].Count == 1)
{
realName = (string)adsSearchResult.Properties["displayname"][0];
}
userOk = true;
}
}
}
If your real requirement is actually a validity check of user+password, you can do it in one of these ways.
However, if it is a "normal application", which just wants to check if the entered credentials are valid, you should rethink your logic. In this case, you better should rely on the single sign on capabilities of AD.
If there are further questions, please comment.
b. Remote ActiveDirectory machine's username and password.
This sounds a bit unclear. I assume you mean "a username and corresponding password in the remote domain".
There is also the concept of a machine account, which is the hostname appended with $. But that's another topic.
Creating new user
Option 1
using (DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://192.168.1.1/CN=Users,DC=ad,DC=local",
name, password))
{
using (DirectoryEntry newUser = directoryEntry.Children.Add("CN=CharlesBarker", "user"))
{
newUser.Properties["sAMAccountName"].Value = "CharlesBarker";
newUser.Properties["givenName"].Value = "Charles";
newUser.Properties["sn"].Value = "Barker";
newUser.Properties["displayName"].Value = "CharlesBarker";
newUser.Properties["userPrincipalName"].Value = "CharlesBarker";
newUser.CommitChanges();
}
}
Option 2
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, "192.168.1.1",
"CN=Users,DC=ad,DC=local", name, password))
{
using (UserPrincipal userPrincipal = new UserPrincipal(principalContext))
{
userPrincipal.Name = "CharlesBarker";
userPrincipal.SamAccountName = "CharlesBarker";
userPrincipal.GivenName = "Charles";
userPrincipal.Surname = "Barker";
userPrincipal.DisplayName = "CharlesBarker";
userPrincipal.UserPrincipalName = "CharlesBarker";
userPrincipal.Save();
}
}
I leave as an exercise to you to find out which attribute goes into which User dialog entry field :-)
I'm trying to query a domain to determine if:
User is a valid user (and has the correct password)
User is enabled
User belongs to group x
My development machine does not belong to this domain.
I want to specify the username and password via my application
I'm using the System.DirectoryServices.AccountManagement namespace as this seems to be the most efficient way doing this, however I've struggling to get even the most basic of information out of my domain controller.
I can explore LDAP via another tool.
First test is to collect user information, the code below returns null on user.
The user however is valid.
What am I doing wrong?
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "server","CN=Users,DC=doom,DC=home", "ldapuser","password");
// get user contect
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, IdentityType.Name, username);
//is user locked?
var locked = user.Enabled;
Update:
Having defined the bind method as below, I now receive error
"Information about the domain could not be retrieved (1355)."
var ctx = new PrincipalContext(ContextType.Domain, "server", "DC=doom,DC=home", ContextOptions.SimpleBind, "ldapuser", "password");
Sorted.
This answer resolves the two issues I came across when attempting to connect to a domain controller that I am not a member of.
This article get me the final answer:
http://elegantcode.com/2009/03/21/one-scenario-where-the-systemdirectoryservices-accountmanagement-api-falls-down/
you need to define the Bind in the context (i.e. ContextOptions.SimpleBind)
You must set up the domain server in your Network adaptors DNS settings as the first DNS server to use.
I can now connect to my AD and collect data.
I have a client that's utilizing a windows service I wrote that polls a specified active directory LDAP server for users in specified groups within that LDAP server.
Once it finds a user, it fills out the user information (i.e. username, email, etc.) and attempts to retrieve the user's domain within that LDAP server.
When I attempt to retrieve the user's domain for this specific client, I'm hitting a DirectoryServicesCOMException: Logon failure: unkonwn user name or bad password.
This exception is being thrown when I attempt to reference a property on the RootDSE DirectoryEntry object I instantiate.
This client has a Forest with two roots, setup as follows.
Active Directory Domains and Trusts
ktregression.com
ktregression.root
I assume this is the issue.
Is there any way around this? Any way to still retrieve the netbiosname of a specific domain object without running into this exception?
Here is some sample code pointing to a test AD server setup as previously documented:
string domainNameLdap = "dc=tempe,dc=ktregression,dc=com";
DirectoryEntry RootDSE = new DirectoryEntry (#"LDAP://10.32.16.6/RootDSE");
DirectoryEntry servers2 = new DirectoryEntry (#"LDAP://cn=Partitions," + RootDSE.Properties["configurationNamingContext"].Value ); //*****THIS IS WHERE THE EXCEPTION IS THROWN********
//Iterate through the cross references collection in the Partitions container
DirectorySearcher clsDS = new DirectorySearcher(servers2);
clsDS.Filter = "(&(objectCategory=crossRef)(ncName=" + domainNameLdap + "))";
clsDS.SearchScope = SearchScope.Subtree;
clsDS.PropertiesToLoad.Add("nETBIOSName");
List<string> bnames = new List<string>();
foreach (SearchResult result in clsDS.FindAll() )
bnames.Add(result.Properties["nETBIOSName"][0].ToString());
It seems that the user account with which the Active Directory tries to authenticate "you" does not exist as your DirectoryServicesCOMException reports it.
DirectoryServicesCOMException: Logon failure: unkonwn user name or bad password.
Look at your code sample, it seems you're not using impersonation, hence the security protocol of the Active Directory take into account the currently authenticated user. Make this user yourself, then if you happen not to be defined on both of your domain roots, one of them doesn't know you, which throws this kind of exception.
On the other hand, using impersonation might solve the problem here, since you're saying that your Windows Service account has the rights to query both your roots under the same forest, then you have to make sure the authenticated user is your Windows Service.
In clear, this means that without impersonation, you cannot guarantee that the authenticated user IS your Windows Service. To make sure about it, impersonation is a must-use.
Now, regarding the two roots
ktregression.com;
ktregression.root.
These are two different and independant roots. Because of this, I guess you should go with two instances of the DirectoryEntry class fitting one for each root.
After having instantiated the roots, you need to search for the user you want to find, which shall be another different userName than the one that is impersonated.
We now have to state whether a user can be defined on both roots. If it is so, you will need to know when it is better to choose one over the other. And that is of another concern.
Note
For the sake of simplicity, I will take it that both roots' name are complete/full as you mentioned them.
private string _dotComRootPath = "LDAP://ktregression.com";
private string _dotRootRootPath = "LDAP://ktregression.root";
private string _serviceAccountLogin = "MyWindowsServiceAccountLogin";
private string _serviceAccountPwd = "MyWindowsServiceAccountPassword";
public string GetUserDomain(string rootPath, string login) {
string userDomain = null;
using (DirectoryEntry root = new DirectoryEntry(rootPath, _serviceAccountLogin, _serviceAccountPwd))
using (DirectorySearcher searcher = new DirectorySearcher()) {
searcher.SearchRoot = root;
searcher.SearchScope = SearchScope.Subtree;
searcher.PropertiesToLoad.Add("nETBIOSName");
searcher.Filter = string.Format("(&(objectClass=user)(sAMAccountName={0}))", login);
SearchResult result = null;
try {
result = searcher.FindOne();
if (result != null)
userDomain = (string)result.GetDirectoryEntry()
.Properties("nETBIOSName").Value;
} finally {
dotComRoot.Dispose();
dotRootRoot.Dispose();
if (result != null) result.Dispose();
}
}
return userDomain;
}
And using it:
string userDomain = (GetUserDomain(_dotComRoot, "searchedLogin")
?? GetUserDomain(_dotRootRoot, "searchedLogin"))
?? "Unknown user";
Your exception is thrown only on the second DirectoryEntry initilization which suggests that your default current user doesn't have an account defined on this root.
EDIT #1
Please see my answer to your other NetBIOS Name related question below:
C# Active Directory: Get domain name of user?
where I provide a new and probably easier solution to your concern.
Let me know if you have any further question. =)
I believe that the DirectoryEntry has properties to specify for an AD account that can perform LDAP queries or updates, you can also delegate that control down from your parent domain.
I am working on an C# and ASP.Net application, that uses Windows Authentication.
i.e. in Web.config:
<system.web>
<authentication mode="Windows" />
</system.web>
I want to get details for the current user (full name, email address, etc) from Active Directory.
I can get their pre Windows 2000 user login name (eg: SOMEDOMAIN\someuser) by using
string username = HttpContext.Current.Request.ServerVariables["AUTH_USER"];
I've worked out the LDAP query for the user, using their current login name (not their pre Windows 2000 user login name):
DirectorySearcher adSearch = new DirectorySearcher(
"(userprincipalname=someuser#somedomain.com.au)");
SearchResult adSearchResult = adSearch.FindOne();
However, I don't know how to either search AD for the user using their pre W2K login name, or get their login name in the 'someuser#somedomain.com.au' format.
Any ideas?
The "pre Windows 2000" name i.e. DOMAIN\SomeBody, the Somebody portion is known as sAMAccountName.
So try:
using(DirectoryEntry de = new DirectoryEntry("LDAP://MyDomainController"))
{
using(DirectorySearcher adSearch = new DirectorySearcher(de))
{
adSearch.Filter = "(sAMAccountName=someuser)";
SearchResult adSearchResult = adSearch.FindOne();
}
}
someuser#somedomain.com.au is the UserPrincipalName, but it isn't a required field.
Alan already gave you the right answer - use the sAMAccountName to filter your user.
I would add a recommendation on your use of DirectorySearcher - if you only want one or two pieces of information, add them into the "PropertiesToLoad" collection of the DirectorySearcher.
Instead of retrieving the whole big user object and then picking out one or two items, this will just return exactly those bits you need.
Sample:
adSearch.PropertiesToLoad.Add("sn"); // surname = last name
adSearch.PropertiesToLoad.Add("givenName"); // given (or first) name
adSearch.PropertiesToLoad.Add("mail"); // e-mail addresse
adSearch.PropertiesToLoad.Add("telephoneNumber"); // phone number
Those are just the usual AD/LDAP property names you need to specify.
Add reference to COM "Active DS Type Library"
Int32 nameTypeNT4 = (int) ActiveDs.ADS_NAME_TYPE_ENUM.ADS_NAME_TYPE_NT4;
Int32 nameTypeDN = (int) ActiveDs.ADS_NAME_TYPE_ENUM.ADS_NAME_TYPE_1779;
Int32 nameTypeUserPrincipalName = (int) ActiveDs.ADS_NAME_TYPE_ENUM.ADS_NAME_TYPE_USER_PRINCIPAL_NAME;
ActiveDs.NameTranslate nameTranslate = new ActiveDs.NameTranslate();
// Convert NT name DOMAIN\User into AD distinguished name
// "CN= User\\, Name,OU=IT,OU=All Users,DC=Company,DC=com"
nameTranslate.Set(nameTypeNT4, ntUser);
String distinguishedName = nameTranslate.Get(nameTypeDN);
Console.WriteLine(distinguishedName);
// Convert AD distinguished name "CN= User\\, Name,OU=IT,OU=All Users,DC=Company,DC=com"
// into NT name DOMAIN\User
ntUser = String.Empty;
nameTranslate.Set(nameTypeDN, distinguishedName);
ntUser = nameTranslate.Get(nameTypeNT4);
Console.WriteLine(ntUser);
// Convert NT name DOMAIN\User into AD UserPrincipalName Name.User#Company.com
nameTranslate.Set(nameTypeNT4, ntUser);
String userPrincipalName = nameTranslate.Get(nameTypeUserPrincipalName);
Console.WriteLine(userPrincipalName);
If you're using .NET 3.5 SP1+ the better way to do this is to take a look at the
System.DirectoryServices.AccountManagement namespace.
It has methods to find people and you can pretty much pass in any username format you want and then returns back most of the basic information you would need. If you need help on loading the more complex objects and properties check out the source code for http://umanage.codeplex.com its got it all.
Brent