Last active
April 24, 2025 17:08
-
-
Save PanosGreg/37b55c1c1f50a73f831dd69c1c8e906a to your computer and use it in GitHub Desktop.
Check if a password meets a minimum required complexity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Test-PasswordComplexity { | |
<# | |
.SYNOPSIS | |
It checks if a password meets the minimum required password complexity | |
.EXAMPLE | |
Test-PasswordComplexity -Password 123asdASD -MinComplex 2 -MinLength 6 -MinUpper 2 -MinLower 2 -MinDigits 2 -MinSpecial 2 | |
Returns True. Because the password is more than 6 characters long. It is actually 9 characters long. | |
And it has more than 2 different character types. Namely it has lower, upper and numbers which are 3 different types. | |
And out of those character types is has the minimum number of characters required for each type. | |
Namely it has 3 lower-case, 3 upper-case and 3 numbers. | |
.EXAMPLE | |
Test-PasswordComplexity 123asdASD | |
.EXAMPLE | |
Test-PasswordComplexity 123asdASD -Exclude `',`",-,/ | |
NOTES | |
Author: Panos Grigoriadis | |
Date: 24-Apr-2025 | |
Usually these are all the special characters: | |
" ' ! @ # £ $ % ? - _ = + . , @ | ~ * : ; & ( ) [ ] { } \ / < > | |
and also the white-space | |
Note: obviously this entire function can also be written in Pester as a test. | |
But if you don't want the dependency from Pester, then this function should suffice. | |
Note2: even though this function can easily check a small number of passwords in a foreach loop | |
it is not written with performance in mind if you need to check thousands of passwords. | |
#> | |
[OutputType([bool],[psobject])] # <-- default is [bool], and with -PassThru it returns a [psobject] | |
[CmdletBinding(DefaultParameterSetName='PlainString')] | |
param ( | |
[Parameter(Mandatory=$true,Position=0,ParameterSetName='PlainString')] | |
[string]$Password, | |
[Parameter(Mandatory=$true,Position=0,ParameterSetName='SecureString')] | |
[System.Security.SecureString]$SecurePassword, | |
[ValidateRange(1,4)] | |
[int]$MinComplexity = 3, | |
[byte]$MinLength = 8, | |
[byte]$MinUpper = 1, | |
[byte]$MinLower = 1, | |
[byte]$MinDigits = 1, | |
[byte]$MinSpecial = 1, | |
[char[]]$Exclude, # <-- optional parameter, to exclude specific characters | |
[switch]$PassThru | |
) | |
# Convert secure string to a regular string | |
if ($PSCmdlet.ParameterSetName -eq 'SecureString') { | |
$BinaryString = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword) | |
$Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BinaryString) | |
# Note: PSv5 does not support ConvertFrom-SecureString -AsPlainText. | |
# Only PSv7+ has that option, hence the .NET classes. | |
} | |
# This variable increments if the generated password meets each complexity requirement | |
$Complexity = 0 | |
# Minimum upper case characters | |
$UpperCharCount = [RegEx]::Matches($Password,'[A-Z]').Count | |
$HasUpperChars = $Password -cmatch '[A-Z]' | |
$HasMinUpperCount = $UpperCharCount -ge $MinUpper | |
if ($HasUpperChars -and $HasMinUpperCount) {$Complexity++} | |
elseif (-not $HasUpperChars) {Write-Verbose 'The password does not have any Upper-Case characters'} | |
elseif (-not $HasMinUpperCount) {Write-Verbose "The password should have at least $MinUpper Upper-Case characters but had only $UpperCharCount"} | |
# Minimum lower case characters | |
$LowerCharCount = [RegEx]::Matches($Password,'[a-z]').Count | |
$HasLowerChars = $Password -cmatch '[a-z]' | |
$HasMinLowerCount = $LowerCharCount -ge $MinLower | |
if ($HasLowerChars -and $HasMinLowerCount) {$Complexity++} | |
elseif (-not $HasLowerChars) {Write-Verbose 'The password does not have any Lower-Case characters'} | |
elseif (-not $HasMinLowerCount) {Write-Verbose "The password should have at least $MinLower Lower-Case characters but had only $LowerCharCount"} | |
# Minimum number of digits | |
$DigitCharCount = [RegEx]::Matches($Password,'[0-9]').Count | |
$HasDigitChars = $Password -match '[0-9]' | |
$HasMinDigitCount = $DigitCharCount -ge $MinDigits | |
if ($HasDigitChars -and $HasMinDigitCount) {$Complexity++} | |
elseif (-not $HasDigitChars) {Write-Verbose 'The password does not have any Numbers'} | |
elseif (-not $HasMinDigitCount) {Write-Verbose "The password should have at least $MinDigits Numbers but had only $DigitCharCount"} | |
# Minimum special characters | |
$SpecialCharCount = [RegEx]::Matches($Password,'[^a-zA-Z0-9]').Count | |
$HasSpecialChars = $Password -match '[^a-zA-Z0-9]' | |
$HasMinSpecialCnt = $SpecialCharCount -ge $MinSpecial | |
if ($HasSpecialChars -and $HasMinSpecialCnt) {$Complexity++} | |
elseif (-not $HasSpecialChars) {Write-Verbose 'The password does not have any Special characters'} | |
elseif (-not $HasMinSpecialCnt) {Write-Verbose "The password should have at least $MinSpecial Special characters but had only $SpecialCharCount"} | |
# Excluded characters | |
if ($Exclude.Count -gt 0) { | |
$ExcludedChars = $Exclude -join '' | |
$HasExcludedChars = $Password -cmatch "[$ExcludedChars]" | |
if ($HasExcludedChars) {Write-Verbose "The password contains excluded characters: $ExcludedChars"} | |
} | |
else {$HasExcludedChars = $false ; $ExcludedChars = $null} | |
# finally calculate the result | |
$Result = $Complexity -ge $MinComplexity -and $Password.Length -ge $MinLength -and -not $HasExcludedChars | |
if ($PassThru) { | |
[PSCustomObject] @{ | |
PSTypeName = 'Password.Complexity' | |
Success = $Result | |
Complexity = $Complexity | |
Length = $Password.Length | |
UpperCase = $UpperCharCount | |
LowerCase = $LowerCharCount | |
Numbers = $DigitCharCount | |
Special = $SpecialCharCount | |
Exclusions = $ExcludedChars | |
} | |
} | |
else {Write-Output $Result} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment