[英]How can I change several Active Directory user passwords?
I'm trying to programaticaly change several user's password, specifically without using System.DirectoryServices.AccountManagement (PrincipalContext) I have this piece of working code: 我正在尝试以编程方式更改多个用户的密码,特别是在不使用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);
}
}
}
}
This is an example with a single user, but what my program should do is iterate through ~120k users. 这是一个只有一个用户的示例,但是我的程序应该做的是遍历约120k个用户。 However, the operation of setting the search filter, finding one result and getting the DirectoryEntry takes about 2 or 3 seconds per user so I'm trying to use the DirectoryEntries structure given by the DirectoryEntry.Children property, which means replacing the six lines after "try{" with simply
DirectoryEntry userentry = directoryEntry.Children.Find("CN=" + userName);
但是,设置搜索过滤器,查找一个结果并获取DirectoryEntry的操作每个用户大约需要2到3秒钟,因此我试图使用DirectoryEntry.Children属性提供的DirectoryEntries结构,这意味着在之后替换六行仅使用
DirectoryEntry userentry = directoryEntry.Children.Find("CN=" + userName);
“ try {” DirectoryEntry userentry = directoryEntry.Children.Find("CN=" + userName);
So the example's code would look like this: 因此,示例代码如下所示:
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);
}
}
}
}
But this code gets the following error in the invocation line (userEntry.Invoke("SetPassword", new object[] { newPassword });
: 但是此代码在调用行中出现以下错误
(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)
which in english means RCP server unavailable. 用英语这意味着RCP服务器不可用。 I've been stuck here for some days, and I've only found that it can be because of some authentication problem.
我已经在这里停留了几天,但我发现这可能是由于身份验证问题所致。 Invoking the method "Groups" works (
userEntry.Invoke("Groups");
) and the administrator, which is the user I'm loging in with to the ActiveDirectory has all the privileges. 调用方法“ Groups”有效(
userEntry.Invoke("Groups");
),并且管理员(我将用来登录ActiveDirectory的用户)具有所有特权。 Also the password policy is completly permissive with no minimum lenght or complexity. 同样,密码策略是完全允许的,没有最小长度或复杂性。
Again, because the program must iterate through, the real program actually iterates through the DirectoryEntry's children with: 同样,由于程序必须迭代,因此实际程序实际上通过以下方式遍历DirectoryEntry的子级:
foreach(DirectoryEntry child in directoryEntry.Children)
{
child.Invoke("SetPassword", new object[] { newPassword });
child.CommitChanges();
}
Thank you very much! 非常感谢你!
I don't think using directoryEntry.Children.Find("CN=" + userName)
will give you much performance improvement. 我认为使用
directoryEntry.Children.Find("CN=" + userName)
不会给您带来很多性能改进。 The sAMAccountName
attribute is an indexed attribute, so the search is very fast. sAMAccountName
属性是索引属性,因此搜索非常快。 That's one of the fastest searches you can make. 这是您可以进行的最快搜索之一。
But note that your two code blocks are not equal. 但是请注意,您的两个代码块不相等。
Find("CN=" + userName)
is trying to match userName
to the name of the account: the cn
attribute. Find("CN=" + userName)
试图将userName
与帐户名: cn
属性进行匹配。 But your code block with the DirectorySearcher
is matching userName
to the sAMAccountName
attribute. 但是,带有
DirectorySearcher
代码块会将userName
与sAMAccountName
属性匹配。 The cn
and sAMAccountName
attributes are not necessarily the same (although they might be in your domain). cn
和sAMAccountName
属性不一定相同(尽管它们可能在您的域中)。
But, if you still want to use Children.Find()
, I suspect that the problem might be in your Path
of the DirectoryEntry
. 但是,如果您仍然想使用
Children.Find()
,我怀疑问题可能出在您的DirectoryEntry
Path
中。 Why are you doing this? 你为什么做这个?
userEntry.Path = userEntry.Path.Replace(":389", "");
Does your ADScriptService.Properties.Settings.Default.ActiveDirectoryPath
have :389
? 您的
ADScriptService.Properties.Settings.Default.ActiveDirectoryPath
是否具有:389
? It doesn't need to if it starts with LDAP://
(the default LDAP port is 389). 如果它以
LDAP://
开头(默认LDAP端口为389),则不需要。
Your userEntry.Path
should look something like (depending on your domain) LDAP://CN=user,OU=Users,DC=domain,DC=com
. 您的
userEntry.Path
应该看起来像(取决于域) LDAP://CN=user,OU=Users,DC=domain,DC=com
。 If it's not, then you need to fix that. 如果不是,那么您需要修复它。
A side note: There is something you can do to speed this up a lot more than changing the search. 旁注:除了更改搜索之外,您还可以采取其他措施来加快速度。 The
Properties
collection uses a cache. Properties
集合使用缓存。 When you access a property, it checks if it is already in the cache and, if so, uses the cache. 当您访问属性时,它将检查该属性是否已在缓存中,如果已使用,则使用该缓存。 But if the property is not in the cache, then it will ask Active Directory for every attribute that has a value .
但是,如果该属性不在缓存中,则它将向Active Directory询问每个具有value的属性 。 That is expensive and unnecessary if you only want to read one or two attributes (especially if you're doing it for thousands of accounts).
如果您只想读取一个或两个属性(特别是要对成千上万个帐户执行此操作),则这是昂贵且不必要的。
A way around this is to tell it to get only the attributes that you want using RefreshCache
, before you access any of the Properties
. 解决此问题的一种方法是,在访问任何
Properties
之前,告诉它仅使用RefreshCache
获取所需的Properties
。 Like this: 像这样:
userEntry.RefreshCache(new [] { "sAMAccountName", "userAccountControl" });
Then when you access those properties, it will already have them in the cache and not reach out to AD to get anything. 然后,当您访问这些属性时,它将已经将它们存储在缓存中,并且不会联系AD来获取任何内容。
Also, if you are running this in a big loop over thousands of accounts, then I suggest you put the DirectoryEntry
in a using
statement (or call userEntry.Dispose()
when you're done with it). 另外,如果要在成千上万的帐户中大循环运行此程序,则建议您将
DirectoryEntry
放入using
语句中(或在完成后调用userEntry.Dispose()
)。 Usually you don't need to since garbage collection is pretty good at cleaning them up. 通常您不需要这样做,因为垃圾回收非常擅长清理它们。 But because you're running a big loop, the garbage collector doesn't have a chance to do any cleaning, so your process can end up taking up more and more and more memory until the loop finally stops.
但是,由于您正在运行一个大循环,因此垃圾收集器没有机会进行任何清理,因此您的过程最终可能会占用越来越多的内存,直到循环最终停止为止。 I have had big jobs like this take several GB of memory until I started disposing the unused
DirectoryEntry
objects. 在进行开始处理未使用的
DirectoryEntry
对象之前,我曾有过像这样的大工作占用数GB内存。
Update: Actually, forget about what I said about the DirectoryEntry
above. 更新:实际上,请忘记我对上面的
DirectoryEntry
所说的内容。 You don't actually need to use the DirectoryEntry
at all. 实际上,您根本不需要使用
DirectoryEntry
。 Don't use searchResult.GetDirectoryEntry()
. 不要使用
searchResult.GetDirectoryEntry()
。 The problem here is that you've already made a search to find the account. 这里的问题是您已经进行搜索以找到该帐户。 But now you're creating a
DirectoryEntry
, which is just going to make another call out to AD to get information you already have. 但是现在您正在创建
DirectoryEntry
,它将再次调用AD以获取您已经拥有的信息。 That's probably where your main performance hit is. 那可能就是您主要表现受到打击的地方。
Instead, use the attributes returned from your search. 而是使用搜索返回的属性。 Now, just like
DirectoryEntry.Properties
, if you don't specify which attributes you want returned in the search, then it will return all of them, which you don't necessarily need. 现在,就像
DirectoryEntry.Properties
一样,如果您不指定要在搜索中返回的属性,那么它将返回所有它们,而您不一定需要这些属性。 So you should set the PropertiesToLoad
collection, like this: 因此,应设置
PropertiesToLoad
集合,如下所示:
search.PropertiesToLoad.AddRange(new [] { "sAMAccountName", "userAccountControl" });
Then, after the search, you can use this to get the sAMAccountName
of the account you found: 然后,在搜索之后,您可以使用它来获取找到的帐户的
sAMAccountName
:
searchResult.Properties["sAMAccountName"][0]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.