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





5.00/5 (4投票s)
将 Active Directory (AD) 用户同步到 SharePoint 列表。我们将为此创建一个计时器作业。该计时器作业将定期将 AD 用户同步到 SharePoint 列表。
引言
在企业组织中,员工/用户由 Active Directory 维护。有时,他们可能需要将所有员工/用户同步到 SharePoint 列表。比如说,您希望开发一个像“了解你的同事”这样的 Web 部件。在此 Web 部件中,员工/用户将按部门(IT、市场营销等)显示。也许您的 Active Directory 管理员已经在(按部门)维护这些信息。现在您的管理层要求您在 SharePoint 中显示这些员工/用户。最简单的解决方案可能是“将所有用户导出为 csv 文件,然后将其导入到列表中”。但是,您必须每天执行此操作以保持所有内容同步(用户可能会被删除或更新)。
我们需要一个自动化的解决方案来为我们完成所有这些任务。目前,SharePoint 没有提供任何内置功能。因此,本文旨在向大家展示我们如何实现它。
解决方案
为了解决这个问题,我将开发一个计时器作业,它会定期为我们执行以下任务。
- 从 Active Directory 获取您需要的用户
- 如果用户不存在则保存,如果需要则更新
- 如果用户在 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
对象,它接受 SyncPath
、UserName
和 Password
作为参数。在 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
,它有几个重载,但我使用了下面这一个。
它接受以下参数。让我们逐一讨论。
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: 您希望在哪个范围内进行搜索。它可以是 Base
、LevelOne
或 Subtree
。由于我们希望从 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);
}
我将我的 syncPath
、syncUserName
、syncPassword
、syncSiteUrl
保存在我的 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
列进行更新。如果我发现用户已经存在,那么我会比较 whenChanged
和 WhenModified
来检查是否有修改。如果我看到用户存在于“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
关注点
我希望我已经清楚地说明了我是如何做到这一点的。现在轮到您深入研究并发现更多内容了。如果您发现了新东西,请不要忘记分享,如果您遇到困难,也请告诉我。