Providers/AD/Private/Get-ADTestNameToken.ps1
|
function Get-ADTestNameToken { <# .SYNOPSIS Returns the parts of an account's name that its password may not contain .DESCRIPTION Windows password complexity is usually described as "three of five character classes", and that is only half of it. The check also refuses any password that contains the account's sAMAccountName, or any token of its display name three characters or longer, compared case-insensitively. The display name is split on comma, full stop, hyphen, underscore, space, tab and hash; tokens shorter than three characters are ignored, because they would refuse almost everything. Two things about the seeded accounts make this bite rather than stay theoretical: - The seed prefixes every display name, so "TEST" is a checked token on every single account this module creates. - Several service accounts carry a three-letter word of their own - Web, SQL, API, CRM, ERP, Dev, Log - and a three-letter token is by far the most likely to turn up by chance in a random password. Active Directory reports the refusal as "The password does not meet the length, complexity, or history requirement of the domain", naming none of the three, so the failure reads as a generator that produced something weak rather than one that produced something that happened to spell a word in the account's own name. Measured against the real generator across sixty thousand samples, this refused about one seed run in three hundred - often enough to be seen, rare enough to be dismissed as a fluke. Passing these tokens to New-TestPassword removes it entirely. .PARAMETER DisplayName The display name the account will carry, including any seed prefix. Split as Windows splits it. .PARAMETER SamAccountName The logon name the account will carry. Checked whole, not split. .OUTPUTS System.String[], the tokens a password for this account must not contain. Empty when the name yields nothing long enough to matter. .EXAMPLE PS> Get-ADTestNameToken -DisplayName 'ZZ-TEST-Web Application Service' -SamAccountName 'svc-webapp' DESCRIPTION: Returns the tokens Windows would check for this account OUTPUT: TEST, Web, Application, Service, svc-webapp USE CASE: Passed to New-TestPassword -NotContaining before creating the account .NOTES Author: Jeffrey Stuhr Blog: https://www.techbyjeff.net LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/ #> [CmdletBinding()] [OutputType([string[]])] param( [Parameter()] [AllowNull()] [AllowEmptyString()] [string]$DisplayName, [Parameter()] [AllowNull()] [AllowEmptyString()] [string]$SamAccountName ) # Ordinal-ignore-case, because the comparison Windows makes is case-insensitive and a set # that held both "Test" and "TEST" would only cost extra draws. $tokens = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) if (-not [string]::IsNullOrWhiteSpace($DisplayName)) { foreach ($piece in ($DisplayName -split '[,\.\-_ \t#]+')) { if ($piece.Length -ge 3) { $null = $tokens.Add($piece) } } } # The logon name is checked whole rather than split, so it goes in as it stands. if (-not [string]::IsNullOrWhiteSpace($SamAccountName) -and $SamAccountName.Length -ge 3) { $null = $tokens.Add($SamAccountName) } # A typed empty array rather than a one-element array wrapping an empty one, which is what # the comma operator produces and what a caller counting the result would then see as one # forbidden token that is not there. return [string[]]@($tokens) } |