65.9K
CodeProject 正在变化。 阅读更多。
Home

如何将 Active Directory 用户同步到 SharePoint 列表

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2015 年 9 月 20 日

CPOL

6分钟阅读

viewsIcon

40188

downloadIcon

461

将 Active Directory (AD) 用户同步到 SharePoint 列表。我们将为此创建一个计时器作业。该计时器作业将定期将 AD 用户同步到 SharePoint 列表。

引言

在企业组织中,员工/用户由 Active Directory 维护。有时,他们可能需要将所有员工/用户同步到 SharePoint 列表。比如说,您希望开发一个像“了解你的同事”这样的 Web 部件。在此 Web 部件中,员工/用户将按部门(IT、市场营销等)显示。也许您的 Active Directory 管理员已经在(按部门)维护这些信息。现在您的管理层要求您在 SharePoint 中显示这些员工/用户。最简单的解决方案可能是“将所有用户导出为 csv 文件,然后将其导入到列表中”。但是,您必须每天执行此操作以保持所有内容同步(用户可能会被删除或更新)。

我们需要一个自动化的解决方案来为我们完成所有这些任务。目前,SharePoint 没有提供任何内置功能。因此,本文旨在向大家展示我们如何实现它。

解决方案

为了解决这个问题,我将开发一个计时器作业,它会定期为我们执行以下任务。

  1. 从 Active Directory 获取您需要的用户
  2. 如果用户不存在则保存,如果需要则更新
  3. 如果用户在 Active Directory 中不存在则删除

我希望大家都知道如何开发 SharePoint 计时器作业。我不想在这里展示创建计时器作业的步骤,因为这会不必要地拉长我的文章。您可以查看这篇文章来了解如何创建计时器作业。

假设我们有一个名为“Active Directory 用户”的列表,它包含以下列。

列名 映射的 AD 属性
全称 displayName
职位 title
部门 department
UserName sAMAccountName
电子邮件 userPrincipalName
ManagerEmail 将从 manager 提取
WhenModified whenChanged

 

现在我们的目标是从 Active Directory 读取用户并将其保存到我们的列表中。当他们在 Active Directory 中发生更改时,我们也会更新/删除列表项。让我们看看如何做到这一点。

使用 C# 获取 AD 用户

我们可以非常容易地获取 AD 用户。在这种情况下,我们需要在项目中添加 System.DirectoryServices 程序集引用。通过此程序集引用,您可以执行任何操作,如创建、更新、读取和删除。请查看 msdn 上的示例代码。我假设您熟悉 AD 的关键字。为了开发此解决方案,您必须熟悉以下关键字。在 AD 中,用户通过其可分辨名称 (distinguished names) 来区分。通常,可分辨名称看起来像下面这样。它可能因容器而异,但组件始终相同。

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

CN:commonName

OU:organizationalUnitName

DC:domainComponent。您必须单独指定每个组件,例如顶级域名和二级域名。我的域名是 atishlab.com 因此,在可分辨名称中,它显示为 DC=atishlab(二级)和 DC=com(顶级)。

要从 AD 同步用户,我们首先必须选择我们的 OU(组织单位)。假设我们希望从 Junior OU 同步用户。Junior OU 的位置如下所示。

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

基本上,我们希望从 IT 部门同步我们的初级工程师。为了获取用户,我们必须定义一个路径,即我们希望从哪里搜索用户。如果我们考虑 Junior OU,我们的路径应该如下所示。

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

您可能认为构建路径非常复杂。实际上,一点也不。它非常简单。只需记住路径总是从下到上。现在您的获取用户的方法应该如下所示:

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

请花几分钟看看我的代码。首先,我创建了一个 directoryInfo 对象,它接受 SyncPathUserNamePassword 作为参数。在 SyncPath 中,您必须附加您的服务器 URL 以及 OU 路径。

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

在我的实验环境中,由于我的服务器名称是 WIN-AD,域名是 atishlab.com,路径如下:

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

我们的下一个工作是创建一个 DirectorySearcher,它有几个重载,但我使用了下面这一个。

di-over-load它接受以下参数。让我们逐一讨论。

searchRoot: 我们已经创建了它。它应该是 directoryInfo

filter: 您必须传递一个字符串。它表示您实际想要搜索什么。它可以是计算机、组织单位或用户。在我们的例子中,我们希望搜索用户。所以 filter 应该如下所示。对于其他类型的筛选器,请参阅文档。

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

propertiesToLoad:AD 用户有许多属性,您也可以添加自定义属性。在此参数中,您必须指定要在此搜索中加载哪些属性。根据我们的“Active Directory 用户”列表,我们需要以下属性。

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

scope: 您希望在哪个范围内进行搜索。它可以是 BaseLevelOneSubtree。由于我们希望从 Junior OU(包括其子 OU)中获取所有用户,因此我们选择了 SearchScope.Subtree

现在 directoryInfoSearch.FindAll() 将为我们返回所有匹配的用户,以便我们可以将它们保存到“Active Directory 用户”列表中。

通过计时器作业将 AD 用户保存到 SharePoint 列表

现在我们必须创建一个计时器作业,以便我们可以在一定时间间隔后同步我们的用户。我不会在这里展示创建计时器作业的步骤。请参阅上面提供的链接来创建计时器作业。您也可以查看我的演示解决方案。我将从我们计时器作业的 Execute 方法开始。它看起来像下面这样:

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

我将我的 syncPathsyncUserNamesyncPasswordsyncSiteUrl 保存在我的 Web 应用程序的 web.config 文件中。所以打开你的 web.config 文件,并将以下内容放在 appSettings 下。

<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>

实际上,我编写了 AdUserSyncHelper 类来同步用户。在 adUserSyncHelper.Sync() 方法中,我遍历了所有 AD 用户(在指定的 OU 中),如果用户不存在则保存,或者根据 AD 的 whenChanged 属性和“Active Directory 用户”列表的 WhenModified 列进行更新。如果我发现用户已经存在,那么我会比较 whenChangedWhenModified 来检查是否有修改。如果我看到用户存在于“Active Directory 用户”列表中,但不在 AD 中,那么我就删除了它。实际上,我开发了自己的工作流来同步用户。我希望您能找到比我更好的工作流,并与我们分享。所以我在我的 AdUserSyncHelper 类中所做的如下所示。

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;
        }
    }
}

更新 1:

本次更新的功劳归于 @PIEBALDconsult。他分享了处理大量用户和日期时间转换问题的宝贵知识。我更新了我的代码片段以解决 DateTime 问题。当您下载我的演示源代码时,请更新它。要处理大量用户,请访问以下链接。

  • 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

关注点

我希望我已经清楚地说明了我是如何做到这一点的。现在轮到您深入研究并发现更多内容了。如果您发现了新东西,请不要忘记分享,如果您遇到困难,也请告诉我。

如何将 Active Directory 用户同步到 SharePoint 列表 - CodeProject - 代码之家
© . All rights reserved.