1. Create a connection to Active Directory
///<summary>
///Method used to create an entry to the AD.
///Replace the path, username, and password.
///</summary>
///<returns>DirectoryEntry</returns>
publicstaticDirectoryEntry GetDirectoryEntry()
{
DirectoryEntry de = newDirectoryEntry();
de.Path = LDAP://192.168.1.1/CN=Users;DC=Yourdomain;
de.Username = @"yourdomain\sampleuser";
de.Password = "samplepassword";
returnde;
}
2. Create a secure connection to Active Directory
To connect to the AD, you need a user account that belongs to the domain you want to connect to. Most user accounts have permissions to search the AD; however, to modify the AD, you need a user account that is a member of the group of Domain Administrators (DomainAdmin). An account that belongs to this group has high privileges and hardcoding the user and password of this account in your code can compromise the security of the AD. I don't recommend you to create directory entries where usernames and passwords are hardcoded. Try to connect to the AD using a secure connection.
///<summary>
///Method used to create an entry to the AD using a secure connection.
///Replace the path.
///</summary>
///<returns>DirectoryEntry</returns>
publicstaticDirectoryEntry GetDirectoryEntry()
{
DirectoryEntry de = newDirectoryEntry();
de.Path = LDAP://192.168.1.1/CN=Users;DC=Yourdomain;
de.AuthenticationType = AuthenticationTypes.Secure;
returnde;
}
To connect to the AD using a secure connection, you need to delegate the permissions of a user account with DomainAdmin permissions to the thread that is running a program. For instance, I created an exe and I ran the program using the Run As command to start a program. I delegated the user's principal identity and culture to the current thread that runs the program. To delegate the principal identity and culture to the current thread, I used the following code:
///<summary>
///Establish identity (principal) and culture for a thread.
///</summary>
publicstaticvoidSetCultureAndIdentity()
{
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
WindowsPrincipal principal = (WindowsPrincipal)Thread.CurrentPrincipal;
WindowsIdentity identity = (WindowsIdentity)principal.Identity;
System.Threading.Thread.CurrentThread.CurrentCulture = newCultureInfo("en-US");
}
3. Validate if a user exists
///<summary>
///Method to validate if a user exists in the AD.
///</summary>
///<param name="UserName"></param>
///<returns></returns>
publicboolUserExists(stringUserName)
{
DirectoryEntry de = ADHelper.GetDirectoryEntry();
DirectorySearcher deSearch = newDirectorySearcher();
deSearch.SearchRoot =de;
deSearch.Filter = "(&(objectClass=user) (cn=" + UserName +"))";
SearchResultCollection results = deSearch.FindAll();
if(results.Count == 0)
{
returnfalse;
}
else
{
returntrue;
}
}
4. Set user's properties
///<summary>
///Helper method that sets properties for AD users.
///</summary>
///<param name="de"></param>
///<param name="PropertyName"></param>
///<param name="PropertyValue"></param>
publicstaticvoidSetProperty(DirectoryEntry de, stringPropertyName, stringPropertyValue)
{
if(PropertyValue!=null)
{
if(de.Properties.Contains(PropertyName))
{
de.Properties[PropertyName][0]=PropertyValue;
}
else
{
de.Properties[PropertyName].Add(PropertyValue);
}
}
5. Set user's country
To set the country property for a user was one of the tasks that took me some time to figure out. After some hours of research I realized that you need to know the ISO 3166 Codes for countries and set three properties to define a user's country: c, co, and countryCode.
// Set the co property using the name of the country.
SetProperty(newuser,"co","MEXICO");
// Set the c property using the two-letter country code (ISO 3166 A 2).
SetProperty(newuser,"c","MX");
// Set the countryCode property using the numeric value (ISO 3166 Number) of the country.
SetProperty(newuser,"countryCode","484");
}
6. Set user's password
Setting the password for a user requires some work. I will walk you through the steps I followed to set a password for a user:
a) Create or download a helper class that generates random passwords that comply with the strong password rules. I was short of time and couldn't develop one, so I downloaded the RandomPassword class created by Obviex.
b) Create a method that consumes the RandomPassword helper class
///<summary>
///Method that consumes a helper class library
///to generate random passwords.
///</summary>
///<returns></returns>
publicstringSetSecurePassword()
{
RandomPassword rp = newRandomPassword();
returnrp.Generate(8,8);
}
c) Set the password property using the usr.Invoke method.
///<summary>
///Method to set a user's password
///<param name="path"></param>
publicvoidSetPassword(stringpath)
{
DirectoryEntry usr = newDirectoryEntry();
usr.Path = path;
usr.AuthenticationType = AuthenticationTypes.Secure;
object[] password = newobject[] {SetSecurePassword()};
objectret = usr.Invoke("SetPassword", password );
usr.CommitChanges();
usr.Close();
}
The usr.Invoke method can be called once within the same AppDomain, otherwise your program will crash. If you place a call to the usr.Invoke method inside a for construct, the first run will be succesful, but the second one will crash the compiler. I created a workaround that helped me to solve this problem. I made a separate console application (SetPassword.exe) and I called and started the process programatically from the SetPassword method.
///</summary>
///Method that calls and starts SetPassword.exe
///<param name="path"></param>
///<param name="password"></param>
publicvoidSetPassword(stringpath, stringpassword)
{
StringBuilder args = newStringBuilder();
args.Append(path);
args.Append(" ");
args.Append(password);
ProcessStartInfo startInfo = newProcessStartInfo("SetPassword.exe",args.ToString());
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(startInfo);
}
7. Enable a user account
///<summary>
///Method to enable a user account in the AD.
///</summary>
///<param name="de"></param>
privatestaticvoidEnableAccount(DirectoryEntry de)
{
//UF_DONT_EXPIRE_PASSWD 0x10000
intexp = (int) de.Properties["userAccountControl"].Value;
de.Properties["userAccountControl"].Value = exp | 0x0001;
de.CommitChanges();
//UF_ACCOUNTDISABLE 0x0002
intval = (int) de.Properties["userAccountControl"].Value;
de.Properties["userAccountControl"].Value = val & ~0x0002;
de.CommitChanges();
}
8. Add a user to a group
///<summary>
///Method to add a user to a group
///</summary>
///<param name="de"></param>
///<param name="deUser"></param>
///<param name="GroupName"></param>
publicstaticvoidAddUserToGroup(DirectoryEntry de, DirectoryEntry deUser, stringGroupName)
{
DirectorySearcher deSearch = newDirectorySearcher();
deSearch.SearchRoot = de;
deSearch.Filter = "(&(objectClass=group) (cn=" + GroupName +"))";
SearchResultCollection results = deSearch.FindAll();
boolisGroupMember = false;
if(results.Count>0)
{
DirectoryEntry group = GetDirectoryEntry(results[0].Path);
objectmembers = group.Invoke("Members",null);
foreach( objectmember in(IEnumerable) members)
{
DirectoryEntry x = newDirectoryEntry(member);
if(x.Name! = deUser.Name)
{
isGroupMember = false;
}
else
{
isGroupMember = true;
break;
}
}
if(!isGroupMember)
{
group.Invoke("Add", newobject[] {deUser.Path.ToString()});
}
group.Close();
}
return;
}
9. Generate a mailbox for a user in Microsoft Exchange Server
You might need to create a mailbox for a user in Microsoft Exchange Server. Network configuration and server architecture can add complexity to the process of programmatically creating mailboxes for users, but you know, there's always a workaround. You can invoke a script that creates mailboxes from a remote machine. I will walk you through the steps I followed to create a mailbox for a user in Microsoft Exchange Server.
///<summary>
///Method that calls and starts a WSHControl.vbs
///</summary>
///<param name="userAlias"></param>
publicvoidGenerateMailBox(stringuserAlias)
{
StringBuilder mailargs = newStringBuilder();
mailargs.Append("WSHControl.vbs");
mailargs.Append(" ");
mailargs.Append(userAlias);
ProcessStartInfo sInfo = newProcessStartInfo("Wscript.exe",mailargs.ToString());
sInfo.WindowStyle = ProcessWindowStyle.Hidden;;
Process.Start(sInfo);
}
10. Create a user account
///<summary>
///Method that creates a new user account
///</summary>
///<param name="employeeID"></param>
///<param name="name"></param>
///<param name="login"></param>
///<param name="email"></param>
///<param name="group"></param>
publicvoidCreateNewUser(stringemployeeID, stringname, stringlogin, stringemail, stringgroup)
{
Catalog catalog = newCatalog();
DirectoryEntry de = ADHelper.GetDirectoryEntry();
///1. Create user account
DirectoryEntries users = de.Children;
DirectoryEntry newuser = users.Add("CN=" + login, "user");
///2. Set properties
SetProperty(newuser,"employeeID", employeeID);
SetProperty(newuser,"givenname", name);
SetProperty(newuser,"SAMAccountName", login);
SetProperty(newuser,"userPrincipalName", login);
SetProperty(newuser,"mail", email);
newuser.CommitChanges();
///3. Set password
SetPassword(newuser.Path);
newuser.CommitChanges();
///4. Enable account
EnableAccount(newuser);
///5. Add user account to groups
AddUserToGroup(de,newuser,group);
///6. Create a mailbox in Microsoft Exchange
GenerateMailBox(login);
newuser.Close();
de.Close();
}
11. Disable a user account
///<summary>
///Method that disables a user account in the AD and hides user's email from Exchange address lists.
///</summary>
///<param name="EmployeeID"></param>
publicvoidDisableAccount(stringEmployeeID)
{
DirectoryEntry de = GetDirectoryEntry();
DirectorySearcher ds = newDirectorySearcher(de);
ds.Filter = "(&(objectCategory=Person)(objectClass=user)(employeeID=" + EmployeeID + "))";
ds.SearchScope = SearchScope.Subtree;
SearchResult results = ds.FindOne();
if(results != null)
{
DirectoryEntry dey = GetDirectoryEntry(results.Path);
intval = (int)dey.Properties["userAccountControl"].Value;
dey.Properties["userAccountControl"].Value = val | 0x0002;
dey.Properties["msExchHideFromAddressLists"].Value = "TRUE";
dey.CommitChanges();
dey.Close();
}
de.Close();
}
12. Update user account
///<summary>
///Method that updates user's properties
///</summary>
///<param name="employeeID"></param>
///<param name="department"></param>
///<param name="title"></param>
///<param name="company"></param>
publicvoidModifyUser(stringemployeeID, stringdepartment, stringtitle, stringcompany)
{
DirectoryEntry de = GetDirectoryEntry();
DirectorySearcher ds = newDirectorySearcher(de);
ds.Filter = "(&(objectCategory=Person)(objectClass=user)(employeeID=" + employeeID + "))";
ds.SearchScope = SearchScope.Subtree;
SearchResult results = ds.FindOne();
if(results!=null)
{
DirectoryEntry dey = GetDirectoryEntry(results.Path);
SetProperty(dey, "department", department);
SetProperty(dey, "title", title);
SetProperty(dey, "company", company);
dey.CommitChanges();
dey.Close();
}
de.Close();
}
13. Validate if a string has a correct email pattern.
///<summary>
///Method that validates if a string has an email pattern.
///</summary>
///<param name="mail"></param>
///<returns></returns>
publicboolIsEmail(stringmail)
{
Regex mailPattern = newRegex(@"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*");
returnmailPattern.IsMatch(mail);
}
14. Extract a user alias from an email account.
///<summary>
///Method to extract the alias from an email account.
///dada una cuenta de correo electrónico
///</summary>
///<param name="mailAddress"></param>
///<returns></returns>
publicstringGetAlias(stringmailAddress)
{
if(IsEmail(mailAddress))
{
returnmailAddress.Substring(0,mailAddress.IndexOf("@"));
}
else
{
return"";
}
}
15. Format dates to AD date format (AAAAMMDDMMSSSS.0Z)
///<summary>
///Method that formats a date in the required format
///needed (AAAAMMDDMMSSSS.0Z) to compare dates in AD.
///</summary>
///<param name="date"></param>
///<returns>Date in valid format for AD</returns>
publicstringToADDateString(DateTime date)
{
stringyear = date.Year.ToString();
intmonth = date.Month;
intday = date.Day;
StringBuilder sb = newStringBuilder();
sb.Append(year);
if(month <10)
{
sb.Append("0");
}
sb.Append(month.ToString());
if(day <10)
{
sb.Append("0");
}
sb.Append(day.ToString());
sb.Append("000000.0Z");
returnsb.ToString();
}
16. Search users
When you use Directory Services, you can accomplish many interesting tasks such as searching and filtering users. The DirectorySearcher object allows you to query the AD. The following sample code queries the AD to search all the user accounts that were modified from a given date. The results are stored in a DataTable, so you can easily databind them.
///<summary>
///Method that returns a DataTable with a list of users modified from a given date.
///</summary>
///<param name="fromdate"></param>
publicDataTable GetModifiedUsers(DateTime fromdate)
{
DataTable dt = newDataTable();
dt.Columns.Add("EmployeeID");
dt.Columns.Add("Name");
dt.Columns.Add("Email");
DirectoryEntry de = GetDirectoryEntry();
DirectorySearcher ds = newDirectorySearcher(de);
StringBuilder filter = newStringBuilder();
filter.Append("(&(objectCategory=Person)(objectClass=user)(whenChanged>=");
filter.Append(date.ToADDateString());
filter.Append("))");
ds.Filter=filter.ToString();
ds.SearchScope = SearchScope.Subtree;
SearchResultCollection results= ds.FindAll();
foreach(SearchResult result inresults)
{
DataRow dr = dt.NewRow();
DirectoryEntry dey = GetDirectoryEntry(result.Path);
dr["EmployeeID"] = dey.Properties["employeeID"].Value;
dr["Name"] = dey.Properties["givenname"].Value;
dr["Email"] = dey.Properties["mail"].Value;
dt.Rows.Add(dr);
dey.Close();
}
de.Close();
returndt;
}