Ever need to check the complexity of a password that will be used in AD? Ever need to import a list of users or reset their passwords in AD from a predefined list that has been given to you? I have updated my code for my AD Password Complexity check. Check it out.
First off before we can talk about complex passwords, we need to all understand what the criteria of a complex password for an Active Directory account is. It’s quite well defined here – https://technet.microsoft.com/en-us/library/cc786468(v=ws.10).aspx .

As that link exists on the Internet, everyone has their own interpretation and implementation. In my implementation I’ve skipped over looking at the password history. I’m only checking to see that the password is the minimum length and that it follows the complexity rules (if set in AD). Using the link above, here is a summary of the complexity rules:

  • Passwords must not contain the user’s entire samAccountName (Account Name) value or entire displayName (Full Name) value. Both checks are not case sensitive:
    • The samAccountName is checked in its entirety only to determine whether it is part of the password. If the samAccountName is less than three characters long, this check is skipped.

       

    • The displayName is parsed for delimiters: commas, periods, dashes or hyphens, underscores, spaces, pound signs, and tabs. If any of these delimiters are found, the displayName is split and all parsed sections (tokens) are confirmed not to be included in the password. Tokens that are less than three characters in length are ignored, and substrings of the tokens are not checked. For example, the name “Erin M. Hagens” is split into three tokens: “Erin,” “M,” and “Hagens.” Because the second token is only one character long, it is ignored. Therefore, this user could not have a password that included either “erin” or “hagens” as a substring anywhere in the password.

     

  • Passwords must contain characters from three of the following five categories:
    • Uppercase characters of European languages (A through Z, with diacritic marks, Greek and Cyrillic characters)
    • Lowercase characters of European languages (a through z, sharp-s, with diacritic marks, Greek and Cyrillic characters)
    • Base 10 digits (0 through 9)
    • Nonalphanumeric characters: ~!@#$%^&*_-+=`|\(){}[]:;”‘<>,.?/
    • Any Unicode character that is categorized as an alphabetic character but is not uppercase or lowercase. This includes Unicode characters from Asian languages.

Now that we are on the same page of what AD Complexity means, let’s look at some code I wrote.

A Look at the Code

First off, we need to get the password complexity of the AD. So let’s import the ActiveDirectory module and get the password Default Domain Policy setting.

Code Preparation

Import-Module ActiveDirectory
 

To make this code re-usable, I’ll create a function called Test-PasswordForDomain. As you’ve seen from the complexity rules above, I’m going to pass in some variables, but only one is manadory, the password! The other 3 parameters are optional, but will help increase the chances of your password passing the AD complexity rules. If you look above, you’ll see that part of the complexity check is to ensure that the password does not contain the SamAccountName or any part of the display name in the password. If you don’t have those, we can check the other complexity rules, but again, it can’t fully ensure that Active Directory will accept your password.

Create a Function

Function Test-PasswordForDomain {
    Param (
        [Parameter(Mandatory=$true)][string]$Password,
        [Parameter(Mandatory=$false)][string]$AccountSamAccountName = "",
        [Parameter(Mandatory=$false)][string]$AccountDisplayName,
        [Microsoft.ActiveDirectory.Management.ADEntity]$PasswordPolicy = (Get-ADDefaultDomainPasswordPolicy -ErrorAction SilentlyContinue)
    )
<#
	Rest of the code in the blog goes here
#>
    return $false
}
	
    return $false
}

Below are the code snippets that we’ll copy into the function. Then we’ll have the correct tests and make the base function comply as well as we can do the domain policy.

Check 1 – Minimum Password Length

This check is simple. Are we meeting the minimum password length?

    If ($Password.Length -lt $PasswordPolicy.MinPasswordLength) {
        return $false
    }

 

Check 2 – Is the SAM Account Name part of the Password

Again, simple check, that is, if we passed it into the function. If we didn’t, we just skip the check.

    if (($AccountSamAccountName) -and ($Password -match "$AccountSamAccountName")) {
        return $false
    }

 

Check 3 – Is the Display Name part of the Password

Now this is still a simple check, but you have to read the text above that Microsoft provides. It says if ANY PART of the display name that is split by the characters below, the password should fail the complexity rules.

    if ($AccountDisplayName) {
        $tokens = $AccountDisplayName.Split(",.-,_ #`t")
        foreach ($token in $tokens) {
            if (($token) -and ($Password -match "$token")) {
                return $false
            }
        }
    }

 

Check 4 – Complexity Rules for the Password

The code makes sense, but we need to make sure that we satisfy as many combinations of the complexity rules defined above.

  • First cmatch “[A-Z\p{Lu}\s]” – Match any UPPERCASE characters A-Z and spaces, but the funky \p{Lu} also means any UNICODE (accented characters) that are also upper case
  • Second cmatch “[a-z\p{Ll}\s]” – Match any lowercase characters a-z and spaces (Yes I know I check twice for spaces), but the funky \p{Ll} also means any UNICODE (accented characters) that are also lower case
  • [\d] – We found a numerical digit [0-9]
  • [^\w] – \w means [A-Za-z _]. So with a ^ in the front it’s basically saying, any other character. Example: !@#$%^&*()[];

Pretty simple right?

  • Looks like we fulfilled these Microsoft requirements
    • Uppercase characters of European languages (A through Z, with diacritic marks, Greek and Cyrillic characters)
    • Lowercase characters of European languages (a through z, sharp-s, with diacritic marks, Greek and Cyrillic characters)
    • Base 10 digits (0 through 9)
    • Nonalphanumeric characters: ~!@#$%^&*_-+=`|\(){}[]:;”‘<>,.?/
    • Any Unicode character that is categorized as an alphabetic character but is not uppercase or lowercase. This includes Unicode characters from Asian languages. Honestly, I did not test this, it may work with the \p{Ll or Lu} test.

    if ($PasswordPolicy.ComplexityEnabled -eq $true) {
        If (
                 ($Password -cmatch "[A-Z\p{Lu}\s]") `
            -and ($Password -cmatch "[a-z\p{Ll}\s]") `
            -and ($Password -match "[\d]") `
            -and ($Password -match "[^\w]")  
        ) { 
            return $true
        }
    } else {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return $false
&nbsp;&nbsp;&nbsp;&nbsp;}

Testing the Passwords

Now we need an example to work with, so let’s set a few different passwords
Now that the function is built, here are a few of the test scenarios that I used to run through the function.

Test-PasswordForDomain "a1B@"  # Fail - Valid Character Set, but too short
Test-PasswordForDomain "a1B@password"  # Pass - Valid Character Set
Test-PasswordForDomain "ß!1B@PASSWORD" # Pass - Valid Character Set even with unicode characters (Used a lowercase ß)
Test-PasswordForDomain "ẞ!1B@PASSWORD" # Fail - No lowercase (Used an uppercase ẞ)
Test-PasswordForDomain "!äÄöäöAl5lajrnäöäö1" -AccountDisplayName "Allan. Rafuse, Jr" # Fail - Contains JR (case insensitive) which is part of the split apart Display Name
Test-PasswordForDomain "!äÄöäöAl5lanäöäö1" -AccountDisplayName "Allan. Rafuse, Jr" # Pass - JR (case insensitive) removed from the password

 

Enjoy!