Friday, September 18, 2009

VBScript – Set User Password to Never Expire

A while back I fought VBScript to create a user account with specific flags set by default. One we needed was the "Password never expires" value. At first I used net. That got me a little ways, but, nowhere near what I needed. With work pressing, I eventually gave up and moved on just telling folks, do it manually; it's not that hard anyway. Well, as fate would have it, user creation scripts came back around. This time, I must have hit the Google "find it" button the right way today. I hit this link:

http://www.activexperts.com/activmonitor/windowsmanagement/adminscripts/usersgroups/localusers/#ConfigLocalUserPassNeverExp.htm

Wanting to try it out, I ran the following script after creating an account called test and manually setting this flag via the user's GUI from the computer management console.

Set objNetwork = WScript.CreateObject("WScript.Network")
strComputer = CStr(objNetwork.ComputerName)
WScript.Echo strComputer
Set objUser = GetObject("WinNT://" & strComputer & "/test")
objUserFlags = objUser.Get("UserFlags")
WScript.Echo objUserFlags


As it turned out, it worked! I was stoked. I love when old problems finally get solved. It's got such a sense of resolution. As it turns out, if you want to dig into the enum used for these values, Microsoft has the UserAccessControl definition here:

http://msdn.microsoft.com/en-us/library/aa772300(VS.85).aspx

Denoted specifically in the link are these notes:

"The ADS_USER_FLAG_ENUM enumeration defines the flags used for setting user properties in the directory. These flags correspond to values of the userAccountControl attribute in Active Directory when using the LDAP provider, and the userFlags attribute when using the WinNT system provider."


Here the explicit enumeration:


typedef enum  {

  ADS_UF_SCRIPT                                   = 1,         // 0x1

  ADS_UF_ACCOUNTDISABLE                           = 2,         // 0x2

  ADS_UF_HOMEDIR_REQUIRED                         = 8,         // 0x8

  ADS_UF_LOCKOUT                                  = 16,        // 0x10

  ADS_UF_PASSWD_NOTREQD                           = 32,        // 0x20

  ADS_UF_PASSWD_CANT_CHANGE                       = 64,        // 0x40

  ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED          = 128,       // 0x80

  ADS_UF_TEMP_DUPLICATE_ACCOUNT                   = 256,       // 0x100

  ADS_UF_NORMAL_ACCOUNT                           = 512,       // 0x200

  ADS_UF_INTERDOMAIN_TRUST_ACCOUNT                = 2048,      // 0x800

  ADS_UF_WORKSTATION_TRUST_ACCOUNT                = 4096,      // 0x1000

  ADS_UF_SERVER_TRUST_ACCOUNT                     = 8192,      // 0x2000

  ADS_UF_DONT_EXPIRE_PASSWD                       = 65536,     // 0x10000

  ADS_UF_MNS_LOGON_ACCOUNT                        = 131072,    // 0x20000

  ADS_UF_SMARTCARD_REQUIRED                       = 262144,    // 0x40000

  ADS_UF_TRUSTED_FOR_DELEGATION                   = 524288,    // 0x80000

  ADS_UF_NOT_DELEGATED                            = 1048576,   // 0x100000

  ADS_UF_USE_DES_KEY_ONLY                         = 2097152,   // 0x200000

  ADS_UF_DONT_REQUIRE_PREAUTH                     = 4194304,   // 0x400000

  ADS_UF_PASSWORD_EXPIRED                         = 8388608,   // 0x800000

  ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION   = 16777216   // 0x1000000

} ADS_USER_FLAG_ENUM;
The one I had been looking for all along was  

ADS_UF_DONT_EXPIRE_PASSWD = 65536, // 0x10000


You can compound these values rather easily if you don't know what the actual flag you need is. Here's an example using some simple numbers in hex format:

hex1 = &h1
hex2 = &h10
WScript.Echo CInt(hex1) 'Will evaluate to 1
Wscript.Echo CInt(hex2) 'will evaluate to 16
WScript.Echo hex1 + hex2

The output for this script is

1
16
17

For instance, if you want to combine the ADS_UF_PASSWD_CANT_CHANGE and the ADS_UF_DONT_EXPIRE_PASSWD values, do this in VBScript:

Const ADS_UF_PASSWD_CANT_CHANGE = 0&40
Const ADS_UF_DONT_EXPIRE_PASSWD = 0&10000
Dim customUserFlag = ADS_UF_PASSWD_CANT_CHANGE + ADS_UF_DONT_EXPIRE_PASSWD

Another way you can accomplish this is to use the OR function. For example:

Const ADS_UF_PASSWD_CANT_CHANGE = 0&40 '64
Const ADS_UF_DONT_EXPIRE_PASSWD = 0&10000 '65356
Dim customUserFlag = ADS_UF_PASSWD_CANT_CHANGE + ADS_UF_DONT_EXPIRE_PASSWD

This will output:

&h10040 = 65600

So, you can simply add constants, as outlined based on the enumeration, and compound those based on the value you want. Or, you can use the OR technique outlined in the script above.

When I applied this back to my user account I knew I only had 5 controls that I could manipulate via the GUI. After combining all the options, here's what I concluded are the most direct value combinations accessible via the interface. For each decimal representation I provide the equivalent hex and binary values. These lists indicate which of the 5 controls are set (checked), unset (unchecked) or disabled (inactive).

513 - &h201 - 1000000001
-unset:
-- User must change password at next logon
-- User cannot change password
-- password never expires
-- account is locked out
-disabled:
-- account is locked out

515 - &h201 - 1000000011
-set
-- Account is disabled-unset
-- User must change password at next logon
-- User cannot change password
-- password never expires
-disabled:
-- account is locked out

579 - &h243 - 1001000011
-set
-- Account is disabled
-- User cannot change password
-unset
-- password never expires
-disabled:
-- User must change password at next logon
-- account is locked out

66051 - &h10203 - 10000001000000011
-set
-- Account is disabled
-- password never expires
-unset
-- User cannot change password
-disabled:
-- User must change password at next logon
-- account is locked out

66115 - &h10243 - 10000001001000011
-set
-- Account is disabled
-- User cannot change password
-- password never expires
-disabled:
-- User must change password at next logon
-- account is locked out

8389121 - &h800201 - 100000000000001000000001
-set
-- User must change password at next logon
-unset
-- Account is disabled
-disabled:
-- User cannot change password
-- password never expires
-- account is locked out

8389123 - &h800203 - 100000000000001000000011
-set
-- User must change password at next logon
-- Account is disabled
Account is disabled
-disabled:
-- User cannot change password
-- password never expires
-- account is locked out

From this list, it is easy to see how the bits work for this enumeration.

Although it is clear there are additional options that can be explored to set up more advanced user accounts, this covers the majority of what I needed to learn about the Password never expires option.

Below is a script that will create a new account and set the password to never expire. After the account is created it will display the flag value. If you are using cscript as your default shell interface, it will display in the cli, whereas a default of wscript will prompt a single dialog. If you are unsure of which you are using, set it to wscript (it's easier to know) by starting a cmd shell and typing:

wscript //h:wscript

then hitting [Enter]. Here is the script itself

'Declare constants for non-expiring password
Const ADS_UF_DONT_EXPIRE_PASSWD = &h10000

'create local machine reference and get machine name
Set objNetwork = WScript.CreateObject("WScript.Network")
strComputer = CStr(objNetwork.ComputerName)

'create reference to WinNT user store (localmachine)
Set colAccounts = GetObject("WinNT://" & strComputer & "")

'Create new user account
Set objUser = colAccounts.Create("user", "Admin2")

'Set passwordobj
User.SetPassword("H@rdP@ssw0rd")

'Retrieve and set UserFlagsobjPasswordExpirationFlag = ADS_UF_DONT_EXPIRE_PASSWD
objUser.Put "userFlags", objPasswordExpirationFlag
objUser.SetInfo

'Validate userFlagsobjUserFlags = objUser.Get("UserFlags")
WSCript.Echo objUserFlags

0 comments:

Post a Comment