Click here to Skip to main content
15,867,308 members
Articles / Productivity Apps and Services / Sharepoint / SharePoint 2013

How to Synchronize Active Directory Users to a SharePoint List

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
21 Sep 2015CPOL6 min read 38.3K   436   6   9
Synchronize Active Directory (AD) users to a SharePoint List. We will create a timer job for that. This timer job will periodically sync AD users to a SharePoint list.

Introduction

In enterprise organizations, employees/users are maintained by the Active Directory. Sometime, they may need to sync all employees/users to a SharePoint list. Let's say, you wish to develop a Web Part like Know Your Colleague. In this Web Part, Employees/users will be shown as department (IT, Marketing and so on) wise. May be your Active Directory Admin is maintaining it (department wise) already. Now your management has asked you to show these employess/users in SharePoint. The easiest solution can be "Exporting all users in csv file and then import them in a list". So, you have to perform this job every day to keep everything synchronized (User may be deleted or updated).

We need an automated solution which will do all these tasks for us. Currently, SharePoint does not offer any built in feature for it. So this article aims to show you floks how we can achieve it. 

Solution

To overcome this, I am going to develop a timer job which will perform following tasks for us periodically.

  1. Get your desired users from Active Directory
  2. Save them if does not exist or Update them if it is need
  3. Delete them if does not exist in Active Directory

I hope you all know how to develop SharePoint timer job. I do not want to show the steps to creating timer job over here because it will elaborate my article unnecessarily. You may check this article for creating timer job.

Let's say, we have a list named Active Directory Users and it has following columns. 

Column Name Mapped AD property
Full Name displayName
Position title
Department department
UserName sAMAccountName
Email userPrincipalName
ManagerEmail will be extract from manager
WhenModified whenChanged

 

Now our goal is to read users from Active Directory and save them in our list. We will also update/delete items when they will be changed in Active Directory. Let's see how we do this.

Getting AD users using C#

We can get AD users very easily. In that case we need to add System.DirectoryServices assembly reference in our project. You can perform any operation like create, update, read and delete by this assembly reference. Check for sample code in msdn. I am assuming that you are familiar with AD keywords. For developing this solution, you must be familiar with following keywords. In AD users are differentiated by their distinguished names. Generally, distinguished names look like following. It may vary based container but components are always same.

CN=Atish Dipongkor,OU=IT,DC=atishlab,DC=com

CN: commonName

OU: organizationalUnitName

DC: domainComponent. You have to specify each component separately like Top-Level domain name and Secondly-Level domain name. My domain name is atishlab.com. So in distinguished name, it is showing something like DC=atishlab (Second-Level) and DC=com (Top-Level).

To sync users from AD, at first we have to choose our OU (organizational units). Let's say, we wish to sync users from Junior OU. The location of Junior OU looks like following.

atishlab.com // main domain
--Department // OU
  --IT
    --Engineer
      --Junior

Basically, we wish to sync our junior engineers from IT department. For getting users, we have to define a path. I mean from where we wish to search for users. If we consider Junior OU, our path should look like following.

OU=Junior,OU=Engineer,OU=IT,OU=Department,DC=atishlab,DC=com

You may think that path constructing is very complex. Actually, it is not at all. It's so simple. Just think path always will be Bottom to Top. Now your get user method should look like following

C#
using (var directoryInfo = new DirectoryEntry(SyncPath, UserName, Password))
            {
                var userFindingfilter = "(&(objectClass=user)(objectCategory=person))";
                var userProperties = new string[] { "title", "whenChanged", "displayName", "department", "sAMAccountName", "userPrincipalName", "manager" };
                using (var directoryInfoSearch = new DirectorySearcher(directoryInfo, userFindingfilter, userProperties, SearchScope.Subtree))
                {
                    var directoryEntryUserSearchResults = directoryInfoSearch.FindAll();
                    foreach (SearchResult searchResult in directoryEntryUserSearchResults)
                    {
                        var searchResultDirectoryEntry = searchResult.GetDirectoryEntry();
                        if (searchResultDirectoryEntry.Properties["manager"].Value == null)
                            continue;
                        var managerDnName = searchResultDirectoryEntry.Properties["manager"].Value.ToString();
                        var manager = new DirectoryEntry("LDAP://" + managerDnName);
                        SaveItemIfNotExists(searchResultDirectoryEntry, manager);
                    }
                }
            }

Look in my code for few minutes. At first, I have created directoryInfo object that takes SyncPath, UserName and Password as parameters. In the the SyncPath, you have to append your server url along with OU path. 

LDAP://YourServerNameWithDomain/OU=Junior,OU=Engineer,OU=IT,OU=Department,DC=atishlab,DC=com

In my lab environment, the path becomes as following as my server name is WIN-AD and domain name is atishlab.com

LDAP://WIN-AD.atishlab.com/OU=Junior,OU=Engineer,OU=IT,OU=Department,DC=atishlab,DC=com

Our next job is to create a DirectorySearcher and that has several overloads but I have used following one.

di-over-loadIt takes following parameters. Let's discuss one by one.

searchRoot: We have created it already. It should directoryInfo.

filter: You have to pass a string. It means what you actually want to search. It may Computer, Organizational Unite or User. In our case, we wish to search users. So the filter should look like following. For other types of filters, please see the documentation.

C#
var userFindingfilter = "(&(objectClass=user)(objectCategory=person))";

propertiesToLoad: AD users have many properties, and you can add your custom properties also. In this paramater, you have to specify what properties you want to load in this search. According to our Active Directory Users list, we need following properties.

C#
var userProperties = new string[] { "title", "whenChanged", "displayName", "department", "sAMAccountName", "userPrincipalName", "manager" };

scope: In which scope, you wish to search. It may be Base or LevelOne or Subtree. As we wish to get all user from Junior OU (Including its sub OUs), so we have chosen SearchScope.Subtree.

Now directoryInfoSearch.FindAll() will return all matched users for us so that we can save them into Active Directory Users list.

Save AD users to SharePoint list by timer job

Now we have to create a timer job so that we can sync our users after a certain interval. I am not going to show the steps of creating timer job here. See the link provided above for creating time job. Also you can see my demo solution. I am starting from Execute method of our timer job. It looks like following

C#
public override void Execute(Guid targetInstanceId)
        {
            try
            {
                SPWebApplication webApplication = this.Parent as SPWebApplication;
                var config = WebConfigurationManager.OpenWebConfiguration("/", webApplication.Name);
                var syncPath = config.AppSettings.Settings["syncPath"].Value;
                var syncUserName = config.AppSettings.Settings["syncUserName"].Value;
                var syncPassword = config.AppSettings.Settings["syncPassword"].Value;
                var syncSiteUrl = config.AppSettings.Settings["syncSiteUrl"].Value;
                var adUserSyncHelper = new AdUserSyncHelper(syncPath, syncUserName, syncPassword, syncSiteUrl);
                adUserSyncHelper.Sync();
                adUserSyncHelper.RemoveItemsIfNotExistInAd();
            }
            catch (Exception ex)
            {
                //Handle exception here
            }
            base.Execute(targetInstanceId);
        }

I kept my syncPath, syncUserName, syncPassword, syncSiteUrl in my web application's web.config file. So open your web.config and place following things under appSettings.

XML
<appSettings>
    <!--Settings for SyncAdUser-->
    <add key="syncUserName" value="UserName" />
    <add key="syncPassword" value="Password" />
    <add key="syncPath" value="LDAP://WIN-AD.atishlab.com/OU=Junior,OU=Engineer,OU=IT,OU=Department,DC=atishlab,DC=com" />
    <add key="syncSiteUrl" value="http://win-spe:5001/sites/dev" />
    <!--Settings for SyncAdUser-->
  </appSettings>

Actually, I have written AdUserSyncHelper class to sync users. In adUserSyncHelper.Sync() method, I have iterated over all AD users (desired OU) and save them if user does not exist or update them based on AD whenChanged property and WhenModified column of Active Directory Users list. If I found user already exists, then I compaired whenChanged and WhenModified to check for modification. If I see user exists in Active Directory Users list but not in AD, then I have deleted it. Actually, I have developed my own workflow to sync users. I hope you will find a better workflow than mine, and you will share with us. So I have done in my AdUserSyncHelper class is given bellow. 

C#
using Microsoft.SharePoint;
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SyncAdUserToList
{
    class AdUserSyncHelper
    {
        private string SyncPath { get; set; }
        private string UserName { get; set; }
        private string Password { get; set; }
        private string SiteUrl { get; set; }
        public AdUserSyncHelper(string syncPath, string userName, string password, string siteUrl)
        {
            SyncPath = syncPath;
            UserName = userName;
            Password = password;
            SiteUrl = siteUrl;
        }
        public void Sync()
        {
            using (var directoryInfo = new DirectoryEntry(SyncPath, UserName, Password))
            {
                var userFindingfilter = "(&(objectClass=user)(objectCategory=person))";
                var userProperties = new string[] { "title", "whenChanged", "displayName", "department", "sAMAccountName", "userPrincipalName", "manager" };
                using (var directoryInfoSearch = new DirectorySearcher(directoryInfo, userFindingfilter, userProperties, SearchScope.Subtree))
                {
                    var directoryEntryUserSearchResults = directoryInfoSearch.FindAll();
                    foreach (SearchResult searchResult in directoryEntryUserSearchResults)
                    {
                        var searchResultDirectoryEntry = searchResult.GetDirectoryEntry();
                        if (searchResultDirectoryEntry.Properties["manager"].Value == null)
                            continue;
                        var managerDnName = searchResultDirectoryEntry.Properties["manager"].Value.ToString();
                        var manager = new DirectoryEntry("LDAP://" + managerDnName);
                        SaveItemIfNotExists(searchResultDirectoryEntry, manager);
                    }
                }
            }
        }
        private void SaveItemIfNotExists(DirectoryEntry user, DirectoryEntry manager)
        {
            using (var spSite = new SPSite(SiteUrl))
            {
                using (var spWeb = spSite.OpenWeb())
                {
                    spWeb.AllowUnsafeUpdates = true;
                    var spList = spWeb.Lists["Active Directory Users"];
                    var spQuery = new SPQuery();
                    spQuery.Query = @"<Where><Eq><FieldRef Name='UserName' /><Value Type='Text'>" + user.Properties["sAMAccountName"].Value.ToString() + "</Value></Eq></Where>";
                    var spItems = spList.GetItems(spQuery);
                    if (spItems.Count == 0)
                    {
                        var newItem = spList.AddItem();
                        newItem["Full Name"] = user.Properties["displayName"].Value == null ? "Not Set" : user.Properties["displayName"].Value.ToString();
                        newItem["UserName"] = user.Properties["sAMAccountName"].Value.ToString();
                        newItem["Position"] = user.Properties["title"].Value == null ? "Not Set" : user.Properties["title"].Value.ToString();
                        newItem["Department"] = user.Properties["department"].Value == null ? "Not Set" : user.Properties["department"].Value.ToString();
                        newItem["Email"] = user.Properties["userPrincipalName"].Value.ToString();
                        newItem["ManagerEmail"] = manager.Properties["userPrincipalName"].Value.ToString();
                        newItem["WhenModified"] = (DateTime) user.Properties["whenChanged"].Value;                        newItem.Update();
                    }
                    else
                    {
                        var itemModified = (DateTime) spItems[0]["WhenModified"];
                        var directoryEntryModified = (DateTime) user.Properties["whenChanged"].Value;
                        if (directoryEntryModified > itemModified)
                        {
                            var existingItem = spItems[0];
                            existingItem["Full Name"] = user.Properties["displayName"].Value == null ? "Not Set" : user.Properties["displayName"].Value.ToString();
                            existingItem["UserName"] = user.Properties["sAMAccountName"].Value.ToString();
                            existingItem["Position"] = user.Properties["title"].Value == null ? "Not Set" : user.Properties["title"].Value.ToString();
                            existingItem["Department"] = user.Properties["department"].Value == null ? "Not Set" : user.Properties["department"].Value.ToString();
                            existingItem["Email"] = user.Properties["userPrincipalName"].Value.ToString();
                            existingItem["ManagerEmail"] = manager.Properties["userPrincipalName"].Value.ToString();
                            existingItem["WhenModified"] = (DateTime) user.Properties["whenChanged"].Value;
                            existingItem.Update();
                        }
                    }
                    spWeb.AllowUnsafeUpdates = false;
                }
            }
        }
        public void RemoveItemsIfNotExistInAd()
        {
            using (var spSite = new SPSite(SiteUrl))
            {
                using (var spWeb = spSite.OpenWeb())
                {
                    var spListItemCollection = spWeb.Lists["Active Directory Users"].Items;
                    foreach (SPListItem spItem in spListItemCollection)
                    {
                        if (!IsItemExistInAd(spItem["UserName"].ToString()))
                        {
                            DeleteItem(spItem);
                        }
                    }
                }
            }
        }
        private void DeleteItem(SPListItem spItem)
        {
            using (var spSite = new SPSite(SiteUrl))
            {
                using (var spWeb = spSite.OpenWeb())
                {
                    spWeb.AllowUnsafeUpdates = true;
                    var spList = spWeb.Lists["Active Directory Users"];
                    var item = spList.GetItemById(spItem.ID);
                    item.Delete();
                    spWeb.AllowUnsafeUpdates = false;
                }
            }
        }
        private bool IsItemExistInAd(string sAMAccountName)
        {
            using (var directoryInfo = new DirectoryEntry(SyncPath, UserName, Password))
            {
                using (var directoryInfoSearch = new DirectorySearcher(directoryInfo, String.Format("(sAMAccountName={0})", sAMAccountName)))
                {
                    var directoryInfoSearchResult = directoryInfoSearch.FindOne();
                    if (directoryInfoSearchResult == null) return false;
                }
            }
            return true;
        }
    }
}

Update 1: 

Credit of this update goes to @PIEBALDconsult. He has shared his valuable knowledge for dealing large number of users and date time casting issue. I have updated my code snippet for DateTime issue. Please update my demo source code when you will download it. For dealing large number of users, please follow below links.

  • http://geekswithblogs.net/mnf/archive/2005/12/20/63581.aspx
  • http://stackoverflow.com/questions/3488394/c-sharp-active-directory-services-findall-returns-only-1000-entries

Points of Interest

I hope I have made myself clear how I did this. So now it is your job to dig into it and discover more and more. Please do not forget to share if you find something new and also let me know where you get stuck.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Instructor / Trainer Jashore University of Science and Technology
Bangladesh Bangladesh
2016 Microsoft MVP

Currently, I am devoted to provide technical and development support to the SharePoint clients and also I am working on angularjs. I am experienced with C#, ASP.NET, SharePoint, SignalR, angularjs, MS SQL, Oracle 11g R2, Windows Phone, Firefox OS and so on. I have fallen in love with many technologies but never got married to any of them. I am evolving myself as full stack developer. I always like to share knowledge as much as to gather from you.

Comments and Discussions

 
GeneralFor the Enterprise SKU of SharePoint, you do not need this.. Pin
ReleaseTheHounds4-Apr-17 1:15
ReleaseTheHounds4-Apr-17 1:15 
GeneralThoughts Pin
PIEBALDconsult20-Sep-15 5:28
mvePIEBALDconsult20-Sep-15 5:28 
GeneralRe: Thoughts Pin
Garth J Lancaster21-Sep-15 3:04
professionalGarth J Lancaster21-Sep-15 3:04 
GeneralRe: Thoughts Pin
Atish Dipongkor21-Sep-15 3:23
professionalAtish Dipongkor21-Sep-15 3:23 
GeneralRe: Thoughts Pin
PIEBALDconsult21-Sep-15 6:00
mvePIEBALDconsult21-Sep-15 6:00 
GeneralRe: Thoughts Pin
Atish Dipongkor21-Sep-15 6:18
professionalAtish Dipongkor21-Sep-15 6:18 
GeneralRe: Thoughts Pin
PIEBALDconsult21-Sep-15 6:20
mvePIEBALDconsult21-Sep-15 6:20 
GeneralRe: Thoughts Pin
PIEBALDconsult21-Sep-15 6:12
mvePIEBALDconsult21-Sep-15 6:12 
QuestionImage missing Pin
Atish Dipongkor20-Sep-15 1:35
professionalAtish Dipongkor20-Sep-15 1:35 
QuestionMessage Closed Pin
20-Sep-15 0:15
Member 1199753320-Sep-15 0:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.