Getting password complexity requirements with VBScript and Powershell

Written by:
Published on

I was helping with a method to check a user’s password against the domain via a program called UMRA. The idea was to use basic JavaScript validation for some of the password checks, but then to send the validation to the server for true verification. There currently is no process for checking a password without first creating the account and checking the error code from Active Directory. I did some searching and found various different methods from Microsoft, but nothing that hit the nail on the head. Then doing one final Google search for “vbscript domain policy” lead me to this website.

This script provides the basic information for the default domain password policy.

Const MIN_IN_DAY = 1440
Const SEC_IN_MIN = 60
 
Set objDomain = GetObject("WinNT://fabrikam")
Set objAdS = GetObject("LDAP://dc=fabrikam,dc=com")
 
intMaxPwdAgeSeconds = objDomain.Get("MaxPasswordAge")
intMinPwdAgeSeconds = objDomain.Get("MinPasswordAge")
intLockOutObservationWindowSeconds = objDomain.Get("LockoutObservationInterval")
intLockoutDurationSeconds = objDomain.Get("AutoUnlockInterval")
intMinPwdLength = objAds.Get("minPwdLength")
 
intPwdHistoryLength = objAds.Get("pwdHistoryLength")
intPwdProperties = objAds.Get("pwdProperties")
intLockoutThreshold = objAds.Get("lockoutThreshold")
intMaxPwdAgeDays = _
  ((intMaxPwdAgeSeconds/SEC_IN_MIN)/MIN_IN_DAY) & " days"
intMinPwdAgeDays = _
  ((intMinPwdAgeSeconds/SEC_IN_MIN)/MIN_IN_DAY) & " days"
intLockOutObservationWindowMinutes = _
  (intLockOutObservationWindowSeconds/SEC_IN_MIN) & " minutes"
 
If intLockoutDurationSeconds <> -1 Then
  intLockoutDurationMinutes = _
(intLockOutDurationSeconds/SEC_IN_MIN) & " minutes"
Else
  intLockoutDurationMinutes = _
    "Administrator must manually unlock locked accounts"
End If

Dim out: out = "" 
out = out & "maxPwdAge = " & intMaxPwdAgeDays & vbCrLf
out = out & "minPwdAge = " & intMinPwdAgeDays & vbCrLf 
out = out & "minPwdLength = " & intMinPwdLength & vbCrLf
out = out & "pwdHistoryLength = " & intPwdHistoryLength & vbCrLf
out = out & "pwdProperties = " & intPwdProperties & vbCrLf
out = out & "lockOutThreshold = " & intLockoutThreshold & vbCrLf
out = out & "lockOutObservationWindow = " & intLockOutObservationWindowMinutes & vbCrLf
out = out & "lockOutDuration = " & intLockoutDurationMinutes & vbCrLf

Note: The script above is copied from the linked website for backup purposes. I give full credit of the above script to the original – undocumented – author. The website claims no responsibility for damages, as neither do I, however the above script is only pulling information. The website also does not list a copyright, so until they contact me you can find this script here as well.

Using the above script as a platform for what I wanted to do, I converted the code to Powershell and added some tests of my own to check if a supplied password is able to be reset on the user account and if the password matches the criteria. The script supplies either the domain password options, or a TRUE/FALSE value if the password works.

Below is the full PowerShell script with simple usage instructions. You can save it to a file like “GPPassword.ps1″ and execute it using powershell with “./GPPassword.ps1 somedomain.local [username] [testpassword]“. The username and testpassword are optional if you want to test a password for a user (eg. for password validation via the web).

$MIN_IN_DAY = 1440;
$SEC_IN_MIN = 60;

#options for pwdProperties
$DOMAIN_PASSWORD_COMPLEX = 1; 
#The password must have a mix of at least two of the following types of characters:
#Uppercase characters
#Lowercase characters
#Numerals
$DOMAIN_PASSWORD_NO_ANON_CHANGE = 2; 
#The password cannot be changed without logging on. Otherwise, if your password has expired, you can change your password and then log on.
$DOMAIN_PASSWORD_NO_CLEAR_CHANGE = 4; 
#Forces the client to use a protocol that does not allow the domain controller to get the plaintext password.
$DOMAIN_LOCKOUT_ADMINS = 8;
#Allows the built-in administrator account to be locked out from network logons.
$DOMAIN_PASSWORD_STORE_CLEARTEXT = 16;
#The directory service is storing a plaintext password for all users instead of a hash function of the password.
$DOMAIN_REFUSE_PASSWORD_CHANGE = 32;
#Removes the requirement that the machine account password be automatically changed every week.
#This value should not be used as it can weaken security.

if($args.Length -gt 0){	
	#Gets the domain arguments
	$FQDN = $args[0];
	$objDomain = [ADSI]"WinNT://$($FQDN)";

	$SplitDomain = $FQDN.Split(".");
	$LDAPDomain = ""
	foreach ($dc in $SplitDomain) { $LDAPDomain += "dc=$($dc),"}
	
	if($LDAPDomain.substring($LDAPDomain.Length-1,1) -eq ","){ 
		$LDAPDomain = $LDAPDomain.substring(0,$LDAPDomain.Length-1); 
	}	


	$objAds = New-Object DirectoryServices.DirectoryEntry("LDAP://$($LDAPDomain)");

	$intMaxPwdAgeSeconds = $objDomain.MaxPasswordAge;
	$intMinPwdAgeSeconds = $objDomain.MinPasswordAge;
	$intLockOutObservationWindowSeconds = $objDomain.LockoutObservationInterval;
	$intLockoutDurationSeconds = $objDomain.AutoUnlockInterval;
	$intMinPwdLength = $objAds.minPwdLength.Value;

	$intPwdHistoryLength = $objAds.pwdHistoryLength.Value;
	$intPwdProperties = $objAds.pwdProperties;
	$intLockoutThreshold = $objAds.lockoutThreshold;

	$intMaxPwdAgeDays = (($intMaxPwdAgeSeconds.Value/$SEC_IN_MIN)/$MIN_IN_DAY).ToString() + " days";
	$intMinPwdAgeDays = (($intMinPwdAgeSeconds.Value/$SEC_IN_MIN)/$MIN_IN_DAY).ToString() + " days";
	$intLockOutObservationWindowMinutes = ($intLockOutObservationWindowSeconds.Value/$SEC_IN_MIN).ToString() + " minutes"; 
	if($intLockoutDurationSeconds -ne -1){
  		$intLockoutDurationMinutes = ($intLockOutDurationSeconds.Value/$SEC_IN_MIN).ToString() + " minutes";
	}else{
  		$intLockoutDurationMinutes = "Administrator must manually unlock locked accounts";
	}


	$domainPasswordOptions = New-Object System.Object
	$domainPasswordOptions |Add-Member -type NoteProperty -name maxPwdAge -value $intMaxPwdAgeDays
	$domainPasswordOptions |Add-Member -type NoteProperty -name minPwdAge -value $intMinPwdAgeDays
	$domainPasswordOptions |Add-Member -type NoteProperty -name minPwdLength -value $intMinPwdLength
	$domainPasswordOptions |Add-Member -type NoteProperty -name pwdHistoryLength -value $intPwdHistoryLength
	$domainPasswordOptions |Add-Member -type NoteProperty -name pwdProperties -value $intPwdProperties


	$domainPasswordOptions |Add-Member -type NoteProperty -name complexPassword -value ([int]($intPwdProperties.ToString()) -band $Domain_PASSWORD_COMPLEX)
	$domainPasswordOptions |Add-Member -type NoteProperty -name noAnonymousChange -value ([int]($intPwdProperties.ToString()) -band $DOMAIN_PASSWORD_NO_ANON_CHANGE)
	$domainPasswordOptions |Add-Member -type NoteProperty -name noCleartextChange -value ([int]($intPwdProperties.ToString()) -band $DOMAIN_PASSWORD_NO_CLEAR_CHANGE)
	$domainPasswordOptions |Add-Member -type NoteProperty -name lockoutAdmins -value ([int]($intPwdProperties.ToString()) -band $DOMAIN_LOCKOUT_ADMINS)
	$domainPasswordOptions |Add-Member -type NoteProperty -name storeCleartext -value ([int]($intPwdProperties.ToString()) -band $DOMAIN_PASSWORD_STORE_CLEARTEXT)
	$domainPasswordOptions |Add-Member -type NoteProperty -name refuseMachineWeeklyPwdChange -value ([int]($intPwdProperties.ToString()) -band $DOMAIN_REFUSE_PASSWORD_CHANGE)

	$domainPasswordOptions |Add-Member -type NoteProperty -name lockOutThreshold -value $intLockoutThreshold
	$domainPasswordOptions |Add-Member -type NoteProperty -name lockOutObservationWindow -value $intLockOutObservationWindowMinutes
	$domainPasswordOptions |Add-Member -type NoteProperty -name lockOutDuration -value $intLockoutDurationMinutes

	if($args.Length -eq 1){	
		#####uncomment the following line to see a report in the logs.
		$domainPasswordOptions
	}elseif($args.Length -eq 3){
		$user = $args[1];
		$searcher = New-object System.DirectoryServices.DirectorySearcher $objAds;
		$searcher.filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$($user)))";
		$user = $searcher.FindOne();
		$username = $user.Properties.Item("samaccountname");
		$userPwdLastSet = [datetime]::fromfiletime($user.Properties.Item("pwdLastSet")[0]);
		$CurrentDate = Get-Date
		$diff = $CurrentDate-$userPwdLastSet
		#$diff
		$passwordCheckResult = $TRUE;

		#check the password age
		if($domainPasswordOptions.minPwdAge -gt $diff.Days -and $passwordCheckResult){
			$passwordCheckResult = $FALSE;
		}

		#check the length
		if(([String]$args[2]).Length -lt $domainPasswordOptions.minPwdLength -and $passwordCheckResult){
			$passwordCheckResult = $FALSE;
		}

		#check if the password contains the username, or the username contains the password
		if((([String]$args[2]).ToLower().Contains(([String]$username).ToLower()) -or ([String]$username).ToLower().Contains(([String]$args[2]).ToLower())) -and $passwordCheckResult){
			$passwordCheckResult = $FALSE;
		}

		#check if the password complexity rule is enabled
		if($domainPasswordOptions.complexPassword -eq $DOMAIN_PASSWORD_COMPLEX -and $passwordCheckResult){
			#check password for complexity
			if($args[2].Length -ge 6){
				$count = 0;
				if($args[2] -match "[A-Z]+"){
					$count++;
				}
				if($args[2] -match "[a-z]+"){
					$count++;
				}
				if($args[2] -match "\\d+"){
					$count++;
				}
				if($args[2] -match "\\W+"){
					$count++;
				}
				if($args[2] -match "\\X+"){
					$count++;
				}
				if($count -ge 3){
					$passwordCheckResult = $TRUE;
				}else{
					$passwordCheckResult = $FALSE;
				}
			}else{
				$passwordCheckResult = $FALSE;
			}
		}
		echo $passwordCheckResult
		exit;
	}else{
		echo "Usage1: ./PasswordOptions.ps1 domain.local [SomeUsername] [testPassword]"
	}
}else{
	echo "Usage: ./PasswordOptions.ps1 domain.local [SomeUsername] [testPassword]"
}

This entry was posted in Active Directory, Powershell, UMRA, VBScript and tagged , , , , , by Allan Bogh. Bookmark the permalink.
Allan Bogh

About Allan Bogh

I'm a lead engineer with Disney ID, developing the next generation login website that business entities will incorporate into their websites.

I'm an Active Directory consultant, programmer, web developer, database developer, and tinkerer. I am also the administrator of The Open Code Project. I created and moderate r/ActiveDirectory and, am the primary developer of Reddit Uppers and Downers Enhanced (a part of RES). I also have a love of robotics, working with the Arduino platform.

Sample Projects:

 

  • Disney Examples to come!
  • ImgAlbum - ImgAlbum is a picture album website that includes speedy image delivery using custom GD2 PHP code and folder/subfolder traversal. Other album websites such as Picasa don't include sub-albums or subfolder uploads, so I am developing my own system where a user can upload an entire folder structure full of images and the system will automatically process the images to create albums.

 

 

  • Reddit Uppers and Downers Enhanced - Lead developer, improved speed significantly, incorporated other developers' additions with attribution, project is a part of RES and is used by tens of thousands of people every day.

 

 

  • RE/Max Properties website - Created MLS replication system, pulling NWMLS XML records using PHP. Created a Google Maps interface to display NWMLS results on the website (no longer in use). Before leaving I created a home value calculator based on the MLS data and make the website into a template system for agents.

 

 

  • Stealth Media Solutions - A website and graphic design company that I used to work for. The website was designed by our designer, I implemented the code with rudimentary means compared to the frameworks available today.

 

 

  • Photography Plus - Another design of Stealth Media Solutions. This website includes a WYSIWYG editor for the owner to modify pages. It uses PHP, and Flash.

 

 

  • Highpointe Church - Flash PoC. Highpointe Church wanted a proof of concept design from Stealth Media Solutions. Our designer made it and I created the webpage in Flash. The customer was very specific about using Flash, although it may not have been the most flexible choice. Custom javascript was developed to resize the background image dynamically (worked best in Firefox and IE).

 




Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>