强制 Azure DevOps Server 2019 手动与 ActiveDirectory 同步

Force Azure DevOps Server 2019 to manually sync with ActiveDirectory

我们有一个与企业 ActiveDirectory 配合使用的本地 Azure DevOps 服务器。添加新用户时,Azure DevOps Server 从 ActiveDirectory 中提取他们的信息。但是用户的信息后来在 ActiveDirectory 中更新以解决一个问题——他们的电子邮件帐户丢失了。

过去,我能够删除用户并将其重新添加到 Azure DevOps Server 来解决问题,因为我的管理帐户具有访问权限并且可以在 ActiveDirectory 中查看用户的电子邮件。但是用户不再被 Azure DevOps Server 中的同步作业接收,因此他们的电子邮件地址仍然是空白的。 (用户已添加数周或数月,但仍未获取更新。)

我们已验证 Azure DevOps Server 服务帐户在登录服务器时可以在 ActiveDirectory 中看到电子邮件地址。所以这不是服务帐户的访问问题。

如何手动强制 Azure DevOps Server 运行 ActiveDirectory 同步?在以前的 TFS 版本中曾经有一个 JobService Web 服务,我可以为此访问它,但该服务似乎不再可用,或者不再计划 运行.

TFS/Azure DevOps Server 使用后台同步作业,每小时安排一次,以查找 Active Directory 中的更改。因此,您对 Active Directory 组所做的更改不会立即反映在 TFS 中。相反,TFS 将定期同步这些组(默认情况下每小时)。

您可以尝试重新启动 TFS Job Agent 服务 看看是否有帮助。

由于没有任何解决方案有效,我决定从编码的角度看看可以做些什么。答案很简单。 注意:请确保在编码方法之前检查下面提供的解决方案,因为 Azure DevOps Server 应该会自动刷新身份。

首先,我找到了一篇关于按名称查找用户的 Stack Overflow 文章:

TFS get user by name

这可用于使用 ReadIdentity 方法按显示名称和其他属性获取用户或组。

同一个 IIDentityServiceProvider 也有一个名为 RefreshIdentity 的方法。当使用用户的 IdentityDescriptor 调用此方法时,会强制立即从其提供者处刷新身份。请参阅此处的文档:

https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2013/ff734945(v=vs.120)?redirectedfrom=MSDN

此方法returns如果刷新成功则为真,如果刷新失败则为假。刷新也有可能抛出异常。例如,名为 "Project Collection Build Service" 的 Azure DevOps 身份在检索时被列为用户,但此身份在刷新时会引发异常。

使用这些方法,可以构建一个完整的工具来修复单个用户的身份,或者扫描组中的所有用户 "Project Collection Valid Users" 并刷新整个系统。使用此工具,我们能够解决 Azure DevOps Server 和 Active Directory 之间的同步问题。

以下示例代码展示了如何使用这些方法:

string rootSourceControlUrl = "TODO: Root URL of Azure DevOps";
string projectCollection = "TODO: Individual project collection within Azure DevOps";    

TfsTeamProjectCollection tfsCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri($"{rootSourceControlUrl}/{projectCollection}"));
IIdentityManagementService ims = (IIdentityManagementService)tfsCollection.GetService(typeof(IIdentityManagementService));
TeamFoundationIdentity foundUser = ims.ReadIdentity(IdentitySearchFactor.DisplayName, 
                                       "TODO: Display name of user", MembershipQuery.Direct, 
                                       ReadIdentityOptions.ExtendedProperties);
if(foundUser != null)
{
    try
    {
        if (ims.RefreshIdentity(foundUser.Descriptor))
        {
            // Find the user by its original IdentityDescriptor, which shouldn't change during the refresh
            TeamFoundationIdentity refreshedUser = ims.ReadIdentity(foundUser.Descriptor, 
                          MembershipQuery.Direct, ReadIdentityOptions.ExtendedProperties);

            // TODO : Display changes from foundUser to refreshedUser, using individual properties 
            //        and the method foundUser.GetProperties(), which returns an 
            //        IEnumerable<KeyValuePair<string, object>> collection.
        }
        else
        {
             // TODO : Notify that user failed to refresh
        }
    }
    catch(Exception exc)
    {
        // TODO : Notify that exception occurred
    }
}
else
{
    // TODO : Notify that user was not found
}

我尝试了以上所有建议并且 none 有效!

最后,这段代码解决了我的问题:

update [Tfs_Configuration].dbo.tbl_Identity
set 
    AccountName = 'New Name', 
    DistinguishedName = 'CN=*New Name*, OU=..., OU= ... ,OU=... ,OU=...,OU=...,DC=...,DC=...',
    MailAddress = 'New eMail'
where *Your Condition*