Index out of range when trying to retrieve userPrincipalName from AD - c#

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.

Related

Missing Active Directory groups when DirectorySearcher or SearchRequest is used

I want to list all the groups in a domain. If I use DirectorySearcher or LdapConnection and SearchRequest objects, some of the groups are missing in the returned list. But I can get the groups if I traverse all the tree with DirectoryEntry class starting from the root of the directory.
I checked the attributes of the returned and missing groups with AD Explorer tool but I could not see any difference between them. I need to use LdapConnection + SearchRequest since DirectoryEntry does not allow me to manage certificate issues if I need to use LDAP+SSL.
Did anyone enconter the same proble? What may be wrong?
Sample code for search operation;
LdapConnection _connection = new LdapConnection(new LdapDirectoryIdentifier("MTS", 389));
_connection.AuthType = AuthType.Basic;
_connection.Credential = new NetworkCredential("MTS\user1", "test123");
string _target = "dc=MTS,dc=com";
SearchRequest _request = new SearchRequest(_target, "(&(objectCategory=Group)(objectClass=group))", System.DirectoryServices.Protocols.SearchScope.Subtree, new string[] { "sAMAccountName" });
var _response = (SearchResponse)_connection.SendRequest(_request);
List<string> _namelist = new List<string>(16);
foreach (SearchResultEntry entry in _response.Entries)
{
if (entry.Attributes["sAMAccountName"].Count > 0)
_namelist.Add(entry.Attributes["sAMAccountName"][0].ToString());
}
Edit 1
If I change the search filter and search only for the missing group, it finds that group. Following search filter works and gets the group,
"(&(sAMAccountName=testGroup)(objectCategory=Group)(objectClass=group))"
There may be a limitation but I set Sizelimit and TimeLimit so high and search never returns an error about any limitation.
I had the same issue, no timeout or error but incomplete results.
I will try the PageResultRequestControl, thanks.

Get UserPrincipal by employeeID

I have implemented System.DirectoryServices.AccountManagement for authentication into my webapps finding users (UserPrincipal) byidentity given username. However, I have several cases where I need to get AD accounts given only an employeeID. Is there a good way to get a UserPrincipal (or even just the sAMAccountName) given an employeeID in AccountManagement?
I currently have this working to grab users by username:
PrincipalContext adAuth = new PrincipalContext(ContextType.Domain, Environment.UserDomainName);
//get user
UserPrincipal usr = UserPrincipal.FindByIdentity(adAuth, username);
I have been searching and can't seem to find answers to confirm whether this can or cannot be done. If I can't do it with AccountManagement, what's the best way to get sAMAccountName given employeeID?
You don't need to go outside of the System.DirectoryServices.AccountManagement namespace.
UserPrincipal searchTemplate = new UserPrincipal(adAuth);
searchTemplate.EmployeeID = "employeeID";
PrincipalSearcher ps = new PrincipalSearcher(searchTemplate);
UserPrincipal user = (UserPrincipal)ps.FindOne();
In this example, if no user is found the user object will be null. If you want to find a collection of UserPrinicipal object you can use the FindAll method on the PrincipalSearcher (ps) object.
Also note that the FindOne method returns a Principal object, but we know it is really a UserPrincipal and should be handled (casted) as such since UserPrincipal is part of the search filter.
So, I found a way using System.DirectoryServices as below, but it seems rather lengthy:
string username = "";
DirectoryEntry entry = new DirectoryEntry(_path);
//search for a DirectoryEntry based on employeeID
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(employeeID=" + empID + ")";
//username
search.PropertiesToLoad.Add("sAMAccountName");
SearchResult result = search.FindOne();
//get sAMAccountName property
username = result.Properties["sAMAccountName"][0].ToString();
Of course, I could use this for the other attributes, but I really like the strongly-typed attributes with AccountManagement.

Search Users in Active Directory based on First Name, Last Name and Display Name

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.

Get user information from DN

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

If an OU contains 3000 users, how to use DirectorySearcher to find all of them?

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.

Categories

Resources