Print UserAccountControl AD property programmatically.

Even there are a few standard AD Editors, including AD MMC, ADSIEdit, and LDM, I needed to read the UserAccountControl AD property from my application. UserAccountControl is a bit flags attribute,  so I had to create C# enum similar to C++ ADS_USER_FLAG_ENUM enum from MSDN.


Below is the code of functions GetUserAccountControl(DirectoryEntry anEntry) and
UserAccountControlToString(int? nUserAccountControl) :


        public static int? GetUserAccountControl(DirectoryEntry anEntry)


        {  //MNF 10/8/2005 if Properties[“userAccountControl”] is not found ignore and return null


            int? val=null;


            if (null != anEntry)


            {


                PropertyCollection collProperties = anEntry.Properties;


                if ((null != collProperties) && (collProperties.Count > 0))


                {


                    object prop = anEntry.Properties[“userAccountControl”];


                    if (null != prop && (prop is PropertyValueCollection))


                    {


                        object oVal = ((PropertyValueCollection)prop).Value;


////if property doesn’t exist, than value is null See http://geekswithblogs.net/mnf/archive/2005/08/10/49754.aspx


                         val = (int?)oVal;


                    }


                }


            }


            Debug.Assert(val.HasValue , “anEntry.Properties[“userAccountControl”] not found”);


            return val;


        }


        public static string UserAccountControlToString(int? nUserAccountControl) //, string sDelimeter)


        {


            if (!nUserAccountControl.HasValue )


            {


                return “UserAccountControl property wasn’t loaded”;


            }


            ADS_USER_FLAG_ENUM enUserAccountControl = (ADS_USER_FLAG_ENUM)nUserAccountControl;


            string sRet = enUserAccountControl.ToString();


            sRet=sRet.Replace(“ADS_UF_”,“”); //remove the prefixes that looks redundunt


            return sRet;


        }


    }//class ADHepler


    //Alternatively you can use ActiveDs interop http://network.programming-in.net/articles/art14-2.asp?Interop=ActiveDs


    [Flags]


    public enum ADS_USER_FLAG_ENUM //http://msdn.microsoft.com/library/default.asp?url=/library/en-us/adsi/adsi/ads_user_flag_enum.asp


    {


            ADS_UF_SCRIPT = 0X0001,


            ADS_UF_ACCOUNTDISABLE = 0X0002,


            ADS_UF_HOMEDIR_REQUIRED = 0X0008,


            ADS_UF_LOCKOUT = 0X0010,


            ADS_UF_PASSWD_NOTREQD = 0X0020,


            ADS_UF_PASSWD_CANT_CHANGE = 0X0040,


            ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0X0080,


            ADS_UF_TEMP_DUPLICATE_ACCOUNT = 0X0100,


            ADS_UF_NORMAL_ACCOUNT = 0X0200,


            ADS_UF_INTERDOMAIN_TRUST_ACCOUNT = 0X0800,


            ADS_UF_WORKSTATION_TRUST_ACCOUNT = 0X1000,


            ADS_UF_SERVER_TRUST_ACCOUNT = 0X2000,


            ADS_UF_DONT_EXPIRE_PASSWD = 0X10000,


            ADS_UF_MNS_LOGON_ACCOUNT = 0X20000,


            ADS_UF_SMARTCARD_REQUIRED = 0X40000,


            ADS_UF_TRUSTED_FOR_DELEGATION = 0X80000,


            ADS_UF_NOT_DELEGATED = 0X100000,


            ADS_UF_USE_DES_KEY_ONLY = 0x200000,


            ADS_UF_DONT_REQUIRE_PREAUTH = 0x400000,


            ADS_UF_PASSWORD_EXPIRED = 0x800000,


            ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x1000000


      } ;//enum ADS_USER_FLAG_ENUM

Advertisements

Function to Print DirectoryEntry properties

When I debugging function that retrieved active directory properties, Visual Studio 2005 debugger show them as a collection , but not as an individual values. So I wrote function, copied from GetPropertyList function from LDAP, IIS and WinNT Directory Services  article written by


            [Conditional(“DEBUG”)]


            public static void PrintDirectoryEntryProperties(System.DirectoryServices.DirectoryEntry entry, string sComment)


       


            // loop through all the properties and get the key for each


            foreach (string Key in entry.Properties.PropertyNames)


            {


                string sPropertyValues = String.Empty;


                // now loop through all the values in the property;


                // can be a multi-value property


                foreach (object Value in entry.Properties[Key])


                    sPropertyValues += Convert.ToString(Value) + “;”;


                // cut off the separator at the end of the value list


                sPropertyValues = sPropertyValues.Substring(0, sPropertyValues.Length – 1);


                // now add the property info to the property list


                Debug.WriteLine(Key + “=” + sPropertyValues);


            }


        }


Ideally it should be possible to write  Debug Visualizer for them. 



 

DirectorySearcher.FindAll() -should have PageSize=1000

When you are calling DirectorySearcher.FindAll() and there is  a chance to have more than 1000 records back, you must set PageSize to non-zero value, preferably 1000. Otherwise only the first 1000 records will be returned and other entries will be missed without any warning.

The names of DirectorySearcher members  and documentation is quite messy. FindAll() method should return ALL records, not the first 1000. Setting PageSize doesn’t mean that you get back only single page, but triggers returning of the values into “page size” chunks transparently to you in the background. If you set SizeLimit to a value that is larger than
the server-determined default of 1000 entries, the server-determined default is used.

Thanks to Joe Kaplan’s post that finally helped to find solution.

I have a simple program that imported AD users to application database. It was tested and worked fine on a few sites.

However one of our customers complained that some users were missed during import when others were imported successfully. No pattern was found what is the difference between imported and missed user accounts.

After some investigation it was noticed that only 1000 records are returned by DirectorySearcher.FindAll.  Ok, documentation describes DirectorySearcher.SizeLimit with default value is zero, which means to use the server-determined default size limit of 1000 entries. I’ve changed SizeLimit to 100000, but it didn’t help -still only the first 1000 was returned. 
Reading further, I found that the server-determined default is used so SizeLimit is useless, if you expect  big number of results. In a few posts I found how to change the default value on the server, but there were also recommendations not to change server setting, but use paging instead. OK but how to use paging? I’ve started to search for code examples, expecting that some complicated coding will be required.

Finally  Joe Kaplan’s post explains that only code change that required is
PageSize=1000
and all paging will happen in background.

MS should have better  DirectorySearcher documentation and implement background paging by default.

COMException (0x80004005): Unspecified error] in DirectorySearcher.FindAll() means LDAP string is invalid.

I’ve got an error COMException (0x80004005) when called DirectorySearcher.FindAll() and had no idea, why it happened.


Thanks to DirectorySearcher.FindAll() causes Unspecified Error in C# but not in VB.NET discussion I was pointed, that


the error is shown if DirectorySearcher.SearchRoot  path is not valid, e.g. it is “DomainName” instead of “LDAP://DomainName“.


I beleive MS could provide more clear error description.


 

System.DirectoryServices.PropertyCollection.Item -what returns if propertyName is invalid

I am retrieving some info from Active directory and  I had a question what happens if invalid (non-existing) Property Name to System.DirectoryServices.PropertyCollection.Item  will be passed


E.g -what  would happened if  the code reads  anEntry.Properties[“userAccountControl”].Value ,but DirectoryEntry doesn’t have property “userAccountControl“ .


It is not documented in MSDN  so I had to investigate it myself.


In .Net 1.1 I found that anEntry.Properties[“userAccountControl”] returns not null PropertyValueCollection object, but prop.Value is null.


So the safe code to retrieve properties value should be similar the following:


      int val = 0;
      object oVal =  anEntry.Properties[“userAccountControl”].Value;
      if (null!= oVal) //if property doesn’t exist, than value is null
      {
          val = (int) oVal;
      }
      else   Debug.Assert(false,”anEntry.Properties[“userAccountControl”] not found”);