Link to home
Start Free TrialLog in
Avatar of jeffeld
jeffeld

asked on

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
Avatar of jdavistx
jdavistx

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.
}
Avatar of jeffeld

ASKER

Thanks for the comment. I'll try checking the property tomorrow.

There is only one DC in this scenario.

Cheers,
Jeff
Avatar of jeffeld

ASKER

Thanks for the comment. I'll try checking the property tomorrow.

There is only one DC in this scenario.

Cheers,
Jeff
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.
Avatar of jeffeld

ASKER

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
I'm curious to see what value you have when retrieving after the commit.
ASKER CERTIFIED SOLUTION
Avatar of jeffeld
jeffeld

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of jeffeld

ASKER

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

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()
Avatar of jeffeld

ASKER

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.