I am iterating through some Active Directory PrincipalSearcher Principal results using the code below. In my foreach loop where I am assigning result properties to my _user object I can break in debug and see a result.EmailAddress value. When I try to assign result.EmailAddress to _user.EmailAdress the code will not even compile. I'm guessing EmailAddress is a dynamic property of result. Is there a way to check for this property so I can add the users AD email address to my _user object?
private static void GetAllActiveDirectoryUsers()
{
PrincipalContext context = new PrincipalContext(
ContextType.Domain, Environment.UserDomainName);
UserPrincipal user = new UserPrincipal(context);
// create a principal searcher for running a search operation
PrincipalSearcher pS = new PrincipalSearcher(user);
// run the query
PrincipalSearchResult<Principal> results = pS.FindAll();
foreach (Principal result in results)
{
Console.WriteLine(result.DisplayName);
Console.ReadKey();
User _user = new User();
_user.Description = result.Description;
_user.DisplayName = result.DisplayName;
_user.DistinguishedName = result.DistinguishedName;
_user.Guid = result.Guid ?? null;
_user.Name = result.Name;
_user.Sid = result.Sid?.ToString();
Users.Add(_user);
}
}
It's not a dynamic property. EmailAddress is a property of the UserPrincipal class, which inherits from Principal. Your result is actually of type UserPrincipal, which is why you see the property when you debug, but you are accessing it as type Principal, which doesn't have a property called EmailAddress, so it is not available to you in your code.
If you want access to the EmailAddress property, you need to cast your result to UserPrincipal. Since you are sure all of your results will be user objects, then you can do that in the foreach:
foreach (UserPrincipal result in results)
You will want to get the DirectoryEntry object from the Principal and query it's properties.
Something like this - assuming email is stored in the 'mail' attribute.
var directoryEntry = result.GetUnderlyingObject() as DirectoryEntry;
if (directoryEntry != null && directoryEntry.Properties.Contains("mail"))
{
_user.EmailAddress = directoryEntry.Properties[property].Value.ToString();
}
Here is an extension method that uses the above. It simply takes a string for the attribute that you are searching for.
public static string GetPropertyValue(this Principal principal, string property)
{
var directoryEntry = principal.GetUnderlyingObject() as DirectoryEntry;
if (directoryEntry != null && directoryEntry.Properties.Contains(property))
{
return directoryEntry.Properties[property].Value.ToString();
}
return null;
}
Related
What would be the best way in C# to use directory entry to find all users with the attribute wWWHomePage filled in.
I am able to see if a specific user has it but I have not used Directory Entry to search all users for something like this.
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, myDomain, Login.authUserName, Login.authPassword);
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, username);
if (user != null) {
DirectoryEntry de = (user.GetUnderlyingObject() as DirectoryEntry);
if (de != null) {
string whatIWant = de.Properties["wWWHomePage"].Value.ToString();
}
}
use DirectoryEntry with DirectorySearcher and specify the search Filter to get what you want.
the Filter template you want is :
(&(objectClass=user)(objectCategory=person)(PROPERTY_NAME=SEARCH_TERM))
where PROPERTY_NAME is the property you want to search in, and SEARCH_TERM is the value. you could use the * as a wildcard search, it would give you all objects that has this property.
here is a quick example :
// set the properties you need.
var propertiesToLoad = new string[] { "sAMAccountName", "wWWHomePage" };
using(var searcher = new DirectorySearcher(new DirectoryEntry(ldap), "(&(objectClass=user)(objectCategory=person)(wWWHomePage=*))", propertiesToLoad))
{
foreach (SearchResult result in searcher.FindAll())
{
if(result == null) continue;
var samAccount = result.Properties["sAMAccountName"][0];
var wWWHomePage = result.Properties["wWWHomePage"][0];
// complete the code with your logic
}
}
I'm trying to get all users from AD and their passwords expiration dates with the following code, but got the error.
System.InvalidOperationException: 'The Principal object must be persisted before this method can be called.'
The code:
PrincipalContext domain = new PrincipalContext(ContextType.Domain,"AAA","OU=USERS,OU=TEST,DC=AAA,DC=AA, DC=A");
UserPrincipal user = new UserPrincipal(domain);
PrincipalSearcher pS = new PrincipalSearcher(user);
foreach (UserPrincipal result in pS.FindAll())
{
if (result != null && result.DisplayName != null)
{
DirectoryEntry entry = (DirectoryEntry)user.GetUnderlyingObject();
IADsUser native = (IADsUser)entry.NativeObject;
Console.WriteLine(result.DisplayName, native.PasswordExpirationDate);
}
}
The exception means that you're calling GetUnderlyingObject() on a UserPrincipal object that hasn't been saved.
You're calling GetUnderlyingObject() on the user object that you created as the filter for your PrincipalSearcher. I think you intended to call it on result:
DirectoryEntry entry = (DirectoryEntry)result.GetUnderlyingObject();
Is it possible to get a user's email from Active Directory using employeenumber as the query term?
I am using C#'s System.DirectoryServices and am a little bit lost. The previous developer at my company was using this process, but he had an email and was querying for the employee number. I have changed it to what I believe it should be, but to be honest, I don't understand the code that well.
Is there something wrong with my code? every time i run it, I get a Null Reference error on the DirectoryEntry up_user = line. I assume it is because the previous line is not getting any entities.
Also, is there any good documentation on this topic? Everywhere I look, the posts are from 2011 or 2013.
I have the following:
try
{
string email = string.Empty;
ContextType authenticationType = ContextType.Domain;
PrincipalContext principalContext = new PrincipalContext(authenticationType, "MATRIC");
UserPrincipal userPrincipal = null;
userPrincipal = UserPrincipal.FindByIdentity(principalContext, empnum);
DirectoryEntry up_User = (DirectoryEntry)userPrincipal.GetUnderlyingObject();
DirectorySearcher deSearch = new DirectorySearcher(up_User);
SearchResultCollection results = deSearch.FindAll();
if (results != null && results.Count > 0)
{
ResultPropertyCollection rpc = results[0].Properties;
foreach (string rp in rpc.PropertyNames)
{
if (rp == "mail")
{
email = rpc["mail"][0].ToString();
}
}
if (email != string.Empty)
{
return email;
}
return null;
}
return null;
}
catch (Exception ex)
{
throw ex;
}
UserPrincipal.FindByIdentity only works for finding a user by what AD considers an identifying attribute. These are listed in the IdentityType enumeration. The employee number isn't one of those.
Are you using employeeId or employeeNumber in AD? They are different attributes, although both are just strings with no special meaning or restrictions in AD.
The employeeId attribute is exposed in the UserPrincipal class, so you can search by it with UserPrincipal as described in the answer here:
UserPrincipal searchTemplate = new UserPrincipal(principalContext);
searchTemplate.EmployeeID = employeeId;
PrincipalSearcher ps = new PrincipalSearcher(searchTemplate);
UserPrincipal user = (UserPrincipal)ps.FindOne();
Then you can use the EmailAddress property of the account you find (you don't need to do what you're doing with the DirectorySearcher).
var emailAddress user?.EmailAddress;
If you're using employeeNumber, then you will need to use DirectorySearcher to find it. Something like this:
var search = new DirectorySearcher(new DirectoryEntry("LDAP://yourdomain.com"));
search.Filter = $"(&(ObjectClass=user)(employeeNumber={employeeNumber}))";
search.PropertiesToLoad.Add("mail");
var result = search.FindOne();
string emailAddress = null;
if (result.Properties.Contains("mail")) {
emailAddress = result.Properties["mail"][0].Value as string;
}
I've read plenty of similar StackOverflow questions but none seem to address the issue I'm seeing. If I query for a user using userprincipalname I get back a search result with exactly 34 properties. None of the custom properties are returned. If I query again using a custom property like employeeNumber I get back a result with 71 properties. All custom properties are included.
My issue is that I don't have the employeeNumber at run time, just the userprincipalname. I need to get back all of the custom properties all of the time. Hopefully that makes sense. Here's my practice code:
string sid = "";
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
{
UserPrincipal user = UserPrincipal.Current;
//sid = user.SamAccountName;
sid = user.UserPrincipalName;
//sid = user.Sid.ToString();
DirectoryEntry entry = user.GetUnderlyingObject() as DirectoryEntry;
if (entry.Properties.Contains("employeeNumber"))
{
//this doesn't work
}
}
DirectoryEntry ldapConnection = new DirectoryEntry("companyname.com");
ldapConnection.Path = "LDAP://DC=companyname,DC=com";
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
DirectorySearcher search = new DirectorySearcher(ldapConnection);
search.Filter = string.Format("(&(ObjectClass=user)(userprincipalname={0}))", sid); // <-- this doesn't get custom properties
//search.Filter = string.Format("(employeeNumber={0})", "11663"); <-- this works
var result = search.FindOne(); // FindOne();
if (result.Properties.Contains("employeeNumber"))
{
//this never happens either :(
}
The above never returns the employeeNumber field, but if I uncomment the second search.Filter line and manually search by employeeNumber I find a result and it contains all of the fields that I need.
EDIT: I found a very good MSDN article Here which describes how to extend the UserPrincipal object to get custom attributes. The only issue is that it's giving me an empty string every time I access it even though I have verified that the property IS set in AD! Any help is appreciated.
EDIT 2: Here's the code for the custom principal extension:
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("Person")]
public class UserPrincipalExtension : UserPrincipal
{
public UserPrincipalExtension(PrincipalContext context)
: base(context)
{
}
public UserPrincipalExtension(PrincipalContext context, string samAccountName, string password, bool enabled)
: base(context, samAccountName, password, enabled)
{
}
public static new UserPrincipalExtension FindByIdentity(PrincipalContext context, IdentityType type, string identityValue)
{
return (UserPrincipalExtension)FindByIdentityWithType(context, typeof(UserPrincipalExtension), type, identityValue);
}
PersonSearchFilter searchFilter;
new public PersonSearchFilter AdvancedSearchFilter
{
get
{
if (searchFilter == null)
searchFilter = new PersonSearchFilter(this);
return searchFilter;
}
}
[DirectoryProperty("employeeNumber")]
public string EmployeeNumber
{
get
{
if (ExtensionGet("employeeNumber").Length != 1)
return string.Empty;
return (string)ExtensionGet("employeeNumber")[0];
}
set
{
ExtensionSet("employeeNumber", value);
}
}
}
And the custom search filter:
public class PersonSearchFilter : AdvancedFilters
{
public PersonSearchFilter(Principal p)
: base(p)
{
}
public void SAMAccountName(string value, MatchType type)
{
this.AdvancedFilterSet("sAMAccountName", value, typeof(string), type);
}
}
Usage:
UserPrincipalExtension filter = new UserPrincipalExtension(context);
filter.AdvancedSearchFilter.SAMAccountName(UserPrincipal.Current.SamAccountName, MatchType.Equals);
PrincipalSearcher search = new PrincipalSearcher(filter);
foreach (var result in search.FindAll())
{
var q = (UserPrincipalExtension)result;
var m = q.EmployeeNumber;
}
var m is always an empty string even though ALL AD entries have an employeeNumber.
EDIT: From active directory:
I am more confidant in how to fix your second method so I will answer that first. You need to specify the property in DirectorySearcher.PropertiesToLoad when you do your search for the property to show up.
DirectorySearcher search = new DirectorySearcher(ldapConnection);
search.Filter = string.Format("(&(ObjectClass=user)(userprincipalname={0}))", sid);
search.PropertiesToLoad.Add("employeeNumber");
var result = search.FindOne(); // FindOne();
if (result.Properties.Contains("employeeNumber"))
{
//This should now work.
}
The reason it worked for string.Format("(employeeNumber={0})", "11663"); is because any search clause you add automatically gets put in to the PropertiesToLoad collection.
For your first method, I think you need to call DirectoryEntry.RefreshCache and pass in the property to make it show up.
string sid = "";
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
{
UserPrincipal user = UserPrincipal.Current;
//sid = user.SamAccountName;
sid = user.UserPrincipalName;
//sid = user.Sid.ToString();
DirectoryEntry entry = user.GetUnderlyingObject() as DirectoryEntry;
entry.RefreshCache(new[] {"employeeNumber"});
if (entry.Properties.Contains("employeeNumber"))
{
}
}
But I am not 100% sure if that will work or not.
For your custom user principal, I am not sure what is wrong. The only difference I see between what you are doing and what I have in a project that does it that I know works is you use [DirectoryObjectClass("Person")] but I have [DirectoryObjectClass("user")]. Perhaps that is the issue
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("user")] //Maybe this will fix it???
public class UserPrincipalExtension : UserPrincipal
{
The below code works on my local machine by returning the user's full name from Active Directory:
string principal = System.Web.HttpContext.Current.Request.LogonUserIdentity.Name.Remove(0, 12);
string filter = string.Format("(&(ObjectClass={0})(sAMAccountName={1}))", "person", principal);
string[] properties = new string[] { "fullname" };
DirectoryEntry adRoot = new DirectoryEntry("LDAP://myserver.com");
adRoot.AuthenticationType = AuthenticationTypes.Secure;
DirectorySearcher searcher = new DirectorySearcher(adRoot);
searcher.SearchScope = SearchScope.Subtree;
searcher.ReferralChasing = ReferralChasingOption.All;
searcher.PropertiesToLoad.AddRange(properties);
searcher.Filter = filter;
SearchResult result = searcher.FindOne();
DirectoryEntry directoryEntry = result.GetDirectoryEntry();
string displayName = directoryEntry.Properties["displayName"][0].ToString();
if (string.IsNullOrEmpty(displayName) == false)
{
return displayName;
}
When I publish it to the development server I get the following error:
System.NullReferenceException: Object reference not set to an instance
of an object.
The error is thrown on the following line:
DirectoryEntry directoryEntry = result.GetDirectoryEntry();
I have tried
DirectoryEntry adRoot = new DirectoryEntry("LDAP://" + domain, AdAdminUsername, AdAdminPassword, AuthenticationTypes.Secure);
but still no joy.
Any ideas?
Thanks!
Just from outside : your string called principal is built by the following :
string principal = System.Web.HttpContext.Current.Request.LogonUserIdentity.Name.Remove(0, 12);
Have you log 'System.Web.HttpContext.Current.Request.LogonUserIdentity.Name' on your dev server ? This static construction may be the begining of you troubles because if principal is'nt what you think it is, the result of FindOne may be NULL.
In your current code, you must check for NULL after the call to FindOne() - it could return a NULL value if no matching directory entry was found.
See the MSDN docs:
If more than one entry is found during the search, only the first
entry is returned. If no entries are found to match the search
criteria, a null reference (Nothing in Visual Basic) is returned.
Plus, you should also always check for the presence of a property before accessing it - it could be absent.....
Therefore, your code should be something like this:
SearchResult result = searcher.FindOne();
if(result != null)
{
DirectoryEntry directoryEntry = result.GetDirectoryEntry();
if(directoryEntry.Properties["displayName"] != null &&
directoryEntry.Properties["displayName"].Length > 0)
{
string displayName = directoryEntry.Properties["displayName"][0].ToString();
if (!string.IsNullOrEmpty(displayName))
{
return displayName;
}
}
}
But: if you're on .NET 3.5 or newer, you should check out the System.DirectoryServices.AccountManagement namespace which makes a lot of things a lot easier when dealing with Active Directory.
You can use a PrincipalSearcher and a "query-by-example" principal to do your searching:
// create your domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// define a "query-by-example" principal - here, we search for a UserPrincipal
// and with the first name (GivenName) of "Bruce"
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.SamAccountName = "whatever you're looking for.....";
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
// find all matches
foreach(var found in srch.FindAll())
{
// do whatever here - "found" is of type "Principal" - it could be user, group, computer.....
}
If you haven't already - absolutely read the MSDN article Managing Directory Security Principals in the .NET Framework 3.5 which shows nicely how to make the best use of the new features in System.DirectoryServices.AccountManagement
Of course, depending on your need, you might want to specify other properties on that "query-by-example" user principal you create:
Surname (or last name)
DisplayName (typically: first name + space + last name)
SAM Account Name - your Windows/AD account name
User Principal Name - your "username#yourcompany.com" style name
You can specify any of the properties on the UserPrincipal and use those as "query-by-example" for your PrincipalSearcher.