Search This Blog

Saturday, September 4, 2010

ACTIVE DIRECTORY AND .NET

Some time ago I was working in a project where I had to design and develop a service
to synchronize a human resources database with an Active Directory (AD). To accomplish my goal, I created a service that used .NET Directory Services, and after some months, the project succeeded. I had to invest a ton of extra hours to the project because I had a hard time finding sample .NET code and documentation related to specific tasks needed to work with AD User Accounts.



Microsoft Technet offers a
script repository
to work with AD User Accounts; however, I needed to work with .NET and I could not find samples for all the tasks I needed to program. I promised to myself that one day I would publish the code samples I found and created to help other developers who are working with Directory Services. So, I wish you a happy AD.NET programming and I hope my work saves you some time. The code samples I provide are written in C#.


How to...



  1. Create a connection to Active Directory

  2. Create a secure connection to Active Directory

  3. Validate if a user exists


  4. Set user's properties

  5. Set user's country

  6. Set user's password

  7. Enable a user account

  8. Add a user to a group

  9. Generate a mailbox for a user in Microsoft Exchange Server

  10. Create a user account

  11. Disable a user account

  12. Update user account


  13. Validate if a string has a correct email pattern

  14. Extract a user alias from an email account

  15. Format dates to AD date format (AAAAMMDDMMSSSS.0Z)

  16. Search users



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>

public static DirectoryEntry GetDirectoryEntry(){

    DirectoryEntry de 
= new DirectoryEntry();


    
de.Path "LDAP://192.168.1.1/CN=Users;DC=Yourdomain";

    
de.Username @"yourdomain\sampleuser";

    
de.Password "samplepassword";

    return 
de;


}


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>

public static DirectoryEntry GetDirectoryEntry(){


    DirectoryEntry de 
= new DirectoryEntry();

    
de.Path "LDAP://192.168.1.1/CN=Users;DC=Yourdomain";

    
de.AuthenticationType AuthenticationTypes.Secure;


    return 
de;




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>

public static void SetCultureAndIdentity(){


    AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal)
;

    
WindowsPrincipal principal (WindowsPrincipal)Thread.CurrentPrincipal;

    
WindowsIdentity identity (WindowsIdentity)principal.Identity;

    
System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("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>

public bool UserExists(string UserName){


    DirectoryEntry de 
ADHelper.GetDirectoryEntry();

    
DirectorySearcher deSearch = new DirectorySearcher();

    
deSearch.SearchRoot =de;


    
deSearch.Filter "(&(objectClass=user) (cn=" + UserName +"))"

    
SearchResultCollection results deSearch.FindAll();


    if
(results.Count == 0){

         
return false;

    
}

    
else{

        
return true;


    
}

}



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>


public static void SetProperty(DirectoryEntry de, string PropertyName, string PropertyValue){

    
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:




  1. 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™.



  2. Create a method that consumes the RandomPassword helper class.

    /// <summary>

    /// Method that consumes a helper class library

    /// to generate random passwords.


    /// </summary>

    /// <returns></returns>

    public string SetSecurePassword(){

        RandomPassword rp 
    = new RandomPassword();


        return 
    rp.Generate(8,8);

    }





  3. Set the password property using the usr.Invoke method.


    /// <summary>

    /// Method to set a user's password

    /// <param name="path"></param>

    public void SetPassword(string path){


        DirectoryEntry usr 
    = new DirectoryEntry();

        
    usr.Path path;

        
    usr.AuthenticationType AuthenticationTypes.Secure;


        object
    [] password = new object[] {SetSecurePassword()};

        object 
    ret 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.





    1. Download the SetPassword project.

    2. Copy the SetPassword.exe file in your application.

    3. Call and start SetPassword.exe from you application.

      /// </summary>

      /// Method that calls and starts SetPassword.exe


      /// <param name="path"></param>

      /// <param name="password"></param>

      public void SetPassword(string path, string password){


          StringBuilder args 
      = new StringBuilder();

          
      args.Append(path);

          
      args.Append(" ");

          
      args.Append(password);


          
      ProcessStartInfo startInfo = new ProcessStartInfo("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>

private static void EnableAccount(DirectoryEntry de){

    
//UF_DONT_EXPIRE_PASSWD 0x10000

    
int exp (int) de.Properties["userAccountControl"].Value;


    
de.Properties["userAccountControl"].Value exp | 0x0001;

    
de.CommitChanges();

    
//UF_ACCOUNTDISABLE 0x0002

    
int val (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>


public static void AddUserToGroup(DirectoryEntry de, DirectoryEntry deUser, string GroupName){

    DirectorySearcher deSearch 
= new DirectorySearcher();


    
deSearch.SearchRoot de;

    
deSearch.Filter "(&(objectClass=group) (cn=" + GroupName +"))"

    
SearchResultCollection results deSearch.FindAll();




    bool 
isGroupMember = false;



    if 
(results.Count>0){

        DirectoryEntry group 
GetDirectoryEntry(results[0].Path);


            

        object 
members group.Invoke("Members",null);

        foreach 
object member in (IEnumerable) members){


            DirectoryEntry x 
= new DirectoryEntry(member);

            if 
(x.Name! deUser.Name){

                isGroupMember 
= false;

            
}


            
else{

                isGroupMember 
= true;

                break;

            
}

        }

        

        
if (!isGroupMember){


            group.Invoke(
"Add"new object[] {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.



  1. On the Domain Controller server, create the directory C:\TestRemoteMailbox.


  2. Copy the script Mailbox.vbs to the directory C:\TestRemoteMailbox.



    Note: Mailbox.vbs is a script that creates MailBoxes in Microsoft Exchange.




  3. Copy the script WSHControl.vbs to your application directory.




    Note: WSHControl.vbs is a script that invokes the MailBox.vbs script on a remote machine (Domain Controller).





  4. From your application, call and start WSHControl.vbs.

    /// <summary>

    /// Method that calls and starts a WSHControl.vbs

    /// </summary>


    /// <param name="userAlias"></param>

    public void GenerateMailBox(string userAlias){

        StringBuilder mailargs 
    = new StringBuilder();


        
    mailargs.Append("WSHControl.vbs");

        
    mailargs.Append(" ");

        
    mailargs.Append(userAlias);

                

        
    ProcessStartInfo sInfo = new ProcessStartInfo("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>

public void CreateNewUser(string employeeID, string name, string login, string email, string group){


    Catalog catalog 
= new Catalog();

    
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>

public void DisableAccount(string EmployeeID){

    DirectoryEntry de 
GetDirectoryEntry();


    
DirectorySearcher ds = new DirectorySearcher(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);

        int 
val (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>

public void ModifyUser(string employeeID, string department, string title, string company){


    DirectoryEntry de 
GetDirectoryEntry();

    
DirectorySearcher ds = new DirectorySearcher(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>

public bool IsEmail(string mail){


    Regex mailPattern
 = new Regex(@"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*");

    return 
mailPattern.IsMatch(mail);

}


14. Extract a user alias from an email account





/// <summary>

/// Method to extract the alias from an email account.


/// </summary>

/// <param name="mailAddress"></param>

/// <returns></returns>

public string GetAlias(string mailAddress){


    
if (IsEmail(mailAddress)){

        
return mailAddress.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>

public string ToADDateString(DateTime date){

    
string year = date.Year.ToString();


    int 
month = date.Month;

    int 
day = date.Day;



    
StringBuilder sb = new StringBuilder();


    
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");


    return 
sb.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>

public DataTable GetModifiedUsers(DateTime fromdate){

    DataTable dt 
= new DataTable();


    
dt.Columns.Add("EmployeeID");

    
dt.Columns.Add("Name");

    
dt.Columns.Add("Email");

            

    
DirectoryEntry de GetDirectoryEntry();


    
DirectorySearcher ds = new DirectorySearcher(de);



    
StringBuilder filter = new StringBuilder();

    
filter.Append("(&(objectCategory=Person)(objectClass=user)(whenChanged>=");


    
filter.Append(date.ToADDateString());

    
filter.Append("))");

            

    
ds.Filter=filter.ToString();

    
ds.SearchScope SearchScope.Subtree;


    
SearchResultCollection resultsds.FindAll();

        

    foreach
(SearchResult result in results){

        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()
;

    return 
dt;

}

No comments:

Post a Comment