如何更改几个Active Directory用户密码?

时间:2018-11-06 00:28:18

标签: c# .net active-directory directoryservices

我正在尝试以编程方式更改多个用户的密码,特别是不使用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();
}

非常感谢您!

1 个答案:

答案 0 :(得分:0)

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

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

但是,如果您仍然想使用Children.Find(),我怀疑问题可能出在您的Path的{​​{1}}中。你为什么要这样做?

DirectoryEntry

您的userEntry.Path = userEntry.Path.Replace(":389", ""); ADScriptService.Properties.Settings.Default.ActiveDirectoryPath吗?不必以:389开头(默认LDAP端口为389)。

您的LDAP://应该看起来像(取决于您的域)userEntry.Path。如果不是,那么您需要修复它。

附注:除了更改搜索之外,您还可以采取其他措施来加快速度。 LDAP://CN=user,OU=Users,DC=domain,DC=com集合使用缓存。当您访问属性时,它将检查该属性是否已在缓存中,如果已使用,则使用该缓存。但是,如果该属性不在缓存中,则它将向Active Directory询问每个具有值的属性。如果您只想读取一个或两个属性(特别是要对成千上万的帐户进行此操作),则这是昂贵且不必要的操作。

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

Properties

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

此外,如果您正在成千上万个帐户中进行大循环运行,那么我建议您将userEntry.RefreshCache(new [] { "sAMAccountName", "userAccountControl" }); 放在DirectoryEntry语句中(或在您进入时调用using完成)。通常您不需要这样做,因为垃圾回收非常擅长清理它们。但是,由于您正在运行一个大循环,因此垃圾收集器没有机会进行任何清理,因此您的过程最终可能会占用越来越多的内存,直到循环最终停止为止。在进行开始处理未使用的userEntry.Dispose()对象之前,我曾经历过这样的大工作,需要占用数GB的内存。

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

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

PropertiesToLoad

然后,在搜索之后,您可以使用它来获取找到的帐户的search.PropertiesToLoad.AddRange(new [] { "sAMAccountName", "userAccountControl" });

sAMAccountName