Udostępnij za pośrednictwem


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):

VBS:

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).

ChangePassword():

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.

SetPassword():

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: https://msdn.microsoft.com/en-us/library/cc223320.aspx) – 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: https://support.microsoft.com/?id=2386717 .

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)
        {
            try
            {
                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("contoso.com:" + port.ToString()))
                {
                    if (!usebasicauth)
                    {
                        // enable Kerberos encryption
                        ldapCon.SessionOptions.Sealing = true;
                    }

                    else
                    {
                        // 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;

                    //bind
                    ldapCon.Bind();

                    // change pwd
                    PasswordChanger(ldapCon,
                                    dn,
                                    pwdDepricate: @"0ldPa55W0rd",
                                    pwdSet: @"N3wPa55W0rdH15t0ryT3st");

                    // reset pwd without utilizing pwd history
                    PasswordChanger(ldapCon,
                                    dn,
                                    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
                        PasswordChanger(ldapCon,
                                        dn,
                                        pwdSet: @"N3wPa55W0rdH15t0ryT3st",
                                        enforceHistory: true);
                    }
                }
            }

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

            Console.WriteLine("Press any key");

            Console.ReadKey();
        }

        /// <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
                damDelete.Add(BuildBytePWD(pwdDepricate));

                // 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
                damAdd.Add(BuildBytePWD(pwdSet));

                // 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
                damReplace.Add(BuildBytePWD(pwdSet));

                // 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" : 
                                                                      "1.2.840.113556.1.4.2239";

                    // 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
                    mrCall.Controls.Add(pwdHistory);
                }

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

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

                string msg = "";

                try
                {
                    /* 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); }

                Console.WriteLine(msg);
            }
        }

        /// <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))
                    {
                        try
                        {
                            Win32Exception wex = new Win32Exception(lex);

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

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

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

                else
                {
                    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.

9/7/2015

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.

4/30/2016

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

 

All the best

Michael

PFE | Have keyboard. Will travel.

Comments

  • Anonymous
    December 11, 2013
    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:'DC=abc,DC=com'"Please guide me to resolve this issue.RegardsDanish-
    • Anonymous
      July 28, 2017
      Did anybody respond to this post. I too get hte same error
      • Anonymous
        August 12, 2017
        The comment has been removed
  • Anonymous
    December 11, 2013
    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.Example:correct distinguishedName:     CN=Administrator,CN=Users,DC=abc,DC=commistyped:     CN=Administrator,OU=Users,DC=abc,DC=comsaying 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 bestMichaelPFE | Have keyboard. Will travel.
  • Anonymous
    December 12, 2013
    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.RegardsDanish-
  • Anonymous
    September 30, 2014
    Hi,

    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
    Indu
  • Anonymous
    October 01, 2014
    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

    Regards
    Shashi
  • Anonymous
    October 01, 2014
    Did any one of you guys had this enforcePasswordHistory working ?

    Regards
    Shashi
  • Anonymous
    October 02, 2014
    @ 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.

    Hth.

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

    Michael

    PFE | Have keyboard. Will travel.
  • Anonymous
    October 02, 2014
    The comment has been removed
  • Anonymous
    February 23, 2015
    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.

    @Override
    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);
    pwdArray[i
    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.setRequestControls(controls);

    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.
  • Anonymous
    February 23, 2015
    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 http://support.microsoft.com/?id=2386717 - 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
    Michael
  • Anonymous
    April 07, 2015
    The comment has been removed
  • Anonymous
    April 08, 2015
    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...)
  • Anonymous
    April 08, 2015
    @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 :-)

    Michael

    PFE | Have keyboard. Will travel.

  • Anonymous
    April 08, 2015
    @mf9000 -> glad the post was helpful to you :-)

    Michael

    PFE | Have keyboard. Will travel.
  • Anonymous
    April 09, 2015
    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 ?
  • Anonymous
    April 09, 2015
    Hi Sagar,

    - what exception do you get?

    - did you install KB2386717 on the DCs?

    Michael

    PFE | Have keyboard. Will travel.
  • Anonymous
    May 20, 2015
    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.

    Cheers
  • Anonymous
    May 20, 2015
    fabulous work by you. Keep doing it.
  • Anonymous
    August 26, 2015
    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.
  • Anonymous
    August 31, 2015
    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.
  • Anonymous
    September 07, 2015
    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.

    Michael

    PFE | Have keyboard. Will travel.
  • Anonymous
    December 01, 2015
    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?
  • Anonymous
    December 01, 2015
    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

    Michael

    PFE | Have keyboard. Will travel.
  • Anonymous
    January 10, 2016
    why not honor the password???????please tell me??????
  • Anonymous
    January 10, 2016
    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 regardsMichaelPFE | Have keyboard. Will travel.
  • Anonymous
    January 27, 2016
    Hi,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?.RegardsGopal
  • Anonymous
    January 27, 2016
    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 regardsMichaelPFE | Have keyboard. Will travel.
  • Anonymous
    May 25, 2016
    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 :)
  • Anonymous
    June 09, 2016
    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. https://social.technet.microsoft.com/Forums/en-US/7af21e17-18dc-4eea-8439-ffd9a2d5bcaf/reset-password-with-history-resulting-in-the-server-does-not-support-the-control-the-control-is?forum=winserverDS
  • Anonymous
    August 02, 2016
    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 : https://social.technet.microsoft.com/Forums/en-US/7af21e17-18dc-4eea-8439-ffd9a2d5bcaf/reset-password-with-history-resulting-in-the-server-does-not-support-the-control-the-control-is?forum=winserverDSbest regardsMichaelPFE | Have keyboard. Will travel.
    • Anonymous
      August 02, 2016
      Thanks Michael,I've replied to your post on social, appreciate your time.
  • Anonymous
    June 20, 2017
    The comment has been removed
    • Anonymous
      June 20, 2017
      I you sendmail resetpassword,
  • Anonymous
    August 09, 2017
    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 ?
    • Anonymous
      August 12, 2017
      Hi Mike,I need a bit more context to answer your question - can you elaborate please with more details?best regardsMichaelPFE | Have keyboard. Will travel.
  • Anonymous
    September 20, 2017
    Hi Michael Frommhold MSFT,I am trying reset password functionality using ADLDS and c# asp.net. 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
    • Anonymous
      November 01, 2017
      Hi Sagar,like stated at the end of the article - this functionality is not immplemented in AD LDS, sorry for that.best regardsMichaelPFE | Have keyboard. Will travel.
  • Anonymous
    May 24, 2019
    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.