Last active
September 9, 2025 04:17
-
-
Save dadatuputi/d2ead8ee219da5922bbcbf6b7325857f to your computer and use it in GitHub Desktop.
Powershell Script to generate new DS Login password
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
| # To install, copy Invoke-WebRequest command below in your favorite scripts directory | |
| # Invoke-WebRequest -Uri "https://gist.githubusercontent.com/dadatuputi/d2ead8ee219da5922bbcbf6b7325857f/raw/New-DSLoginPassword.ps1" -OutFile "New-DSLoginPassword.ps1" | |
| param( | |
| [int]$Length = 128, | |
| [Parameter()] | |
| [Alias('h', '?')] | |
| [switch]$Help | |
| ) | |
| if ($Help) { | |
| Write-Host @" | |
| Generates DS Logon compliant passwords | |
| PARAMETERS | |
| -Length <int> | |
| Length of password (15-128 chars) | |
| Default: 128 | |
| -Help | |
| Shows this help message | |
| BEHAVIOR | |
| Always prompts for current password. If empty password entered, | |
| generates completely new password. If current password provided, | |
| ensures new password meets '8 different characters' requirement. | |
| EXAMPLES | |
| Generate password (will prompt for current): | |
| .\New-DSLoginPassword.ps1 -Length 30 | |
| Show help: | |
| .\New-DSLoginPassword.ps1 -Help | |
| "@ | |
| exit | |
| } | |
| # Always prompt for current password | |
| $currentPass = Read-Host "Enter current password (press Enter for none)" -AsSecureString | |
| # Check if password is empty without converting to plain text | |
| $isEmpty = $currentPass.Length -eq 0 | |
| # Define character sets | |
| $lowercase = 'abcdefghijklmnopqrstuvwxyz' | |
| $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | |
| $numbers = '0123456789' | |
| $special = '@_#/,;~`%&='':!$*+().{}|?><^[]-"\' # String literal, except single quote ' is escaped with double single quotes '' | |
| # Number of characters that must be different from the current password - not what you think, see | |
| # https://www.reddit.com/r/VeteransBenefits/comments/1avm2wf/satisfying_ds_logon_password_requirements/ | |
| $newPWCharsDifferent = 8 | |
| function New-DSLoginPassword { | |
| param ( | |
| [int]$Length = 128, | |
| [System.Security.SecureString]$CurrentPassword | |
| ) | |
| # If a current password is provided, include $newPWCharsDifferent characters to guarantee it passes | |
| if ($CurrentPassword -and $CurrentPassword.Length -gt 0) { | |
| # Convert SecureString to plain text only when necessary for comparison | |
| $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($CurrentPassword) | |
| $plainCurrentPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) | |
| # Ensure we have at least $newPWCharsDifferent characters not in the old password | |
| $charsNotInCurrent = ($lowercase + $uppercase + $numbers + $special).ToCharArray() | | |
| Where-Object { !$plainCurrentPassword.Contains($_) } | |
| if ($charsNotInCurrent.Count -lt $newPWCharsDifferent) { | |
| throw "Not enough unique characters available different from current password. You may have to reset your password through the 'Forgot Password' process." | |
| } | |
| # Guarantee at least $newPWCharsDifferent different characters | |
| $guaranteedDifferent = $charsNotInCurrent | Get-Random -Count $newPWCharsDifferent | |
| # Clean up sensitive data immediately | |
| [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR) | |
| $plainCurrentPassword = $null | |
| } | |
| # Limits of when we need to reserve characters for future passwords | |
| $upperLimit = ($lowercase + $uppercase + $numbers + $special).Length | |
| $lowerLimit = $upperLimit - $newPWCharsDifferent | |
| # Calculate reserve count based on length | |
| $reserveCount = if ($Length -ge $upperLimit) { | |
| $newPWCharsDifferent # Above $upperLimit, we need to reserve $newPWCharsDifferent characters | |
| } elseif ($Length -lt $lowerLimit) { | |
| 0 # Below $lowerLimit, no need to reserve any | |
| } else { | |
| $Length - $lowerLimit # Between $lowerLimit and $upperLimit, reserve number of characters $Length goes above $lowerLimit | |
| } | |
| # Remove $reserveCount characters so a valid password can be generated next time | |
| if ($reserveCount -gt 0) { | |
| $allChars = $lowercase + $uppercase + $numbers + $special | |
| $reservedChars = $allChars.ToCharArray() | Get-Random -Count $reserveCount | |
| foreach ($char in $reservedChars) { | |
| $lowercase = $lowercase.Replace($char.ToString(), '') | |
| $uppercase = $uppercase.Replace($char.ToString(), '') | |
| $numbers = $numbers.Replace($char.ToString(), '') | |
| $special = $special.Replace($char.ToString(), '') | |
| } | |
| Write-Host "Reserved $reserveCount characters for future use: $(-join $reservedChars | Sort-Object)" | |
| } | |
| # Meet the password complexity requirements with 1 character from each type | |
| $password = @( | |
| $lowercase[(Get-Random -Maximum $lowercase.Length)], | |
| $uppercase[(Get-Random -Maximum $uppercase.Length)], | |
| $numbers[(Get-Random -Maximum $numbers.Length)], | |
| $special[(Get-Random -Maximum $special.Length)] | |
| ) | |
| # Add necessary unique characters so this will be accepted by DS Login | |
| if ($CurrentPassword) { | |
| $password += $guaranteedDifferent | |
| } | |
| # Fill the rest randomly - duplicates allowed | |
| $allCharsArray = ($lowercase + $uppercase + $numbers + $special).ToCharArray() | |
| while ($password.Length -lt $Length) { | |
| $password += $allCharsArray[(Get-Random -Maximum $allCharsArray.Length)] | |
| } | |
| # Shuffle the password | |
| return -join ($password | Sort-Object {Get-Random}) | |
| } | |
| # Validation test | |
| function Test-DSLoginPassword { | |
| param( | |
| [System.Security.SecureString]$CurrentPassword, | |
| [string]$NewPassword | |
| ) | |
| # Convert SecureString temporarily for comparison | |
| $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($CurrentPassword) | |
| $plainCurrentPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) | |
| $differentChars = $NewPassword.ToCharArray() | | |
| Where-Object { !$plainCurrentPassword.Contains($_) } | | |
| Select-Object -Unique | Sort-Object | |
| Write-Host "Number of unique characters in new password not present in current password: $($differentChars.Count)" | |
| Write-Host "Different characters: $(-join $differentChars)" | |
| # Clean up | |
| [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR) | |
| $plainCurrentPassword = $null | |
| } | |
| # Execution | |
| if ($isEmpty) { | |
| Write-Host "No current password provided - generating completely new password" | |
| $newPass = New-DSLoginPassword -Length $Length | |
| } else { | |
| Write-Host "Current password provided - ensuring new password is sufficiently different" | |
| $newPass = New-DSLoginPassword -Length $Length -CurrentPassword $currentPass | |
| Test-DSLoginPassword -CurrentPassword $currentPass -NewPassword $newPass | |
| } | |
| # Clean up SecureString | |
| $currentPass.Dispose() | |
| Write-Host "New password: $newPass" | |
| $newPass | Set-Clipboard | |
| Write-Host "Password copied to clipboard" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment