[英]How can I change several Active Directory user passwords?
我正在尝试以编程方式更改多个用户的密码,特别是在不使用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);
“ try {” 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服务器不可用。 我已经在这里停留了几天,但我发现这可能是由于身份验证问题所致。 调用方法“ Groups”有效( userEntry.Invoke("Groups");
),并且管理员(我将用来登录ActiveDirectory的用户)具有所有特权。 同样,密码策略是完全允许的,没有最小长度或复杂性。
同样,由于程序必须迭代,因此实际程序实际上通过以下方式遍历DirectoryEntry的子级:
foreach(DirectoryEntry child in directoryEntry.Children)
{
child.Invoke("SetPassword", new object[] { newPassword });
child.CommitChanges();
}
非常感谢你!
我认为使用directoryEntry.Children.Find("CN=" + userName)
不会给您带来很多性能改进。 sAMAccountName
属性是索引属性,因此搜索非常快。 这是您可以进行的最快搜索之一。
但是请注意,您的两个代码块不相等。 Find("CN=" + userName)
试图将userName
与帐户名: cn
属性进行匹配。 但是,带有DirectorySearcher
代码块会将userName
与sAMAccountName
属性匹配。 cn
和sAMAccountName
属性不一定相同(尽管它们可能在您的域中)。
但是,如果您仍然想使用Children.Find()
,我怀疑问题可能出在您的DirectoryEntry
Path
中。 你为什么做这个?
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询问每个具有value的属性 。 如果您只想读取一个或两个属性(特别是要对成千上万个帐户执行此操作),则这是昂贵且不必要的。
解决此问题的一种方法是,在访问任何Properties
之前,告诉它仅使用RefreshCache
获取所需的Properties
。 像这样:
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]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.