我正在尝试以编程方式更改多个用户的密码,特别是不使用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();
}
非常感谢您!
答案 0 :(得分:0)
我认为使用directoryEntry.Children.Find("CN=" + userName)
不会给您带来很多性能改进。 sAMAccountName
属性是一个索引属性,因此搜索非常快。这是您可以进行的最快的搜索之一。
但是请注意,您的两个代码块不相等。 Find("CN=" + userName)
试图将userName
与帐户名:cn
属性进行匹配。但是您的带有DirectorySearcher
的代码块会将userName
与sAMAccountName
属性进行匹配。 cn
和sAMAccountName
属性不一定相同(尽管它们可能在您的域中)。
但是,如果您仍然想使用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