IQueryable to non-AD LDAP - c#

Our institution has a large LDAP system which we access over LDAPS on port 636. I'm attempting to create an IQueryable interface to this LDAP which maps onto existing People entities but am having trouble.
I mention the above so folks know my end goal but I'd be satisified today if someone could help me simply run a successful query against this non-AD LDAP, even if its outside of the IQueryable realm. Here's what I have so far (I've edited the password, uid and ou for security):
var url = #"ldaps://ldap.ucdavis.edu:636/uid=s1,ou=s2,dc=ucdavis,dc=edu";
var password = #"something";
DirectoryEntry entry = new DirectoryEntry(url);
entry.Password = password;
entry.AuthenticationType = AuthenticationTypes.Secure;
DirectorySearcher mySearcher = new DirectorySearcher(entry);
SearchResultCollection results;
results = mySearcher.FindAll();
foreach(SearchResult resEnt in results) {
ResultPropertyCollection propcoll = resEnt.Properties;
foreach (string key in propcoll.PropertyNames)
{
foreach (object values in propcoll[key])
{
switch (key)
{
case "sn":
//sb.Append(key.ToString() + "<surname>"
//+ values.ToString() + "</surname>");
break;
case "cn":
//sb.Append(key.ToString() + "<cn>"
//+ values.ToString() + "</cn>");
break;
case "name":
//sb.Append(key.ToString() + "<name>"
//+ values.ToString() + "</name>");
break;
}
}
}
}
But I keep getting an "unknown error" on the line mySearcher.FindAll(). Anybody see an obvious problem here? Am I specifying the ou and uid correctly?

>see an obvious problem here?
Where's the search query itself?
DirectorySearcher(entry);
It initializes the searcher given your base path.
Try setting the "filter" property to a valid LDAP search string, such as "(objectClass=inetOrgPerson)".
Also, try setting the SearchScope to OneLevel.
Anyway, I don't really think that will help.
AFAIK DirectorySearcher is merely a wrapper over the ADSI. What you should use instead is the classes from System.DirectoryServices.Protocols namespace, see this article: http://msdn.microsoft.com/en-us/library/bb332056.aspx

I wonder if the LDAP DN is correct? AD used dc=acme, dc=com, but other LDAP servers might use ou=ucdavis, o=edu perhaps.

Related

c# DirectoryEntry.Properties vs DirectoryEntry.InvokeGet?

I have a strange problem when I tried to retrieve the "AccountExpirationDate" from the active directory.
I use the following code to retrieve the user:
DirectoryEntry dirEntry = new DirectoryEntry(Path);
DirectorySearcher search = new DirectorySearcher(dirEntry);
// specify the search filter
search.Filter = "(&(objectClass=user)(mail=" + email + "))";
// perform the search
SearchResult result = search.FindOne();
DirectoryEntry user = result.GetDirectoryEntry();
And then I retrieve the "AccountExpirationDate":
object o1 = user.Properties["accountExpires"].Value; //return a COM object and I cannot retrieve anything from it
object o2 = user.Properties["AccountExpirationDate"].Value; //return null
object o3 = user.InvokeGet("AccountExpirationDate"); //return the DateTime
So I would like to what happened here?
Why I cannot use DirectoryEntry.Properties to retrieve the AccountExpirationDate?
What is the different between DirectoryEntry.Properties vs DirectoryEntry.InvokeGet?
Thanks a lot.
You can tell a directorySearcher which properties to load as follows:
// specify the search filter
search.Filter = "(&(objectClass=user)(mail=" + email + "))";
search.PropertiesToLoad.Add("AccountExpirationDate");
search.PropertiesToLoad.Add("displayname");
after performing search you need to go through the properties of the SearchResult to get values
i.e.
object o1 = result.Properties["AccountExpirationDate"][0];
DirectoryEntry.Properties - Gets the Active Directory Domain Services properties for this DirectoryEntry object.
DirectoryEntry.InvokeGet - Gets a property from the native Active Directory Domain Services object.
//Microsoft doesn't recommend the use of InvokeGet method.

How to use LINQ in C# to query windows local user accounts

I wanted to query windows local user accounts from C# using ADSI.
Since WinNt provider is used, Directory SEarcher is not supported.
I am doing the following :
DirectoryEntries entries = hostMachineDirectory.Children;
var users = from DirectoryEntry childEntry in hostMachineDirectory.Children
where (childEntry.Name.StartsWith("User1"))
select childEntry;
Querying with Name is possible. It is not possible to get the description like childEntry.Description. It is possible to get the description by using properties childEntry.Properties["Description"].So is it possible to query with attributes like 'Description' ?
I don't know much about AD, but I guess, assuming that Properties["Description"].Value returns string, you can try it this way :
var users = from DirectoryEntry childEntry in entries
where ((string)childEntry.Properties["Description"].Value).StartsWith("User1")
select childEntry;
Ok since you confirmed that my assumption was wrong, Properties["Description"].Value doesn't always return string, it returns NULL sometimes, we need to check for null first before checking with .StartsWith or Contains or any other string checking operation :
var users = from DirectoryEntry childEntry in entries
where childEntry.Properties["Description"].Value != null &&
((string)childEntry.Properties["Description"].Value).StartsWith("User1")
select childEntry;
If you are trying to get string, you should do it this way:
DirectoryEntries entries = hostMachineDirectory.Children;
var users = (from DirectoryEntry childEntry in hostMachineDirectory.Children
where (childEntry.Name.StartsWith("User1"))
select childEntry).SingleOrDefault<string>();
Here's how I got it to work.
var users = from DirectoryEntry childEntry in hostMachineDirectory.Children
where childEntry.SchemaClassName == "User"
select childEntry;
try {
var UserName = users.Single(s => s.Properties["Description"].Value.ToString().StartsWith("Whatever description")).Name;
Console.WriteLine("User: " + UserName);
}
catch (Exception e) {
var err = e.Message;
Console.WriteLine("Error message: " + err);
}

Active Directory: find details of users in group without mass search

OK, working with active directory, my one remaining requirement is to pull back all the users in a given group and get their details - first name, last name, username, email.
Having read up around this I was surprised to discover that there doesn't seem to be a way of doing this in an efficient fashion. I've worked out two ways of getting the results, but both seem absurdly wasteful.
The first is to search members of a group, like this:
DirectoryEntry searchRoot = new DirectoryEntry("LDAP://" + server, username, password);
DirectorySearcher search = new DirectorySearcher(searchRoot);
search.Filter = string.Format("(|(CN={0})(CN={1}))", "Group1", "Group2");
search.PropertiesToLoad.Add("member");
SearchResultCollection results = search.FindAll();
Which brings back a single result containing usernames, and usernames only. You can dissect this into an array of individual usernames, but then to get the details of each user you have to search AD again on a per-name basis.
The other approach is to do this:
DirectoryEntry searchRoot = new DirectoryEntry("LDAP://" + server, username, password);
DirectorySearcher search = new DirectorySearcher(searchRoot);
search.Filter = string.Format("(&(&(objectClass=user)(objectCategory=person))(memberOf=*))");
search.PropertiesToLoad.Add("memberOf");
search.PropertiesToLoad.Add("name");
search.PropertiesToLoad.Add("mail");
search.PropertiesToLoad.Add("displayName");
SearchResultCollection mySearchResultColl = search.FindAll();
foreach (SearchResult result in mySearchResultColl)
{
foreach (string prop in result.Properties["memberOf"])
{
if (prop.Contains("Group1") || prop.Contain("Group2"))
{
//add user to list
}
}
}
Which gets me what I want but involves retrieving every active directory user and then iterating through the collection to find ones that match. That works fine on my little test directory, but I shudder to think how slow it's going to be on a system with thousands of users.
I'm aware you can do what I need using the PrinicpalContext object, but as best I can tell that only works if the code is running in the same domain, which I can't guarantee. I need to be able to query across domains.
Is there a better way of doing this? Or am I just going to have to suck on the performance issues?
If you only need to find direct members, you can use Attribute Scope Query (ASQ).
This requires domain/forest functional level of 2003 (forgot domain or forest).
DirectoryEntry groupEntry = new DirectoryEntry("LDAP://<server>/<group DN>", "user", "pwd");
DirectorySearcher searcher = new DirectorySearcher(groupEntry);
searcher.SearchScope = SearchScope.Base;
searcher.AttributeScopeQuery = "member";
searcher.Filter = "(&(objectCategory=person)(objectClass=user))";
searcher.PropertiesToLoad.Clear();
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("mail");
searcher.PropertiesToLoad.Add("displayName");
foreach (SearchResult result in searcher.FindAll())
{
Console.WriteLine(result.Path);
}
For nested group members, you may use the LDAP_MATCHING_RULE_IN_CHAIN matching rule.
This requires domain/forest functional level of 2008 R2 (again, forgot domain or forest).
DirectoryEntry rootEntry = new DirectoryEntry("GC://<server>", "user", "pwd");
DirectorySearcher searcher = new DirectorySearcher(rootEntry);
searcher.SearchScope = SearchScope.Subtree;
searcher.Filter = "(&(objectCategory=person)(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=<group DN>))";
searcher.PropertiesToLoad.Clear();
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("mail");
searcher.PropertiesToLoad.Add("displayName");
foreach (SearchResult result in searcher.FindAll())
{
Console.WriteLine(result.Path);
}
Limitations:
Both methods don't handle primary group membership
ASQ don't work across domain, an exception will be thrown if member from another domain is found. So normally you can only perform this for global groups.
You could utilize an ANR search for some of those attributes For more information, see this article - the functionality has been there since 2000:
http://support.microsoft.com/kb/243299
In order to search for other attributes not in the default set, you'd need to tweak the schema (which may not be suitable in your situation).

How can I list ous (organizational units) below a specified ou in Active Directory C#

As I'm not very experienced in terms of retrieving information from the Active Directory I hope to get some pointers into the right direction.
I'd like to list all ous (organizational units) below a specified ou, but unfortunately I don't know how to get things working.
Let's say that the structure in our AD is as follows
SomeStartingPoint
|-MySpecifiedOuName
  |-SubOuName1
    |-SubOuName2
What I've got so far is
DirectoryEntry rootDSE = new DirectoryEntry( "LDAP://RootDSE" );
string defaultNamingContext = rootDSE.Proeprties[ "defaultNamingContext" ].Value.ToString();
DirectoryEntry entry = new DirectoryEntry( "LDAP://" + defaultNamingContext );
DirectorySearcher ouSearch =
new DirectorySearcher( entry.Path ) { Filter = "(objectCategory=organizationalUnit)", SearchScope = SearchScope.Subtree };
ouSearch.PropertiesToLoad.Add( "name" );
ouSearch.PropertiesToLoad.Add( "adspath" );
SearchResultCollection allOUs = ouSearch.FindAll();
Now I can loop over the allOUs and access .Properties[ "name" ][ 0 ] and .Properties[ "adspath" ][ 0 ] to list the values for all OUs.
Now when I try to use a different filter as in Filter = (&(objectCategory=organizationalUnit)(ou=MySpecifiedOuName)), I do get the single entry corresponding to exactly MySpecifiedOuName, but not the underlying OUs below, even though they contain MySpecifiedOuName within their path. This is probably down to the fact that in my example I query the wrong thing (the OU directly), but I don't know any other way.
Any ideas?
Just try with this filter :
"(objectCategory=CN=Organizational-Unit,CN=Schema,CN=Configuration,DC=dom,DC=fr)"
Adapted to your domain with configurationNamingContext from the RootDSE for CN=Configuration,DC=dom,DC=fr
This may be explained by the fact that objectCategory is a distinguich name, I know that Microsoft tools are making the translation but it seems not to work for you here.
-----Edited-----
As #Desmond insist on the fact "(objectCategory=organizationalUnit)" I just test it an it works. "(objectCategory=CN=Organizational-Unit,CN=Schema,CN=Configuration,DC=dom,DC=fr)" also works.
DirectoryEntry deBase = new DirectoryEntry("LDAP://WM2008R2ENT:389/dc=dom,dc=fr", "jpb", "Pwd");
/* Directory Search
*/
DirectorySearcher dsLookForOUs = new DirectorySearcher(deBase);
dsLookForOUs.Filter = "(objectCategory=organizationalUnit)";
dsLookForOUs.SearchScope = SearchScope.Subtree;
dsLookForOUs.PropertiesToLoad.Add("cn");
dsLookForOUs.PropertiesToLoad.Add("ou");
SearchResultCollection srcOUs = dsLookForOUs.FindAll();
foreach (SearchResult srOU in srcOUs)
{
Console.WriteLine("{0}", srOU.Path);
}
The only way to do what you want is in a recursive fashion. ou is the RDN attribute for the OU (much like CN is for users). Thus doing a search for ou=foo will only get you OUs with their OU attribute set to foo.
In order to walk the chain, you're going to need to do a search for all the OUs at the current level (use a OneLevel search instead of SubTree), and then recurse through there. This is not at all efficient, though, since you're going to be issuing numerous queries to AD.
Instead, you could do what you're doing and then build your hierarchy by sorting based on DN/depth. This is more complex but it will be more efficient from a resource access point of view.
You most likley are only missing the subtree option:
ouSearch.SearchScope = SearchScope.Subtree;

Active Directory search - filter by Manager

I'm trying to get a list of users from the Active Directory, who have a specified manager.
I used the following LDAP filter without success:
(manager=CN=Misterboss_n*)
However, it returns no result. Users have the following value in the manager attribute:
"CN=Misterboss_n,OU=xyz user,DC=xyz,DC=local"
What am I doing wrong? If I replace the above filter with something like this:
(givenName=John*)
it works okay (returns all users whose given name is John).
Wider context:
public List<ADUserDetail> GetAllEmployeesUnderMisterboss()
{
List<ADUserDetail> userlist = new List<ADUserDetail>();
string filter = "";
_directoryEntry = null;
DirectorySearcher directorySearch = new DirectorySearcher(SearchRoot);
directorySearch.Asynchronous = true;
directorySearch.CacheResults = true;
filter = "(manager=CN=Misterboss_n*)";
directorySearch.Filter = filter;
SearchResultCollection userCollection = directorySearch.FindAll();
foreach (SearchResult users in userCollection)
{
DirectoryEntry userEntry = new DirectoryEntry(users.Path, LDAPUser, LDAPPassword);
ADUserDetail userInfo = ADUserDetail.GetUser(userEntry);
userlist.Add(userInfo);
}
return userlist;
}
Thanks for the help!
I don't think there is a start-of-field search available for DN-typed properties. You will have to use the full DN of the manager. If you don't know the full DN, find the manager's LDAP object first and use its distinguishedName property.
Be sure to escape the DN value properly before building your filter - not every character that is valid in a DN is also valid in an LDAP filter expression:
* as \2a
( as \28
) as \29
\ as \5c
NUL as \00
/ as \2f
For code samples, see this related thread where I answered a very similar question: Getting all direct Reports from Active Directory

Categories