Skip to content

Instantly share code, notes, and snippets.

@PanosGreg
Last active April 24, 2025 17:08
Show Gist options
  • Save PanosGreg/37b55c1c1f50a73f831dd69c1c8e906a to your computer and use it in GitHub Desktop.
Save PanosGreg/37b55c1c1f50a73f831dd69c1c8e906a to your computer and use it in GitHub Desktop.
Check if a password meets a minimum required complexity
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