Resetting passwords honoring password history (or what’s happening under the hood when changing / resetting passwords)

Todays topic:

Resetting passwords honoring password history
(or what's happening under the hood when changing / resetting passwords)

You may have already came across the task to programmatically change or reset passwords on user accounts in Active Directory. Thanks to the the ChangePassword() and SetPassword() macros of the Active Directory Service Interface (ADSI) implementation this is an easy and straight forward coding and in most cases you need not take care about what's happening on the Domain Controller performing the password handling for you.
Anyhow it still may come in handy knowing how this is processed from the Active Directory service (NTDS) on a DC – especially when we want to accomplish what's mentioned in the headline (Resetting passwords honoring password history).

First of all – and for the sake of completeness – let's list the usage of the two ADSI macros ChangePassword() and SetPassword() in VBS and .Net (note –.Net System.DirectoryService namespace is wrapping ADSI):


Set IADsUser = GetObject("LDAP://CN=TheCN,OU=TheOU,DC=contoso,DC=com")

    IADsUser.ChangePassword "0ldPa55W0rd", "N3wPa55W0rd"

        IADsUser.SetPassword "N3wPa55W0rd"

.Net (System.DirectoryServices):

DirectoryEntry IADsUser = new DirectoryEntry("LDAP://CN=TheCN,OU=TheOU,DC=contoso,DC=com");

IADsUser.Invoke("ChangePassword", new object[] { "0ldPa55W0rd", "N3wPa55W0rd" });

IADsUser.Invoke("SetPassword", new object[] { "N3wPa55W0rd" });

Now let's have a look at what's actually happening when calling the ADSI macros:

The password change or reset call is actually an attribute modification request against the unicodePwd attribute of the user account which requests the NTDS service to handle the incoming modification request appropriately (note – the password is NOT stored in this attribute).


When changing the password we send a modification request to our directory connection, that was established against a domain controller when 'connecting' the user object, that contains:

  • the distinguishedName (internal directory path - in our sample "CN=TheCN,OU=TheOU,DC=contoso,DC=com")
  • the name of the attribute to modify (unicodePwd)
  • a delete attribute value modification containing the old password
  • an add attribute value modification containing the new password

When the modification request arrives at the domain controller the NTDS service does the following:

  • check whether the hash of the old password to be deleted is in the list of remembered password hashes (this is the verification part of the old password) -> if so proceed, if not return an error "password incorrect"
  • check whether the new password meets password policy rules (like comlpexity, password history, password length) -> if so proceed, if not return error "passwort does not meet pwd complexity rules"

Thus we see - the password rule checks are done when performing an add operation to the unicodPwd attribute.


Resetting the password sends a modification request to our directory connection containing:

  • the distinguishedName (internal directory path - in our sample "CN=TheCN,OU=TheOU,DC=contoso,DC=com")
  • the name of the attribute to modify (unicodePwd)
  • a replace attribute value modification containing the new password

The only password rule checks that are done while proceeding a replace operation are password complexity and password length – password history is not checked here. Why? Because we only check the existance of a value while modifying an attribute when deleting or adding values.

Reset password honoring password history:

Knowing the above described functionalities this sounds easy – just send an add operation with the new password -  unfortunately you cannot send an add operation to the unicodePwd attribute without a preceding delete operation (means you have to know the old password). But we have an Identity Management solution in place that should be able to reset passwords and not reuse previously set passwords + the helpdesk will not and should not know the old password – so how can this be achieved?

We have to say good bye to the handy ADSI implementation and code closer to the LDAP APIs (no worries – we will still use managed code!). Since .Net 2.0 we have the namespace System.DirectoryServices.Protocols in place, wrapping the LDAP APIs directly.

See following illustration how the various implementations are talking to LDAP:

Here we can perform our modification request ourselves and control what has to be send and how this has to be handled.

When sending requests to a directory connection we can additionally send Extended Controls with the request (find a list of controls here: – you may know one from LDAP queries when using paged queries. In this case ADSI is sending a search request with the extended control for paged search to the DC.
If you check the list in the above link you will find an Extended Control called LDAP_SERVER_POLICY_HINTS_OID (1.2.840.113556.1.4.2066) with the following description: "Used with an LDAP operation to enforce password history policies during password set.".

Cool – we have all there what we need – unfortunately not necessarily.
If you check the answer of an UDP call against rootDSE in your domain (ex: ldp.exe -> Connect) you will see a list of OIDs in the attribute supportedControl.
Depending on  the OS version of your DCs the POLICY_HINTS OID may be missing.
On DCs with OS Windows Server 2008 (R2) it's not there by default. To enable the usage of this Extended Control on Windows Server 2008 (R2) DCs  you must first introduce the OID and it's usage to the DCs by applying the following hotfix: .

Since Windows Server 2012 we do have a new OID for the Extended Control LDAP_SERVER_POLICY_HINTS_OID (1.2.840.113556.1.4.2239). The OID 1.2.840.113556.1.4.2066 is still valid on Windows Server 2012 (R2) ADs but it's now called LDAP_SERVER_POLICY_HINTS_DEPRECATED_OID.

Suggest you check the supportedControl attribute of a rootDSE call and check, whether you find LDAP_SERVER_POLICY_HINTS_OID = 1.2.840.113556.1.4.2239. If so you should use the new OID.

If one of the above OIDs is present we are now able to send our modification request containing the the Extended Control LDAP_SERVER_POLICY_HINTS_OID with the value 0x1 to honor password history when resetting passwords.

!Note: There are several things to keep in mind when establishing an LdapConnection in code:

  • The connection must be encrypted, either by Kerberos or SecureSocketLayer.
    If you chose SSL as encryption method make sure to use port 636 for establishing the LdapConnection.
  • Sending the extended control for honoring the password history is only functional if we ensure that LDAP protocol version 3 is used.
  • Setting authentication Type of the LdapConnection to Basic Authentication will cause an internal fallback to LDAP protocol version 2
    -> thus the extended control for honoring the password history will just be dropped and the password history will not be honored unless we enforce protocol version 3.

Sample Code:

using System.ComponentModel;
using System.DirectoryServices.Protocols;
using System.Net;
using System.Text;

namespace CodingFromTheField.PwdChanger
    class Program
        public static void Main(string[] args)
                string dn = "CN=TheCN,OU=TheOU,DC=contoso,DC=com";

                bool usebasicauth = false;

                int port = 389;

                if (usebasicauth)
                { port = 636; }

                /* initialize LdapConnection which inherites from DirectoryConnection  -
                 * DirectoryConnection cannot be initialized passing a directory to connect to */
                using (LdapConnection ldapCon = new LdapConnection("" + port.ToString()))
                    if (!usebasicauth)
                        // enable Kerberos encryption
                        ldapCon.SessionOptions.Sealing = true;

                        // enable SSL encryption
                        ldapCon.SessionOptions.SecureSocketLayer = true;

                        // set authentication type to Basic Authentication
                        ldapCon.AuthType = AuthType.Basic;

                        // pass credentials
                        ldapCon.Credential = new NetworkCredential("theadmin", "thepassword", "contoso");

                    // enforce LDAP protocol version 3 usage before binding LdapConnection
                    ldapCon.SessionOptions.ProtocolVersion = 3;


                    // change pwd
                                    pwdDepricate: @"0ldPa55W0rd",
                                    pwdSet: @"N3wPa55W0rdH15t0ryT3st");

                    // reset pwd without utilizing pwd history
                                    pwdSet: @"N3wP@55W0rdH15t0ryT3st");

                    /* ensure protocol version 3 usage - 
                    if protocol version 2 -> do not try to reset the password honoring password history - 
                    the password will be set anyways */
                    if (ldapCon.SessionOptions.ProtocolVersion == 3)
                        // reset pwd utilizing pwd history
                                        pwdSet: @"N3wPa55W0rdH15t0ryT3st",
                                        enforceHistory: true);

            catch (Exception ex)
            { Console.WriteLine(ex.ToString()); }

            Console.WriteLine("Press any key");


        /// <summary>
        /// Change or reset pwds on given object
        /// </summary>
        /// <param name="dcCon">established DirectoryConnection</param>
        /// <param name="distinguishedName">path to the object</param>
        /// <param name="pwdDepricate">when changing pwds - pass the current pwd in here</param>
        /// <param name="pwdSet">new pwd to be set</param>
        /// <param name="enforceHistory">when resetting pwd -> should we utilize exetended control
        /// for pwd history usage</param>
        /// <param name="useOldOID">use depricated OID or new OID</param>
        private static void PasswordChanger(LdapConnection ldapCon,
                                            string distinguishedName,
                                            string pwdDepricate = null,
                                            string pwdSet = null,
                                            bool enforceHistory = false,
                                            bool useOldOID = false)
            bool letsgo = false;

            // the 'unicodePWD' attribute is used to handle pwd handling requests
            string attribute = "unicodePwd";

            // our modification control
            DirectoryAttributeModification[] damList = null;

            // the modifiy request
            ModifyRequest mrCall = null;

            //do we have an old and a new pwd -> change pwd
            if (!String.IsNullOrEmpty(pwdDepricate) && !String.IsNullOrEmpty(pwdSet))
                // modification control for the delete operation
                DirectoryAttributeModification damDelete = new DirectoryAttributeModification();

                // attribute to handle
                damDelete.Name = attribute;

                // value to be send with the request

                // this is a delete operation
                damDelete.Operation = DirectoryAttributeOperation.Delete;

                // modification control for the add operation
                DirectoryAttributeModification damAdd = new DirectoryAttributeModification();

                // attribute to handle
                damAdd.Name = attribute;

                // value to be send with the request

                // this is an add operation
                damAdd.Operation = DirectoryAttributeOperation.Add;

                // combine modification controls
                damList = new DirectoryAttributeModification[] { damDelete, damAdd };

                // init modify request
                mrCall = new ModifyRequest(distinguishedName, damList);

                // we do have something to handle
                letsgo = true;

            //do we have a pwd to set -> set pwd
            else if (!String.IsNullOrEmpty(pwdSet))
                // modification control for the replace operation
                DirectoryAttributeModification damReplace = new DirectoryAttributeModification();

                // attribute to handle
                damReplace.Name = attribute;

                // value to be send with the request

                // this is a replace operation
                damReplace.Operation = DirectoryAttributeOperation.Replace;

                // combine modification controls
                damList = new DirectoryAttributeModification[] { damReplace };

                // init modify request
                mrCall = new ModifyRequest(distinguishedName, damList);

                // should we utilize pwd history on the pwd reset?
                if (enforceHistory)
                    // the actual extended control OID                     
                    string LDAP_SERVER_POLICY_HINTS_OID = useOldOID ? "1.2.840.113556.1.4.2066" : 

                    // build value utilizing berconverter
                    byte[] value = BerConverter.Encode("{i}", new object[] { 0x1 });

                    // init extended control
                    DirectoryControl pwdHistory = new DirectoryControl(LDAP_SERVER_POLICY_HINTS_OID, 
                                                                       value, false, true);

                    // add extended control to modify request

                // we do have something to handle
                letsgo = true;

            // something to be handled?
            if (letsgo)
                DirectoryResponse drResult = null;

                string msg = "";

                    /* send the request into the DirectoryConnection
                     * and receive the response */
                    drResult = ldapCon.SendRequest(mrCall);

                    // display result code
                    msg = TranslateEx(drResult, null, distinguishedName);

                catch (DirectoryOperationException doex)
                { msg = TranslateEx(drResult, doex, distinguishedName); }

                catch (Exception ex)
                { msg = TranslateEx(drResult, ex, distinguishedName); }


        /// <summary>
        /// build byte array from string pwd
        /// </summary>
        /// <param name="pwd">pwd string</param>
        /// <returns>byte array</returns>
        private static byte[] BuildBytePWD(string pwd)
            return (Encoding.Unicode.GetBytes(String.Format("\"{0}\"", pwd)));

        /// <summary>
        /// decode exception thrown
        /// </summary>
        /// <param name="dr">Directoryresponse from the SendRequest call</param>
        /// <param name="ex">the exception to decode</param>
        /// <param name="dn">the distinguishedName of the object we touched</param>
        /// <returns></returns>
        private static string TranslateEx(DirectoryResponse dr, Exception ex, string dn)
            string ret = "";

            bool success = false;

            if (dr != null)
            { success = (dr.ResultCode == ResultCode.Success) ? true : false; }

            if (success)
            { ret = String.Format("Update pwd result: {0} \n\tfor {1}\n", 
                                  dr.ResultCode.ToString(), dn); }

            else if (!success && (ex != null))
                if (ex is DirectoryOperationException)

                    DirectoryOperationException doex = (DirectoryOperationException)ex;

                    ret = String.Format("Update pwd result: {0} \n\tfor {1}\n", 
                                        doex.Response.ResultCode.ToString(), dn);

                    string hex = doex.Response.ErrorMessage.Split(new char[] { ':' })[0];

                    int lex = 0;

                    if (int.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out lex))
                            Win32Exception wex = new Win32Exception(lex);

                            ret = ret + String.Format("{0} ({1}) [{2}]\n", 
                                                      wex.Message, doex.Response.ErrorMessage, doex.Message);

                        { ret = ret + String.Format("{0} [{1}]\n", 
                                                    doex.Response.ErrorMessage, doex.Message); }

                    { ret = ret + String.Format("{0} [{1}]\n", 
                                                doex.Response.ErrorMessage, doex.Message); }

                    ret = String.Format("Update pwd result: Error \n\tfor {0}\n", dn);

                    ret = ret + String.Format("{0}\n", ex.Message);

            return ret;


Hope you had some fun reading and wish fun with testing.


There were several queries regarding AD LDS and the above code.
Unfortunately the code path handling this in NTDS is not implemented in AD LDS -> no chance to implement this for AD LDS with built in mechanisms.


Added !Note section above sample code.
Updated sample code to honor !Note.


All the best


PFE | Have keyboard. Will travel.

Comments (40)
  1. gregenstein says:

    I wanted to thank you for posting such a useful article. I had no idea these “hints” even existed. We had been using the DirectoryServices.AccountManagement API to handle our password sets/changes, so throwing back with some old school DirectoryAttributeModifcation requests was a new challenge.

    To that end, the “deprecated” OID worked for me, and . But, it does the job just fine.

    The TranslateEx function is gold. Great for helping debug and provide feedback to end users.

  2. Sagar Patil says:

    Hi Michael Frommhold MSFT,
    I am trying reset password functionality using ADLDS and c# for that we create ADLDS instance on Windows 2008 R2 Service Pack 1, and it contains directory extended control (LDAP_SERVER_POLICY_HINTS_OID = “1.2.840.113556.1.4.2066”. )
    And use same code as above example. But long but I am not able to enforce password history. It take last password and set it successfully.
    Appreciate Your help.
    Thanks in advance

    1. Hi Sagar,

      like stated at the end of the article – this functionality is not immplemented in AD LDS, sorry for that.

      best regards

      PFE | Have keyboard. Will travel.

  3. Mike says:

    Good day,
    I have tried this sample. But however every time i am trying to set password it always succeeds, but when I am trying to change specifying old one I am getting policy error. ldap policy is set to 3, what can be the reason ?

    1. Hi Mike,

      I need a bit more context to answer your question – can you elaborate please with more details?

      best regards


      PFE | Have keyboard. Will travel.

  4. hi daqiang says:

    Hello ! I want to use “Self Service Password 1.0 ” soft web application ,reset remote LDAP user password ,When reset the password to determine last time history and new password cannot be the same. I read your article is very enlightening,but I don’t know how to combine into a web page,Here are my resetpassword PHP page content,please help me.thank you very much.

    verify($_POST[‘g-recaptcha-response’], $_SERVER[‘REMOTE_ADDR’]);
    if (!$resp->isSuccess()) {
    $result = “badcaptcha”;
    error_log(“Bad reCAPTCHA attempt with user $login”);
    foreach ($resp->getErrorCodes() as $code) {
    error_log(“reCAPTCHA error: $code”);

    # Check question/answer
    if ( $result === “” ) {

    # Connect to LDAP
    $ldap = ldap_connect($ldap_url);
    ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
    if ( $ldap_starttls && !ldap_start_tls($ldap) ) {
    $result = “ldaperror”;
    error_log(“LDAP – Unable to use StartTLS”);
    } else {

    # Bind
    if ( isset($ldap_binddn) && isset($ldap_bindpw) ) {
    $bind = ldap_bind($ldap, $ldap_binddn, $ldap_bindpw);
    } else {
    $bind = ldap_bind($ldap);

    $errno = ldap_errno($ldap);
    if ( $errno ) {
    $result = “ldaperror”;
    error_log(“LDAP – Bind error $errno (“.ldap_error($ldap).”)”);
    } else {

    # Search for user
    $ldap_filter = str_replace(“{login}”, $login, $ldap_filter);
    $search = ldap_search($ldap, $ldap_base, $ldap_filter);

    $errno = ldap_errno($ldap);
    if ( $errno ) {
    $result = “ldaperror”;
    error_log(“LDAP – Search error $errno (“.ldap_error($ldap).”)”);
    } else {

    # Get user DN
    $entry = ldap_first_entry($ldap, $search);
    $userdn = ldap_get_dn($ldap, $entry);

    if( !$userdn ) {
    $result = “badcredentials”;
    error_log(“LDAP – User $login not found”);
    } else {

    # Check objectClass to allow samba and shadow updates
    $ocValues = ldap_get_values($ldap, $entry, ‘objectClass’);
    if ( !in_array( ‘sambaSamAccount’, $ocValues ) and !in_array( ‘sambaSAMAccount’, $ocValues ) ) {
    $samba_mode = false;
    if ( !in_array( ‘shadowAccount’, $ocValues ) ) {
    $shadow_options[‘update_shadowLastChange’] = false;

    # Get user email for notification
    if ( $notify_on_change ) {
    $mailValues = ldap_get_values($ldap, $entry, $mail_attribute);
    if ( $mailValues[“count”] > 0 ) {
    $mail = $mailValues[0];

    # Get question/answer values
    $questionValues = ldap_get_values($ldap, $entry, $answer_attribute);
    $match = 0;

    # Match with user submitted values
    foreach ($questionValues as $questionValue) {
    $answer = preg_quote(“$answer”,”/”);
    if (preg_match(“/^\{$question\}$answer$/i”, $questionValue)) {
    $match = 1;

    if (!$match) {
    $result = “answernomatch”;
    error_log(“Answer does not match question for user $login”);


    # Check and register new passord
    # Match new and confirm password
    if ( $result === “” ) {
    if ( $newpassword != $confirmpassword ) { $result=”nomatch”; }

    # Check password strength
    if ( $result === “” ) {
    $result = check_password_strength( $newpassword, “”, $pwd_policy_config, $login );

    # Change password
    if ($result === “”) {
    $result = change_password($ldap, $userdn, $newpassword, $ad_mode, $ad_options, $samba_mode, $samba_options, $shadow_options, $hash, $hash_options, “”, “”);
    if ( $result === “passwordchanged” && isset($posthook) ) {
    exec(escapeshellcmd(“$posthook $login $newpassword”));

    # HTML

    <div class="result alert alert-“>
    <i class="fa ” aria-hidden=”true”>

    if ( $show_help ) {
    echo "”;
    echo “ “;
    echo $messages[“resetbyquestionshelp”];
    echo “\n”;

    <input type="text" name="login" id="login" value="” class=”form-control” placeholder=”” />

    $text ) {
    echo “$text”;

    <input type="text" name="answer" id="answer" class="form-control" placeholder="” />

    <input type="password" name="newpassword" id="newpassword" class="form-control" placeholder="” />

    <input type="password" name="confirmpassword" id="confirmpassword" class="form-control" placeholder="” />

    <div class="g-recaptcha" data-sitekey="” data-theme=”” data-type=”” data-size=””>
    <script type="text/javascript" src="“>

    $login, “mail” => $mail, “password” => $newpassword);
    if ( !send_mail($mailer, $mail, $mail_from, $mail_from_name, $messages[“changesubject”], $messages[“changemessage”], $data) ) {
    error_log(“Error while sending change email to $mail (user $login)”);


    1. hi daqiang says:

      I you sendmail resetpassword,

  5. Hi Geoff,

    sorry that I haven’t seen your query before.

    Several things need to be changed in your PoSh:
    – You cannot use userPassword attribute to modify pwd -> you must use unicodePwd attribute

    – the pwd cannot be passed as string – it must be passed as byte array of the formatted pwd -> format should be double-quote + pwd + double-quote
    (see BuildbytePwd function)

    – the DirectoryControl for honoring pwd history is NOT critical -> don’t use $true for the critical parameter in the DirectoryControl constructor

    – you should inspect the exception thrown – “The server cannot handle directory requests.” will be thrown even if the operation worked and we get an error because of pwd history violated…
    (see TranslateEx function)

    PoSh sample see here :
    best regards


    PFE | Have keyboard. Will travel.

    1. Geoff says:

      Thanks Michael,

      I’ve replied to your post on social, appreciate your time.

  6. Geoff says:

    Hi Michael,

    I’m trying to convert this to a powershell cmdlet but am now stuck with the following exception.
    System.DirectoryServices.Protocols.DirectoryOperationException: The server cannot handle directory requests.

    I have a question up on social with more detail and code, if you or anyone else could take a look.

  7. Nethushanka says:

    Michael Frommhold Thank you verymuch. I got it working :). I used jxplorer to inspect RootDSE for supported controls. The code wasn’t working for me first because only one of our DCs supported 1.2.840.113556.1.4.2066.
    Cheers :)

  8. Hi Gopal,

    like mentioned in the update from 3/31/2014 :
    “Suggest you check the supportedControls attribute of a rootDSE call against the DC you want to use for the operation and check, whether you find LDAP_SERVER_POLICY_HINTS_OID = 1.2.840.113556.1.4.2239. If so you should use the new OID”

    Saying – ask your DCs dynamically (may be with some caching functionality) whether they expose the new OID or not and use it if present.

    If you need help in coding the rootDSE call – let me know :-)

    best regards


    PFE | Have keyboard. Will travel.

  9. Gopal says:


    The code worked for me when the domain and forest functional level were in Windows 2003, but we had both 2003 and 2008 R2 domain controllers in the forest.

    Recently we upgraded the domain and functional level to 2008 R2. Now we have 2008 R2 and 2012 domain controllers. The 2012 server lists the LDAP_SERVER_POLICY_HINTS_OID = 1.2.840.113556.1.4.2239 where as 2008 R2 server lists OID = “1.2.840.113556.1.4.2066”.

    However, my code stopped working and does not enforce the history while changing the password.

    What could be going wrong?.


  10. Hi,
    I would really like to answer your question but unfortunately I don’t get what you want to know. Can you please provide more details for your question?

    best regards


    PFE | Have keyboard. Will travel.

  11. dea says:

    why not honor the password???????
    please tell me??????

  12. Hi CD Smith,

    the constructor for System.DirectoryServices.Protocols.LdapConnection comes with 3 overrides:
    – LdapConnection(string server)
    – LdapConnection(System.DirectoryServices.Protocols.LdapDirectoryIdentifier identifier) (used in the code above)
    – LdapConnection(System.DirectoryServices.Protocols.LdapDirectoryIdentifier identifier, System.Net.NetworkCredential credential)
    – LdapConnection(System.DirectoryServices.Protocols.LdapDirectoryIdentifier identifier, System.Net.NetworkCredential credential, System.DirectoryServices.Protocols.AuthType authType)

    Saying – you have two overrides where you can pass Credentials for your admin account.

    But – a big but – I’d suggest to rather run the code in a context that is able to do what you coded than storing passwords and usernames somewhere and carrying them in plaintext in memory during runtime.

    And – I don’t see how you would be able to accomplish the above task (Resetting passwords honoring password history) with S.DS.AM.
    Apart from that – I’d suggest not to use S.DS.AM – it’s the opposite of good performance :-)

    hth – and have fun coding


    PFE | Have keyboard. Will travel.

  13. CD Smith says:

    Question – when using the S.DS.AM dll in my code, I connect to AD for a password reset like this:

    using (var context = new PrincipalContext(ContextType.Domain, ActiveDirectoryConnectionString, ActiveDirectoryAdminAccount, ActiveDirectoryAdminPassword))

    I don’t see anything in your example that connects to LDAP using an admin level account. Am I missing something here that’s implied? How could simply connecting to the DC have rights to make changes without having appropriate access rights to do so?

  14. Regarding AD LDS / ADAM and honoring password history when resetting PWDs:
    I talked to the product guys and unfortunately the code path handling this in NTDS is not implemented in AD LDS / ADAM.

    Sorry about bad news.


    PFE | Have keyboard. Will travel.

  15. Eric says:

    I am in the same boat as Gopal, I am using AD LDS on Windows Server 2012R2 and it is allowing the password to be set regardless of history. I know it has the policy correctly because if I do a "ChangePassword" on the System.DirectoryServices.AccountManagement.AuthenticablePrincipal
    it correctly throws errors for password history.
    thanks for any help.

  16. Gopal says:

    I am trying to reset the password on AD LDS (Windows 2008 R2). I could see POLICY_HINT via LDAP.exe but password reset is always successful, and history is not honored. I am hitting all of the lines in your previous comment.
    It works for me on Active Directory (Windows 2008 R2) but not on AD LDS..
    Any help is much appreciated.

  17. RoHit says:

    fabulous work by you. Keep doing it.

  18. Maya says:

    Heya Michael,

    I can see the POLICY_HINT via LDAP.exe but the implementation is going ahead and changing my password without respecting password history. Suggestions? It’s hitting all of the lines in your previous comment.


  19. Hi Sagar,

    – what exception do you get?

    – did you install KB2386717 on the DCs?


    PFE | Have keyboard. Will travel.

  20. Sagar Bhingare says:

    It is not working on Windows server 2008 SP2. giving exception for each password that I am trying to set . The password which i am trying to set is according to constrains set in password policy. Will it work for Windows server 2008 R1 ?

  21. @mf9000 -> glad the post was helpful to you :-)


    PFE | Have keyboard. Will travel.

  22. @Garrett -> Thx for proofreading. Looks like a VS plugin that copies source code as HTML formatted data made a formatting error – and I didn’t see it while proofreading myself.
    Corrected :-)


    PFE | Have keyboard. Will travel.

  23. mf9000 says:

    Thank you so much for this code! It was extremely useful to me. And your answers are also full of information.
    I was really struggling to do exactly that, a SetPassword() that checks the password history…

    (Also as Garrett said, I guess a space is missing in the sample…)

  24. Garrett says:

    (minor) I believe there should be a space between new and object[] on this line: byte[] value = BerConverter.Encode("{i}", newobject[] { 0x1 });

  25. Hi,
    two things to check here:

    1. open ldp.exe on a DC in your AD and – you will see the rootDSE attributes returned.
    In supportedControls attribute you'll find a list of all control OIDS supported on this DC – check whether you have 1.2.840.113556.1.4.2239. If not check for presence of 1.2.840.113556.1.4.2066. Use the one you find.
    If none of the two is present – you should install – this will introduce 1.2.840.113556.1.4.2066 OID to the DC.

    2. a BasicControl in Java LDAP implementation contains the string OID (correct), boolean critical (could be false or true – correct), byte[] value (should be berencoded from '1' -> 0x30 0x84 0x00 0x00 0x00 0x03 0x02 0x01 0x01 -> correct).
    What I'm actually missing in the BasicControl in Java as corresponding class to DirectoryControl in .Net is the serverSide switch – this control must be declared serverside otherwise the control will not be recognized correctly by NTDS.

    Do you get any exception – and if yes – what does it tell you?

    All the best

  26. Chaitanya Velaga says:

    I am trying to implement reset password functionality for accounts in Windows 2012 R2 AD LDS via java ldap api. But it is not honoring password history constraint. When I tried to implement change password it is enforcing password history. I am using the
    following code to reset password.

    public void updatePassword(String password) throws LdapException {
    try {
    String quotedPassword = """ + password + """;
    char unicodePwd[] = quotedPassword.toCharArray();
    byte pwdArray[] = new byte[unicodePwd.length * 2];
    for (int i=0; i pwdArray[i2 + 1] = (byte) (unicodePwd[i] >>> 8);
    2 + 0] = (byte) (unicodePwd[i] & 0xff);
    ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REPLACE_ATTRIBUTE,new BasicAttribute("UnicodePwd", pwdArray))};
    LdapContext ldapContext = (LdapContext)ldapTemplate.getContextSource().getReadWriteContext();

    final byte[] controlData = {48,(byte)132,0,0,0,3,2,1,1};
    BasicControl[] controls = new BasicControl[1];
    final String LDAP_SERVER_POLICY_HINTS_OID = "1.2.840.113556.1.4.2239";
    controls[0] = new BasicControl(LDAP_SERVER_POLICY_HINTS_OID, true, controlData);

    ldapContext.modifyAttributes(getRelativeDistinguishedName(), mods);
    } catch (Exception e) {
    throw new LdapException("Failed to update password for:" + this.getDistinguishedName(), e);
    Please let me know if I am doing anything wrong.

  27. @ Shashi
    I got feedback from several people who implemented this successfully (like Mohammad above).
    Just retested it in W2K8 R2, W2K8 R2 Sp1, W2K12 and W2K12 R2 domains with success.

    Some checks:

    Did you check the presence of the control "1.2.840.113556.1.4.2066" via ldp.exe -> Connect -> supportedControls attribute?

    When debugging your code – does the following code path get hit?
    // the actual extended control OID
    string LDAP_SERVER_POLICY_HINTS_OID = "1.2.840.113556.1.4.2066";

    // build value utilizing berconverter
    byte[] value = BerConverter.Encode("{i}", newobject[] { 0x1 });

    // init exetnded control
    DirectoryControl pwdHistory = new DirectoryControl(LDAP_SERVER_POLICY_HINTS_OID, value, false, true);

    // add extended control to modify request

    Did you run the assembly elevated?

    If you need more support – don’t hesitate to come back :-)


    PFE | Have keyboard. Will travel.

  28. @ Indu Ganti
    Just imagine these scenarios:

    A) You have a custom NetworkProvider which allows the user to reset his password by himself after answering some security questions without logging in

    B) you have set up a SelfService-PWDReset-Website that a user can access within a login session from any colleague where he can reset his pwd afte answering some security questions.

    In both cases the user would be able to pretend having forgotten his pwd and reset the pwd to the one he is currently using if you do not check pwd history – and that’s for eternity. This will break your PWD policy implementation in your AD.

    A real life (not joking) scenario:
    Helpdesk uses an Identitymanagement WebSIte to reset pwds.
    Since there are pwd complexity rules in place the Helpdesk always uses the same pwd for reset to match the complexity rules – let’s say "Start$12".
    If a user had to get his pwd resettet several times he will find out that the new pwd will always be the same ->

    He just calls the helpdesk, pretending to be another user who is on holidays. Then he waits for the callbackand answers with "Sorry colleague not here in the moment – please try later".
    Now he knows the new pwd of the other user, log’s in as the user, set’s a new pwd an can do whatever he want’s as this user.


    If you have any concerns do not hesitat to come back.


    PFE | Have keyboard. Will travel.

  29. Shashi says:

    Did any one of you guys had this enforcePasswordHistory working ?


  30. Shashi says:

    Hi Mike,

    I implemented this solution to enforepasswordHistory as true, and the installation hotfix and even testing you code i can still set the password which was in past history.

    Please adivce


  31. Indu Ganti says:


    Just a general question about password history. At change password, I see the benefit of validating the password history. But at a reset , is there any benefit to compare the history?

    In a website , where a reset email is sent for the user to rest the password with some security questions, what is the benefit of comparing password history? It might give the user more information about a possible password or a bad user experience (frustrating
    to enter many passwords, if I look as last 8).

    Any help greatly appreciated.

    Best Regards

  32. Mohammad Danish says:

    Thank you so much for the reply. It helped and issue is now resolved by passing the correct distinguished name.

    Thanks again your post, it really helped me to solve the issue which has been pending for so long.



  33. Hi,

    thx for your posting. The error you are facing should only be raised when the path (distinguishedName) to the user object could not be found.

    "best match of: 'DC=abc,DC=com'" indicates that the ou / container in the path just before DC=abc,DC=com does not exist.


    correct distinguishedName:




    saying OU=Users does not exist – it's CN=Users -> this will throw the error you've seen.

    Hope this helps – if not – do not hesitate to come back.

    All the best


    PFE | Have keyboard. Will travel.

  34. Mohammad Danish says:

    Hi Mike,

    Thank you so much for this post. We follow the step you provided but unfortunately I stuck with an error @  DirectoryResponse drResult = dcCon.SendRequest(mrCall);

    "The object does not exist." and error message as

    "0000208D: NameErr: DSID-03100213, problem 2001 (NO_OBJECT), data 0, best match of:


    Please guide me to resolve this issue.



    1. abhishek says:

      Did anybody respond to this post. I too get hte same error

      1. Yes I answered this 2013/12/11 at 9:40 am and the answer solved Mohammad’s issue.

        best regards


        PFE | Have keyboard. Will travel.

Comments are closed.

Skip to main content