批量更新 SharePoint Online 的自定义用户配置文件属性

作为客户端对象模型 (CSOM) 版本(4622.1208 或更高版本)的一部分,SharePoint 具有用于批量导入自定义用户配置文件属性的功能。 对于此版本之前的版本,唯一的选择是利用用户配置文件 CSOM 操作来为个人用户配置文件更新特定属性。 但是,这种方法效率低下且耗时(特别是在处理数以千计的配置文件时)。

许多企业需要将自定义属性复制到 SharePoint User Profile Service,因此发布了性能更佳的用户配置文件批量 API,即 UserProfile.BatchUpdate.API

批量用户配置文件更新过程概述

批量 UPA 更新流

以下列表介绍了批量用户配置文件更新过程流:

  1. 用户属性从公司 Active Directory 同步到 Azure Active Directory (Azure AD)。 你可以选择跨本地和 Azure 复制哪些属性。
  2. 标准化的属性集从 Azure AD 复制到 Office 365 中的 SharePoint Online User Profile 存储。 不同于本地 SharePoint,无法自定义这些属性。
  3. 自定义同步工具利用批量更新 API。 此工具可将 JSON 文件上载到 Office 365 租户,并将导入过程排入队列。 使用托管代码 (.NET) 实现此工具,或者使用 CSOM API 将其作为 PowerShell 脚本实现。
  4. 业务线 (LOB) 系统或任何外部系统,这是 JSON 文件中的信息源。 这也可以是来自 Active Directory 的数据和来自任何外部系统的数据的组合。 请注意,从 API 的角度来看,LOB 系统甚至可能是本地 SharePoint 场。
  5. 在 SharePoint Online 中运行的开箱即用服务器端计时器作业,它可检查排队的导入请求,并将基于 API 调用和提供的文件中的信息执行实际导入操作。
  6. 扩展的用户配置文件信息也适用于用户配置文件,可用于 SharePoint Online 中的任何开箱即用或自定义功能。

注意

只能导入最终用户尚未设置为可编辑的用户配置文件属性。 这是为了避免用户配置文件导入过程重写最终用户已更新的任何信息。 此外,导入仅允许非 Active Directory 核心属性的自定义属性。 这些属性必须同步到 Azure AD。 有关这些核心目录属性的列表,请参阅本文后面的“常见问题解答”部分列出的表格。

以下是一个简短的视频,演示了如何使用来自托管代码 (.NET) 和 PowerShell 的 CSOM API UserProfile.BatchUpdate.API

用户配置文件属性导入过程

下面是完整的过程:

  1. 创建或将用户同步到 Office 365 租户或与之关联的 Azure AD。
    • 当用户同步到 Azure AD 时,它还会将标准化的属性集同步到 SharePoint Online User Profile Service。
  2. 在 User Profile Service 中创建所需的任何自定义属性。
    • 由于没有任何远程 API 向 User Profile Service 创建自定义属性,必须为需要自定义用户配置文件属性的每个租户手动执行此步骤(对每个租户只需执行一次)。
    • 只能导入不“允许最终用户编辑”的用户配置文件属性。 调用 CSOM API 时,尝试将 JSON 对象属性导入到标记为“最终用户可编辑”的用户配置文件属性将导致异常。
  3. 创建 JSON 文件并将其上载到 Office 365 租户。
    • 你必须将包含要更新的信息的 JSON 数据文件上载到 Office 365 租户。
    • 为防止在导入过程中出现任何异常,SharePoint 将提供保存在同一文档库中的其他日志记录信息,其中文件放在新的子文件夹中。
    • 系统不会自动清除日志文件和 JSON 文件,该操作由使用 API 的自定义解决方案负责执行。 在实施中,应考虑这些文件的生命周期。 这些文件存储在文档库中,因此它们将占用一部分分配给网站集的存储空间。
  4. 调用批量 UPA 导入 API 以将导入作业排入队列。
    • 使用 CSOM API 将导入过程排入队列。 这可以通过使用托管代码 (.NET) 或 PowerShell 执行 CSOM 代码来实现。
    • 用于将作业排入队列的方法将需要属性映射信息和数据文件的位置。 此方法将快速执行,因为它是将实际导入过程排入队列,稍后它将在 SharePoint Online 中作为后端进程的一部分执行。
  5. 检查导入作业的状态
    • 你还可以使用远程 API 来检查特定导入作业或所有最近导入作业的状态。 若要能够查看特定调用的状态,应存储将作业排入队列时作为返回值收到的唯一作业标识符。

创建并上载 JSON 文件

导入过程使用包含属性及其值的 JSON 文件。 下面是该文件的预期结构:

{
   "value": [
     {
       "<IdName>": "<UserIdValue_1>",
       "<AttributeName_1>": "<User1_AttributeValue_1>",
       "<AttributeName_2>": "<User1_AttributeValue_2>",
     },
     {
       "<IdName>": "<UserIdValue_2>",
       "<AttributeName_1>": "<User2_AttributeValue_1>",
       "<AttributeName_2>": "<User2_AttributeValue_2>",
     },
     {
       "<IdName>": "<UserIdValue_n>",
       "<AttributeName_1>": "<Usern_AttributeValue_1>",
       "<AttributeName_2>": "<Usern_AttributeValue_2>",
     }
   ]
}

示例

以下是使用上面的示例的一个简单示例文件。 在上例中,标识解析将基于 IdName 属性,我们有两个名为 CityOffice 的待更新属性。 文件包含租户中四个不同帐户的信息。 在源文件中使用的属性名称并不一定要与在 SharePoint Online User Profile Service 中使用的名称相同,因为我们将在代码中提供正确的属性映射。 

{
  "value": [
    {
      "IdName": "vesaj@contoso.com",
      "City": "Helsinki",
      "Office": "Viper"
    },
    {
      "IdName": "bjansen@contoso.com",
      "City": "Brussels",
      "Office": "Beetle"
    },
    {
      "IdName": "unknowperson@contoso.com",
      "City": "None",
      "Office": ""
    },
    {
      "IdName": "erwin@contoso.com",
      "City": "Stockholm",
      "Office": "Elite"
    }
  ]
}

源数据文件限制

以下是对单个源数据文件的限制:

  • 最大文件大小:2 GB
  • 最大属性数量:500,000
  • 属性值中的反斜杠 () 需要通过附加另一个反斜杠进行转义
  • 源文件必须上载到启动该过程的同一个 SharePoint Online 租户

注意

如果任意字符不是默认西欧编码 (iso-8859-1) 的一部分,则必须指定更新文件的字节顺序标记 (BOM)。 使用默认编码可能无法正确解释国际字符,因此建议使用 UTF-8 编码。 此编码的十六进制 BOM 为“EF BB BF”。 在创建文件流时,这将放在文件字节数组的开头。 如果使用文本编辑器,请在保存之前选择 UTF-8 作为编码类型。

将导入过程排入队列

你可以通过调用位于 Office365Tenant 对象中 QueueImportProfileProperties,将 CSOM API 排入队列以进行批量导入过程。 这是异步调用,不会下载源数据或执行导入;只是将工作项目添加到队列以便稍后完成此操作。

下面是此方法的完整签名:

public ClientResult<Guid> QueueImportProfileProperties(
                          ImportProfilePropertiesUserIdType idType,
                          string sourceDataIdProperty,
                          IDictionary<string, string> propertyMap,
                          string sourceUri);

参数

  • idTypeImportProfilePropertiesUserIdType

    查找用户配置文件时要使用的 ID 类型。 可能的值为“电子邮件”、“CloudId”和“PrincipalName”。 请注意,无论类型如何,用户都必须已存在于 User Profile Service 中,这样导入才有效。 建议使用“CloudId”值以确保唯一性。

    ID 类型和 Azure AD 属性之间的属性映射:

    UPA 批量导入 ID 类型 Azure AD 属性
    CloudId ObjectID
    PrincipalName userPrincipalName
    电子邮件 mail
  • sourceDataIdProperty:System.String

    源数据中 ID 属性的名称。 源数据中的属性值将用于查找用户。 用于查找的 User Profile Service 属性取决于“idType”的值。

  • propertyMap:IDictionary<string, string>

    从源属性名称到 User Profile Service 属性名称的映射。 请注意,User Profile Service 属性必须已存在。 密钥是源文件中使用的属性名称,值是 User Profile service 中使用的属性名称。

  • sourceUri:System.String

    要导入的源数据文件的 URI。 该文件不应该立即移动或删除,因为它在某些时间内可能无法下载。

返回值

用于标识已排入队列的导入作业的 GUID。

示例

下面是通过使用上一个示例文件使用 C# 开始此过程的示例:

// Create an instance of the Office 365 Tenant object. Loading this object is not technically needed for this operation.
Office365Tenant tenant = new Office365Tenant(ctx);
ctx.Load(tenant);
ctx.ExecuteQuery();

// Type of user identifier ["PrincipalName", "Email", "CloudId"] in the
// user profile service. In this case, we use Email as the identifier at the UPA storage
ImportProfilePropertiesUserIdType userIdType =
      ImportProfilePropertiesUserIdType.Email;

// Name of the user identifier property within the JSON file
var userLookupKey = "IdName";

var propertyMap = new System.Collections.Generic.Dictionary<string, string>();

// The key is the property in the JSON file
// The value is the user profile property Name in the user profile service
// Notice that we have 2 custom properties in UPA called 'City' and 'OfficeCode'
propertyMap.Add("City", "City");
propertyMap.Add("Office", "OfficeCode");

// Returns a GUID that can be used to check the status of the execution and the end results
var workItemId = tenant.QueueImportProfileProperties(
      userIdType, userLookupKey, propertyMap, fileUrl
      );

ctx.ExecuteQuery();

检查导入作业的状态

你可以通过使用 CSOM API 来检查 User Profile Service 导入作业的状态。 在租户对象中,有两个方法可用。

  • 若要检查单个导入作业的状态,请使用 GetImportProfilePropertyJob 方法。
  • 若要检查所有导入作业的状态,请使用 GetImportProfilePropertyJobs 方法。

单个导入作业

通过使用位于Office365Tenant 对象的 GetImportProfilePropertyJob 方法,可以检查单个导入作业的状态。 你必须具有作为此方法的参数提供的特定导入作业的唯一标识符。

下面是此方法的完整签名:

public ImportProfilePropertiesJobInfo GetImportProfilePropertyJob(Guid jobId);

参数

  • jobID:System.Guid

    要获取其高级别状态的作业的 ID。

返回值

具有指定作业的高级别状态信息的 ImportProfilePropertiesJobStatus 对象。

示例

以下是使用存储的标识符检索特定导入作业的状态的 C# 使用示例:

// Check the status of a specific request based on the job id received when we queued the job
Office365Tenant tenant = new Office365Tenant(ctx);
var job = tenant.GetImportProfilePropertyJob(workItemId);
ctx.Load(job);
ctx.ExecuteQuery();

所有导入作业

通过使用位于 Office365Tenant 对象的 GetImportProfilePropertyJobs 方法,可以检查所有导入作业的状态。

下面是此方法的完整签名:

public ImportProfilePropertiesJobStatusCollection GetImportProfilePropertyJobs(); 

参数

返回有导入状态信息的 ImportProfilePropertiesJobInfo 对象具有以下属性。 

  • JobId:System.Guid

    导入作业的 ID。

  • 状态ImportProfilePropertiesJobState

    具有以下值的枚举:

    • 未知 - 无法确定作业的状态。
    • 已提交 - 作业已提交到系统。
    • 正在处理 - 正在处理作业。
    • 已排入队列 - 作业已通过验证并排队等待导入到 UPA。
    • 已成功 - 作业已完成,无错误。
    • 出错 - 作业已完成,有错误。
  • SourceUri:System.String

    数据源文件的 URI。

  • 错误ImportProfilePropertiesJobError

    代表可能的错误的枚举:

    • NoError - 未发现错误。
    • InternalError - 服务中故障导致的错误。
    • DataFileNotExist - 找不到数据源文件。
    • DataFileNotInTenant - 数据源文件不属于同一个租户。
    • DataFileTooBig - 数据文件过大。
    • InvalidDataFile - 该数据源文件未通过验证(日志文件中可能存在其他详细信息)。
    • ImportCompleteWithError - 已导入数据,但出现错误。
  • ErrorMessage:System.String

    错误消息。

  • LogFileUri:System.String

    已在其中写入日志的文件夹的 URI。

返回值

ImportProfilePropertiesJobStatusCollection 对象是每个作业的高级别状态信息的 ImportProfilePropertiesJobStatus 对象的集合。

示例

以下是使用 C# 获取当前保存在租户中的所有导入作业状态的示例。 这些可能是已处理或已排入队列的作业:

// Load all import jobs – old and queued ones
Office365Tenant tenant = new Office365Tenant(ctx);
var jobs = tenant.GetImportProfilePropertyJobs();
ctx.Load(jobs);
ctx.ExecuteQuery();
foreach (var item in jobs)
{
   // Check whatever properties needed
   var state = item.State;
}

从 PowerShell 调用导入 API

你可以通过 PowerShell 利用 User Profile Service 批量导入 API。 这意味着你将在使用必要参数的 PowerShell 脚本中直接使用 CSOM 代码。 这要求将更新后的 CSOM 可再发行包安装在执行脚本的计算机上。

通过使用 PowerShell,无需在 Visual Studio 中编译代码,这可能是更适合某些客户的模型。

或者,你可以使用 PnP PowerShell,这将允许你使用用户配置文件服务批量导入 API,而无需在系统上安装任何其他库。

使用 CSOM 库的 PowerShell 脚本示例

下面是一个示例 PowerShell 脚本,它执行与上一个代码相同的操作:

# Get needed information from the end user
$adminUrl = Read-Host -Prompt 'Enter the admin URL of your tenant'
$userName = Read-Host -Prompt 'Enter your user name'
$pwd = Read-Host -Prompt 'Enter your password' -AsSecureString
$importFileUrl = Read-Host -Prompt 'Enter the URL to the file located in your tenant'

# Get instances to the Office 365 tenant using CSOM
$uri = New-Object System.Uri -ArgumentList $adminUrl
$context = New-Object Microsoft.SharePoint.Client.ClientContext($uri)

$context.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($userName, $pwd)
$o365 = New-Object Microsoft.Online.SharePoint.TenantManagement.Office365Tenant($context)
$context.Load($o365)

# Type of user identifier ["Email", "CloudId", "PrincipalName"] in the user profile service
$userIdType=[Microsoft.Online.SharePoint.TenantManagement.ImportProfilePropertiesUserIdType]::Email

# Name of user identifier property in the JSON
$userLookupKey="idName"

# Create property mapping between the source file property name and the Office 365 property name
# Notice that we have here 2 custom properties in UPA called 'City' and 'OfficeCode'
$propertyMap = New-Object -type 'System.Collections.Generic.Dictionary[String,String]'
$propertyMap.Add("City", "City")
$propertyMap.Add("Office", "OfficeCode")

# Call to queue UPA property import
$workItemId = $o365.QueueImportProfileProperties($userIdType, $userLookupKey, $propertyMap, $importFileUrl);

# Execute the CSOM command for queuing the import job
$context.ExecuteQuery();

# Output the unique identifier of the job
Write-Host "Import job created with the following identifier:" $workItemId.Value

使用 PnP PowerShell 更新 SharePoint Online 用户配置文件

若要将用户配置文件属性从 Azure Active Directory 同步到 SharePoint Online 用户配置文件,可以利用 PnP PowerShell cmdlet Sync-PnPSharePointUserProfilesFromAzureActiveDirectory

或者,如果要从其他源同步用户配置文件属性,则可以使用 New-PnPUPABulkImportJob

若要检查批量导入作业的处理状态,可以使用 Get-PnPUPABulkImportStatus

注意

PnP PowerShell 是一种开放源代码解决方案,其中包含为其提供支持的活动社区。 没有用于 Microsoft 开放源代码工具支持的 SLA。

处理异常

使用此 API 时,存在两个级别的验证。 使用 CSOM 将导入过程排入队列时,将对所提供的值进行初始级别验证。 这包括确认所提供的映射属性存在于 User Profile Service 中,并且最终用户不能编辑这些属性。 调用队列 API 时,仅应用验证的初始级别,实际执行导入作业时,对所提供的信息执行最终验证。

如果实际导入作业执行期间没有任何异常,具有其他详细信息的日志文件会生成到导入文件所在的同一个文档库。 特定导入作业的日志文件将保存到使用特定导入作业唯一标识符命名的子文件夹。

以下是运行导入作业的结果示例。 在下图中,可以看到在存储有导入文件的文档库上创建的两个不同执行文件的两个子文件夹。

作业异常子文件夹

实际日志文件保存在子文件夹中,你可以从 Office 365 下载以进行详细分析。

作业异常日志文件

常见异常

下表包含开始使用 User Profile Service 批量 API 时可能会遇到的典型异常。

异常示例 详细信息
用户可编辑属性名 [AboutMe]。 调用 ExecuteQuery 方法以向租户提交作业时,CSOM API 会引发此异常。 API 将验证用户不可编辑当前要映射的所有属性。 异常会指出不能使用的属性。

在本示例中,我们尝试在 User Profile Service 属性中将 JSON 属性映射到 AboutMe 属性,但不允许这样做,因为 AboutMe 是用户可编辑的属性。
InvalidProperty - vesaj@contoso.com 属性“AboutMe”未映射到用户配置文件应用程序中的任何属性。 在 SharePoint Online 中,JSON 数据文件包含尚未映射到用户配置文件服务属性的属性。 这意味着源数据文件包含你未在 propertyMap 参数中为其提供映射的属性。 必须具有 JSON 数据对象中每个属性的映射定义。
MissingIdentity - 用户对象缺少标识 在用户对象中找不到标识属性。 最可能的原因是为 QueueImportProfileProperties 方法错误地设置了 sourceDataIdProperty 属性。 确保 JSON 源文件中具有正确属性,并且相应地为此属性分配了代码/脚本。
无法解析 IdentityNotResolvable unknown@contoso.com 用户标识 包含标识的数据文件无法进行解析或不存在于用户配置文件服务中。 在这种情况下,电子邮件为 的 unknown@contoso.com 用户配置文件无法在用户配置文件服务中找到。
DataFileNotJson - JsonToken EndObject 对于关闭 JsonType 数组无效。 路径“value”,第 8 行,位置 10。 导入文件格式不是有效的 JSON 且与预期格式不匹配。 

常见问题

我是否可以使用仅应用/仅外接程序权限执行代码?

可以,不会通过调用方的标识同步执行文件的实际导入,因此,此操作适用于仅应用上下文,而不会出现任何问题。

若要将仅应用上下文与 SharePoint 加载项模型配合使用,需要注册一个客户端 ID 和密码,以便能够按照本指南执行 API。 此外,注册 SharePoint 加载项时,必须使用以下 XML 代码段授予权限:

<AppPermissionRequests AllowAppOnlyPolicy="true">
  <AppPermissionRequest Scope="http://sharepoint/content/tenant" Right="FullControl" />
  <AppPermissionRequest Scope="http://sharepoint/social/tenant" Right="FullControl" />
</AppPermissionRequests>

重要

自 2023 年 11 月 27 日起,已停用 Azure ACS (访问控制 Services for SharePoint Online) ,请查看完整的停用公告以了解详细信息。 在 SharePoint 上下文之外使用 Azure ACS 已于 2018 年 11 月 7 日停用,现已停用。

停用意味着该功能不会获得任何新投资,但仍受支持。 生命周期结束意味着该功能将停用,不再可供使用。

若要将仅应用与 Azure Active Directory 中注册的应用程序结合使用,你需要注册应用程序提供一个 X.509 证书用于身份验证(这是 Azure Active Directory 中 SharePoint Online 仅应用身份验证的要求),并授予以下针对应用程序的 SharePoint Online 权限范围:Sites.FullControl.All and User.ReadWrite.All。

此 API 正在更新 User Profile Service 中的属性,但如何在租户中创建这些属性?

没有能以编程方式创建自定义用户配置文件属性的远程 API,因此这是人工操作,需要根据每个给定租户完成一次此操作。 有关如何创建这些自定义属性的说明,请参阅在 SharePoint Online 中添加和编辑用户配置文件属性

本地 SharePoint 中提供此功能吗?

非常抱歉,目前仅在 SharePoint Online 中提供此功能。 在本地 SharePoint 中,此功能有用,但并不重要,因为可以在本地 User Profile Service 应用程序中修改属性映射。 还可以通过 SharePoint 2013 中的业务连接服务 (BCS) 来利用导入用户配置文件属性。 但是,无法在 SharePoint 2016 中这样做。也就是说,对于 SharePoint 2016,目前只能实现利用用户配置文件 Web 服务的自定义。

我可以使用此 API 将用户配置文件属性值从本地 SharePoint 场同步到 SharePoint Online 吗?

是的,可以像使用任何其他源系统一样使用本地 SharePoint。 你必须将用户配置文件值从本地 SharePoint 导出到 JSON 文件格式,过程与从任何其他系统中导入值是完全相同的。

我可以导入基于字符串的多值属性吗?

不可以。此 API 暂不支持这样做。

必须拥有什么权限才能执行此 API?

除非使用仅应用身份验证上下文,否则当前必须具有全局管理员权限。 SharePoint 管理员权限是不够的。

我可以导入基于分类的属性吗?

不可以。此 API 暂不支持这样做。

如果我在代码中定义不使用的映射或 JSON 文件中具有未映射的属性,会怎么样?

如果代码/脚本定义不使用的映射或数据文件中不包含相应映射的属性,执行将继续且不会出现异常,将根据映射的属性进行导入。 但是,如果 JSON 文件中具有未映射的属性,导入过程将中止,日志文件中将提供有关特定作业异常的异常详细信息。

如果我需要更新的自定义属性大小超出此批量 API 的大小限制(即大于 2 GB 文件或多于 500,000 个属性),会怎么样?

你必须通过依次触发多个作业,相应地对作业进行批处理(即,根据此 API 的最大限制,一次完成一个作业)。 你应该预料得到这些高带宽的导入将需要很长的时间才能完成。 此外,应只对自定义配置文件属性中的增量更改优化导入作业,而不是导入所有作业中的整套值。

默认情况下,哪些 Azure AD 属性将同步到 SharePoint Online 用户配置文件?

有关同步属性及其在 Azure AD 和 SharePoint Online User Profile Service 之间的映射的官方列表,请查看下表。

Azure 目录属性 SharePoint Online 配置文件属性
ObjectSid SPS-SavedSID
msonline-UserPrincipalName UserName
msonline-UserPrincipalName AccountName
msonline-UserPrincipalName SPS-ClaimID
msonline-UserPrincipalName SPS-UserPrincipalName
GivenName FirstName
sn LastName
Manager Manager
DisplayName PreferredName
telephoneNumber WorkPhone
proxyAddresses WorkEmail
proxyAddresses SPS-SIPAddress
PhysicalDeliveryOfficeName Office
标题 标题
Title SPS-JobTitle
Department Department
Department SPS-Department
ObjectGuid ADGuid
WWWHomePage PublicSiteRedirect
DistinguishedName SPS-DistinguishedName
msOnline-ObjectId msOnline-ObjectId
PreferredLanguage SPS-MUILanguages
msExchHideFromAddressList SPS-HideFromAddressLists
msExchRecipientTypeDetails SPS-RecipientTypeDetails
msonline-groupType IsUnifiedGroup
msOnline-IsPublic IsPublic
msOnline-ObjectId msOnline-ObjectId
msOnline-UserType SPS-UserType
GroupType GroupType
SPO-IsSharePointOnlineObject SPO-IsSPO

另请参阅