Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people, just like you, are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
Solved

Programmatically forcing a password change

Posted on 2011-03-10
10
2,819 Views
Last Modified: 2012-05-11
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
Comment
Question by:jeffeld
  • 6
  • 4
10 Comments
 
LVL 7

Expert Comment

by:jdavistx
ID: 35100765
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
 

Author Comment

by:jeffeld
ID: 35100819
Thanks for the comment. I'll try checking the property tomorrow.

There is only one DC in this scenario.

Cheers,
Jeff
0
 

Author Comment

by:jeffeld
ID: 35100824
Thanks for the comment. I'll try checking the property tomorrow.

There is only one DC in this scenario.

Cheers,
Jeff
0
VMware Disaster Recovery and Data Protection

In this expert guide, you’ll learn about the components of a Modern Data Center. You will use cases for the value-added capabilities of Veeam®, including combining backup and replication for VMware disaster recovery and using replication for data center migration.

 
LVL 7

Expert Comment

by:jdavistx
ID: 35100880
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
 

Author Comment

by:jeffeld
ID: 35101096
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
 
LVL 7

Expert Comment

by:jdavistx
ID: 35103124
I'm curious to see what value you have when retrieving after the commit.
0
 

Accepted Solution

by:
jeffeld earned 0 total points
ID: 35107294
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
 

Author Comment

by:jeffeld
ID: 35107551
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
 
LVL 7

Expert Comment

by:jdavistx
ID: 35108411
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
 

Author Closing Comment

by:jeffeld
ID: 35145252
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

Featured Post

Free learning courses: Active Directory Deep Dive

Get a firm grasp on your IT environment when you learn Active Directory best practices with Veeam! Watch all, or choose any amount, of this three-part webinar series to improve your skills. From the basics to virtualization and backup, we got you covered.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

In this article, I am going to show you how to simulate a multi-site Lab environment on a single Hyper-V host. I use this method successfully in my own lab to simulate three fully routed global AD Sites on a Windows 10 Hyper-V host.
This article shows how to deploy dynamic backgrounds to computers depending on the aspect ratio of display
This tutorial will walk an individual through the process of configuring their Windows Server 2012 domain controller to synchronize its time with a trusted, external resource. Use Google, Bing, or other preferred search engine to locate trusted NTP …
This video shows how to use Hyena, from SystemTools Software, to bulk import 100 user accounts from an external text file. View in 1080p for best video quality.

838 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question