{"id":223,"date":"2007-02-09T00:00:00","date_gmt":"2007-02-08T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/building-active-directory-wrappers-in-net\/"},"modified":"2021-05-17T18:36:50","modified_gmt":"2021-05-17T18:36:50","slug":"building-active-directory-wrappers-in-net","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/building-active-directory-wrappers-in-net\/","title":{"rendered":"Building Active Directory Wrappers in .NET"},"content":{"rendered":"<p>From authenticating application users against Active Directory, to programmatically adding users to Active Directory groups, it seems that a developer in a Microsoft supported environment is never too far away from Active Directory. At least this has been true in my experience. In recent years, there have been many times when I&#8217;ve needed to read or modify values in an Active Directory repository. Each time, I&#8217;ve found myself going back to previous projects or resorting to internet searching, in order to re-learn the steps and components involved.<\/p>\n<p>Eventually, I decided to build a .NET library containing wrappers and components that would allow me to interact with Active Directory without having to remember each time how everything in the <b>System.DirectoryServices<\/b> namespace works.<\/p>\n<p>This article will explain pieces of this library by walking through how to convert four commonly used Active Directory data types into data types that can be used in a .NET application. It will also explain how to:<\/p>\n<ul>\n<li>Retrieve a user from Active Directory using the <b>System.DirectoryServices <\/b>namespace<\/li>\n<li>Read the user&#8217;s properties<\/li>\n<li>Commit any changes back to the Active Directory repository.<\/li>\n<\/ul>\n<p>The source code for this article (see the Code Download link in the box to the right of the article title) contains the full .NET 2.0 solution, written in Visual Basic.NET. This solution contains two projects: A class library called <b>SimpleTalk_ADDataWrappers<\/b> which contains the four wrapper classes mentioned above and a console application called <b>TestConsole<\/b> which retrieves a user from an Active Directory repository and demonstrates how each of the four wrappers can be used. In this article, the wrapper objects and the console application are explained in detail.<\/p>\n<h2>The problem with values in Active Directory<\/h2>\n<p>At first glance, building this library seemed pretty easy, but I quickly hit my first road block. When you retrieve an object from Active Directory there are no strong typed properties or intellisense to help you get to the information you need. For example, you can&#8217;t type <b>user.displayName<\/b> to get the string representing the user&#8217;s display name. Once you get the user object from the repository, you would access the user&#8217;s display name as <b>user.properties(&#8220;displayName&#8221;)<\/b> which returns an array of objects. So, not only do you have to know that there is a property on the user object called <b>displayName<\/b> but also what data type you&#8217;re expecting it to return, and whether you should expect multiple values or just need to look at the first position in the array.<\/p>\n<p>Having finally mastered <b>displayName<\/b>, what if you wanted to know when the user had last logged onto the network. You would start by getting the property named <b>lastLogOn<\/b> which will also return an array of objects. However, the returned data type is a COM object of type <b>Interop.ActiveDs.IADsLargeInteger<\/b> which is a large integer object with a high and low part representing a date and time. To make easy use of this data it would need to be converted into a <b>Date<\/b> object, and if you wanted to save it back to the repository it would need to be converted back again.<\/p>\n<p>Aside from <b>IADsLargeInteger<\/b>, there are three more values that are returned from Active Directory that require some sort of conversion if you want to leverage them in a .NET application:<\/p>\n<ol>\n<li>The object GUID<\/li>\n<li>The object SID<\/li>\n<li>The user account control value.<\/li>\n<\/ol>\n<p>First, let&#8217;s look at how to convert the <b>IADsLargeInteger<\/b> into something that can be easily used.<\/p>\n<h2>IADsLargeInteger wrapper<\/h2>\n<p>The <b>ADDateTime<\/b> class in our .NET 2 library wraps the <b>IADsLargeInteger<\/b> object. Several Active Directory schema properties including <b>accountExpires<\/b><i>, <\/i><b>badPasswordTime<\/b><i>, <\/i><b>lastLogon<\/b><i>, <\/i><b>lastLogoff<\/b><i> <\/i>and <b>passwordExpirationDate<\/b><i> <\/i>return <b>IADsLargeInteger<\/b> values, all of which can be wrapped using the <b>ADDateTime<\/b> object.<\/p>\n<p>The wrapper exposes the following three members.<\/p>\n<ul>\n<li><span class=\"CodeInText\">Sub New(ByVal ADsLargeInteger As IADsLargeInteger)<\/span><\/li>\n<\/ul>\n<p>The constructor for the wrapper is straight forward and accepts the <b>IADsLargeInteger<\/b> value from the Active Directory repository.<\/p>\n<ul>\n<li><span class=\"CodeInText\">Public ReadOnly Property ADsLargeInteger() As IADsLargeInteger<\/span><\/li>\n<\/ul>\n<p>This read-only property is also fairly straight forward. It exposes the <b>IADsLargeInteger<\/b> and is used for getting the value back from the wrapper when saving it to the repository.<\/p>\n<ul>\n<li><span class=\"CodeInText\">Public Property StandardDateTime() As DateTime<\/span><\/li>\n<\/ul>\n<p>This property exposes the <b>ADsLargeInteger<\/b> value as a standard .NET <b>DateTime<\/b> object. The conversions to the underlying <b>IADsLargeInteger<\/b> are done when the property is invoked (both getting and setting) ensuring that the latest version of the <b>IADsLargeInteger<\/b> will be returned when reading this property, as well as when invoking the read only <b>ADsLargeInteger<\/b> property.<\/p>\n<p>The code for the <b>StandardDateTime() <\/b>property is listed below:<\/p>\n<pre><\/pre>\n<p>Public Property StandardDateTime() As DateTime<br \/>\n\u00a0\u00a0 Get<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0 Return Me.IADsLargeIntegerToDateTime(Me._ADLI)<\/p>\n<p>\u00a0\u00a0 End Get<br \/>\n\u00a0\u00a0 Set(ByVal Value As DateTime)<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0 Me._ADLI = Me.DateTimeToIADsLargeInteger(Value)<\/p>\n<p>\u00a0\u00a0 End Set<br \/>\nEnd Property<\/p>\n<p>The private member <b>_ADLI<\/b> is the underlying <b>IADsLargeInteger<\/b> value. The two methods that this property uses are private methods of the <b>ADDateTime<\/b> class. These methods convert between <b>IADsLargeInteger<\/b> values<i> <\/i>and standard <b>DateTime<\/b> objects using calls to unmanaged code, and can be found in the source code region <b>IADsLargeInteger CONVERSION METHODS<\/b>.<\/p>\n<h2>ObjectGuid wrapper<\/h2>\n<p>The <b>ADObjectGuid<\/b> object wraps identifier byte arrays returned from Active Directory. Every schema object in Active Directory, including users and groups, is uniquely identified using a string of bytes. This value is returned by the Active Directory schema property <b>objectGuid<\/b>. Because the identifier values should not be modified, only read only properties are exposed on this class. The <b>ADObjectGuid<\/b> opbject exposes four members. Firstly, the constructor accepts the 128 bit byte array returned from the Active Directory repository.<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">\u00a0\u00a0\u00a0\u00a0\u00a0 Sub New(ByVal bytes As Byte())<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Me._bytes = bytes<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0 End Sub<\/p>\n<p>The read only property, <b>bytes<\/b>, returns the byte array as it was passed into the constructor.<\/p>\n<pre><\/pre>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0 Public ReadOnly Property bytes() As Byte()<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Get<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Return Me._bytes<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 End Get<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 End Property<\/p>\n<p>The read only property, <b>guid<\/b>, returns the byte array in the form of a <b>Guid<\/b>.<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">\u00a0\u00a0\u00a0\u00a0\u00a0 Public ReadOnly Property guid() As Guid<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Get<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Return New Guid(Me._bytes)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 End Get<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 End Property<\/p>\n<p>The read only property, <b>splitOctetString<\/b>, returns the identifier byte array as an octet string with each byte displayed as a hexadecimal representation and delimited by a &#8216;<strong>\\<\/strong>&#8216; character. This format is required when using the <b>System.DirectoryServices.DirectorySearcher<\/b> to search for Active Directory objects by the <strong>objectGUID<\/strong> schema property.<\/p>\n<pre><\/pre>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0 Public ReadOnly Property splitOctetString() As String<br \/>\n\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Get<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Dim iterator As Integer<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Dim builder As StringBuilder<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Dim values() As Byte = Me._bytes<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 builder = _<br \/>\n\u00a0\u00a0\u00a0 New StringBuilder((values.GetUpperBound(0) + 1) * 2)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 For iterator = 0 To values.GetUpperBound(0)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 builder.Append(&#8220;\\&#8221; &amp; values(iterator).ToString(&#8220;x2&#8221;))<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Next<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Return builder.ToString()<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 End Get<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 End Property<\/p>\n<h2>ObjectSid wrapper<\/h2>\n<p>The <b>ADObjectSid<\/b> object is used to wrap the value of an Active Directory object&#8217;s <b>objectSid<\/b> schema property. It is very similar to the <b>ADObjectGuid<\/b>. In fact, they both have the same constructor and all of the same properties, except <b>ADObjectSid<\/b> does not have a <b>guid<\/b> property. That&#8217;s because the object&#8217;s SID byte array is 224 bits instead of 128 and cannot be converted to the <b>Guid<\/b> data type. When an Active Directory object is created, it is assigned a SID value by the system. This value can subsequently be changed by the system but once it changes the system will never again reuse the old value with a different object. Old values are stored in the object&#8217;s schema property, <b>sidHistory<\/b>, which returns an object array of SID byte arrays.<\/p>\n<p>Like the <b>ADObjectGuid<\/b>, the <b>splitOctetString<\/b> property of the <b>ADObjectSid<\/b> can also be used in search filters when searching for objects by the object&#8217;s SID value. This value is also often used when searching for objects by association as the association often references this value.<\/p>\n<h2>UserAccountControl wrapper<\/h2>\n<p>The <b>ADUserAccountControl<\/b> object wraps the value of the Active Directory schema property, <b>userAccountControl<\/b>. The value is simply an integer that represents several different common account control flags. Once you know what the flags are and their values, you only need to perform bitwise operations on the value to set the flag or see if the flag is set. The following snippet from the <b>ADUserAccountControl<\/b> class is the enumeration of the available flags with their values.<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Public Enum enumUserAccountControlFlag<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SCRIPT = &amp;H1<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ACCOUNT_DISABLED = &amp;H2<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 HOMEDIR_REQUIRED = &amp;H8<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 LOCKED_OUT = &amp;H10<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PASSWD_NOT_REQD = &amp;H20<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PASSWD_CANT_CHANGE = &amp;H40<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ENCRYPTED_TEXT_PASSWD_ALLWD = &amp;H80<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 TEMP_DUPLICATE_ACCT = &amp;H100<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NORMAL_ACCOUNT = &amp;H200<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INTERDOMAIN_TRUST_ACCT = &amp;H800<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WORKSTATION_TRUST_ACCT = &amp;H1000<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SERVER_TRUST_ACCT = &amp;H2000<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PASSWD_NO_EXPIRE = &amp;H10000<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 MNS_LOGON_ACCT = &amp;H20000<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SMART_CART_REQD = &amp;H40000<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0TRUSTED_FOR_DELEGATION = &amp;H80000<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NOT_DELEGATED = &amp;H100000<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 USE_DES_KEY_ONLY = &amp;H200000<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PREAUTH_NOT_REQD = &amp;H400000<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PASSWD_EXPIRED = &amp;H800000<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 TRUSTED_TO_AUTH_FOR_DELEGATION = &amp;H1000000<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 End Enum<\/p>\n<p>Most of the rest of the class contains public properties, one for each flag above, to set the flag or see if the flag is set. As an example, below, is the code for the <b>LOCKED_OUT<\/b> property.<\/p>\n<pre><\/pre>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Public Property accountLockedOut() As Boolean<br \/>\n\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Get<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Return Me.isFlagSet(enumUserAccountControlFlag.LOCKED_OUT)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 End Get<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Set(ByVal value As Boolean)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Me.updateFlag(enumUserAccountControlFlag.LOCKED_OUT, value)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 End Set<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 End Property<\/p>\n<p>The bitwise operations actually take place in two convenience methods that can be seen used above:<\/p>\n<ul>\n<li><b>isFlagSet<\/b>, which takes an <b>enumUserAccountControlFlag<\/b> and returns a <b>Boolean<\/b> value indicating whether or not the flag is set<\/li>\n<li><b>updateFlag<\/b> which takes an <b>enumUserAccountControlFlag<\/b> and a <b>Boolean<\/b> value, <i>true<\/i> to set the flag and <i>false<\/i> to remove it.<\/li>\n<\/ul>\n<p>Every other flag property of the <b>ADUserAccountControl <\/b>class is implemented this way as well.<\/p>\n<h2>Using the wrapper classes<\/h2>\n<p>The <b>TestConsole<\/b> project included with this article explains how a user can be retrieved from an Active Directory repository, and how the wrapper objects examined above can be leveraged to make useful information out of the data returned from the user&#8217;s schema properties.<\/p>\n<p>The <b>TestConsole<\/b> project has the following three references (beyond the default references included when the project is first created):<\/p>\n<ol>\n<li><b>System.DirectoryServices<\/b> is used by the application to interact with the Active Directory repository<\/li>\n<li><b>Interop.ActiveDs<\/b> contains many of the data types returned from Active Directory<\/li>\n<li><b>SimpleTalk_ADDataWrappers <\/b>is the class library containing the wrapper objects described earlier in this article<\/li>\n<\/ol>\n<p>The <b>Main<\/b> method of the <b>TestConsole<\/b> application starts by setting up some configuration variables that will be used during the execution.<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">Dim repositoryPath As String = &#8220;LDAP:\/\/yourRepositoryPathGoesHere&#8221;<br \/>\nDim username As String = &#8220;usernameOfUserToQuery&#8221;<br \/>\nDim filter As String = _<br \/>\n&#8220;(&amp;(objectClass=user)(sAMAccountName=&#8221; &amp; username &amp; &#8220;))&#8221;<br \/>\nDim ADUsername As String = &#8220;ADUsername&#8221;<br \/>\nDim ADPassword As String = &#8220;ADPassword&#8221;<\/p>\n<p>The <b>repositoryPath<\/b> string will specify the path to your Active Directory repository. The <b>username<\/b> string contains the domain username of the user you are going to query. The <b>filter<\/b> specifies the &#8220;query&#8221;, if you will, that will be executed to find this user. The <b>ADUsername<\/b> and <b>ADPassword<\/b> strings specify the credentials for the user that will be used when binding to the user entry that you are searching for in the Active Directory repository.<\/p>\n<p>In the typical domain environment, your domain credentials will give you access to bind to your own user object entry in the Active Directory repository. In other words, depending on your domain&#8217;s security settings, you may not have access to query any other username in the repository but your own. Therefore, if you run into any problems running the example code, try setting the value of <b>username<\/b> to your domain username and the values for <b>ADUsername<\/b> and <b>ADPassword<\/b> to your domain username and password.<\/p>\n<p><b>NOTE<\/b>:<br \/>\n<em>It&#8217;s worth mentioning at this point that although rare, depending on your domain&#8217;s security settings, this application may not work at all for your credentials. If you continue to experience problems after a tweaking the configuration variables a few times, you may need to contact your system administrator to gain the necessary access.<\/em><\/p>\n<p>Once the configuration variables have the correct values, the application can initialize the directory service objects.<\/p>\n<pre><\/pre>\n<p>Dim repositoryRootEntry As New _<br \/>\nDirectoryServices.DirectoryEntry(repositoryPath, _<br \/>\nADUsername, ADPassword)<\/p>\n<p>Dim directorySearcher As New _<br \/>\nDirectoryServices.DirectorySearcher(repositoryRootEntry, filter)<\/p>\n<p>The <b>repositoryRootEntry<\/b> will be the starting location in the search. In my case, when I instantiated the <b>repositoryRootEntry<\/b>, I passed in the path to the root of my domain&#8217;s Active Directory tree and the administrator&#8217;s username and password. The <b>directorySearcher<\/b> is used in executing searches against an entry, in our case the <b>repositoryRootEntry<\/b> using the specified filter.<\/p>\n<p>Next, the application executes the search by invoking the <b>directorySearcher<\/b>&#8216;s <b>FindOne<\/b> method to return a <b>DirectoryServices.SearchResult<\/b> object:<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">Dim result As DirectoryServices.SearchResult = _ <br \/>\ndirectorySearcher.FindOne()<\/p>\n<p>The <b>directorySearcher<\/b> also exposes a <b>FindAll<\/b> method which returns a <b>DirectoryServices.SearchResultCollection<\/b>. The <b>FindOne<\/b> method is used in this case because there is only one user in the repository that has the specified <b>username<\/b>. So, if the number of return results can be expected to be only one, the <b>FindOne<\/b> method can be used, otherwise use <b>FindAll<\/b>. Also, note that the rest of the code is wrapped in a Try\/Catch block, because from this point on if anything is going to go wrong it will happen once the connection to the repository is made.<\/p>\n<p>If the result is not null, then the <b>directorySearcher<\/b> succeeded in finding the user, which will be returned as a <b>DirectoryServices.DirectoryEntry<\/b>.<\/p>\n<pre><\/pre>\n<p>Dim user As DirectoryServices.DirectoryEntry = result.GetDirectoryEntry<\/p>\n<p>The remaining four method calls test the four wrapper methods described earlier in the article. Since they are all similar, let&#8217;s look at the <b>testADObjectGuid<\/b> in detail.<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &#8216; object guid<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Dim value As Object = getProperty(user, &#8220;objectGUID&#8221;)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 If Not value Is Nothing Then<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Dim wraper As New ADObjectGuid(value)<br \/>\nConsole.WriteLine(&#8220;User&#8217;s Guid Identifier:&#8221; &amp; _ <br \/>\nControlChars.Tab &amp; ControlChars.Tab &amp; _<br \/>\nControlChars.Tab &amp; wraper.guid.ToString)<br \/>\nConsole.WriteLine(&#8220;User&#8217;s Split Octet Identifier:&#8221; &amp;_ <br \/>\nControlChars.Tab &amp; ControlChars.Tab &amp; wraper.splitOctetString.ToString)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Else<br \/>\nConsole.WriteLine(&#8220;Something is wrong &#8211; this user has no unique identifier.&#8221;)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 End If<\/p>\n<p>It may look like more code than necessary, but that&#8217;s because the <b>ControlChars<\/b> end up bloating the <b>Console.WriteLine<\/b> lines. First, the value is retrieved using another method in the module, <b>getProperty<\/b>, which takes the <b>user<\/b> <b>DirectoryEntry <\/b>and the name of the property to retrieve, in this case, <b>objectGUID<\/b>. If <b>getProperty<\/b> returns nothing then a message is printed. Otherwise, the <b>value<\/b> object is loaded into a new <b>ADObjectGuid<\/b> wrapper object. Finally, the method writes the <b>Guid<\/b> and the split octet string representations of the identifier to the console.<\/p>\n<p>The <b>getProperty <\/b>function is used to retrieve the value because it takes a few lines of code to get just one value from an entry. That&#8217;s because, as mentioned earlier, when you request a property from a <b>DirectoryEntry<\/b> it returns an array of objects. All of the properties being requested in this module are only expected to return one value. So, this function extracts that value from the first index of the array returned. The code for the <b>getProperty <\/b>function is listed below.<\/p>\n<pre><\/pre>\n<p>\u00a0\u00a0\u00a0 Private Function getProperty _<br \/>\n(ByVal user As DirectoryServices.DirectoryEntry, _<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ByVal propertyName As String) As Object<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0 If user.Properties.Contains(propertyName) Then<br \/>\nDim properties As _ DirectoryServices.PropertyValueCollection = _<br \/>\nuser.Properties(propertyName)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Return properties(0)<br \/>\n\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0End If<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Return Nothing<br \/>\n\u00a0\u00a0\u00a0 End Function<\/p>\n<p>Notice that before the function actually requests the value from the <b>directoryEntry<\/b>, it first checks to see if the entry contains the property. Although the schema may support a property on the entry, if the entry doesn&#8217;t have a value for that property, it won&#8217;t exist.<\/p>\n<p>The properties that the entry has values for can be obtained by invoking <b>DirectoryEntry.Properties.PropertyNames<\/b>, which is an array of strings representing the property names of the properties that have values for the specific entry. There are actually several hundred properties available for many types of <b>DirectoryEntry <\/b>objects. A list of the properties can be obtained programmatically including information about which properties are mandatory and which are optional. However, this being outside of the scope for this article, to find out more information go to:<\/p>\n<p><a href=\"http:\/\/msdn2.microsoft.com\/en-us\/library\/ms675085.aspx\">http:\/\/msdn2.microsoft.com\/en-us\/library\/ms675085.aspx<\/a>.<\/p>\n<h2>Updating the Active Directory entry<\/h2>\n<p>If you have downloaded the solution, you may have already noticed that there is one more method call commented out after the four tests. The <b>updateUserEmailAddress<\/b> method accepts the <b>user DirectoryEntry<\/b> and a new email address string. Before you uncomment this method, it may be a good idea to contact your network administrator to ensure that updating Active Directory properties won&#8217;t have adverse effects on the other systems running on the network. For example, there may be a system on your network, like a spam filter or scheduled task that is expecting a certain user&#8217;s email address to be a certain value. If this value is changed, this system may not be able to send out a notification to that user.<\/p>\n<p>First, the method retrieves the user&#8217;s email address, just as for the previous four tests, by calling the <b>getProperty<\/b> function and passing in the <b>user<\/b> and the property name<b> mail<\/b>, which returns the user&#8217;s email address. Once it has the email address, it writes the current address to the console and then writes the <b>newEmailAddress<\/b> to the console. Then the user&#8217;s email address is updated to the repository using the following code.<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">user.Properties(&#8220;mail&#8221;)(0) = newEmailAddress<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 user.CommitChanges()<\/p>\n<p>When using the wrapper classes, the values are saved back to the repository in the same way. Simply replace the <b>newEmailAddress <\/b>value with the underlying value of the wrapper object. For example, updating the <b>accountExpires<\/b> property of a user would look like this:<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">user.Properties(&#8220;accountExpires&#8221;)(0) = _<br \/>\nADDateTimeWrapper.ADsLargeInteger\u00a0\u00a0\u00a0\u00a0\u00a0 <br \/>\nuser.CommitChanges()<\/p>\n<p>At this point, an exception may be thrown if the <b>ADUsername<\/b> and <b>ADPassword<\/b> do not belong to a user with sufficient rights to update the entry&#8217;s properties. Typically, as mentioned earlier, users have sufficient rights to bind to and read from their own entries but not enough rights to commit changes to the entry. Depending on your domain security settings you may experience varied results when invoking this method.<\/p>\n<p>To make sure that the change worked, if you have access to actually view the Active Directory users and computers on your network, you will see that the email address for the given user has been updated. If you don&#8217;t have access, you can run the <b>TestConsole<\/b> a second time and see what email address is written to the console before the email address is changed.<\/p>\n<h2>Things to keep in mind<\/h2>\n<p>While experimenting with this code, keep in mind that the scenarios described here may not work if the user being used to bind to the Active Directory entries does not have sufficient rights. Further, even if the user does have sufficient rights to read, the user may not have sufficient rights to commit changes. In the end, although the examples described in this article are pretty straight forward, different users may experience varying experiences based on the domain&#8217;s security settings. For example, by default, administrators are the only users on the domain with sufficient rights to update a user entry. If this is the case on your domain, if you are not an administrator, the <b>updateUserEmailAddress<\/b> method will fail with an <b>UnauthorizedAccessException<\/b> error, when it calls <b>user.commitChanges<\/b>. For more information on best practices and how to modify Active Directory security settings visit:<\/p>\n<p><a href=\"https:\/\/technet.microsoft.com\/en-gb\/library\/dd277405.aspx\">https:\/\/technet.microsoft.com\/en-gb\/library\/dd277405.aspx<\/a><\/p>\n<p>Also, not all properties can be directly updated. For example, using the <b>ADUserAccountControl<\/b> wrapper to update the <b>PASSWD_CANT_CHANGE<\/b> flag will not actually change whether or not the user&#8217;s password can be modified. In addition, depending on the state of the entry (disabled, password expired, etc.), some properties may be read only and although it may look like the changes have been committed, they have not. I&#8217;ve discovered some of these anomalies from my own experimentation and have tried to document the code where I have encountered these situations.<\/p>\n<h2>Conclusion &#8211; possible improvements or additions<\/h2>\n<p>I believe this code is a great foundation for anyone wanting to gain a deeper expertise on leveraging an Active Directory repository in a .NET application. However, as anyone reading this article can see, there are several areas for improvements and additions. One glaring issue that I have struggled with is the fact that under different scenarios in addition to insufficient access rights, this code may not work as expected or at all. Every domain is different and different user states require different implementations of the code described in this article. For example, as mentioned in the latter section, although some of these user states may be predictable, writing code that can detect and work through some of these situations is outside of the scope of this article.<\/p>\n<p>Also, now that this article describes how to wrap different data types returned from Active Directory repositories, it may be nice to have a wrapper object for wrapping an entire Active Directory user entry or even a group entry.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The ability to authenticate .NET application users against Active Directory is a common requirement. Here, Jeff Hewitt demonstrates how to build wrapper classes in Visual Basic that can convert AD data types into ones that can be used in a .NET application.&hellip;<\/p>\n","protected":false},"author":98324,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538],"tags":[4143,4229,4652,4654,4178,4194,4653],"coauthors":[50376],"class_list":["post-223","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","tag-net","tag-net-framework","tag-active-directory","tag-active-directory-wrappers","tag-bi","tag-visual-basic","tag-visual-basic-net"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/223","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/98324"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=223"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/223\/revisions"}],"predecessor-version":[{"id":74547,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/223\/revisions\/74547"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=223"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=223"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=223"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=223"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}