Searching for users across multiple Active Directory domains - c#

I'm using the System.DirectoryServices.AccountManagement to provide user lookup functionality.
The business has several region specific AD domains: AMR, EUR, JPN etc.
The following works for the EUR domain, but doesn't return users from the other domains (naturally):
var context = new PrincipalContext(ContextType.Domain, "mycorp.com", "DC=eur,DC=mycorp,DC=com");
var query = new UserPrincipal(GetContext());
query.Name = "*Bloggs*";
var users = new PrincipalSearcher(query).FindAll().ToList();
However, if I target the entire directory, it doesn't return users from any of the region specific domains:
var context = new PrincipalContext(ContextType.Domain, "mycorp.com", "DC=mycorp,DC=com");
How do I search the entire directory?
Update
Read up on "How Active Directory Searches Work":
http://technet.microsoft.com/en-us/library/cc755809(v=ws.10).aspx
If I suffix the server name with port 3268 it searches against the Global Catalog:
var context = new PrincipalContext(ContextType.Domain, "mycorp.com:3268", "DC=mycorp,DC=com");
However it's very, very slow. Any suggestions on how to improve performance?

Queries which have initial wildcards (*Bloggs*) will be slow unless you have a tuple index on the attribute being queries. None of the attributes in AD have this set by default. Better to not do initial wildcards.

Related

Search in global catalog of AD Forest

I need to do LDAP query across all DCs of Forest. I found similar post, from which is following code:
using (Forest currentForest = Forest.GetCurrentForest())
{
using (GlobalCatalog globalCatalog = currentForest.FindGlobalCatalog())
{
using (DirectorySearcher searcher = globalCatalog.GetDirectorySearcher())
{
searcher.Filter = "filter to verify existence of user account according to supplied sid";
SearchResult result = searcher.FindOne();
return (result != null);
}
}
}
Above code works in my test scenario. Is this the correct way to query information across domains? There are a lot of other posts where they manually enter GC location or search through list of domains. Therefore is there anything I should pay attention to or that could go wrong with this code?
Use the Domains property of System.DirectoryServices.ActiveDirectory.Forest to iterate through the domains and then the DomainControllers property of System.DirectoryServices.ActiveDirectory.Domain to iterate though domain controllers, running whatever query you want on each.

Active Directory group name uniqueness requirement (query from C# client)

Our company's C# product uses System.DirectoryServices.AccountManagement to query Active Directory for users and groups. We use the following method to get the principal:
...
PrincipalContext principalContext = new PrincipalContext(ContextType.Domain);
return principalContext;
...
We get Active Directory groups using (e.g. groupName = "Devs"):
...
GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(this.principalContext, groupName);
...
Everything works fine with this setup when we run it on a simple, one domain Active Directory database.
My question is, what will happen when we run this code on a big forest with more than one "Devs" group? Can there be more than one "Devs" security group in a forest? If so, how will it resolve "Devs"? Do I have to switch to using the method:
public static GroupPrincipal FindByIdentity(
PrincipalContext context,
IdentityType identityType,
string identityValue
)
I cannot simulate this currently (lack of resources and lack of time) and I have been reading a lot about this. I know there are local, global and universal security groups, spread among domain trees. But domain trees in a forest have some sort of trust among the roots, so they are not completely ignorant of each other. What is the worst case of having "Devs" duplicates in the forest and how could the application handle it?
It's pretty common task to search through domain hierarchy. With AccountManagement classes you can do the following:
// Connect to global catalog of the forest
var context = new PrincipalContext(ContextType.Domain, "contoso.com:3268", "DC=contoso,DC=com");
// Build a filter principal by name and context
var groupFilter = new GroupPrincipal(context) {Name = "Devs"};
// Build a searcher with a filter applied
var searcher = new PrincipalSearcher(groupFilter);
// This should return all groups in all subdomains matching specified name
var groups = searcher.FindAll().ToList();
foreach (var group in groups)
{
Console.WriteLine(group.DistinguishedName);
}
You will not have any duplicates cause there can't be more than one group with this name ("Devs") in domain. In AccountManagement terms you create GroupPrincipal object with context and name parameters and can't have more than one in context with the same name.
If you connect to the domain controller (new PrincipalContext(ContextType.Domain)) then FindByIdentity will search this single domain. If you connect to global catalog of the forest (like in my example, port 3268) then FindByIdentity will search entire forest. The DistinguishedName property will show which domain a group belongs to.
As to cross-forest access there you need to connect to global catalog in every forest separately, because there's no user/group data replication between forests global catalogs.

adding user to AD security group

I have a pretty simple task – given the list (from file) of user names and their emails, synchronize it with the members of AD (Active Directory) users, meaning – add users that exist in the list and missing in AD group, and remove users that exist in AD group and missing in the list. The complication is that the company is very big and uses a lot of domains around the world based on their location – let’s say it.comany.com, fr.company.com, uk.company.com, us.company.com, etc. – each of them contains tons of users and users to add can be in any of them.
I use the following code to find the AD group:
var groupSearch = new DirectorySearcher(new DirectoryEntry(“LDAP://company.com”), “(&(ObjectClass=Group)(CN=TheNameOfTheADGroup))”);
var dirEntry = new DirectoryEntry(groupSearch.FindOne().Path);
and it finds the group successfully.
Now, I need to add user to this group (knowing only his username, like willsmith). This can be done by calling
dirEntry.Invoke(“Add”, new object[] { dirPath })
where dirPath is the FULL LDAP path of the user. I can get it by searching in specific domain, e.g.:
var userSearch = new DirectorySearcher(new DirectoryEntry(“LDAP://xx.company.com”), “(&(ObjectClass=User)(CN=willsmith))”);
var dirPath = userSearch.FindOne().Path;
//and then use dirPath in Invoke(“Add”…) – see above
but the problem is that I don’t know which exact domain the user is in, and the number of domains is around 30, each user search takes about 2-3 seconds, so it will be a very long process.
Any ideas how to define domain for the user? Or maybe some other solution for the problem?
P.S. I do not use the new AD management capabilities introduced in .NET 3.5 (PrincipalContext, GroupPrincipal.FindByIdentity), because they don’t allow to specify the root for the search (which was the first parameter in DirectorySearcher constructor), which I need because the AD group is located in company.com domain not us.company.com from where is my account.

How to check if address lists exist in Active Directory?

I need to check if All Global Address Lists, All Address List and All System Address Lists exist in Active Directory before get all item from them.
Could you give me some advices or article that I can solve my problem?
Thanks.
Address Lists are part of Exchange functionality not Active Directory which is what I think people are confused about.
Anyway, Address List data is stored in the Active Directory Configuration context under:
CN=Address Lists Container,CN=<EXCHANGE ORGANIZATION NAME>,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=<DEFAULT NAMING CONTEXT>
You can use ADSIEdit to view the information.
In C# you can use an LDAP query to retrieve information for existing Address Lists.
Edit: Something like this:
DirectoryEntry rootDSE = new DirectoryEntry("LDAP://RootDSE");
DirectoryEntry configContainer = new DirectoryEntry("LDAP://" + rootDSE.Properties["configurationNamingContext"].Value);
DirectorySearcher configSearcher = new DirectorySearcher(configContainer);
configSearcher.Filter = "(&(objectClass=addressBookContainer))";
configSearcher.PropertiesToLoad.Add("displayName");
configSearcher.PageSize = 10000;
configSearcher.SearchScope = SearchScope.Subtree;
SearchResultCollection results = configSearcher.FindAll();

Any way to distinguish between "people user accounts" and "computer user accounts"?

When querying Active Directory for users - is there a way to filter out user accounts created for computers? Ideally a way which is common across most typical networks. e.g.:
DirectorySearcher ds = new DirectorySearcher(new DirectoryEntry([Users_OU_root]));
ds.filter = "(&(objectClass=User)([CRITERIA_TO_FILTER_OUT_COMPUTER_USER_ACCOUNTS]))";
ds.FindAll();
...
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
// do something here....
}
// find the group in question
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, "YourGroupNameHere");
// if found....
if (group != null)
{
// iterate over members
foreach (Principal p in group.GetMembers())
{
Console.WriteLine("{0}: {1}", p.StructuralObjectClass, p.DisplayName);
// do whatever you need to do to those members
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD:
Computer accounts will show up as ComputerPrincipal (derived from Principal) - so you can easily keep users and computer accounts apart.
If you cannot or don't want to move to S.DS.AM - you can also keep user and computers apart by using the objectCategory instead of the objectClass in your LDAP filter. objectCategory is beneficial anyway, since it's indexed, and not multi-valued - so query performance will be much better.
For a real-life user, use objectCategory = Person, while for a computer, use objectCategory = Computer in your LDAP filter.
Even if I agree with the answer. Active-Directory remain an LDAP server. Here is the filter you are looking for :
(&(objectCategory=user)(objectClass=user)(...))
'objectCategory=user' is a shortcut for 'objectCategory=CN=User,CN=Schema,CN=Configuration,DC=dom,DC=fr' understood by Active-Directory but it's also a way in others Directories, that's why I put an answer, even if another answer is accepted.

Categories