Invert filter in AD search - c#

I want to search in AD all users which names are NOT starts with prefix.
How shoud I do this?
This does not work
using (var context = new PrincipalContext(ContextType.Domain, "my_do_main"))
{
UserPrincipal template = new UserPrincipal(context);
template.UserPrincipalName = "!my_prefix*"; //invertion NOT works
using (var searcher = new PrincipalSearcher(template))
{
foreach (var result in searcher.FindAll())
{
var de = result.GetUnderlyingObject() as DirectoryEntry;
Console.WriteLine(de.Properties["userPrincipalName"].Value);
}
}
}

You can't do this using PrincipalSearcher, but you can do it using DirectorySearcher, which is what PrincipalSearcher uses behind the scenes anyway. Here is a quick example:
var search = new DirectorySearcher(new DirectoryEntry("LDAP://my_do_main")) {
PageSize = 1000,
Filter = "(&(objectClass=user)(!userPrincipalName=my_prefix*))"
};
search.PropertiesToLoad.Add("userPrincipalName");
using (var results = search.FindAll()) {
foreach (SearchResult result in results) {
Console.WriteLine((string) result.Properties["userPrincipalName"][0]);
}
}
You'll find this will perform much faster anyway. In my experience, using DirectorySearcher and DirectoryEntry directly is always much faster than using PrincipalSearcher (or anything in the AccountManagement namespace). A little while ago I wrote an article about that subject: Active Directory: Better Performance

Related

C# DirectoryEntry find all users with a specific attribute (wWWHomePage)

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
}
}

Memory exceptions when scraping Active Directory

I am trying to scrape all the groups a user is a member of in Active Directory (including groups that they are indirect members of), but I am running into memory exception errors.
Latest error message passed was:
Insufficient memory to continue the execution of the program
The code I run is started in a backgroundworker on a schedule.
I have looked through ALL the SO active directory group scraping posts, and tried as many of the iterations of this process as I could get to work. This one will (usually) work if i don't run it on a schedule. Sometimes it will crash the program though.
Our Active Directory database is quite considerably large.
Any help you could offer with this would be great. I don't mind it taking a bit longer, speed is not an overly great concern, as long as I can get it to perform well, and correctly.
Here's what I have:
DataTable resultsTable = new DataTable();
resultsTable.Columns.Add("EmailID");
resultsTable.Columns.Add("UserID");
resultsTable.Columns.Add("memberOf");
resultsTable.Columns.Add("groupType");
resultsTable.Columns.Add("record_date");
try
{
string RecordDate = DateTime.Now.ToString(format: "dd/MM/yyyy", provider: CultureInfo.CreateSpecificCulture("en-GB"));
string ou = "OU=Groups,OU=nonya,DC=my,DC=domain,DC=net";
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, "my.domain.net", ou))
{
using (GroupPrincipal GroupPrin = new GroupPrincipal(context))
{
using (var searcher = new PrincipalSearcher(GroupPrin))
{
using (var results = searcher.FindAll())
{
foreach (var result in results)
{
DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
string GUID = de.Guid.ToString();
string testname1 = de.Name;
string testParentName1 = de.Parent.Name;
string MasterGroupname = testname1.Substring(3, (testname1.Length - 3));
string type = testParentName1.Substring(3, (testParentName1.Length - 3));
using (var group = GroupPrincipal.FindByIdentity(context, IdentityType.Guid, GUID))
{
using (var users = group.GetMembers(true))
{
foreach (Principal user in users)
{
DataRow dr1 = resultsTable.NewRow();
dr1["EmailID"] = user.UserPrincipalName;
dr1["UserID"] = user.SamAccountName;
dr1["memberOf"] = MasterGroupname;
dr1["groupType"] = type;
dr1["record_date"] = RecordDate;
resultsTable.Rows.Add(dr1);
user.Dispose();
}
}
}
}
}
}
}
}
//Table is uploaded to SQL database here, unless it crashes before reaching this point
}
catch (Exception E)
{
logger.Error($"ADGroupScrape error. System says: {E.Message}");
return false;
}
finally
{
resultsTable.Dispose();
}
Disposing each object at the end of the loop will probably solve your problem (result.Dispose()). I've had memory problems with long loops when working with AD. Assuming you're working with thousands of results, and you're allocating memory for each, and there's no break between processing each result, the garbage collector doesn't have an opportunity to clean up for you.
But you're also unnecessarily going back out to AD to search for the group you already found (GroupPrincipal.FindByIdentity). That would also increase memory consumption, but also slow down the whole operation. Instead, just cast result to a GroupPrincipal so you can call .GetMembers() on it.
Unrelated, but helpful: When you have multiple using blocks nested like that, you can combine them all into one block. It saves you indenting so much.
This is what your code would look like with all those suggestions:
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, "my.domain.net", ou))
using (GroupPrincipal GroupPrin = new GroupPrincipal(context))
using (var searcher = new PrincipalSearcher(GroupPrin))
using (var results = searcher.FindAll())
{
foreach (GroupPrincipal result in results)
{
DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
string GUID = de.Guid.ToString();
string testname1 = de.Name;
string testParentName1 = de.Parent.Name;
string MasterGroupname = testname1.Substring(3, (testname1.Length - 3));
string type = testParentName1.Substring(3, (testParentName1.Length - 3));
using (var users = result.GetMembers(true))
{
foreach (Principal user in users)
{
DataRow dr1 = resultsTable.NewRow();
dr1["EmailID"] = user.UserPrincipalName;
dr1["UserID"] = user.SamAccountName;
dr1["memberOf"] = MasterGroupname;
dr1["groupType"] = type;
dr1["record_date"] = RecordDate;
user.Dispose();
}
}
result.Dispose();
}
}
This will probably work, but could probably still be much faster. Using Principal objects (and the whole AccountManagement namespace) is a wrapper around DirectoryEntry/DirectorySearcher which makes things somewhat easier for you, but at the cost of performance. Using DirectoryEntry/DirectorySearcher directly is always faster.
If you want to experiment with that, I wrote a couple articles that will help:
Active Directory: Better performance
Active Directory: Find all the members of a group
So the issue didn't appear to be with my code, but thank you all for the suggestions in cleaning it up.
I changed the Target Platform in Configuration Manager (Visual Studio) to X64, as it was originally set to "Any CPU", and my code now runs successfully every time.
Couple of months down the line and i started getting out of memory exceptions again.
added pagination (which i didnt actually know you could do) and it seems to have resolved the issue. The line i added was ((DirectorySearcher)searcher.GetUnderlyingSearcher()).PageSize = 500; in between the searcher declaration and the searcher.FindAll().
using (var searcher = new PrincipalSearcher(GroupPrin))
{
((DirectorySearcher)searcher.GetUnderlyingSearcher()).PageSize = 500;
using (var results = searcher.FindAll())
{

C# Active Directory Search

I have this powershell function and i want to make it as a C# function.
How can i put it into C#?
Get-ADComputer -filter {Name -Like 'myComp'} -property * | select DistinguishedName
You should be able to do this quite easily. Add a reference to System.DirectoryServices.AccountManagement and then use this code:
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, 'YourDomain'))
{
ComputerPrincipal computer = ComputerPrincipal.FindByIdentity (ctx, "name");
if (computer != null)
{
// do whatever you need to do with your computer principal
string distinguishedName = computer.DistinguishedName;
}
}
Update: if you don't know your domain ........ - you can also use:
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
in which case the principal context is created for the current domain you're located in.
You can use C# in the following manner
Connect to the Domain controller and get the DomainContext
Use that to look up the computer objects based on a name.
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, Environment.UserDomainName)
{
using (PrincipalSearcher srch = new PrincipalSearcher(new ComputerPrincipal(ctx) { Name = "ServerName"}))
{
return srch.FindAll().Cast<ComputerPrincipal>().ToList().Select(x => x.DistinguishedName);
}
}
Above returns a list of DistinguishedNames that matches the Server Name.
All the other answers suggest using the System.DirectoryServices.AccountManagement namespace. While that will work, it is really just a wrapper around the System.DirectoryServices namespace to make things a bit easier to use. It does make things easier (sometimes) but it does so at the cost of performance.
For example, in all the examples you've been given, your code will retrieve every attribute with a value from the computer object in AD, even though you only want one attribute.
If you use DirectorySearcher, you can make the search and retrieve only that one attribute that you want:
public string GetComputerDn(string computerName) {
var searcher = new DirectorySearcher {
Filter = $"(&(objectClass=computer)(sAMAccountName={computerName}$))",
PropertiesToLoad = { "distinguishedName" } //return only the distinguishedName attribute
};
var result = searcher.FindOne();
if (result == null) return null;
return (string) result.Properties["distinguishedName"][0];
}
Note that in AD, the sAMAccountName of computer objects is what you would normally refer to as the "computer name", followed by $, which is why the filter is what it is.
Please try this:
Add reference to Active Directory Services (%programfiles%\Reference Assemblies\Microsoft\Framework.NETFramework\\System.DirectoryServices.AccountManagement.dll)
public string GetComputerName(string computerName)
{
using (var context = new PrincipalContext(ContextType.Domain, "your domain name goes here"))
{
using (var group = GroupPrincipal.FindByIdentity(context, "Active Directory Group Name goes here"))
{
var computers = #group.GetMembers(true);
return computers.FirstOrDefault(c => c.Name == computerName).DistinguishedName;
}
}
return null; // or return "Not Found"
}

Performant way of finding specific user by case insensitive SAM in machine context

This code works like a charm in domain context:
var user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username);
It also works in machine context, but it is extremely slow (finding a user among around twenty takes 13s).
But first of all, this method is case insensitive, which is a must for me.
I have found somewhere an alternate code for machine context:
var context = new PrincipalContext(ContextType.Machine);
var user = new UserPrincipal(context)
{
SamAccountName = username
};
using (var searcher = new PrincipalSearcher(user))
{
user = searcher.FindOne() as UserPrincipal;
}
Unfortunately, this code is case sensitive.
Can anyone suggest an approach that is both quick in machine context and case insensitive?
Might not be the best solution if there are many local users, but it works:
var username = "wHaTeVer";
UserPrincipal user = null;
using (var context = new PrincipalContext(ContextType.Machine))
{
user = new UserPrincipal(context);
using (var searcher = new PrincipalSearcher(user))
{
user = searcher.FindAll().FirstOrDefault(x => x.SamAccountName.Equals(username, StringComparison.InvariantCultureIgnoreCase)) as UserPrincipal;
}
}

Active Directory users import in MVC

I am trying to fetch all the users of particular group from Active Directory of LDAP server. Authentication becomes success but i am getting null in result.
Following is my code.
Domain-172.11.12.123
Email-sample#email.com
password-123456
using (var context = new DirectoryEntry(user.Domain, user.Email, user.Password, AuthenticationTypes.Secure))
{
try
{
string FirstName;
string LastName;
string ADUserName;
string Email;
using (var searcher = new DirectorySearcher(context))
{
searcher.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(samaccountname='user3'))";
List<string> Adusers = new List<string>();
System.DirectoryServices.SearchResult result = searcher.FindOne();
}
}
catch (Exception ex)
{
TempData["message"] = "error";
return RedirectToAction("Index", "ADuserList");
}
}
What wrong is going on.
Thanks in advance
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace.
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context for the currently connected AD domain
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// 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!
Read more about it here:
MSDN docs on System.DirectoryServices.AccountManagement
Update: in order to get all the users of a given OU, the approach is quite different.
You need to create a separate PrincipalContext that defines what OU you're interested in - then you need to use a PrincipalSearcher to get all the users from that OU:
// create your domain context and define what OU to use:
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, null, "OU=YourOU,OU=SubOU,dc=YourCompany,dc=com"))
{
// define a "query-by-example" principal - here, we search for any UserPrincipal
UserPrincipal qbeUser = new UserPrincipal(ctx);
// 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.....
}
}

Categories