How to get All attributes from an Active Directory user in C#

14,428

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.

Share:
14,428
Caspi
Author by

Caspi

Updated on June 05, 2022

Comments

  • Caspi
    Caspi almost 2 years

    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.

  • David L
    David L over 7 years
    Link-only or link-primarily answers are strongly discouraged. Please consider citing the source but copying the relevant solution into your answer.
  • Caspi
    Caspi over 7 years
    @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
  • oldovets
    oldovets over 7 years
    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.
  • Caspi
    Caspi over 7 years
    @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...
  • oldovets
    oldovets over 7 years
    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
    oldovets over 7 years
    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
  • Caspi
    Caspi over 7 years
    @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...
  • oldovets
    oldovets over 7 years
    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
    oldovets over 7 years
    Moreover, there is a known memory leak issue using methods like DirectorySearcher.FindXXX. See: stackoverflow.com/questions/5631972/… and connect.microsoft.com/VisualStudio/feedback/details/750167/…‌​. Here is an example of LdapSeacher based on LdapConnection: dunnry.com/blog/2008/06/05/…. I use pretty similar code in my project and everything works fine
  • oldovets
    oldovets over 7 years
    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: msdn.microsoft.com/en-us/library/windows/desktop/…
  • oldovets
    oldovets over 7 years
    Also, you may look at LinqToLdap framework: linqtoldap.codeplex.com Didn't use it by myself.
  • oldovets
    oldovets over 7 years
    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
  • Caspi
    Caspi over 7 years
    @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.
  • oldovets
    oldovets over 7 years
    @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 stackoverflow.com/questions/10291009/…. To avoid hangs on DirectoryEntry.RefreshCache method I created async version of the method that throws timeout if hangs.
  • oldovets
    oldovets over 7 years
    Also, you may want to take a look at direct AD querying using Jet API. See en.wikipedia.org/wiki/Extensible_Storage_Engine blogs.msdn.microsoft.com/windowssdk/2008/10/22/… and managedesent.codeplex.com
  • Caspi
    Caspi over 7 years
    @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?"
  • oldovets
    oldovets over 7 years
    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.
  • Caspi
    Caspi over 7 years
    @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 :)
  • oldovets
    oldovets over 7 years
    @Caspi: You're welcome, BTW, feel free to contact me in chat if you have any further questions or issues regarding your AD project
  • Caspi
    Caspi over 4 years
    @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?
  • oldovets
    oldovets over 4 years
    @Caspi Yes, sure
  • Caspi
    Caspi over 4 years
    @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
  • oldovets
    oldovets about 4 years
    @Caspi ok, I’ll contact you via linked in then
  • oldovets
    oldovets about 4 years
    @Caspi forgot my linked in password and can’t restore it. Let’s make it simple. Add me via skype. My nickname is oldovets
  • Caspi
    Caspi about 4 years
    @oldovets sorry I cant find you on skype. perhaps you can add me instead on join.skype.com/invite/fi57HzS5i0Am
  • oldovets
    oldovets about 4 years
    @Caspi added you via skype. Did you receive my messages?
  • Caspi
    Caspi about 4 years
    @oldovets. Thanks I have replied to your message :)
  • oldovets
    oldovets about 4 years
    @Caspi strange, I do not see any from you. Skype issue maybe