PowerShell script to enforce local password policies on Windows Server
Local accounts on Windows Server are often the weakest link in credential hygiene. When a host is not joined to Active Directory, runs in a workgroup, or hosts a break-glass administrator, the operating system still relies on the Security Account Manager database and a local security policy to govern password length, complexity, history, and lockout thresholds. Misconfiguration here is routinely flagged in penetration test reports and is one of the easiest items for an attacker to exploit through lateral movement or credential stuffing.
PowerShell gives administrators a way to codify these settings instead of relying on the secpol.msc GUI or registry edits. Using cmdlets such as Set-LocalUser and Set-LocalGroup, combined with the secedit tool for system-wide policy, a script can be deployed through a configuration management platform, scheduled task, or remote PSSession. This makes consistent enforcement possible across dozens or hundreds of servers without manual drift.
In Australia, regulators and auditors expect demonstrable password controls. The Australian Cyber Security Centre's Essential Eight maturity model addresses application whitelisting, patching, and multi-factor authentication, and it also references strong authentication practices. APRA's CPS 234 standard obliges banks, insurers, and superannuation funds to maintain information asset security, while the Notifiable Data Breaches scheme under the Privacy Act 1988 makes weak local credentials an accountable risk. A scripted approach provides the evidence trail auditors expect.
Core policy components and what they mean
A compliant local password policy typically specifies a minimum length of 14 characters, complexity requirements that mix character classes, a password history of 24 remembered passwords, and a maximum password age of 90 days. Lockout settings matter too: a threshold of 10 invalid attempts with a 15-minute observation window stops brute-force scans without locking out legitimate technicians. These numbers align with the CIS Microsoft Windows Server Benchmark and the ACSC's hardening guidance.
The difference between per-account overrides and system-wide policy is important. Set-LocalUser can change whether a particular account has "User cannot change password" or "Password never expires" flags, but it cannot set the baseline complexity rules that apply to every new password. For that, you need the secedit.exe tool, which exports and applies the security configuration of the local SAM database and security policy.
Preparing the host and selecting the right cmdlets
Before any script runs, the target server must have PowerShell remoting enabled, the LocalAccount module available, and an elevated token. On Server Core installations, the GUI-based secpol.msc snap-in is missing, so command-line enforcement is the only option. It is also wise to create a local recovery account with a randomly generated passphrase stored in a hardware key or privileged access management vault, so that policy mistakes do not lock the organisation out of its own machine.
Run the script as a scheduled task triggered by a Group Policy refresh, or push it from a tool like Ansible, Puppet, or SaltStack. In Australian datacentres from Sydney to Perth, central orchestration is normal because hybrid footprints span multiple states and time zones. Centralisation also means the script can be version-controlled in a Git repository and reviewed by a change advisory board.
The script itself and how to read it
A practical enforcement script combines three layers. The first calls Set-LocalUser to clear "Password never expires" on every enabled account except designated service or break-glass identities. The second invokes secedit with an exported INF template that defines PasswordComplexity, MinimumPasswordLength, PasswordHistory, and LockoutBadCount. The third writes a transcript or structured log entry to a central location for audit.
| Method | Scope | Best for | Limitations |
|---|---|---|---|
| Set-LocalUser and Set-LocalGroup | Per-account flags | Removing "never expires", disabling accounts, renaming defaults | Cannot set baseline complexity or history |
| Net.exe (Net User, Net Accounts) | Local SAM and policy | Quick configuration of minimum length and lockout | Older interface, limited discovery |
| secedit.exe with INF template | System-wide security policy | Full policy enforcement aligned to CIS benchmarks | Requires INF template maintenance |
A sample skeleton works like this: export the current policy with secedit /export /cfg C:\temp\policy.inf, modify the INF with the required values, then apply it with secedit /configure /db secedit.sdb /cfg C:\temp\policy.inf /quiet. Wrapped in a try-catch block with Start-Transcript, the script becomes evidence an auditor can replay.
Reporting, verification, and exception handling
After deployment, verify with Get-LocalUser to confirm each account's flags, and with secedit /export followed by Get-Content to inspect the applied INF. Output should be shipped to a SIEM such as Microsoft Sentinel, Splunk, or an in-house ELK stack so that drift can be detected. Many Australian managed security service providers, including those operating in Brisbane and Adelaide's growing cyber corridors, expect this telemetry as part of their service-level agreements.
Build exceptions into the script deliberately. Service accounts used by backup software, monitoring agents, or scheduled database jobs often require "Password never expires" because their credentials live in configuration files rather than a secrets vault. Document each exception with its justification, ticket reference, and review date. This satisfies both APRA's information asset register requirements and the Essential Eight's emphasis on controlled exceptions.
Operational habits in an Australian context
Schedule the script outside business hours in AEDT or AEST depending on the state, and account for daylight saving transitions in October and April. For remote sites such as mining operations near Kalgoorlie or council data centres in regional Tasmania, factor in bandwidth limits and consider running the script through a pull agent rather than remote shell. Always store the transcript on a write-once or append-only destination, since tamper-evident logs are a frequent finding in Privacy Act assessments.
Review the policy annually or whenever the ACSC issues new guidance. Password guidance has shifted away from forced periodic rotation toward length and breach-detection checks, so a script that hard-codes 90-day expiry may need adjustment. Treat the script as a living artefact: pull-request reviewed, change-logged, and tested against a representative server in your home lab before it touches production workloads in a Sydney, Melbourne, or Perth region datacentre.