• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 3629
  • Last Modified:

Programmatically forcing a password change

I'm writing a small app to make it easy for a non-technical user to reset passwords in a Windows Server 2008 R2 environment with Active Directory.

Here's my reset password function:

        public static bool ResetPassword (string userDN, string newPassword)
        {
            DirectoryEntry directoryEntry = null;

            try
            {
                directoryEntry = new DirectoryEntry(userDN);
                directoryEntry.Invoke("SetPassword", new object[] {newPassword});
                directoryEntry.Properties["LockOutTime"].Value = 0; // unlock account
                //directoryEntry.Properties["pwdLastSet"].Value = 0; // force change at next logon
                directoryEntry.InvokeSet("pwdLastSet", new object[] { 0 });
                directoryEntry.CommitChanges();
               
            }
            catch (Exception)
            {
                return false;
            }
            finally
            {
                if (directoryEntry != null)
                {
                    directoryEntry.Close();
                    directoryEntry.Dispose();
                }
            }
           
            return true;
        }

The setting of pwdLastSet property is having no effect. The user is able to logon but is not being forced to change their password.

All the articles I've found here and on other sites say that setting pwdLastSet to zero works, but it doesn't for me. As you can see from the code, I've tried a couple of ways to set the property value.

Does anyone have any ideas?

Thanks,
Jeff
0
jeffeld
Asked:
jeffeld
  • 6
  • 4
1 Solution
 
jdavistxCommented:
Are you sure you value is being saved?

After you commit your changes, try re-reading the value in Debug, and see what it retrieves?

public static bool ResetPassword (string userDN, string newPassword)
public static bool ResetPassword (string userDN, string newPassword)
{
	var directoryEntry = new DirectoryEntry();
	try
	{
		using(directoryEntry = new DirectoryEntry(userDN))
		{
			directoryEntry.Invoke("SetPassword", new object[] {newPassword});
			directoryEntry.Properties["LockOutTime"].Value = 0; // unlock account
			directoryEntry.Properties["pwdLastSet"].Value = 0; // force change at next logon
			directoryEntry.CommitChanges();
			//directoryEntry.Properties["pwdLastSet"].Value = ?
		}
		return true;
	}
	catch (Exception){ return false;}
	finally{ directoryEntry.Close(); }
}

Open in new window


Also, it's possible it may be dependent on your domain controllers in that the change has been applied but the domain controller that particular user uses to login with is not synced with the controller that you made the changes to.
}
0
 
jeffeldAuthor Commented:
Thanks for the comment. I'll try checking the property tomorrow.

There is only one DC in this scenario.

Cheers,
Jeff
0
 
jeffeldAuthor Commented:
Thanks for the comment. I'll try checking the property tomorrow.

There is only one DC in this scenario.

Cheers,
Jeff
0
Ultimate Tool Kit for Technology Solution Provider

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy now.

 
jdavistxCommented:
I know your app is simple and with focused intent, but you generally don't want to swallow exceptions.  It might be swallowing the very exception that explains why it's not working!

I'm sure you're aware, but be sure the account that is running the app has the necessary rights to modify AD.  I think you can impersonate network credentials in the app if you need to.
0
 
jeffeldAuthor Commented:
Good points. I'll clarify...

1) No exception is getting thrown. The apps UI displays a message if the function returns false (and no message is displayed). I also have Visual Studio set to stop when an exception is thrown (and not just caught). The password itself is reset ok, its just the forcing the user to change it when next logging on that "fails".

2) Yes, the account I'm developing this under (and the two accounts that will ultimately use it) are Domain Admins.

Cheers,
Jeff
0
 
jdavistxCommented:
I'm curious to see what value you have when retrieving after the commit.
0
 
jeffeldAuthor Commented:
Right. Sorted!

I was at a total dead end with this. No amount of messing about would let me see any details of what pwdLastSet was set to before the reset or after. It seemed to be a totally opaque _ComObject as far as .NET was concerned.

I eventually stumbled across this: http://www.rlmueller.net/UserAttributes.htm and from there a spread sheet with all AD attributes listed. In the comments for pwdLastSet was this little gem:

Last time the password was modified. If this value is set to zero the user must set the password at the next logon if password never expires is not set to True

Low and behold, all the target user accounts for this app have Password Never Expires set. Switch it off and setting pwdLastSet to 0 works!
0
 
jeffeldAuthor Commented:
Just for completeness. here is the final bit of code:

    class ADUser
    {
        private const string UserAccountControl = "userAccountControl";
        private const string SetPassword = "SetPassword";
        private const string LockOutTime = "LockOutTime";
        private const string PwdLastSet = "pwdLastSet";
        private const int DontExpirePassword = 0x10000;

        public static bool ResetPassword (string userDN, string newPassword, out bool removedPasswordNeverExpires)
        {
            DirectoryEntry directoryEntry = null;
            removedPasswordNeverExpires = false;

            try
            {
                directoryEntry = new DirectoryEntry(userDN);

                // If you ever need to know anything about AD user attributes,
                // go here: http://www.rlmueller.net/UserAttributes.htm
                
                // We need to check if the account's password is set to Never Expire.
                // If it is, we need to remove it as you cannot force the user to change their 
                // password at next logon AND have the password to never expire.

                int userAccountControl = (int)directoryEntry.Properties[UserAccountControl].Value;

                bool passwordNeverExpires = (userAccountControl & DontExpirePassword) == DontExpirePassword;
                if (passwordNeverExpires)
                {
                    // Mask off the password never expires bit and save the changes
                    // back to the directory.

                    userAccountControl = userAccountControl & ~DontExpirePassword;
                    directoryEntry.Properties[UserAccountControl].Value = userAccountControl;
                    directoryEntry.CommitChanges();

                    removedPasswordNeverExpires = true;
                }

                // Set the password, unlock the account and force the user to change password at next logon

                directoryEntry.Invoke(SetPassword, new object[] {newPassword});
                directoryEntry.Properties[LockOutTime].Value = 0; // unlock account
                directoryEntry.Properties[PwdLastSet].Value = 0; // force change at next logon
                
                directoryEntry.CommitChanges();

                // NB: We do not attempt to restore the password never expires bit to the
                // userAccountControl flags. To do so would remove the change password at next logon
                // account option, as the two are mutually exclusive.
                
            }
            catch (Exception)
            {
                return false;
            }
            finally
            {
                if (directoryEntry != null)
                {
                    directoryEntry.Close();
                    directoryEntry.Dispose();
                }
            }
            
            return true;
        }
    }
}

Open in new window

0
 
jdavistxCommented:
Good deal!

For brevity, you may consider something like the following

class ADUser
{
	private enum ADAttribute
	{
		userAccountControl,
		SetPassword,
		LockOutTime,
		pwdLastSet
	}

	private const int dontExpirePassword = 0x10000;

	public static bool ResetPassword (string userDN, string newPassword, out bool removedPasswordNeverExpires)
	{
		var directoryEntry = new DirectoryEntry();
		removedPasswordNeverExpires = false;

		try
		{
			using(directoryEntry = new DirectoryEntry(userDN))
			{
				int userAccountControl = (int)directoryEntry.Properties[ADAttribute.userAccountControl.ToString()].Value;

				if ((userAccountControl & dontExpirePassword) == dontExpirePassword)
				{
					userAccountControl = userAccountControl & ~dontExpirePassword;
					directoryEntry.Properties[ADAttribute.userAccountControl.ToString()].Value = userAccountControl;
					directoryEntry.CommitChanges();
					removedPasswordNeverExpires = true;
				}

				directoryEntry.Invoke(ADAttribute.SetPassword.ToString(), new object[] {newPassword});
				directoryEntry.Properties[ADAttribute.LockOutTime.ToString()].Value = 0;
				directoryEntry.Properties[ADAttribute.pwdLastSet.ToString()].Value = 0;
				directoryEntry.CommitChanges();
			}
		}
		catch (Exception){ return false; }
		finally{ directoryEntry.Close(); }
				
		return true;
	}
}

Open in new window


If you use an object that implements IDisposable (such as DirectoryEntry), you can enclose it within a using(){} block, and it will close/dispose itself upon reaching the end of the block.

In the finally block, you can simply do .Close() to ensure that any resources associated with the DirectoryEntry are released.  The Close() method will also call Dispose()
0
 
jeffeldAuthor Commented:
None of the websites I initially came across mentioned the preconditions required for using the pwdLastSet AD attribute.

To reiterate: setting pwdLastSet to 0 only works if the account password has an expiry date. If the password is set to never expire, setting pwdLastSet to force a password change at next logon has no effect.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Creating Active Directory Users from a Text File

If your organization has a need to mass-create AD user accounts, watch this video to see how its done without the need for scripting or other unnecessary complexities.

  • 6
  • 4
Tackle projects and never again get stuck behind a technical roadblock.
Join Now