I have two goals with the below code
1) Get a list of Users who below to a specific AD group
2) Get the email/lastname/first name of all the users that belong to that group
If there is a better way to accomplish both please let me know.
I'm able to get the full DN but I'm not sure how to get the remaining data from the full DN, or if there is a better way to pull this info please let me know. below is the code I'm using but it gets error:
The value provided for adsObject does not implement IADs.
when I tried to do a DirectorySearcher using the full DN.
HashSet<string> User_Collection = new HashSet<string>();
SearchResultCollection sResults = null;
DirectoryEntry dEntryhighlevel = new DirectoryEntry("LDAP://CN=Global_Users,OU=Astrix,OU=Clients,OU=Channel,DC=astro,DC=net");
foreach (object dn in dEntryhighlevel.Properties["member"])
{
DirectoryEntry dEntry = new DirectoryEntry(dn);
Console.WriteLine(dn);
DirectorySearcher dSearcher = new DirectorySearcher(dEntry);
//filter just user objects
dSearcher.SearchScope = SearchScope.Base;
//dSearcher.Filter = "(&(objectClass=user)(dn="+dn+")";
dSearcher.PageSize = 1000;
sResults = dSearcher.FindAll();
foreach (SearchResult sResult in sResults)
{
string Last_Name = sResult.Properties["sn"][0].ToString();
string First_Name = sResult.Properties["givenname"][0].ToString();
string Email_Address = sResult.Properties["mail"][0].ToString();
User_Collection.Add(Last_Name + "|" + First_Name + "|" + Email_Address);
}
Speed is important and yes I understand I'm not using HashSet as it's designed.
I always use the System.DirectoryServices.AccountManagement.
One of the first things you will see is this: "Connections speeds are increased by using the Fast Concurrent Bind (FSB) feature when available. Connection caching decreases the number of ports used."
with that being said I did not test your code against this for speed you will have to do that your self but this is Microsoft's new library.
Here is my code example:
// Create the context for the principal object.
PrincipalContext ctx = new PrincipalContext(ContextType.Domain,
"fabrikam",
"DC=fabrikam,DC=com");
// Create an in-memory user object to use as the query example.
GroupPrincipal u = new GroupPrincipal(ctx) {DisplayName = "Your Group Name Here"};
// Set properties on the user principal object.
// Create a PrincipalSearcher object to perform the search.
PrincipalSearcher ps = new PrincipalSearcher {QueryFilter = u};
// Tell the PrincipalSearcher what to search for.
// Run the query. The query locates users
// that match the supplied user principal object.
PrincipalSearchResult<Principal> results = ps.FindAll();
foreach (UserPrincipal principal in ((GroupPrincipal)results.FirstOrDefault()).Members)
{
string email = principal.EmailAddress;
string name = principal.Name;
string surname = principal.Surname;
}
It looks like you're walking the group membership of some group in AD...(guessing this off of the member reference above)
Anyway, you need to decide what sort of API you're looking for. The one you're using now is a bit lower level (though you could go lower if you want :)). Going higher level is an option as the previous answer eludes to.
To shake out the code a bit more (and help with perf as you mentioned that it is impt to you):
Use the same connection you used for the group membership search itself (ie no additional connect/bind)
Do a base search where the base DN is the user DN, the search filter is (objectclass=*) and the attributes are ONLY the attributes you care about (no *)
You can remove page size. Paging is a way to ask for many objects in groups (aka pages) but a base search only returns 1 object so it doesn't actually do anything.
Base search result count should always be 1.
Keep in mind cross-domain issues too. Make sure you test your code with a 2 domain forest where a group of type domain local in domain1 has a member in it from domain 2. That will yield some additional work to get more properties as you need to connect to a DC in the other domain (or a GC if the few properties you care about are all in the GC partial attribute set...)
Also keep in mind security. If you don't have access to these properties for some user in your domain, what does the code do? The code above would fail in a nasty way. :) You might want to handle this more gracefully...
Hope this helps.
~Eric
Related
I have simple service that gets user details using Novell.Directory.Ldap.NETStandard and F# (I can provide transcript for c# if that is necessary, but this part is very similar) and it looks like this:
use connection = new LdapConnection();
connection.Connect(credentials.host, LdapConnection.DefaultPort);
connection.Bind($"{credentials.domain}\{credentials.username}", credentials.password);
match connection.Connected with
| true ->
let schema = connection.FetchSchema((connection.GetSchemaDn()));
let filter = $"(SAMAccountName={credentials.username})"
let searcher = connection.Search(String.Empty, LdapConnection.ScopeBase, filter, null, false);
return (searcher |> Some, String.Empty)
| false ->
raise (Exception())
return (None, $"Cannot connect to domain {credentials.domain} with user {credentials.username}")
Now I cant find information about group that this user is assign to, normally when I use Directory.Service I just add:
directorySearcher.Filter <- sprintf "(SAMAccountName=%s)"credentials.username
To directory searcher and I can filter this information out (as Directory.Service is windows limited i can not use it in this project), but I can not find any information how to use it in Novell.Directory.Ldap.
You have to provide the required attributes (ie. memberOf in order to read user's group) as an array of strings instead of null when calling Search() :
let attrs = [| "SAMAccountName"; "memberOf"; |];
let searcher = connection.Search(searchbase, scope, filter, attrs, false);
You can also pass "*" to get all non-operational attributes.
I trying to search my organization Active directory for users.
If the FirstName or LastName or DisplayName matches a particular string value, it should return the users.
My Code:
// create your domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.GivenName = "Ramesh*";
// qbeUser.Surname = "Ramesh*";
// qbeUser.DisplayName= "Ramesh*";
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
// find all matches
foreach(var found in srch.FindAll())
{
//
}
The problem is that I am able to search by only one filter.
I am able to AND the filters but not OR. Whether any solutions are available?
See a possible solution for this issue in this other SO question.
You will need to use the extensibility of UserPrincipal to create a descendant class, in order to get access to the anr property (anr = ambiguous name resolution) which allows searches in multiple name-related properties at once.
Have a look at the DirectorySearcher.
This article may help.
I want to find the most specific OU my computer belongs to in C#.
I have code that will get the information I need, but it doesn't feel robust and it would require complicated parsing.
Are there better alternatives? Thoughts? Any help is appreciated!
Really what I want is an equivilent to the command prompt command:
dsquery computer -name COMP-HERE
But I need it in C#, which is proving to be problematic.
DirectorySearcher d = new DirectorySearcher("CN=COMP-HERE");
d.PropertiesToLoad.Add("adspath");
SearchResultCollection results = d.FindAll();
foreach (SearchResult searchResult in results) {
foreach (string propertyKey in searchResult.Properties.PropertyNames) {
ResultPropertyValueCollection valueCollection = searchResult.Properties[propertyKey];
foreach (Object propertyValue in valueCollection) {
Console.WriteLine(
"{0}:{1}",
propertyKey,
propertyValue.ToString());
}
}
}
Console.ReadLine();
Here's a solution using PrincipalContext and ComputerPrincipal in the System.DirectoryServices.AccountManagement namespace
string machineOU;
using (var context = new PrincipalContext(ContextType.Domain))
using (var comp = ComputerPrincipal.FindByIdentity(context, Environment.MachineName))
{
machineOU = String.Join(",", comp.DistinguishedName.Split(',')
.SkipWhile(s => !s.StartsWith("OU="))
.ToArray());
}
The Linq treatment of the machine's distinguished name splits it into it's component elements, skips any elements before the first OU=... element, and then recombines the rest., leaving you with the distinguished name of the containing OU.
More Info
The classes in the System.DirectoryServices.AccountManagement namespace provide a fairly high-level abstraction of various type of security principals (accounts).
The PrincipalContext is basically an abstraction of the account repository. It can refer to a machine's account database (PrincipalType.Machine), an Active Directory domain or Global Catalog (PrincipalType.Domain), or an Active Directory Application partition (PrincipalType.ApplicationDirectory).
new PrincipalContext(ContextType.Domain) creates a PrincipalContext representing the local Active Directory domain.
Once we've got this context we can use the FindBy...() static methods on the various principal types (can be UserPrincipal, ComputerPrincipal or GroupPrincipal) to find the AD object we're looking for.
I've done a simple search on google and found a lot of information on this. I think you are in the good direction, maybe compare your code with these links:
How to get list of Organizational Unit in Active Directory
or here on stack overflow
This is what worked for me
IEnumerable<string> machineOU;
using (var context = new PrincipalContext(ContextType.Domain))
using (var comp = ComputerPrincipal.FindByIdentity(context, Environment.MachineName))
{
machineOU = comp.DistinguishedName.Split(',').Where(s => s.StartsWith("OU="));
}
I use this code:
DirectoryEntry objEntry;
DirectorySearcher objSearchEntry;
SearchResultCollection objSearchResult;
string strFilter = "(&(objectCategory=User))";
objEntry = new DirectoryEntry(conOUPath, conUser, conPwd, AuthenticationTypes.Secure);
objEntry.RefreshCache();
objSearchEntry = new DirectorySearcher(objEntry);
objSearchEntry.Filter=strFilter;
objSearchEntry.SearchScope=SearchScope.Subtree;
objSearchEntry.CacheResults=false;
objSearchResult=objSearchEntry.FindAll();
Each time, it only return 1000 users, but there are 3000 users in that OU.
How can i find all of them ?
If you're on .NET 3.5 or newer, you should check out the PrincipalSearcher and a "query-by-example" principal to do your searching:
// create your domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "YOURDOMAIN", "OU=SomeOU,DC=YourCompany,DC=com");
// 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.GivenName = "Bruce";
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
// set the PageSize on the underlying DirectorySearcher to get all 3000 entries
((DirectorySearcher)srch.GetUnderlyingSearcher()).PageSize = 500;
// 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
Update:
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.
Update #2: If you want to search just inside a given OU, you can define that OU in the constructor of the PrincipalContext.
You need to set the DirectorySearcher.PageSize property to be able to return all the results. For example:
objSearchEntry.PageSize = 500;
Otherwise the number of items returned will be limited by the limit on the server side, which is 1000 by default. There is also something called SizeLimit, which you can set if you want to explicitly limit the number of returned items. If both SizeLimit and PageSize are 0 (default values) then it will use the server side default SizeLimit. A bit counter-intuitive in my opinion.
If you want to return all the results, the only way is to set PageSize to a non-zero value and SizeLimit to 0.
I'm stumped..
I'm trying to get the userPrincipalName from AD as follows:
DirectorySearcher search = new DirectorySearcher("LDAP://DCHS");
search.Filter = String.Format("(SAMAccountName={0})", UserName);
SearchResult result = search.FindOne();
DirectoryEntry entry = result.GetDirectoryEntry();
_UPN = entry.Properties["userPrincipalName"][0].ToString();
But this gives me:
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
Can anyone tell me why this is happening?
EDIT:
This code gets the SSID of the current user. I need to make this work on any user I enter into a text box.
WindowsIdentity windowsId = new WindowsIdentity(WindowsIdentity.GetCurrent().Token);
_SSID = windowsId.User.ToString()
I believe the issue is because you are treating the userPrincipalName entry as an array of values. Try modifying your code as follows:
DirectorySearcher search = new DirectorySearcher("LDAP://DCHS");
search.Filter = String.Format("(SAMAccountName={0})", UserName);
SearchResult result = search.FindOne();
DirectoryEntry entry = result.GetDirectoryEntry();
_UPN = entry.Properties["userPrincipalName"].Value.ToString();
Notice that I changed the last line from [0] to Value. That should fix your issue.
The one thing I would say is that I would do some checking before trying to read this value. There are cases where a user wouldn't have a UPN. In that case, the code would throw an error when you tried to access the field (the field wouldn't exist so it wouldn't be that you just need to make sure it isn't null).
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
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 user by name
UserPrincipal user = UserPrincipal.FindByIdentity(UserName);
if(user != null)
{
string upn = user.UserPrincipalName;
}
The new S.DS.AM makes it really easy to play around with users and groups in AD:
The obvious thing to do to avoid the exception (if that's valid) is to do
if (entry.Properties["userPrincipalName"].Count > 0)
{
_UPN = entry.Properties["userPrincipalName"][0].ToString();
}
but if you were supposed to get a valid result and you aren't then I would check the LDAP connection string and such. There are a few LDAP browsers that you could use (commercial + trial) to get your connection string right.