How can I get the distinguished name from Active Directory of the currently logged in user in C#?
Check following snippet. You have pass to Identity.Name from IPrincipal. I assume that the user is already authenticated in Active Directory (ie. using standard IIS authorization methods).
private string GetUserName(string identity)
{
if (identity.Contains("\\"))
{
string[] identityList = identity.Split('\\');
return identityList[1];
}
else
{
return identity;
}
}
public string GetUserDn(string identity)
{
var userName = GetUserName(identity);
using (var rootEntry = new DirectoryEntry("LDAP://" + adConfiguration.ServerAddress, null, null, AuthenticationTypes.Secure))
{
using (var directorySearcher = new DirectorySearcher(rootEntry, String.Format("(sAMAccountName={0})", userName)))
{
var searchResult = directorySearcher.FindOne();
if (searchResult != null)
{
using (var userEntry = searchResult.GetDirectoryEntry())
{
return (string)userEntry.Properties["distinguishedName"].Value;
}
}
}
}
return null;
}
Why wouldn't you just use: System.DirectoryServices.AccountManagement.UserPrincipal.Current.DistinguishedName
Related
I have created a AD forest that search for a user across all domains in the forest using its global catalog connection string.
I am trying to get thumbnailPhoto of AD user using c# code. But I did not get thumbnailPhoto property in result object even though it exist in AD.
I had verified the thumbnailPhoto prop in AD using powershell. Also I have verified it by getting using LDAP connection string. It both case I got the byte array.
Below is the code to get user and its properties and _configuration.GlobalCatalog returns the Global catalog connections string which is in format (GC://domain-name).
public Task<ProfileImage> GetProfileImageByEmail(string email)
{
var filterQuery = ("mail=" + email);
return Task.FromResult(GetProfileImageFromAD(filterQuery));
}
private ProfileImage GetProfileImageFromAD(string filterQuery)
{
var result = GetADUserDetails(filterQuery);
if (result == null)
return null;
if (result.Properties.Contains("thumbnailPhoto"))
{
var imageBytes = result.Properties["thumbnailPhoto"][0] as byte[];
if (imageBytes != null)
{
return new ProfileImage
{
Content = new MemoryStream(imageBytes),
ContentType = "image/jpeg"
};
}
}
return null;
}
private SearchResult GetADUserDetails(string filterQuery)
{
using (var userBinding = new DirectoryEntry(_configuration.GlobalCatalog))
{
using (DirectorySearcher adSearch = new DirectorySearcher(userBinding))
{
adSearch.ReferralChasing = ReferralChasingOption.All;
adSearch.Filter = filterQuery;
adSearch.PropertiesToLoad.Add("mail");
adSearch.PropertiesToLoad.Add("sn");
adSearch.PropertiesToLoad.Add("givenName");
adSearch.PropertiesToLoad.Add("thumbnailPhoto");
return adSearch.FindOne();
}
}
}
Any help is appreciated.
Update:
On a domain controller open ADSIEdit, connect to Schema Naming Context, find attribute CN=Picture,CN=Schema,CN=Configuration... and go to it's properties. Verify that isMemberOfPartialAttributeSet is set to TRUE
I'm writing some kind of a mini AD tool (with VS-C#) to our organization and got into an issue.
I have a main function that searches the user (when I click on it in a listview) and some functions that manipulate the user's object.
public DirectoryEntry GetUser(string username)
{
try
{
Forest currentForest = Forest.GetCurrentForest();
GlobalCatalog gc = currentForest.FindGlobalCatalog();
using (DirectorySearcher searcher = gc.GetDirectorySearcher())
{
searcher.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(samaccountname=" + username + "*))";
SearchResult results = searcher.FindOne();
if (!(results == null))
{
DirectoryEntry de = new DirectoryEntry(results.Path, strAdminUser, strAdminPass, AuthenticationTypes.Secure);
de.RefreshCache(new string[] { "canonicalName" });
de.Path = de.Properties["canonicalName"].Value.ToString();
de.CommitChanges();
return de;
}
else
{
return null;
}
}
}
catch (DirectoryServicesCOMException e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return null;
}
}
and here's an example of a function that checks if the user is locked:
public bool IsUserLocked(string username)
{
try
{
DirectoryEntry de = GetUser(username);
string attName = "msDS-User-Account-Control-Computed";
de.RefreshCache(new string[] { attName });
const int UF_LOCKOUT = 0x0010;
int userFlags = /*(int)*/Convert.ToInt32(de.Properties[attName].Value);
if ((userFlags & UF_LOCKOUT) == UF_LOCKOUT)
{
return true;
}
de.Dispose();
return false;
}
catch (DirectoryServicesCOMException e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return false;
}
}
The function that checks the locked status of a user always fails with an error: "Unspecified error", but if I'm not changing the Directory Entry's path in the first function I get "The server is unwilling to process the request" error (I'm using proper service username and password with all the permissions needed) but still it happens.
Can someone spot the issue?
How about using System.DirectoryServices.AccountManagement namespace? If you have no issue using the new namespace, there's a simpler way to check if user account is locked and unlock if needed.
public bool IsUserLocked (string username)
{
using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "yourdomain.com")
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(ctx, username)
{
if (user != null) return user.IsAccountLockedOut();
}
}
return null;
}
And similarly, you can unlock the user account if needed.
...
if (user != null)
{
user.UnlockAccount();
user.Save();
}
Got it...
This has solved my issue:
de.Path = results.Path.Replace("GC://DCNAME.", "LDAP://");
Since I'm searcing on the Global Catalog, I had to replace a portion in the path to match it to the correct path:
public DirectoryEntry GetUser(string username)
{
try
{
Forest currentForest = Forest.GetCurrentForest();
GlobalCatalog gc = currentForest.FindGlobalCatalog();
using (DirectorySearcher searcher = gc.GetDirectorySearcher())
{
searcher.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(samaccountname=" + username + "*))";
SearchResult results = searcher.FindOne();
if (!(results == null))
{
DirectoryEntry de = new DirectoryEntry(results.Path, strAdminUser, strAdminPass, AuthenticationTypes.Secure);
de = new DirectoryEntry(results.Path);
de.Path = results.Path.Replace("GC://DCNAME.", "LDAP://");
de.CommitChanges();
//System.Windows.Forms.MessageBox.Show(de.Path);
return de;
}
else
{
return null;
}
}
}
catch (DirectoryServicesCOMException e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return null;
}
}
Now the path returns to the function called GetUser in the correct format :)
I need to let users bind there accounts to the Active Directory. This means that admin needs a GUI where he/she can write a Active Directory account like this : MyDomain\MyName and then get a validation if the users exists before save.
Im using this code to validate the name :
public static bool CheckActiveDirectoryAccount(string account)
{
string ADServer = null;
string ADDomain = null;
string ADUserName = null;
string ADUserPassword = null;
SetADSettings(out ADServer, out ADDomain, out ADUserName, out ADUserPassword);
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, ADServer, ADUserName, ADUserPassword))
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(context, account))
{
if(user != null)
return true;
else
return false;
}
}
}
The problem with this code is that there seems to be no way to check the user for a specific domain? Instead I have to input the server, if I try to input the domain instead there will be exception(Server not found).
How do I let the admin enter domain and username of a AD account and then check it against the AD?
I am able to pass the domain into the principalcontext without issue, I'm not passing in the server. I would expect this to work for you.
public static bool CheckActiveDirectoryAccount(string account, string domain)
{
using (var pc = new PrincipalContext(ContextType.Domain, domain))
{
// Find a user
UserPrincipal user = UserPrincipal.FindByIdentity(pc, account);
if (user == null)
return false;
return true;
}
}
I have noticed poor performance when passing in the NetBIOS domain name, though it does work. As a result I pass in the DNS domain name whenever possible.
I ended up with this :
public static string CheckActiveDirectoryAccount(string account)
{
UserPrincipal user;
PrincipalContext context;
List<string> userPrincipalNameList;
string ADServer = null;
string ADUserName = null;
string ADUserPassword = null;
string userAccount;
account = account.ToLower();
GetADSettings(out ADServer, out ADUserName, out ADUserPassword);
if (ADUserName.Length > 0)
context = new PrincipalContext(ContextType.Domain, ADServer, null, ADUserName, ADUserPassword);
else
context = new PrincipalContext(ContextType.Domain, ADServer);
using (context)
{
if((user = UserPrincipal.FindByIdentity(context, account)) == null)
{
if(account.Contains("\\"))
{
userPrincipalNameList = user.UserPrincipalName.Split('\\').ToList();
if (userPrincipalNameList.Count > 0)
user = UserPrincipal.FindByIdentity(context, userPrincipalNameList[0]);
}
}
if (user != null)
{
using (user)
{
userPrincipalNameList = user.UserPrincipalName.Split('#').ToList();
userAccount = userPrincipalNameList.First();
if (userPrincipalNameList.Count > 1)
userAccount = userPrincipalNameList.Last() + "\\" + userAccount;
if (user != null)
return userAccount.ToLower();
}
}
}
return string.Empty;
}
First of all, please forgive me if I'm not using the correct terminologies. Correct me wherever I'm using the wrong terminology.
The objective is to programmatically retrieve the lastLogon date of a given username.
We have what I believe is a forest; two AD servers like - adserver01.aa.mycompany.com and adserver02.aa.mycompany.com
I connected to these servers from a third machine using Microsoft's ADExplorer to inspect the objects. There I see some users having lastLogon date available in adserver01, but not in adserver02. For example, the value for lastLogon is 0x0 in adserver02 whereas it is a valid date in adserver01 for some users.
The code I've developed so far as a Windows Forms application, works fine if only one AD Server is involved. How do I check both servers and return the non-zero value if available, in either for lastLogon date attribute?
private static string GetLastActivityDate(string UserName)
{
string domainAndUserName = String.Format(#"LDAP://aa.mycompany.com/CN={0},OU=CLIENT_PROD,OU=clients.mycompany.com,DC=aa,DC=mycompany,DC=com", UserName);
string OUAdminUserName = "abc";
string OUAdminPassword = "xyz";
AuthenticationTypes at = AuthenticationTypes.Secure;
DateTime lastActivityDate;
string returnvalue;
long lastLogonDateAsLong;
using (DirectoryEntry entryUser = new DirectoryEntry(domainAndUserName, OUAdminUserName, OUAdminPassword, at))
using (DirectorySearcher mysearcher = new DirectorySearcher(entryUser))
try
{
using (SearchResultCollection results = mysearcher.FindAll())
{
if (results.Count >= 1)
{
DirectoryEntry de = results[0].GetDirectoryEntry();
lastLogonDateAsLong = GetInt64(de, "lastLogon");
try
{
if (lastLogonDateAsLong != -1)
{
lastActivityDate = DateTime.FromFileTime(lastLogonDateAsLong);
returnvalue = lastActivityDate.ToString();
}
else
{
returnvalue = "-Not available-";
}
}
catch (System.ArgumentOutOfRangeException aore)
{
returnvalue = "Not available";
}
}
else
{
returnvalue = string.Empty;
}
}
}
catch (System.DirectoryServices.DirectoryServicesCOMException dsce)
{
returnvalue = "- Not available -";
}
return returnvalue;
}
Thank you.
EDIT:
private static Int64 GetInt64(DirectoryEntry entry, string attr)
{
DirectorySearcher ds = new DirectorySearcher(
entry,
String.Format("({0}=*)", attr),
new string[] { attr },
SearchScope.Base
);
SearchResult sr = ds.FindOne();
if (sr != null)
{
if (sr.Properties.Contains(attr))
{
return (Int64)sr.Properties[attr][0];
}
}
return -1;
}
Forgot to mention, the AD schema, structure etc, looks exactly alike in the two servers.
Check this post
http://www.codeproject.com/Articles/19181/Find-LastLogon-Across-All-Windows-Domain-Controlle
I had the same issue but but only for one domain,
I solved it by using the following code however i'm checking the lastLogin of all users
public static Dictionary<string, DateTime> UsersLastLogOnDate()
{
var lastLogins = new Dictionary<string, DateTime>();
DomainControllerCollection domains = Domain.GetCurrentDomain().DomainControllers;
foreach (DomainController controller in domains)
{
try
{
using (var directoryEntry = new DirectoryEntry(string.Format("LDAP://{0}", controller.Name)))
{
using (var searcher = new DirectorySearcher(directoryEntry))
{
searcher.PageSize = 1000;
searcher.Filter = "(&(objectClass=user)(!objectClass=computer))";
searcher.PropertiesToLoad.AddRange(new[] { "distinguishedName", "lastLogon" });
foreach (SearchResult searchResult in searcher.FindAll())
{
if (searchResult.Properties.Contains("lastLogon"))
{
var lastLogOn = DateTime.FromFileTime((long)searchResult.Properties["lastLogon"][0]);
var username = Parser.ParseLdapAttrValue(searchResult.Properties["distinguishedName"][0].ToString());
if (lastLogins.ContainsKey(username))
{
if (DateTime.Compare(lastLogOn, lastLogins[username]) > 0)
{
lastLogins[username] = lastLogOn;
}
}
else
{
lastLogins.Add(username, lastLogOn);
}
}
}
}
}
}
catch (System.Runtime.InteropServices.COMException comException)
{
// Domain controller is down or not responding
Log.DebugFormat("Domain controller {0} is not responding.",controller.Name);
Log.Error("Error in one of the domain controllers.", comException);
continue;
}
}
return lastLogins;
}
On top of the code you can use the following to get all domains in a forest.
Forest currentForest = Forest.GetCurrentForest();
DomainCollection domains = currentForest.Domains;
foreach(Domain domain in domains)
{
// check code above
}
There may be a simpler approach? There is actually another attribute lastLogonTimestamp, added with 2003 domain level I think, that tries to keep a single, consistent value across the domain for the last login. Alas, it has a bizarre replication time pattern, and could be up to two weeks out of date.
Basically I found a post that has a solution for a problem we are having in our application and the solution was:
private static void listGroupMembers(string groupDistinguishedName, PrincipalContext ctx, List<UserPrincipal> users)
{
DirectoryEntry group = new DirectoryEntry("LDAP://" + groupDistinguishedName);
foreach (string dn in group.Properties["member"])
{
DirectoryEntry gpMemberEntry = new DirectoryEntry("LDAP://" + dn);
System.DirectoryServices.PropertyCollection userProps = gpMemberEntry.Properties;
object[] objCls = (userProps["objectClass"].Value) as object[];
if (objCls.Contains("group"))
listGroupMembers(userProps["distinguishedName"].Value as string, ctx, users);
if (!objCls.Contains("foreignSecurityPrincipal"))
{
UserPrincipal u = UserPrincipal.FindByIdentity(ctx, IdentityType.DistinguishedName, dn);
if(u!=null) // u==null for any other types except users
users.Add(u);
}
}
}
However I am trying to search a Local group so if I change the directory entry to say:
DirectoryEntry groupEntry =
new DirectoryEntry(string.Format("WinNT://{0}/{1},group", Environment.MachineName, groupName));
Then it doesn't work and it says that the property doesn't exist. How can I do the above but for a local group and user?
Basically to fix this I ended up doing:
protected bool IsUserInLocalGroup(string userName, string group)
{
using (DirectoryEntry computerEntry = new DirectoryEntry("WinNT://{0},computer".FormatWith(Environment.MachineName)))
using(DirectoryEntry groupEntry = computerEntry.Children.Find(group, "Group"))
{
foreach (object o in (IEnumerable)groupEntry.Invoke("Members"))
{
using (DirectoryEntry entry = new DirectoryEntry(o))
{
if (entry.SchemaClassName.Equals("User", StringComparison.OrdinalIgnoreCase) && entry.Name.Equals(userName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
return false;
}
}