3

I have been searching for quite some time for a solution using C# code that can query an Active Directory user for all the attributes it has registered to it, whether or not they have a NULL Value. These attributes are visible through the Attribute editor tab in the properties of the user in ADSI Edit on the domain server.

AD user attributes in ADSI edit

I need to dynamically retrieve these attributes, which means I probably can't reliably get these attribute names through the ADSI documentation on MSDN and because not all of these attributes might be user object specific: https://msdn.microsoft.com/en-us/library/ms675090(v=vs.85).aspx

Here is what I have tried so far, but only got a fraction of the attributes of the user object:

  1. PS command Get-ADUser -Identity administrator -Properties: This retrieved a good part of the attributes, but not nearly all of them and I do not know what .NET Classes and methods are invoked during this command, since TypeName = Microsoft.ActiveDirectory.Management.ADUser, which does not exist in the .NET framework. How can I see the specific methods that are using from .NET in PS?

  2. C# calling this method:

    public bool GetUserAttributes(out List<string> userAttributes, string userName)
    {
        userAttributes = new List<string>();
        var valueReturn = false;
    
        try
        {
            const string pathNameDomain = "LDAP://test.local";
    
            var directoryEntry = new DirectoryEntry(pathNameDomain);
    
            var directorySearcher = new DirectorySearcher(directoryEntry)
            {
                Filter = "(&(objectClass=user)(sAMAccountName=" + userName + "))"
            };
    
            var searchResults = directorySearcher.FindAll();
    
            valueReturn = searchResults.Count > 0;
    
            StreamWriter writer = new StreamWriter("C:\\LDAPGETUSERADEXAMPLE.txt");
    
            foreach (SearchResult searchResult in searchResults)
            {
                foreach (var valueCollection in searchResult.Properties.PropertyNames)
                {
                    userAttributes.Add(valueCollection.ToString() + " = " + searchResult.Properties[valueCollection.ToString()][0].ToString());
    
                    try
                    {
                        writer.WriteLine("Bruger attribut:" + valueCollection);
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                }
            }
    
  3. C# calling this method:

    public List<string> GetADUserAttributes()
    {
        string objectDn = "CN=testuser,OU=TEST,DC=test,DC=local";
    
        DirectoryEntry objRootDSE = new DirectoryEntry("LDAP://" + objectDn);
        List<string> attributes = new List<string>();
    
        foreach (string attribute in objRootDSE.Properties.PropertyNames)
        {
            attributes.Add(attribute);
        }
    
        return attributes;
    }
    

What should I do to not filter out any attributes of the user object I am trying to retrieve from?

I am aware that Active Directory by default will only shows attributes that are default or have a value in them, I am trying to overcome this limitation.

EDIT 1:

I have temporarily postponed the specific question. I have been trying to benchmark which of these methods are the fastest at retrieving (READ Operation) the SAM account name of 10.000 individual AD users called for example "testuser", the methods I benchmark are the following:

  1. Time to complete: about 500 msec : ADSI - system.directoryservices
  2. Time to complete: about 2700 msec: Principal - searcher system.directoryservices.accountmanagement
  3. Time to complete: about NOT WORKING :LDAP - System.DirectoryServices.Protocols
  4. Time to complete: about 60 msec : SQL - System.Data.SqlClient

I am querying for the user information from a workstation - Windows 10 machine in the domain I am querying. the workstation (4 vcpu), DC (2vpu) and DB (2vcpu) server is run as Hyper V vm's.

Caspi
  • 85
  • 1
  • 2
  • 6
  • *I am trying to overcome this limitation.* It's not a limitation. What are you trying to achieve, i.e. why do you think you need this? – Ansgar Wiechers Sep 13 '16 at 20:33
  • @AnsgarWiechers : The reason I need to get the attribute names for example an AD User object is because I want to create a nearly identical cache of AD for my project. The idea is to create a column inside a "Users" table in my database for each of the attribute names. Then my frontend website can retrieve these cached entries and their contents from the database instead of AD, which I believe will be much faster, since it only requires a quick SQL query instead of an LDAP query. Hope that clears it up, otherwise comment on it. – Caspi Sep 14 '16 at 18:02
  • Are your LDAP queries too slow? Did you benchmark them? How? Did you try a [caching LDAP proxy](http://www.openldap.org/pub/kapurva/proxycaching.pdf)? – Ansgar Wiechers Sep 14 '16 at 21:25
  • To be precise I am not at the moment using LDAP queries, but rather ADSI through the system.directoryservices class, not specifically LDAP. However I have thought about only using LDAP queries through system.directoryservices.protocols.LDAP. At the moment I have done no benchmarking of which is faster, ldap or sql. Perhaps if I find that LDAP/ADSI is about as fast as SQL, I would have my frontend retrieve the AD information directly, would have the advantage, that I will not have to synchronize the AD and my cache AD DB. I will try creating a benchmark soon. I will look into LDAP proxy cache. – Caspi Sep 15 '16 at 16:19

1 Answers1

5

All attributes that any class can have are defined in Active Directory Schema

Use this to query for the user class. Then just call GetAllProperties method

var context = new DirectoryContext(DirectoryContextType.Forest, "amber.local");

using (var schema = System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema.GetSchema(context))
{
    var userClass = schema.FindClass("user");

    foreach (ActiveDirectorySchemaProperty property in userClass.GetAllProperties())
    {
        // property.Name is what you're looking for
    }
}

However AD schema may vary from one AD environment to another. For example, third party programs or Exchange Server may extend schema with custom attributes. It means that the solution with pre-defined columns will work only for a specific environment.

oldovets
  • 695
  • 4
  • 9
  • 1
    Link-only or link-primarily answers are strongly discouraged. Please consider citing the source but copying the relevant solution into your answer. – David L Sep 14 '16 at 19:49
  • @Dimitry: Thanks it seems just what I was looking for, I will test the solution and let you know if it worked :) In terms of the schema being limited to a specific environment, I am trying to solve this problem by dynamically creating the DB columns upon installation, rather than having fixed columns, which Is why I can't rely on documentation on the attributes for my needs. I do realise that I will need to watch for changes to the schema and extend my DB structure if the schema changes as a result. However the purpose of the database is only to be read from, by the frontend website as cache – Caspi Sep 15 '16 at 16:31
  • Be aware, in case if you are going to store AD data in cache you will have to synchronize it with AD. E. g. somebody changes firstname attribute of a user. In other words your cache should replicate with AD. This can be done by several methods (DirSync, USN polling). Other solution is to collect the entire AD snapshot (e. g. all users in ad) periodically. – oldovets Sep 15 '16 at 17:16
  • @Dimitry: I have already thought about that challenge, but didn't know how to watch the AD changes, but checking for USN is offcourse exactly what is needed. can you provide a couple of references to the mentioned methods. I plan to do realtime replication watching the USN number and writing the change to my DB, however I don't know yet how much overhead will incur to ensure a very fast replication, but I believe that the domain controllers can handle this, since they replicate each other all the time... – Caspi Sep 16 '16 at 15:50
  • Don't use the Change Notifications technique. It simply does not work. Also I'd recommend you to read The .NET Developer's Guide to Directory Services Programming book. It contains examples of how to track changes using Directory Synchronization. The replication process is very fast. Domain controller will return changes, occurred since the moment of last replication. There are some tricky parts that you have to handle, e. g. sync membership in case of user rename\deletion (rename\delete user in group membership in DB as LDAP will return you distinguished names instead of SID) – oldovets Sep 16 '16 at 20:47
  • And I'd recommend you to use LDAP classes (not ADSI). These classes are located under System.DirectoryServices.Protocols namespace (LdapConnection, etc). From my experience, methods of classes like DirectoryEntry\DirectorySearcher may hang with no response occasionally, so the entire application hangs forever – oldovets Sep 16 '16 at 20:55
  • @DmitryAlexandrov: Can you provide any ADSI sample code that would cause the application to hang, so I can test this my self? I am trying to benchmark some different methods of getting the AD user data. At the moment I am strugling to get the attributes of the user object "testuser" I retrieved through System.DirectoryServices.Protocols SendRequestst() method. It seems the attributes of the user object is now being retrieved, as a query on any of the attributes, like "cn" results in NULLReferenceexception even though attributes have not been filtered... – Caspi Sep 25 '16 at 10:52
  • Such code may hang: using (var entry = new DirectoryEntry("LDAP://contoso.com")) { string[] attributes = {"distinguishedName", "canonicalName"}; DirectorySearcher ds = new DirectorySearcher(entry, "(objectClass=domainDNS)", attributes, SearchScope.Base); var result = ds.FindOne(); } It will work in 99% environments and in 1% it will hang periodically – oldovets Sep 25 '16 at 12:04
  • Moreover, there is a known memory leak issue using methods like DirectorySearcher.FindXXX. See: http://stackoverflow.com/questions/5631972/memory-leak-when-using-directorysearcher-findall and https://connect.microsoft.com/VisualStudio/feedback/details/750167/arithmetic-operation-resulted-in-an-overflow-at-system-directoryservices-searchresultcollection-resultsenumerator-getcurrentresult. Here is an example of LdapSeacher based on LdapConnection: http://dunnry.com/blog/2008/06/05/PagedAsynchronousLDAPSearchesRevisited.aspx. I use pretty similar code in my project and everything works fine – oldovets Sep 25 '16 at 12:09
  • And be aware, in case if you need to search for deleted objects in tombstone you need to add ShowDeletedControl to you controls collection when doing SendRequest and if you need the query to also return ntSecurityDescriptor attribute for an object see the following MSDN article: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366987%28v=vs.85%29.aspx – oldovets Sep 25 '16 at 12:21
  • Also, you may look at LinqToLdap framework: https://linqtoldap.codeplex.com/ Didn't use it by myself. – oldovets Sep 25 '16 at 12:28
  • And according to perfomance: System.DirectoryServices.AccountManagement classes use ADSI. ADSI classes call LDAP functions under the hood. So in case of performance the chain will be like LDAP -> ADSI -> AccountManagement, where LDAP classes are the fastest in the chain. However in case if you are going to use SQL, SQL update queries will be 10 or more times slower than any Active Directory queries (my project is doing the same thing - it synchronizes MS SQL database with current AD state). Look at ADUC\ADSIEdit tools. They query LDAP on the fly. If the performance suits you, just do the same – oldovets Sep 25 '16 at 12:51
  • @DmitryAlexandrov. Thanks for all the comments, it is really helpful, I haven't been to active on resolving this questions, since at the moment I am trying to design this AD query functionality in a ASP.NET application. I have hower made progress on setting some read benchmarks, see EDIT 1 for more information. Though I haven't been able to get LDAP protocols to work, so thanks for the code examples on that. Regarding Directoryservices hanging and memory leaks, were you able to zero in on the cause of that, since I haven't experied it hanging at the moment. – Caspi Sep 30 '16 at 16:29
  • @Caspi: The program hangs on WaitForSingleObject method on socket while waiting for network response from the remote computer. To discover more deeply I would have to disassemble native code. Had not enough time to do that. Regarding leak error you can also read the following post http://stackoverflow.com/questions/10291009/system-directoryservices-directorysearcher-causing-arithmetic-operation-resulte. To avoid hangs on DirectoryEntry.RefreshCache method I created async version of the method that throws timeout if hangs. – oldovets Sep 30 '16 at 18:02
  • Also, you may want to take a look at direct AD querying using Jet API. See https://en.wikipedia.org/wiki/Extensible_Storage_Engine https://blogs.msdn.microsoft.com/windowssdk/2008/10/22/esent-extensible-storage-engine-api-in-the-windows-sdk/ and http://managedesent.codeplex.com/ – oldovets Oct 07 '16 at 22:03
  • @DmitryAlexandrov: Thank you so much, the answer you proposed was exactly what I was looking for and it has all the attributes that was missing from querying the active directory user through other means. However I have one additional question: "Can you though querying the Schema also get the value of a particular property or perhaps also change it and submit the change to the active directory?" – Caspi Oct 08 '16 at 12:02
  • ActviveDirectorySchemaProperty class contains Syntax field - enum, that shows you what type of value is stored in the attribute. You need to parse the attribute value according to its syntax. For example, objectGuid property has OctetString type, which means that the value stored as an array of bytes. Also attribute can have single value or multiple values (IsSingleValued is responsible for that). If you want to change and commit the value you need to know the attribute syntax to convert the value in required format and set it to the object. – oldovets Oct 08 '16 at 12:23
  • @DmitryAlexandrov: That makes a lot of sence, had not looked into Syntax property at the time, thank you again for all your invaluable knowledge :) Perhaps we could chat together in stackoverflow chat, perhaps I could also provide som valuable knowledge like you have done :) – Caspi Oct 08 '16 at 18:39
  • @Caspi: You're welcome, BTW, feel free to contact me in chat if you have any further questions or issues regarding your AD project – oldovets Oct 08 '16 at 20:01
  • @oldovets I know its a long time since I made this post but I have some things I would like to discuss with you regarding my project in chat if you have the interest and time. Can I contact you? – Caspi Jan 30 '20 at 13:40
  • @Caspi Yes, sure – oldovets Feb 01 '20 at 08:23
  • @oldovets awesome, I cant seem to find a way to contact you through your profile. I guess its because I dont have enough reputation yet. So could you perhaps contact me instead on my profile if that is an option? :) Alternatively you can connect with me on my linkedin profile: www.linkedin.com/in/casper-rubæk – Caspi Feb 02 '20 at 11:58
  • @Caspi ok, I’ll contact you via linked in then – oldovets Feb 03 '20 at 13:26
  • @Caspi forgot my linked in password and can’t restore it. Let’s make it simple. Add me via skype. My nickname is oldovets – oldovets Feb 03 '20 at 21:00
  • @oldovets sorry I cant find you on skype. perhaps you can add me instead on https://join.skype.com/invite/fi57HzS5i0Am – Caspi Feb 04 '20 at 22:18
  • @Caspi added you via skype. Did you receive my messages? – oldovets Feb 05 '20 at 23:01
  • @oldovets. Thanks I have replied to your message :) – Caspi Feb 07 '20 at 08:28
  • @Caspi strange, I do not see any from you. Skype issue maybe – oldovets Feb 07 '20 at 14:45