Introduction
In this article, we will be focusing on creating, editing, and deleting both user accounts or groups on Active Directory or Machine SAM by using the System.DirectoryServices.AccountManagement
namespace that .NET Framework 3.5 includes. We were able to develop these functionalities by using the Invoke
method of the DirectoryEntry
object which is returned by the DirectorySearcher
component in .NET Framework 2.0, but this new namespace works faster and is easy to develop. It also is in a more object oriented structure.
Before starting, let's just have a sneak peek at the objects we will be using during this article.
PrincipalContext | PrincipalContext is the object that provides directory services such as AD or Machine SAM. In other words, it holds the connection to a directory. |
Principal | Principal is the base class of principal objects that we will be describing below. It includes common methods such as Save , Delete . |
GroupPrincipal | As can be understood from the name, the GroupPrincipal object provides functionality for Group objects at directory level and is derived from the Principal object. |
AuthenticablePricipal | This class is also derived from the Principal class, and besides that, it includes methods for authentication mechanisms. |
UserPricipal | UserPrincipal is the object that represents users on both the Active Directory or the Local SAM, it provides functionality for user items. |
Using the code
I believe that the best learning method in programming is writing code. So, let's start writing our code. Instead of pasting all the code here, I will give some examples of critical points, and try to explain the idea of working with these classes.
For further information, you can download the source from the link above. Here are some functionalities this program includes.
- Groups: Listing, Filtering, Creating, Editing, Deleting, Viewing users of a group.
- Users: Listing, Filtering, Creating, Editing, Deleting, Changing password, Viewing user groups, Adding user to a group, Removing user from a group.
Let's start with creating an instance of a PrincipalContext
object.
PrincipalContext insPrincipalContext =
new PrincipalContext(ContextType.Machine);
PrincipalContext insPrincipalContext = new PrincipalContext(ContextType.Domain, "MyDomain",
"DC=MyDomain,DC=com");
PrincipalContext insPrincipalContext = new PrincipalContext(ContextType.Machine,"TAMERO",
"administrator","password");
As you see, there is no difference between connecting to a Machine SAM and Active Directory. I will continue with my article giving examples from Machine SAM, but as I said, there is no difference. Also, with this object, you can validate a user's credentials. I will provide an example of using this method at the end of the article.
Now, let's look at how to list or filter users or groups.
private void SearchGroups(GroupPrincipal parGroupPrincipal)
{
lbGroups.Items.Clear();
PrincipalSearcher insPrincipalSearcher = new PrincipalSearcher();
insPrincipalSearcher.QueryFilter = parGroupPrincipal;
PrincipalSearchResult<principal> results = insPrincipalSearcher.FindAll();
foreach (Principal p in results)
{
lbGroups.Items.Add(p);
}
}
private void SearchUsers(UserPrincipal parUserPrincipal)
{
lbUsers.Items.Clear();
PrincipalSearcher insPrincipalSearcher = new PrincipalSearcher();
insPrincipalSearcher.QueryFilter = parUserPrincipal;
PrincipalSearchResult<principal> results = insPrincipalSearcher.FindAll();
foreach (Principal p in results)
{
lbUsers.Items.Add(p);
}
}
private void ListGroups()
{
GroupPrincipal insGroupPrincipal = new GroupPrincipal(insPrincipalContext);
insGroupPrincipal.Name = "*";
SearchGroups(insGroupPrincipal);
}
private void ListUsers()
{
UserPrincipal insUserPrincipal = new UserPrincipal(insPrincipalContext);
insUserPrincipal.Name = "*";
SearchUsers(insUserPrincipal);
}
Now, let's look at the logic behind listing and filtering users or groups. You can easily see that both the SearchGroups
and SearchUsers
methods are similar. The most important point is using GroupPrincipal
for Group, and UserPrincipal
for User functions. And, principal searcher is the common class that provides the search functionality. After assigning an object (GroupPrincipal
or UserPrincipal
) that contains filters to the QueryFilter
property of this object, the FindAll
method returns all the records, and the FindOne
method returns the first record. And, another point is how to set search criteria, for example, for a property that contains a "b" character, you can assign "*b*" to that property, or for a property that starts with a "b" character, you can assign "b*" to that property. I will also tell how to set filters that depend on comparison (e.g., PasswordExpirationDate
is in the next 5 days) at the end of this article.
Now, let's look at the code that creates a new user:
UserPrincipal insUserPrincipal = new UserPrincipal(insPrincipalContext);
insUserPrincipal.Save();
insUserPrincipal.Dispose();
MessageBox.Show("User created.");
GroupPrincipal insGroupPrincipal = new GroupPrincipal(insPrincipalContext);
insGroupPrincipal.Save();
insGroupPrincipal.Dispose();
MessageBox.Show("Group created.");
It is very easy to create an entry. As you can see, after creating a principal object (User or Group), invoking its Save
method saves the record. Editing and deleting works the same. Instead of creating a Principal
object, if you get a Principal
object reference, then change the properties and invoke the Save
method, and your object is updated. For deleting an entry, the only thing you should do is call the Delete
method of the Principal
object that references to the entry.
And now, let's talk about changing the password of a user:
UserPrincipal insUserPrincipal = (UserPrincipal)lbUsers.SelectedItem;
insUserPrincipal.SetPassword("12345678");
MessageBox.Show("Password changed.");
You can get the members of a group from the Members
property of the GroupPrincipal
object. You can use this property as an enumeration. To add a user to a group, you should add the UserPrincipal
object that holds the reference of a user to this enumeration. And the same logic for removing a user from a group: you must remove that user from the enumeration. But, do not forget to execute the Save
method of GroupPrincipal
after making changes. You can list the user groups by executing the method GetGroups()
of the UserPrincipal
object. Here are some examples:
GroupPrincipal insGroupPrincipal = (GroupPrincipal)lbGroups.SelectedItem;
List<principal> insListPrincipal = new List<principal>();
foreach (Principal p in insGroupPrincipal.Members)
{
insListPrincipal.Add(p);
}
UserPrincipal insUserPrincipal = (UserPrincipal)lbUsers.SelectedItem;
List<principal> insListPrincipal = new List<principal>();
foreach (Principal p in insUserPrincipal.GetGroups())
{
insListPrincipal.Add(p);
}
UserPrincipal insUserPrincipal = (UserPrincipal)lbUsers.SelectedItem;
GroupPrincipal groupPrincipal =
GroupPrincipal.FindByIdentity(insPrincipalContext, group.GroupName);
if (groupPrincipal.Members.Contains(insPrincipalContext,
IdentityType.SamAccountName, insUserPrincipal.SamAccountName))
{
MessageBox.Show(insUserPrincipal.Name +
" is already a member of group " + group.GroupName);
return;
}
groupPrincipal.Members.Add(insUserPrincipal);
groupPrincipal.Save();
UserPrincipal insUserPrincipal = (UserPrincipal)lbUsers.SelectedItem;
GroupPrincipal groupPrincipal =
GroupPrincipal.FindByIdentity(insPrincipalContext, group.GroupName);
groupPrincipal.Members.Remove(insUserPrincipal);
groupPrincipal.Save();
And, here is the code for validating user credentials and filtering comparison values:
insPrincipalContext.ValidateCredentials("Tamer", "12345678");
parUserPrincipal.AdvancedSearchFilter.AccountExpirationDate(
DateTime.Now.Date.AddDays(10), MatchType.Equals);
parUserPrincipal.AdvancedSearchFilter.BadLogonCount(5, MatchType/span>.GreaterThanOrEquals);
For bug reports and suggestions, feel free to contact me at oztamer@hotmail.com.