【问题标题】:How can I change several Active Directory user passwords?如何更改多个 Active Directory 用户密码?
【发布时间】:2018-11-06 00:28:17
【问题描述】:

我正在尝试以编程方式更改多个用户的密码,特别是不使用 System.DirectoryServices.AccountManagement (PrincipalContext) 我有这段工作代码:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.DirectoryServices;

namespace ADScriptService.core
{
    class ExportTool
    {
        const AuthenticationTypes = AuthenticationTypes.Secure | AuthenticationTypes.Sealing | AuthenticationTypes.ServerBind;
        private static DirectoryEntry directoryEntry = new DirectoryEntry(ADScriptService.Properties.Settings.Default.ActiveDirectoryPath, ADScriptService.Properties.Settings.Default.ServerAdminUser, ADScriptService.Properties.Settings.Default.ServerAdminPwd, AuthenticationTypes.Secure);
        private static DirectorySearcher search = new DirectorySearcher(directoryEntry);
    public void Export()
    {

        string path = ADScriptService.Properties.Settings.Default.ActiveDirectoryPath;
        string adminUser = ADScriptService.Properties.Settings.Default.ServerAdminUser;
        string adminPassword = ADScriptService.Properties.Settings.Default.ServerAdminPwd;

        string userName = "exampleUser";
        string newPassword = "P455w0rd";
        try
        {
            search.Filter = String.Format("sAMAccountName={0}", userName);
            search.SearchScope = SearchScope.Subtree;
            search.CacheResults = false;
            SearchResult searchResult = search.FindOne();
            if (searchResult == null) Console.WriteLine("User Not Found In This Domain");
            DirectoryEntry userEntry = searchResult.GetDirectoryEntry();

            userEntry.Path = userEntry.Path.Replace(":389", "");
            Console.WriteLine(String.Format("sAMAccountName={0}, User={1}, path={2}", userEntry.Properties["sAMAccountName"].Value, userEntry.Username, userEntry.Path));
            userEntry.Invoke("SetPassword", new object[] { newPassword });
            userEntry.Properties["userAccountControl"].Value = 0x0200 | 0x10000;
            userEntry.CommitChanges();
            Console.WriteLine("Se ha cambiado la contraseña");


        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}
}

这是一个使用单个用户的示例,但我的程序应该做的是遍历约 120k 用户。 但是,设置搜索过滤器、查找一个结果并获取 DirectoryEntry 的操作每个用户大约需要 2 或 3 秒,因此我尝试使用 DirectoryEntry.Children 属性给出的 DirectoryEntries 结构,这意味着替换后面的六行“尝试{”只需 DirectoryEntry userentry = directoryEntry.Children.Find("CN=" + userName);

所以示例代码如下所示:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.DirectoryServices;

namespace ADScriptService.core
{
    class ExportTool
    {
        const AuthenticationTypes = AuthenticationTypes.Secure | AuthenticationTypes.Sealing | AuthenticationTypes.ServerBind;
        private static DirectoryEntry directoryEntry = new DirectoryEntry(ADScriptService.Properties.Settings.Default.ActiveDirectoryPath, ADScriptService.Properties.Settings.Default.ServerAdminUser, ADScriptService.Properties.Settings.Default.ServerAdminPwd, AuthenticationTypes.Secure);
        private static DirectorySearcher search = new DirectorySearcher(directoryEntry);
    public void Export()
    {

        string path = ADScriptService.Properties.Settings.Default.ActiveDirectoryPath;
        string adminUser = ADScriptService.Properties.Settings.Default.ServerAdminUser;
        string adminPassword = ADScriptService.Properties.Settings.Default.ServerAdminPwd;

        string userName = "exampleUser";
        string newPassword = "P455w0rd";
        try
        {
            DirectoryEntry userEntry = directoryEntry.Children.Find("CN=" + userName);

            userEntry.Path = userEntry.Path.Replace(":389", "");
            Console.WriteLine(String.Format("sAMAccountName={0}, User={1}, path={2}", userEntry.Properties["sAMAccountName"].Value, userEntry.Username, userEntry.Path));
            userEntry.Invoke("SetPassword", new object[] { newPassword });
            userEntry.Properties["userAccountControl"].Value = 0x0200 | 0x10000;
            userEntry.CommitChanges();
            Console.WriteLine("Se ha cambiado la contraseña");


        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}
}

但此代码在调用行(userEntry.Invoke("SetPassword", new object[] { newPassword }); 中出现以下错误:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Runtime.InteropServices.COMException: The RPC server is unavailable. (Excepción de HRESULT: 0x800706BA)

英文表示RCP服务器不可用。我在这里被困了几天,我只发现这可能是因为一些身份验证问题。 调用“组”方法有效(userEntry.Invoke("Groups");),管理员,即我登录到 ActiveDirectory 的用户,拥有所有权限。此外,密码策略是完全宽松的,没有最小长度或复杂性。

同样,因为程序必须遍历,所以真正的程序实际上会遍历 DirectoryEntry 的子项:

foreach(DirectoryEntry child in directoryEntry.Children) 
{
    child.Invoke("SetPassword", new object[] { newPassword });
    child.CommitChanges();
}

非常感谢!

【问题讨论】:

  • 请将异常信息翻译成英文...
  • 完成,我已经翻译好了

标签: c# .net active-directory directoryservices


【解决方案1】:

我不认为使用directoryEntry.Children.Find("CN=" + userName) 会给你带来很大的性能提升。 sAMAccountName 属性是索引属性,因此搜索速度非常快。这是您可以进行的最快搜索之一。

但请注意,您的两个代码块并不相等。 Find("CN=" + userName) 正在尝试将 userName 与帐户名称匹配:cn 属性。但是您的带有DirectorySearcher 的代码块将userNamesAMAccountName 属性匹配。 cnsAMAccountName 属性不一定相同(尽管它们可能在您的域中)。

但是,如果你还想使用Children.Find(),我怀疑问题可能出在你的Path 中的DirectoryEntry 上。你为什么要这样做?

userEntry.Path = userEntry.Path.Replace(":389", "");

你的ADScriptService.Properties.Settings.Default.ActiveDirectoryPath:389 吗?如果以LDAP://开头则不需要(默认LDAP端口为389)。

您的userEntry.Path 应该类似于(取决于您的域)LDAP://CN=user,OU=Users,DC=domain,DC=com。如果不是,那么您需要修复它。

附注:除了更改搜索之外,您还可以采取一些措施来加快速度。 Properties 集合使用缓存。当您访问一个属性时,它会检查它是否已经在缓存中,如果是,则使用缓存。但是,如果该属性不在缓存中,那么它将向 Active Directory 询问 每个具有值的属性。如果您只想读取一两个属性(尤其是在为数千个帐户执行此操作时),那么这既昂贵又不必要。

解决此问题的一种方法是告诉它在访问任何Properties 之前使用RefreshCache 仅获取您想要的属性。像这样:

userEntry.RefreshCache(new [] { "sAMAccountName", "userAccountControl" });

然后,当您访问这些属性时,它已经将它们保存在缓存中,并且不会与 AD 联系以获取任何内容。

另外,如果您在数千个帐户的大循环中运行此程序,那么我建议您将 DirectoryEntry 放在 using 语句中(或在完成后调用 userEntry.Dispose())。通常你不需要,因为垃圾收集非常擅长清理它们。但是因为你正在运行一个大循环,垃圾收集器没有机会进行任何清理,所以你的进程最终会占用越来越多的内存,直到循环最终停止。在我开始处理未使用的 DirectoryEntry 对象之前,我有过这样的大工作需要几 GB 的内存。

更新:实际上,忘记我上面提到的DirectoryEntry。您实际上根本不需要使用DirectoryEntry。不要使用searchResult.GetDirectoryEntry()。这里的问题是您已经进行了搜索以找到该帐户。但是现在您正在创建一个DirectoryEntry,它将再次调用 AD 以获取您已有的信息。这可能是您的主要性能受到影响的地方。

改为使用搜索返回的属性。现在,就像DirectoryEntry.Properties 一样,如果您没有指定要在搜索中返回哪些属性,那么它将返回所有这些属性,而您不一定需要这些属性。所以你应该设置PropertiesToLoad集合,像这样:

search.PropertiesToLoad.AddRange(new [] { "sAMAccountName", "userAccountControl" });

然后,搜索完之后,就可以用这个来获取你找到的账号的sAMAccountName

searchResult.Properties["sAMAccountName"][0]

【讨论】:

  • 非常感谢您的回答。实际上这两个搜索并不相等,但是它们找到了相同的 DirectoryEntry 对象(在这种情况下 sAMAccountName 和 CN 相同)。我现在尝试了using (DirectoryEntry directoryEntry = new DirectoryEntry(...),然后使用由 SamAccountName 过滤的 DirectorySearcher 执行搜索,但仍然收到错误“RCP 服务器不可用”
  • ADScriptService.Properties.Settings.Default.ActiveDirectoryPath 设置为什么?它应该类似于LDAP://domain.com
  • 设置为LDAP://192.168.13.99:389/CN=Users,DC=ejemplo,DC=com
  • 您使用 IP 地址而不是您的域名是否有原因?你应该可以使用LDAP://ejemplo.com/CN=Users,DC=ejemplo,DC=com
  • 这是一个远程域,尝试使用它返回:El servidor no es funcional.,翻译为“服务器不工作”。
猜你喜欢
  • 2012-06-26
  • 2013-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-02
  • 1970-01-01
  • 2016-10-17
  • 1970-01-01
相关资源
最近更新 更多