When we try to update the user password on an Active
Directory (AD or AD LDS) environment, we usually read on the documentation
about the basic steps that are needed to set or change the password to a user.
If we follow those basic steps, we would be very lucky of all goes well. There
are often several other requirements that need to be met to be able to set the
password. Some of these include:
- The password is clear text
- The password does not meet the minimum length requirement
- The password does not meet the format like numeric or
capitalization (password policy)
- The Authentication type is not high enough
Any of these items can cause COM error similar to this:
“Exception
has been thrown by the target of an invocation COMException (0x80005009)”
|
To illustrate this, let’s looks at what the basic code to set a user’s password looks like:
//add these setting on the configuration file
const string adConnection = "ldap://mydomain:389/CN=ORG,DC=OZKARY,DC=COM";
const string adUsername = "someuser"; //user with admin access to AD
const string adPw = "*******";
public static void SetPassword(string username, string password){
using (var de = new DirectoryEntry(adConnection, adUsername, adPw))
{
//Win
Server 2008 schema or less uses sAMAccount
//not userPrincipalName
string query = String.Format("(&(objectCategory=person)(objectClass=user)(&(userPrincipalName={0}))", username);
using (var ds = new DirectorySearcher(de))
{
ds.SearchScope =
System.DirectoryServices.SearchScope.Subtree;
ds.Filter = query;
SearchResult res =
ds.FindOne();
if (res != null)
{
using (var userEntry =
res.GetDirectoryEntry())
{
userEntry.Invoke("SetPassword", new object[]
{ password
});
}
}
}
}
}
If we are lucky, this should be enough for the operation to
be successful, but we are probably getting errors, and that is why we are
writing this article. The first thing to
do is to figure out more about this error by extracting the COM exception error
code and mapping them to a root cause. This is where this function may be
helpful:
private static void HandleComException(TargetInvocationException tie)
{
if (tie != null && tie.InnerException is COMException)
{
COMException ce = (COMException)tie.InnerException;
int errorCode = ce.ErrorCode;
string message = ce.Message;
message += ce.InnerException !=
null ?
ce.InnerException.Message : string.Empty;
Trace.TraceError(message);
message = "error_not_found";
//
if the exception is due to password not meeting complexity requirements,
// then return ProviderException
if ((errorCode == unchecked((int)0x800708c5))
|| (errorCode == unchecked((int)0x8007202f))
|| (errorCode
== unchecked((int)0x8007052d))
|| (errorCode
== unchecked((int)0x8007052f)))
{
message = "password_not_complex";//password policy not met
}
else if ((errorCode == unchecked((int)0x8000500d)))
{
message = "no_secure_conn_for_password";//need higher than
security=None
}
else if ((errorCode == unchecked((int)0x80072035)))
{
//min
password length or the password was used before
//The server is unwilling to process the request
message="min_password_length_used_before";
}
else if ((errorCode == unchecked((int)0x80005009)))
{
message="password_clear_text";//need to set option to use
clear text
}
throw new ProviderException(message, ce);//need
to match this error
}
}
The exception usually is of type TargetInvocation, but it
does not provide enough information to determine the problem. The function
reads the inner exception (COM exception) and compare the error code to the
possible
Active Directory Service Interface (ADSI) errors. As you
can see there are other possible errors, but we are only handling the ones that
are specific to a password change.
The password not complex and the password length errors
basically mean that the new password is not meeting the password policy
requirements. We need to find out about those requirements from the AD
administrator and make sure that the new password meets them before we attempt
to set the password.
Some requirements may be:
- Length of 8 characters
- At least one capital letter or number
- One special character
The unsecured connection and clear text errors can be
addressed by making modifications to the code. When we try to update the
password for a user, we can’t use a Secure=None level on the authentication
type. We need to provide a higher security by adding these lines of code:
AuthenticationTypes auth = AuthenticationTypes.Signing | AuthenticationTypes.Sealing
| AuthenticationTypes.Secure;
using (var de = new DirectoryEntry(adConnection, adUsername, adPw,auth))
The clear text error is due to the fact that the password is
not hashed, so we need to indicate that this would be OK by adding this line of
code:
userEntry.Options.PasswordEncoding
= PasswordEncodingMethod.PasswordEncodingClear;
Once we have added those changes, the password update should
be able to work or we should at least have added instrumentation in our code to
help identified the problem. This is what the new version looks like:
public static void SetPasswordNew(string username, string password)
{
AuthenticationTypes auth = AuthenticationTypes.Signing | AuthenticationTypes.Sealing | AuthenticationTypes.Secure;
using (var de = new DirectoryEntry(adConnection, adUsername, adPw,auth))
{
//Win
Server 2008 schema or less uses sAMAccount not userPrincipalName.
//add to config
file
string query = String.Format("(&(objectClass=user)(&(userPrincipalName={0}))", username);
using (var ds = new DirectorySearcher(de))
{
ds.SearchScope =
System.DirectoryServices.SearchScope.Subtree;
ds.Filter = query;
SearchResult res =
ds.FindOne();
if (res != null)
{
try
{
using (var userEntry =
res.GetDirectoryEntry())
{
userEntry.Options.PasswordEncoding = PasswordEncodingMethod.PasswordEncodingClear;
userEntry.Invoke("SetPassword", new object[] { password });
}
}
catch (TargetInvocationException
tie)
{
HandleComException(tie);
}
catch (Exception ex)
{
Trace.TraceError(ex.Message); //handle this other error
}
}
}
}
}
By adding these modifications, the password update should be successful.
Hope this helps and thanks.