Recently, a client posed a question: "Do you have a PowerShell script to set the local password policy on our workstations?" To which my initial response was no, but that didn't mean I couldn't create one. Less than an hour later, I had one.
Enter Set-LocalPasswordPolicy
I developed a function known as Set-LocalPasswordPolicy. The objective of this function is to adjust various settings related to the local password policy on a Windows system.
Function Set-LocalPasswordPolicy {
<#
.DESCRIPTION
Sets the local password policy
.SYNOPSIS
Sets the local password policy
.PARAMETER PasswordComplexity
Specifies whether passwords must meet complexity requirements.
.PARAMETER MinimumPasswordLength
Specifies the minimum number of characters that passwords must contain.
.PARAMETER MinimumPasswordAge
Specifies the minimum number of days that passwords must be used before they can be changed.
.PARAMETER MaximumPasswordAge
Specifies the maximum number of days that passwords can be used before they must be changed.
.PARAMETER PasswordHistorySize
Specifies the number of new passwords that have to be associated with a user account before an old password can be reused.
.PARAMETER LockoutBadCount
Specifies the number of failed logon attempts that causes a user account to be locked out.
.PARAMETER ResetLockoutCount
Specifies the number of minutes that must elapse after a failed logon attempt before the failed logon attempt counter is reset to 0 bad logon attempts.
.PARAMETER LockoutDuration
Specifies the number of minutes a user account is locked out after the number of failed logon attempts specified by the LockoutBadCount parameter is exceeded.
.EXAMPLE
Set-LocalPasswordPolicy -PasswordComplexity $true -MinimumPasswordLength 8 -MinimumPasswordAge 1 -MaximumPasswordAge 90 -PasswordHistorySize 24 -LockoutBadCount 5 -ResetLockoutCount 15 -LockoutDuration 15
.NOTES
Author: Gabe Delaney
Date: 05/23/2023
Version: 1.0
Name: Set-LocalPasswordPolicy
Version History:
1.0 - Initial release - 05/23/2023 - Gabe Delaney
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param (
[Parameter(Mandatory=$true)]
[boolean]$PasswordComplexity,
[Parameter(Mandatory=$false)]
[int]$MinimumPasswordLength,
[Parameter(Mandatory=$false)]
[int]$MinimumPasswordAge,
[Parameter(Mandatory=$false)]
[int]$MaximumPasswordAge,
[Parameter(Mandatory=$false)]
[int]$PasswordHistorySize,
[Parameter(Mandatory=$false)]
[int]$LockoutBadCount,
[Parameter(Mandatory=$false)]
[int]$ResetLockoutCount,
[Parameter(Mandatory=$false)]
[int]$LockoutDuration
)
Begin {
# Check if the current user is elevated as admin
$principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$is_admin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
If (!$is_admin) {
Write-Error "You must run this function as an administrator."
Break
}
# Export the current local security policy to a file
$outfile = "$Env:TEMP\secpol.cfg"
secedit /export /cfg $outfile | Out-Null
# Read the current local security policy file
$sec_pol_cfg = Get-Content $outfile
} Process {
# If parameters are specified, update the local security policy file
If ($PSCmdlet.ShouldProcess("$Env:COMPUTERNAME")) {
Foreach ($key in $PSBoundParameters.Keys) {
If (@([System.Management.Automation.Cmdlet]::CommonParameters,"Confirm") -contains $key) {
Continue
}
[int]$value = $PSBoundParameters[$key]
$sec_pol_cfg = $sec_pol_cfg -replace ("$key = \d+", "$key = $value")
}
# Write the updated local security policy file
$sec_pol_cfg | Out-File $outfile -Force
secedit /configure /db c:\windows\security\local.sdb /cfg $outfile /areas SECURITYPOLICY | Out-Null
# Remove the local security policy file
Remove-Item $outfile -Force | Out-Null
}
} End {
Return
}
}
The function offers a range of parameters to customize the policy settings. These include settings such as PasswordComplexity, MinimumPasswordLength, MinimumPasswordAge, MaximumPasswordAge, PasswordHistorySize, and several account lockout settings like LockoutBadCount, ResetLockoutCount, and LockoutDuration.
For instance, if you wanted to ensure password complexity, set a minimum password length of 8 characters, and specify that passwords must be at least 1 day old before they can be changed, you could do so using the command:
Setting lockout duration to 30 minutes:
Under the hood
PowerShell doesn't have a dedicated cmdlet or .NET method to directly alter the security policy. I mean, if it did, you probably wouldn't be here. It leverages `secedit.exe`. So, how does it work? The function uses `secedit.exe` to export the current local security policy to a temporary file. It then reads this file into a variable. The function manipulates the text of the exported policy file to adjust the settings based on the input parameters with some simple regex.
$sec_pol_cfg = Get-Content $outfile
} Process {
# If parameters are specified, update the local security policy file
If ($PSCmdlet.ShouldProcess("$Env:COMPUTERNAME")) {
Foreach ($key in $PSBoundParameters.Keys) {
If (@([System.Management.Automation.Cmdlet]::CommonParameters,"Confirm") -contains $key) {
Continue
}
[int]$value = $PSBoundParameters[$key]
$sec_pol_cfg = $sec_pol_cfg -replace ("$key = \d+", "$key = $value")
}
...
After making the necessary adjustments, it uses `secedit.exe` again to import the updated policy back into the system, and the temporary policy file is deleted.
$sec_pol_cfg | Out-File $outfile -Force
secedit /configure /db c:\windows\security\local.sdb /cfg $outfile /areas SECURITYPOLICY | Out-Null
Conclusion
While I wouldn't recommend this as a first option in lieu of Group Policy or a true endpoint management solution, the Set-LocalPasswordPolicy function is an unconventional approach to modifying local password policy settings in a Windows environment. Though born out of necessity, it serves as an illuminating case study of the flexibility and power of PowerShell scripting.
To try it out for yourself, you can download the Set-LocalPasswordPolicy function from my GitHub repository.