Geeks With Blogs

Mike H. - Another Geek In Need... WebLog

.Net Directory Services Programming – C# - Part 3

 

Topics

DirectorySearcher – the other critical class in the DirectoryServices namespace.

 

 

Review

Because a lot of your Directory Services (DS) development will involve querying DS for data, it makes sense that this is a powerful class offered in the namespace, and below are some of the features:

 

  • DirectorySearcher – Performs the initial queries against AD
  • SearchResult – A single object reference from a search performed by DirectorySearcher
  • ResultPropertyCollection – A collection of properties of the SearchResult instance
  • ResultPropertyValueCollection – The values of properties in a SearchResult instance.
  • SearchResultCollection – Basically a collection of SearchResult instances returned from a query by DirectorySearcher
  • SortOption – Allows a means to sort a result set.

 

 

 

We have briefly touched on DS by introducing some basic DirectoryEntry information and a couple simple examples, and we have introduced some basic properties and examples on how to interact with those.

 

I have not taken a lot of time to review a directory structure – basically assuming that you should have some of this understanding already. As I delve into the next lesson, this assumption holds fast. If you have any issues or are dealing with something that I am not elaborating enough on, please feel free to email me directly at (mikeh AT thedataworks.net) and I will respond as quickly as I can. Please forgive the notation there – but we all are familiar with the latest in SPAM bots and whatnot.

 

Show me the Money!!!

Or at least the code. Lets get started…

 

Parent – Child Relationships

DirectoryEntry objects (DE’s) can be a root object in AD, or an object within another – hence, a child object of another. We reviewed in Part 1 a few of the common references to object types (CN, OU, DC). These are the most prevalent you will deal with.

 

DC is the root level context. OU’s can be off the root, or a child of other OU’s and even Containers. CN’s can be off the root, within OU’s and often are found within Containers.

 

A word about Containers and Security

When planning your DS application, keep this in mind: Containers are non-secure objects! Let me explain. AD security is multi-faceted, yes, but the real security is managed using Group Policy (something we are not going to review here).

 

Containers are basically folders in your directory tree. AD comes with a couple by default. If you promote a server and check your directory tree, you’ll find a Users, a Built-In, and a couple other folders off the root tree (or context/server name) object. These are not OU’s – they are containers.

 

Group Policy (GP) cannot be applied to a Container object in AD. You can apply GP to objects within Containers – depending on what those objects are – but not the Container itself. This is important because a lot of administration can be simplified by applying the GP to the entire Container. So if not the Container, then what? Organizational Units – OU’s. Alas, OU’s are going to be your preferred folder or container (for lack of a better way to put it) for object storage.

 

Basic Searches

In Part 1 we introduced a server name and basic OU’s. To re-cap that:

 

Server: developer.hamilton.com

OU=Accounts – off the root of developer.hamilton.com

OU=Developers – within the OU=Accounts

CN=Mike Hamilton – within the OU=Developers

 

Within the OU=Accounts I might also create service accounts – basic user accounts that are used to run services such as IIS Application Pools, SQL Server, or other server / service applications. I might also have OU’s that represent other departments that pertain to development: BA’s for the Business Analyst, QA’s, etc. You can nest OU’s and accounts in just about any way you want to. NOTE: In AD – if you have not already – you will run into one of the inherent limitations of the AD Users & Computers (dsa.msc) snap-in: It can only display the first 2,000 objects within a Container or OU. So if you anticipate a large volume of users for a system you are designing, keep this in mind. You will want to present 1) a more suitable customized user interface piece for administration of this DS, or 2) design your object hierarchy so that users are more spread out within the DS. I will touch base on this topic in a later Part and introduce a couple ideas for addressing this limitation.

 

DirectorySearcher and the SearchResult

When using the DirectorySearcher class, you have the FindOne() and FindAll() methods that build your result set. As they infer, one finds one occurrence, the other finds all occurrences.

 

Let’s say I had more than one Mike on my team, and I wanted to find all entries that began with Mike?

 

DirectoryEntry rootEntry = new DirectoryEntry();                // Binds root context

// Now find every CN that has Mike at the beginning of the name…

DirectorySearcher searcherResults = new DirectorySearcher(rootEntry,”(CN=Mike*)”);

 

This is a simple example, and the result set will be one or more object references that begin with Mike. Notice that I did a bind on the root of the DS. I could have narrowed the focus of the search to only the developer’s OU by doing the following:

 

DirectoryEntry developersEntry = new DirectoryEntry(“LDAP://developer.hamilton.com/OU=Accounts,OU=Developers,DC=developer,DC=hamilton,DC=com”);

 

This would return an entry object where the parent would be the OU=Developers, and not the default root of the DS tree.

 

Lets say my DS was structured like the following:

 

developer.hamilton.com à root of the DS

            OU=Accounts à First OU of accounts of users.

                        OU=Developers à Key development personnel

                        OU=Bas à Business Analyst

                        OU=QA à Quality Assurance / Test personnel

                        OU=Users à General users on this server.

 

Now, we want to find all users that are in the Users OU, not returning the developers or other personnel within the directory. That’s pretty straight forward.

 

DirectoryEntry developersEntry = new DirectoryEntry(“LDAP://developer.hamilton.com/OU=Accounts,OU=Users,DC=developer,DC=hamilton,DC=com”);

 

// Now return all users in this OU…

DirectorySearcher searcherResults = new DirectorySearcher(rootEntry,”(CN= *)”);

 

As you get more familiar with DS programming, you will find there are other ways to accomplish the same result – I am simply trying to open those doors for you.

 

It is important that you understand how to narrow your search filter in this way because you will find it greatly improves the performance of your application when you know where to search for content and you keep your search filter narrowly defined. For example, if I have 15,000 employees, they would very likely be grouped in AD by their department, and possibly further grouped by sub-departments. I would want to know this information beforehand so I can plan my DS application to be as efficient as possible. I would not want to search the rootDSE each time for a given employee or group of employees, if I know the departments those employees are in.

 

More on the Search Filter

The filter is a LDAP string that allows you to search for objects based on specific criteria. You will also use relational operators to better refine your search. The following list those operators:

 

=                      Equal to

~=                    Is approximately equal to (or like)

<=                    Less than or equal to

>=                    Greater than or equal to

 

NOTE: You cannot use ‘<’ or ‘>’ individually in LDAP – you must use the notation <= or >=.

 

The * character works as a wildcard in your search, as in the example above we searched for CN=Mike* or CN=* - in this case, get all Canonical (common) Names beginning with Mike, or get all names period (respectively).

 

To find all users with a surname of Hamilton you would use

 

(sn = Hamilton)

 

To find all users where the email ends in @hamilton.com

 

(&(objectCategory = person) (objectClass = user) (mail = *@hamilton.com))

 

Notice the Polish notation used here – the operator comes before the condition. LDAP uses this notation in the Filter. The above example would be more familiar possibly as

 

(objectCategory = person) AND (objectClass = user) AND (mail = *@hamilton.com)

 

The following are allowed logical operators:

 

&         AND

|           OR

!           NOT

 

You set the filter for a search using the DirectorySearcher.Filter property. The following code will create a binding to the root context, and return everything from the root down:

 

DirectoryEntry rootEntry = new DirectoryEntry();                // Binds root context

DirectorySearcher rootSearcher = new DirectorySearcher(rootEntry);

 

Let’s say we have a binding to the rootDSE as above, but only want to return all users with a surname of Hamilton? Change the DirectorySearcher to the following:

 

DirectorySearcher rootSearcher = new DirectorySearcher(“(sn=Hamilton)”);

 

You can either pass the DirectoryEntry object, or you can specify a filter in this fashion when creating a DirectorySearcher instance.

 

Loading Specific Properties

By default, when you create a DirectorySearcher instance, you return all properties for that binding (which can be a lot of data if you have a lot of items in your returned result set).

 

You can refine your result set by specifying the exact properties to be returned. This is especially important when you could be returning large collections of users.

 

Following the above example I could create my rootSearcher and then do:

 

rootSearcher.PropertiesToLoad.Add(“name”);

rootSearcher.PropertiesToLoad.Add(“sn”);

rootSearcher.PropertiesToLoad.Add(“mail”);

rootSearcher.PropertiesToLoad.Add(“telephoneNumber”);

 

Here I have specifically limited the result set to only include the name, sn (surname), mail and telephoneNumber properties.

 

Finally, when we define our searcher, we can stipulate the search scope.

 

Search Scope

The search scope determines the extent of the search in a directory, defaulting to beginning at the root.

 

In the System.DirectoryServices.SearchScope you will find the following 3 enumerations specified:

 

Base – Limits the scope to the base, or only 1 object.

 

OneLevel – Searches 1 level of the immediate child objects, excluding the base object.

 

Subtree – Searches all child objects under the base object, including the base object.

 

When creating a DirectorySearcher instance, the following table lists default constructor behaviors:

 

PROPERTY

DEFAULT VALUE

DESCRIPTION

SearchRoot

Null

Searches the root of the domain controller (rootDSE).

Filter

(objectClass=*)

The search retrieves all objects.

PropertiesToLoad

An empty string array

The search retrieves all properties.

SearchScope

Subtree

The search will be performed on the complete subtree of the base object (or entire domain in this case).

 

 

 

 

 

Constructors of the DirectorySearcher Class

The above table displays the default constructor when you create a DirectorySearcher instance like:

 

 

DirectoryEntry rootEntry = new DirectoryEntry();                // Binds root context

DirectorySearcher rootSearcher = new DirectorySearcher(rootEntry);

 

The above specifies the Search Root.

 

Specifying the Filter Only

We showed this earlier,

 

DirectorySearcher rootSearcher = new DirectorySearcher(“(sn=Hamilton)”);

 

Specifying the Search Root and Filter

Create our base / root reference:

 

DirectoryEntry rootEntry = new DirectoryEntry(“LDAP://developer.hamilton.com/OU=Accounts,OU=Developers,DC=developer.DC=Hamilton,DC=com”);

 

// Now specify the filter…

DirectorySearch rootSearcher = new DirectorySearcher(rootEntry, “(sn=Hamilton)”);

 

Specifying the Filter and a List of Properties to load

 

// Create the set of properties to load…

string [] searchProperties = new string[4];

searchProperties[0] = “name”;

searchProperties[1] = “sn”;

searchProperties[2] = “mail”;

searchProperties[3] = “telephoneNumber”;

 

// Get all users that have an email ending in hamilton.com…

DirectorySearcher rootSearcher = new DirectorySearcher(“(mail=*hamilton.com)”, searchProperties);

 

The above example will return all users from the rootDSE with an email ending in hamilton.com. Let’s refine that.

 

Specifying the Root, Search Filter, and Properties to Load

 

// Specify the properties to load… Here we get Full Name, Company, Phone…

string [] searchProperties = new string[3];

searchProperties[0] = “name”;

searchProperties[1] = “co”;

searchProperties[2] = “telephoneNumber”;

 

// Now bind to a more refined root – developers only…

DirectoryEntry rootEntry = new DirectoryEntry(“LDAP://developer.hamilton.com/OU=Accounts,OU=Developers,DC=developer,DC=hamilton,DC=com”);

 

// No get all users from this root with an email ending in hamilton.com…

DirectorySearcher rootSearcher = new DirectorySearcher(rootEntry, “(mail=*hamilton.com)”, searchProperties);

 

You should have a better idea now how to use the various constructors of the DirectorySearcher class.

 

Methods of the DirectorySearcher

When you have created a binding and set filter values, you will use 1 of the 2 DirectorySearcher methods:

 

FindOne() – Returns the first, and only 1 object of the search criteria.

FindAll() – Returns a SearchResultCollection of all objects that matched the search criteria.

 

We’ll do a brief sample for each method and wrap up this series with the Sorter class.

 

Return using FindOne()

 

// The following demonstrates that FindOne() returns only the first entry that matches the search critieria…

DirectoryEntry rootEntry = new DirectoryEntry(“LDAP://developer.hamilton.com/OU=Accounts,OU=Developers,DC=developer,DC=hamilton,DC=com”);

DirectorySearcher rootSearcher = new DirectorySearcher(rootEntry);

 

// What to search for…

rootSearcher.Filter = “(CN=*)”;

 

// Specific properties to load…

rootSearcher.PropertiesToLoad.Add(“name”);                      // Full name…

rootSearcher.PropertiesToLoad.Add(“mail”);                        // Primary email addy…

rootSearcher.PropertiesToLoad.Add(“telephoneNumber”);  // Phone #...

 

SearchResult searchResults = rootSearcher.FindOne();

 

// Extract out the properties returned and display in a console…

ResultPropertyCollection propertiesCollection;

propertiesCollection = searchResults.Properties;

 

// Cycle through and display (or add to a listbox, etc)…

Foreach (string currentProperty in propertiesCollection.PropertyNames)

{

            foreach (Object thisCollection in propertiesCollection[currentProperty])

            {

                        Console.WriteLine(currentProperty + “ = “ + thisCollection);

            }

}

 

Return using FindAll()

Unlike above, the ResultPropertyCollection being returned with FindOne(), FindAll() comes back in a SearchResultCollection object.

 

We would process the result set similar to the following:

 

// Get all objects returned…

foreach (SearchResult searchResults in rootSearcher.FindAll())

{          // Process…

            foreach (string propertName in searchResults.Properties.PropertyNames)

            {

                        foreach (Object retEntry in searchResults.Properties[propertyName])

                        {

                                    Console.WriteLine(propertyName + “ = “ + retEntry);

                        }

            }

            Console.WriteLine(“”);

}

 

If the root of the search is not properly set, a InvalidOperationException exception will be thrown. Also, if the provider you are trying this against is not supported, a NotSupportedException exception is thrown.

 

Sorting the Result Sets

Finally, the SortOption class specifies how to sort the result set of a search. The class also allows you to specify either ascending or descending sort order.

 

Let’s do a search on all users that have an email ending in @hamilton.com and that exist in the Developers OU. Further, restrict the properties returned, sort the list, and using the code snippet above, list the results in a console window.

 

DirectoryEntry rootEntry = new DirectoryEntry(“LDAP://developer.hamilton.com/OU=Accounts,OU=Developers,DC=developer,DC=hamilton,DC=com”);

DirectorySearcher rootSearcher = new DirectorySearcher(rootEntry);

 

// Set our search filter and properties to load…

rootSearcher.Filter = “(mail=*hamilton.com)”;

rootSearcher.PropertiesToLoad.Add(“name”);          // Full name…

rootSearcher.PropertiesToLoad.Add(“mail”);            // Primary email addy…

rootSearcher.PropertiesToLoad.Add(“telephoneNumber”);  // Phone #...

 

// Now sort the result set…

SortOption sortedResults = new SortOption();

sortedResults.PropertyName = “name”;                     // Sort by full name…

sortedResults.Direction.SortDirection.Ascending;

 

// Perform the search and output the results..

rootSearcher.Sort = sortedResults;

foreach (SearchResult searchResults in rootSearcher.FindAll())

{          // Process…

            foreach (string propertName in searchResults.Properties.PropertyNames)

            {

                        foreach (Object retEntry in searchResults.Properties[propertyName])

                        {

                                    Console.WriteLine(propertyName + “ = “ + retEntry);

                        }

            }

            Console.WriteLine(“”);

}

 

Final notes on the following classes: SearchResult, SearchResultCollection, ResultPropertyCollection, and ResultPropertyValueCollection.

 

The SearchResult class consist of the first entry returned during a search. It retrieves the data from the SearchResultCollection, and the FindOne() method of the DirectorySearcher class returns an instance of the SearchResult class.

 

The SearchResultCollection class is actually a collection of SearchResult instances and these are returned using the FindAll() method of the DirectorySearcher class. You use the Count property to get the number of items returned in the collection. Use the Item property to index through the SearchResultCollection to enumerate each property. Finally, the PropertiesLoaded property returns a string array containing the names of the properties loaded for the original search criteria.

 

The ResultPropertyCollection class is accessed as a Properties property of the SearchResult class. It has an Item property you use to index the collection, which is a string value. This index retrieves the values of the property matching the specified index name as a ResultPropertyValueCollection. The following snippet will demonstrate getting all returned properties and outputting the result to a console window:

 

DirectoryEntry rootEntry = new DirectoryEntry(); // Entire domain…

DireectorySearcher rootSearcher = new DirectorySearcher(rootEntry, “(sn=Hamilton)”);

 

SearchResult searchResults = rootSearcher.FindOne();

 

// Get the properties returned…

ResultPropertyCollection propertyCollection =  searchResults.Properties;

 

// Output…

foreach (string thisProperty in propertyCollection.PropertyNames)

{

            foreach (Object propertyValue in propertyCollection[thisProperty])

            {

                        Console.WriteLine(thisProperty + “ = “ + propertyValue);

            }

}

 

In the future I will introduce ADSI API and application code and we will see about developing a small application that will take some of what we have reviewed in these three parts and create a directory browser sample application.

 

I want to note that for the sample code so far, if you are not executing this on an actual AD domain controller, you will need to change the new DirectoryEntry() code to include the root, username and password of the context you are trying to connect as (we have reviewed this in previous lessons).

 

Finally, please forgive any typo’s – I try hard to make sure there are none.

 

I have received a great deal of feedback over the little I have posted so far, and I hope this posting is helpful for some. I apologize again for taking so long to get this series posted.

Posted on Saturday, July 15, 2006 2:47 PM .Net Development | Back to top


Comments on this post: .Net Directory Services Programming - C# - Part 3

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Mike,

Let me start by saying that your series regarding AD programming have been most helpful to me. I'm fairly new to C# programming so your tutorials have been very helpful reading for me.

I have one question for you. I've been asked to create a function to query a MS Exchange server to list:
-All the storagegroups that exists
-All the mailstores that exists in theese storagegroups
-Wich mailstore has the least number of mailboxes.

I have read some snippets on the web, but none of them has done the trick. I know I should query the msExchPrivateMDB, msExchStorageGroup and the homeMDBBL attributes, but I have never really got it all together.

TIA

Patrik
Left by Patrik on Aug 16, 2006 10:46 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Mike,

Thanks for the posting. It helped me out quite a bit with getting started on AD / Web integration.

Jamie.
Left by JamieL on Dec 14, 2006 9:51 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Mike,

This article is much helpful, but I have an issue. I have multiple OU's as like Accounts, Developers. I can only able to access Accounts level, when I am trying to access Developers level, I am getting "There is no such object on the server" error. Any help will be much appreciated.

Thanks,
Vel
Left by Vel on Feb 28, 2007 12:45 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Vel,

If you can post your code here - perhaps I can better respond to what is happening.

Thanks...
Left by MikeH on Mar 01, 2007 4:33 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Mike,

I moving my code to the VS2005 from VS 2003 and I have a project which references the activeds which like i posted earlier is giving me these 39 warnings.

(http://geekswithblogs.net/mhamilton/archive/2005/10/04/55920.aspx?Pending=true)

Anyway I was thinking may be I should go away with the interop and see if I can just use the system.directoryservices.

The things I do in the code using the interop are: ActiveDs.LargeInteger liMaxAge = domainDE.Properties["MaxPwdAge"].Value as LargeInteger;
maxAge = (((long)(liMaxAge.HighPart) << 32) + (long) liMaxAge.LowPart);
LargeInteger li = userDE.Properties["pwdLastSet"].Value as LargeInteger;
IADsUser nativeUser = (IADsUser)userDE.NativeObject;
pwdLastSet = nativeUser.PasswordLastChanged.ToUniversalTime();
badLogonCount = nativeUser.BadLoginCount

Is there a way to do the same without the activeds reference?

Thank You,
Satish
Left by Satish on Apr 12, 2007 1:16 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
i Satish,

At this time - no - ActiveDS is your API for ADSI level programming. Remember, DirectoryServices namespace is just a wrapper for some 'basic' - but most core-level - LDAP stuff. When you want to get down/dirty - you have to use the ActiveDs.dll. Now, that's not to say that you cannot write your own set of classes that wrapper the COM components yourself. I have done this for one engagement - it's not too difficult. The real problem is the obvious one - there is NO documentation to help - I mean zero, zilch, nada - so most of my work is by trail/error. Or, I learn by immersion by fire - if that makes sense?
Left by MikeH on Apr 12, 2007 1:23 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Mike,

Ah.

Ok did you get a chance to look at the warnings am getting which I posted in the link. The warnings are not hurting me but am pushing hard to try and remove them.

Thank you for the help Mike.

Satish
Left by Satish on Apr 12, 2007 1:29 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Satish,

I did look at what you sent and nothing conveys why you are seeing the warnings. I suspect there is much more to the solution that I am not seeing #1, and I strongly suspect it has something to do with a light-weight assembly, 1 that is not strongly names/keyed #2 - if that makes sense?

You may be able to do away with the direct calls to ActiveDs.dll - but you may find that you need some of the features of that API that are not provided by DirectoryServices alone.
Left by MikeH on Apr 15, 2007 6:46 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Mike,

Very comprehensive and nicely worked examples - thanks.

I can't quite figure out how to put this into my real world scenario:

I have 2 problems.

1) I have an intranetUser class that I want to hold each user.
I have a list collection that will then hold all intranetUser objects.

How do I dynamically iterate through the properties of my intranetUser matching with the SearchResult?

e.g
public class IntranetUser
{
// private members
string m_strSAM;
string m_strPayroll;
string m_strSurname;
string m_strForename;
string m_strEmail;
string m_strJobTitle;
string m_strCostCentre;
string m_strDepartment;
DateTime m_dtStartDate;
DateTime m_dtEndDate;
string m_iExtNo;
string m_strReportsTo;
}

pseudo-something:
in a loop
get the sAMAccountName (and all other propertiesToLoad) and populate the appropriate field(s) in the intranetUser object


2) How to distinguish between similar CN - we have some users that appear twice in our AD (freelancer left and re-joines as permanent) - Is the objectguid the property to use?

I probably haven't made much sense but my purpose is to populate our intranet personnel database with info from the AD and refresh it every night.

I thought i'd make an intranetUser class that would handle the user data and I could then do some business logic that would update (or not) personnel info to SQL.

It's a horrible interim but we can't re-write all our apps to intergrate with AD.

Thanks
Waseem
Left by Waseem on Jun 28, 2007 5:21 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Waseem,

Have you tried a little different approach???

Get your collection of users and then check their [memberOf] property to see if they are a member of a security group you added them to in an effort to uniquely group / identify these users?

Mike...
Left by MikeH on Jun 28, 2007 5:27 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Mike,

We have an intranet that is classic ASP with some .NET apps linked off of it. The users have to log in and their details are looked up against a database (manually updated!!) which sets some info in a cookie.

This cookie is read by various apps.

I want that database to be updated from the AD.

I don't see how the "memberOf" helps or what I should "test" this against.

Thanks for your help
Waseem
Left by Waseem on Jun 28, 2007 8:27 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hey Waseem,

You mention using classic ASP but it appears that you're binding to an instance of the AD user object? Is this correct? So you're doing 'some' .Net programming where you do something like DirectoryEntry userEntry = new DirectoryEntry()? or are you doing something totally different?

If you are binding to AD - then you have the means of doing what I'm talking about.

If you are NOT using AD - then I am at a loss - based solely on your first email, which conveyed that it appears that you're working with AD. Perhaps you could share more of the architecture and how you have been working to date?
Left by MikeH on Jun 28, 2007 8:45 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Mike,

Okay, bear with me please.

We have a Full AD with about 1000 users (500 on site)
Our intranet is a bespoke classic ASP app written in-house.
There is a login page that looks up against a PERSONNEL database and stores info in a cooke - but No security at all.

It is the DATABASE that I wish to update automatically by creating a .NET console app in C# that will dump the AD info into similar columns in the Personnel database.

1) Is that a reasonable approach?
2) Can you iterate though an object's properties and compare the "name" of the property

Thanks very much
Waseem
Left by Waseem on Jun 29, 2007 7:10 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
He Waseem,

It sounds like you have inherited a job that I do not envy you for.

With that said, can you simply write a .Net (C#) application that handles the AD authentication and whatnot, and expose methods for the classic ASP to call into? I've done this before, and it sounds much cleaner and easier than what you're dealing with.

In fact, in .Net 2.x - you can create a new logon.aspx object that actually uses the ADMembershipProvider that comes with .Net 2.x. Here, much like you would use the AspNetSqpMembershipProvider - you can authenticate directly against AD - obviating the need for the replicated database of users.

Next, wrapper the directory services pieces you specifically need to leveral in a C#.Net application that'll take care of those calls - and deploy that aspx object in the web site.

I'm just rambling some thoughts here - my apologies. But I would try the shortest, simplest path - and implementing a new forms based auth (FBA) form that knocks out the authentication is the first thing I'd do - then I'd move onto how to read specific properties from those AD users - hence, a new aspx object altogether that can include the appropriate class references and do the work for you - and once done - simply pass back to your previous page(s).
Left by MikeH on Jun 29, 2007 7:47 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Mike,

Eureka!

That sounds like a very good plan.
I'll do the .NET bit, store the details (cookie, db etc)
and flip back out to the intranet.

What I really want to do over the next few months is completely redo our whole "NEW USER" process using .net 3.0 and Workflows but I'm such a noob!

Thanks for all your help Mike!
Waseem


Left by Waseem on Jun 29, 2007 8:57 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Mike,
This is a curious question that perhaps you can answer. I have two sets of code. The first is in ASP.Net webpage and the other is in a Windows Console App. Both using C#.
I am trying to see if a user is in a role.

In the web app I use this
Page.User.IsInRole("RoleName")

and in the windows console app I use
IPrincipal currentUser = new System.Security.Principal.WindowsPrincipal(System.Security.Principal.WindowsIdentity.GetCurrent());

currentUser.IsInRole("RoleName")

the web page version works where the windows console appliction does not unless I do this
currentUser.IsInRolw(@"starhrg/RoleName");

can you possibly explain what the difference is why do I have to append the domain name in a Windows Application?

Thanks
Left by Mark Toth on Dec 12, 2007 12:02 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Mark...

Without working with the actual code - there could be a number of reasons why. From what you provided - you are having to determine the Principal in the console application - using the COM interop piece more than likely, whereas in the IIS application - and assuming you may be running .Net 2.x - you're getting that automatically (the Principal and Identity) - this obviates checking the domain\userName - you have that already.

In .Net 1.1 it was much like you're doing with the console application and was not intuitive.

I'm not sure if this is it - just the ramblings of another Geek In Need...
Left by MikeH on Dec 12, 2007 3:57 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
That's the nail on the head. The Web app uses 2.x and the console app used 1.1

Wow, Geek in need is a Geek indeed!

Thanks for the help! Brilliant catch!
Left by Mark Toth on Dec 12, 2007 5:01 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Mike,
In our application we are querying the details of a user by passing the email-id.
The function used is "search.FindOne()" .
The application is working fine on Windows 2000
but it is throwing ""A local error has occurred." error when the same application is tested on Windows Vista.

The application is built using VS 2005.
Is there any compatibility issue on Vista ?
Left by PAtil on Mar 26, 2008 1:17 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Patil,

I do not think Vista supports the same API / AD schema structure of true Active Directory. I am not certain that it supports the older LDAP as well - I would have to look into this.
Left by MikeH on Mar 26, 2008 6:19 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
What could be the cause of the the error : BAD USERNAME OR PASSWORD-COM EXCEPTION. I get this exception all the time.Plz help
Left by Meera on Jun 13, 2008 6:52 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Meera... If you can post your code here we may be able to provide a little more insight.

Regards...
Left by MikeH on Jun 13, 2008 5:13 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Thank you
Left by parke on Aug 31, 2008 1:21 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
The details provided here are wonderful and also very much helpful.

But I have a query,

Currently we are try to search for the a particular name in the AD which has special charaters "()". We have used directorySearcher.filter for searching but it fails all the time.

Can anyone of you please let me know how do I achieve this functionality?
Left by Shashi on Nov 27, 2008 3:21 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Shashi...

If you could post a sampling of your code, perhaps that would help?

MikeH...
Left by MikeH on Nov 27, 2008 6:45 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
This is such a total geekfeast. By the way, for those gabbers not up to your level, there are TechNet Group Policy fundamentals webcasts at http://www.microsoft.com/events/series/grouppolicy.aspx.
Left by Fred on Feb 13, 2009 5:39 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
3 years later....still incredibly helpful! Thanks!
Left by Shayne on Aug 31, 2009 4:43 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Excellent, thank you for the helpful article.
Left by Steve on Feb 12, 2010 12:10 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hi Mike,

We have a windows application using LDAP to do authentication. It works fine in XP machines, but it throws an exception: System.Runtime.InteroServices.COMException on "ds.FindOne()" when running in Vista. Application is built in VS 2005 and .NET Framework 2.0. Is there a solution for Vista?

Thanks!
Left by Xiaoping on Feb 25, 2010 3:32 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Where can I get a list of AD properties?

I am debugging code that I did not write, and I do not know what the shortcuts stand for ("cn", "anr", etc.).

Is there a list somewhere? What would/should I search for?
Left by Joe on Mar 18, 2010 1:07 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
DirectorySearcher searcher = new DirectorySearcher(domain, "(cn=Faculty)");
searcher.PropertiesToLoad.Add("member");
searcher.Sort.PropertyName = "member";
SearchResultCollection results = searcher.FindAll();


When I try to run the above code, I get the error message "the server does not support the requested critical extension" on searcher.FindAll();

However, if I remove the line "searcher.Sort.PropertyName = "member";" it starts working fine.
Left by Moe on May 15, 2010 8:19 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Great article - thanks heaps.
t
Left by Tim Huffam on Jun 21, 2010 3:53 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Hey Mike! Great article!... I know you wrote this long time ago, but maybe it's not too late and you might be able to help me:

I have to write an HttpModule in VS2003 to handle authorization to my organization's websites.

The module has to get the requester user's AD groups and validate them against a database.

To get the user name I use this:

string str_User_ID = HttpContext.Current.User.Identity.Name.ToString();
int slashIndex = str_User_ID.IndexOf("\\");
string userWithouthDomain = str_User_ID;
if(slashIndex >=0)
{
userWithouthDomain = str_User_ID.Substring(slashIndex + 1);
}

Then, I use the following code to retrieve the user's AD groups:

private string GetUserADGroups(string userName)
{

DirectorySearcher search = new DirectorySearcher();
//SAMAccountName is the user's id in the form of <first_name_initial>+<last_name>
//for instance: pruiz or jdoe
search.Filter = String.Format("(sAMAccountName={0})", userName);
search.PropertiesToLoad.Add("memberOf");
StringBuilder groupsList = new StringBuilder();

SearchResult result = search.FindOne();
if (result != null && result.Path != null)
{
int groupCount = result.Properties["memberOf"].Count;

for(int counter = 0; counter < groupCount; counter++)
{
string aux = (string)result.Properties["memberOf"][counter];
string[] tokens = aux.Split(',');
//First token has the group (C)ommon (N)ame
string[] group = tokens[0].Split('=');
//Second token has the actual group name
string groupName =group[1].ToString();
groupsList.Append( groupName );
groupsList.Append(",");
}
groupsList.Length -= 1; //remove the last ',' symbol
}
return groupsList.ToString();
}

If I use this method in a desktop application with my account, everything works fine. But if I try to get somebody else's groups, by providing another target account (say jdoe) then, the method "FindOne" returns a SearchResult object instance, but, without the "memberOf" property.

Furthermore, if I use this method in my HttpModule which, ultimately, is just a dll that I register in the GAC and then is called by a web application, then the "memberOf" property is also null, even if I'm running the web app with my network credentials.

What can be avoiding the FindOne method to retrieve the "memberOf" property?

Many thanks in advance!
Left by Pedro on Oct 08, 2010 12:41 PM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Question on the sortOption. It seems like using the sortOption returns only records where the sorted attribute value is set, but excludes those without a value set. For instance. We can get a result set (unsorted) that shows all of the records, even those where the givenName attribute is not set. But if we sumbit the same request and sort on givenName, then any records where the givenName attribute is not set are excluded. Are we missing something or is this the normal behavior?
Left by Brian on Apr 07, 2011 10:24 AM

# re: .Net Directory Services Programming - C# - Part 3
Requesting Gravatar...
Many thanks! These three blogs with examples are far superior to MSDN documentation. This is exactly what I needed.
Left by Bill on Mar 13, 2012 10:56 AM

Your comment:
 (will show your gravatar)


Copyright © Michael J. Hamilton, Sr. | Powered by: GeeksWithBlogs.net | Join free