There are 3 interfaces for accessing the Active Directory:
1) LDAP: The Lightweight Directory Access Protocol (LDAP) is the service protocol that runs on a layer above the TCP/IP layer (or stack) and provides an interface for accessing, searching and modifying Internet Directories, and is based on a client-server model.
2) ADSI: The Active Directory Services Interface (ADSI) is a set of COM components (or Interfaces) designed to access the directory services from different network providers in a network setup, it is designed to provide a single, central interface for accessing & managing network resources.
3) System.DirectoryServices: The System.DirectoryServices Namespace is built into the .Net Framework designed to provide programming access to LDAP directories (Active Directory) and is built on the ASDI API.
Before the release of VB.Net (back in the VB6 and prior years) you almost had to take a "brute force" approach when it came to working with the Active Directory, but with the System.DirectoryServices Namespace in .Net 2.0 this got much easier. In this tutorial I will show various functions that can be performed within the Active Directory, but remember, other than simply searching the Active Directory, a user has to have Administrative permissions to modify the Active Directory.
Before we get into the "cool" stuff, there are a couple of function I would like to offer, these are used later in the tutorial and come in quite handy. The first one is used for extracting the domain off of the username. Active Directory usernames are in the format "YOURDOMAIN\UserName", but when searching the directory you don't want the "DOMAINNAME\" on the username, so I wrote this ExtractUserName function:
CODE
''' <summary>
''' Function to extract just the login from the provided string (given in the format YOURDOMAIN\Username)
''' </summary>
''' <param name="path">Full AD login of the associate</param>
''' <returns>The login with the "YOURDOMAIN\" stripped</returns>
''' <remarks></remarks>
Public Shared Function ExtractUserName(ByVal path As String) As String
'Split on the "\"
Dim userPath As String() = path.Split(New Char() {"\"c})
'Return the rest (username part)
Return userPath((userPath.Length - 1))
End Function
The next function (SetADProperty) comes in handy when either modifying someone's account, creating a new user account or many other possible manipulations. This function takes your DirectoryEntry (GetDirectoryEntry function below), the property name and the property value and sets them for you:
CODE
''' <summary>
''' Helper method that sets properties for AD users.
''' </summary>
''' <param name="de">DirectoryEntry to use</param>
''' <param name="pName">Property name to set</param>
''' <param name="pValue">Value of property to set</param>
Public Shared Sub SetADProperty(ByVal de As DirectoryEntry, ByVal pName As String, ByVal pValue As String)
'First make sure the property value isnt "nothing"
If Not pValue Is Nothing Then
'Check to see if the DirectoryEntry contains this property already
If de.Properties.Contains(pName) Then 'The DE contains this property
'Update the properties value
de.Properties(pName)(0) = pValue
Else 'Property doesnt exist
'Add the property and set it's value
de.Properties(pName).Add(pValue)
End If
End If
End Sub
The first thing we want to look at is actually connecting to the Active Directory, for this we need to get an Active Directory Entry. Though there are several ways to accomplish this, I prefer to use this function:
CODE
''' <summary>
''' Method used to create an entry to the AD using a secure connection.
''' Replace the path.
''' </summary>
''' <returns>DirectoryEntry</returns>
Public Shared Function GetDirectoryEntry() As DirectoryEntry
'Of course change the information for the LDAP to your network
Dim dirEntry As New DirectoryEntry("LDAP://192.168.1.1/CN=Users;DC=Yourdomain")
'Setting username & password to Nothing forces
'the connection to use your logon credentials
dirEntry.Username = Nothing
dirEntry.Password = Nothing
'Always use a secure connection
dirEntry.AuthenticationType = AuthenticationTypes.Secure
Return dirEntry
End Function
Next, lets look at verifying that an Active Directory login is a valid login. Like I said earlier, querying the Active Directory is a lot like working with a database, there are certain "fields" you want to look for and read, then compare them with your values. Some of these values are:
SAMAccountName: This is the actual login
givenName: This is the users first name
sn: This is the users Sir Name (Last name)
Granted there are many more "fields" but these are the 3 I am worried about right now. For my valid login function I took it a step further than just simply verifying it's a valid login, I wanted to know if it's a valid login for the user specified (i.e.; First & Last name). This function looks like this:
CODE
''' <summary>
''' Function to search the Active Directory and ensure the Login provided in Agent Process is a valid one. The search is performed
''' to see if the login provided exists for the first and last name of the associate being added
''' </summary>
''' <param name="loginName">Login of the associate to search for</param>
''' <param name="givenName">First name fo the associate being added</param>
''' <param name="surName">Last name of the associate being added</param>
''' <returns>True or False depending if the login provided is a valid one</returns>
Public Function IsValidADLogin(ByVal loginName As String, ByVal givenName As String, ByVal surName As String) As Boolean
Try
'Create a DirectorySearcher Object (used for searching the AD)
Dim search As New DirectorySearcher()
'Set the filter on the searcher object to look for the SAMAccountName, givenName
' and the sn (Sur Name)
search.Filter = String.Format("(&(SAMAccountName={0})(givenName={1})(sn={2}))", ExtractUserName(loginName), givenName, surName)
'Now load these properties to the search
search.PropertiesToLoad.Add("cn")
search.PropertiesToLoad.Add("SAMAccountName") 'Users login name
search.PropertiesToLoad.Add("givenName") 'Users first name
search.PropertiesToLoad.Add("sn") 'Users last name
'Use the .FindOne() Method to stop as soon as a match is found
Dim result As SearchResult = search.FindOne()
'Now check to see if a result was found
If result Is Nothing Then
'Login isn't valid
Return False
Else
'Valid login
Return True
End If
Catch ex As Exception
MessageBox.Show(ex.Message, "Active Directory Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1)
End Try
End Function
Now what if you needed to disable a users account, I'm sure there are many reasons for needing this so here is what you would do. You would have to get a directory entry (GetDirectoryEntry from above), then create a directory search object to search with. You would then need to set the search filter for the search, in the last function I gave you 3 "fields" in the Active Directory, now I'm giving you 2 more:
objectCategory: What category is the object in (Person, Computer, etc)
objectClass: What class is the object (in our case it is a user)
SAMAccount: (This one was defined earlier)
This function not only disables the users account, but it also hides the users email from all Exchange Address Lists, it looks like this:
CODE
''' <summary>
''' Method that disables a user account in the AD
''' and hides user's email from Exchange address lists.
''' </summary>
''' <param name="sLogin">Login of the user to disable</param>
Public Sub DisableAccount(ByVal sLogin As String)
' 1. Search the Active Directory for the desired user
Dim dirEntry As DirectoryEntry = GetDirectoryEntry()
Dim dirSearcher As DirectorySearcher = New DirectorySearcher(dirEntry)
dirSearcher.Filter = "(&(objectCategory=Person)(objectClass=user) _
(SAMAccountName=" & sLogin & "))"
dirSearcher.SearchScope = SearchScope.Subtree
Dim results As SearchResult = dirSearcher.FindOne()
' 2. Check returned results
If Not results Is Nothing Then
' 2a. User was returned
Dim dirEntryResults As DirectoryEntry = GetDirectoryEntry(results.Path)
Dim iVal As Integer = CInt(dirEntryResults.Properties("userAccountControl").Value)
' 3. Disable the users account
dirEntryResults.Properties("userAccountControl").Value = iVal Or &H2
' 4. Hide users email from all Exchange Mailing Lists
dirEntryResults.Properties("msExchHideFromAddressLists").Value = "TRUE"
dirEntryResults.CommitChanges()
dirEntryResults.Close()
End If
dirEntry.Close()
End Sub
The next section is to show how to update/modify a users Active Directory account. The items being updated in this function can be changed to whatever fields you have in your Active Directory and what needs to be updated for a specific user. For arguments sake we're going to update a users:
Department
Title
Phone Extension
To accomplish this first we need to get a directory entry (GetDirectoryEntry from above), then create a search object and set the filter for the search object (for this we will use the same 3 "fields" we did for disabling a users account). This is where the similarities end, with this we have to create a 2nd Directory Entry only this time we pass the search results to it. We then use the SetADProperty function from the beginning of the tutorial to set the properties we want to update. Once we have done that then we commit the changes then close and clean up behind ourselves. Keep in mind to make this function work you need Administrative permissions on the network:
CODE
''' <summary>
''' Method that updates user's properties
''' </summary>
''' <param name="userLogin">Login of the user to update</param>
''' <param name="userDepartment">New department of the specified user</param>
''' <param name="userTitle">New title of the specified user</param>
''' <param name="userPhoneExt">New phone extension of the specified user</param>
Public Sub UpdateUserADAccount(ByVal userLogin As String, _
ByVal userDepartment As String, ByVal userTitle As String, ByVal userPhoneExt As String)
Dim dirEntry As DirectoryEntry = GetDirectoryEntry()
Dim dirSearcher As DirectorySearcher = New DirectorySearcher(dirEntry)
' 1. Search the Active Directory for the speied user
dirSearcher.Filter = "(&(objectCategory=Person)(objectClass=user) _
(SAMAccountName=" & userLogin & "))"
dirSearcher.SearchScope = SearchScope.Subtree
Dim searchResults As SearchResult = dirSearcher.FindOne()
If Not searchResults Is Nothing Then
Dim dirEntryResults As New DirectoryEntry(results.Path)
'The properties listed here may be different then the properties in your Active Directory
'so they may need to be changed according to your network
' 2. Set the new property values for the specified user
SetADProperty(dirEntryResults, "department", userDepartment)
SetADProperty(dirEntryResults, "title", userTitle)
SetADProperty(dirEntryResults, "phone", userPhoneExt)
' 3. Commit the changes
dirEntryResults.CommitChanges()
' 4. Close & Cleanup
dirEntryResults.Close()
End If
' 4a. Close & Cleanup
dirEntry.Close()
End Sub
Now what if you needed to provide a list of all the computer names that are on the Active Directory. Well once you've learned what you've learned so far about using the System.DirectoryServices Namespace this is much simpler. When I created this function I decided to return a collection populated with the computer names, you could return a generic list, a dataset, or anything else. With this function you, once again (see a theme starting here?) use the GetDirectoryEntry function, you create a search object then set the filter. With this function we use the objectClass field, but instead of a Person the class is computer. This is the function:
CODE
''' <summary>
''' Function to query the Active Directory and return all the computer names
'''on the network
''' </summary>
''' <returns>A collection populated with all the computer names</returns>
Public Shared Function ListAllADComputers() As Collection
Dim dirEntry As DirectoryEntry = GetDirectoryEntry()
Dim pcList As New Collection()
' 1. Search the Active Directory for all objects with type of computer
Dim dirSearcher As DirectorySearcher = New DirectorySearcher(dirEntry)
dirSearcher.Filter = ("(objectClass=computer)")
' 2. Check the search results
Dim dirSearchResults As SearchResult
' 3. Loop through all the computer names returned
For Each dirSearchResults In dirSearcher.FindAll()
' 4. Check to ensure the computer name isnt already listed in the collection
If Not pcList.Contains(dirSearchResults.GetDirectoryEntry().Name.ToString()) Then
' 5. Add the computer name to the collection (since it dont already exist)
pcList.Add(dirSearchResults.GetDirectoryEntry().Name.ToString())
End If
Next
' 6. Return the results
Return pcList
End Function
With this next function you can populate a collection (you can alter it to populate a Generic List, DataSet, etc) with the names of all the groups a user is a member of (i.e.; Administrators, Power Users, Guest, etc). With this you create your Directory Object (just not the GetDirectoryEntry function) passing it the path (binding path to the AD), the username & password of the user you want to query. So far we have seen SAMAaacount, givenName, sn, objectClass, and objectCategory. Now we will see the memberOf Property, this is the property telling what group the user is a member of.
Once you have the list of all the groups, then you loop through the list and add them to the collection (or whatever object you choose to use). It looks like this:
CODE
''' <summary>
''' Function to return all the groups the user is a member od
''' </summary>
''' <param name="_path">Path to bind to the AD</param>
''' <param name="username">Username of the user</param>
''' <param name="password">password of the user</param>
Private Function GetGroups(ByVal _path As String, ByVal username As String, _
ByVal password As String) As Collection
Dim Groups As New Collection
Dim dirEntry As New System.DirectoryServices.DirectoryEntry(_path, username, password)
Dim dirSearcher As New DirectorySearcher(dirEntry)
dirSearcher.Filter = String.Format("(sAMAccountName={0}))", username)
dirSearcher.PropertiesToLoad.Add("memberOf")
Dim propCount As Integer
Try
Dim dirSearchResults As SearchResult = dirSearcher.FindOne()
propCount = dirSearchResults.Properties("memberOf").Count
Dim dn As String
Dim equalsIndex As String
Dim commaIndex As String
For i As Integer = 0 To propCount - 1
dn = dirSearchResults.Properties("memberOf")(i)
equalsIndex = dn.IndexOf("=", 1)
commaIndex = dn.IndexOf(",", 1)
If equalsIndex = -1 Then
Return Nothing
End If
If Not Groups.Contains(dn.Substring((equalsIndex + 1), _
(commaIndex - equalsIndex) - 1)) Then
Groups.Add(dn.Substring((equalsIndex + 1), (commaIndex - equalsIndex) - 1))
End If
Next
Catch ex As Exception
If ex.GetType Is GetType(System.NullReferenceException) Then
MessageBox.Show("Selected user isn't a member of any groups at this time.", "No groups listed", MessageBoxButtons.OK, MessageBoxIcon.Error)
'they are still a good user just does not
'have a "memberOf" attribute so it errors out.
'code to do something else here if you want
Else
MessageBox.Show(ex.Message.ToString, "Search Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End If
End Try
Return Groups
End Function
Now we will look into how to determine if a users account has been disabled. This function returns a Boolean (True/False) based on what flag is returned from the query. This function requires an Enumeration of all possible flags for a users account, this is the Enumeration:
CODE
Public Enum ADAccountOptions
UF_TEMP_DUPLICATE_ACCOUNT = 256
UF_NORMAL_ACCOUNT = 512
UF_INTERDOMAIN_TRUST_ACCOUNT = 2048
UF_WORKSTATION_TRUST_ACCOUNT = 4096
UF_SERVER_TRUST_ACCOUNT = 8192
UF_DONT_EXPIRE_PASSWD = 65536
UF_SCRIPT = 1
UF_ACCOUNTDISABLE = 2
UF_HOMEDIR_REQUIRED = 8
UF_LOCKOUT = 16
UF_PASSWD_NOTREQD = 32
UF_PASSWD_CANT_CHANGE = 64
UF_ACCOUNT_LOCKOUT = 16
UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 128
End Enum
Now for the function that checks for the value of 2 (meaning disabled):
CODE
''' <summary>
''' This will perform a logical operation on the userAccountControl values
''' to see if the user account is enabled or disabled. The flag for determining if the
''' account is active is a bitwise value (decimal =2)
''' </summary>
''' <param name="userAccountControl"></param>
''' <returns></returns>
Public Shared Function IsAccountActive(ByVal userAccountControl As Integer) As Boolean
Dim accountDisabled As Integer = Convert.ToInt32(ADAccountOptions.UF_ACCOUNTDISABLE)
Dim flagExists As Integer = userAccountControl And accountDisabled
'if a match is found, then the disabled flag exists within the control flags
If flagExists > 0 Then
Return False
Else
Return True
End If
End Function
I will leave you with one last "tidbit". This function will allow you to add a user to a specific security group. First you create your search object, then you set your filter, this time we will use the objectClass and set it to group (since that is what we're looking for). Then we need to check to see if this user is already a member of the specified group, we do this by looping through all the members in the group (implementing IEnumerable), then checking all names against the user we want to add. Here is the function:
CODE
'' <summary>
''' Method to add a user to a group
''' </summary>
''' <param name="de">DirectoryEntry to use</param>
''' <param name="deUser">User DirectoryEntry to use</param>
''' <param name="GroupName">Group Name to add user to</param>
Public Shared Sub AddUserToGroup(ByVal de As DirectoryEntry, ByVal deUser As DirectoryEntry, ByVal GroupName As String)
Dim deSearch As DirectorySearcher = New DirectorySearcher()
deSearch.SearchRoot = de
deSearch.Filter = "(&(objectClass=group) (cn=" & GroupName & "))"
Dim results As SearchResultCollection = deSearch.FindAll()
Dim isGroupMember As Boolean = False
If results.Count > 0 Then
Dim group As New DirectoryEntry(results(0).Path)
Dim members As Object = group.Invoke("Members", Nothing)
For Each member As Object In CType(members, IEnumerable)
Dim x As DirectoryEntry = New DirectoryEntry(member)
Dim name As String = x.Name
If name <> deUser.Name Then
isGroupMember = False
Else
isGroupMember = True
Exit For
End If
Next member
If (Not isGroupMember) Then
group.Invoke("Add", New Object() {deUser.Path.ToString()})
End If
group.Close()
End If
Return
End Sub
This concludes this tutorial on Active Directory in VB.Net. Remember, this is a semi high level overview of the Active Directory, there are so many things you can do it would take ages for me to list them (and I'm sure DIC++ wouldn't appreciate me using all their HDD space), but this should at least give you a much better understanding of how the System.DirectoryServices Namespace works and what can be done with it. Remember, for anything other than simple searching of the Active Directory Administrative permissions are required. If you have any questions, about this tutorial or some other help with AD feel free to leave me a message here and I will respond as soon as I can. Thanks for reading.
