A small Ansible‐based project to check Active Directory user password expiry on a Windows domain controller and notify users via email if their passwords are about to expire (or have already expired). This playbook runs remotely against one or more domain controllers, gathers password‐expiry information via a PowerShell script, and sends templated HTML emails to affected users.
- Project Overview
- Prerequisites
- Installation
- Configuration
- Usage
- Directory Structure
- How It Works
- License
Many organizations enforce password‐expiration policies for Active Directory users. Rather than waiting until accounts lock out, this playbook:
- Connects to one or more Windows domain controllers (via WinRM).
- Runs a PowerShell snippet (templated by Jinja2) to list all AD users whose passwords will expire within a configurable threshold.
- Parses the JSON output from PowerShell.
- Loops over each user account, checks if an email address exists, and sends a warning (or expired) email using a templated HTML body.
- Writes a daily log file (named
password-expiry-YYYY-MM-DD.log
) under a configurable directory and purges old log files beyond a configured retention period.
-
Ansible Control Node
- Ansible 2.9+ (tested with 2.9.x and 2.10.x)
- Python 3.6+ (with Jinja2, which ships with Ansible)
- WinRM, see https://docs.ansible.com/ansible/latest/os_guide/windows_winrm.html for more details.
-
Windows Domain Controller or Server
- PowerShell 5.1 (or higher)
- WinRM enabled & configured to accept remote commands
- The account used by Ansible must have permission to run
Get-ADUser
and query AD password info. - The accounts fetched by the PowerShell script should have their "E-mail" field set.
-
SMTP Relay
- A reachable SMTP relay (or a server, but better to have a local relay) that allows unauthenticated (or authenticated, if you modify the playbook) email sending.
-
Clone this repository
git clone https://github.com/justinnamilee/check-expiring-domain-users-ansible-playbook cd check-expiring-domain-users-ansible-playbook
-
Install Python dependencies (on your Ansible control node)
pip install ansible pywinrm xmltodict
-
Ensure WinRM connectivity
-
Test your WinRM connection from the control node to the domain controller:
ansible -i inventory.yml YOUR_HOST -m win_ping
-
(Adjust
inventory.yml
to point at your DC and supply the correct credentials.)
-
-
vars/config.yml
(example template)- Create a copy of
vars/config.yml.example
(or simply add your ownvars/config.yml
) and fill in all required values.
Note: A detailed explanation of all variables can be found in
vars/README.md
. - Create a copy of
-
Templates
- PowerShell Script Template (
templates/check-password-expiry.ps1.j2
) Contains the logic to runGet-ADUser
, calculate days until password expiry, and emit JSON. - Email Body Templates (
templates/password-is-expiring.html.j2
&templates/password-is-expired.html.j2
) Provide an HTML structure for “expiring soon” and “already expired” emails. You can customize these to match your corporate branding.
- PowerShell Script Template (
-
Run the Playbook
-
From the project root:
ansible-playbook check-expiring-domain-users.yml
-
Testing (with an override e-mail) To avoid sending real notices while you’re testing, add an extra variable
override_to
:ansible-playbook check-expiring-domain-users.yml \ -e "override_to=your-test-address@example.com"
This forces all “to:” values in the playbook’s
mail:
tasks to go toyour-test-address@example.com
instead of each user’s actual address.
-
-
Check Logs
-
By default, a new log file named
password-expiry-<YYYY-MM-DD>.log
is created under the directory specified bylog_dir
. -
If any users were emailed (or if there were errors), you’ll see lines like:
2025-06-06 14:23:45 INFO: John Doe (jdoe) expires in 5 days — email sent successfully to jdoe@domain.com 2025-06-06 14:23:45 WARNING: Jane Smith (jsmith) expires in 20 days — no notice needed
-
-
Cron / Automation (Optional)
-
Schedule this playbook to run daily (e.g. via a CI/CD pipeline or a simple cron job).
-
Example crontab entry (runs at 8 AM every day):
0 8 * * * cd /path/to/ansible-ad-password-expiry && \ # this assumes you want to capture the raw ansible output ansible-playbook -i inventory.yml check-expiring-domain-users.yml >> /var/log/ansible/expiry-notifier.log 2>&1
-
playbook/
├── check-expiring-domain-users.yml # Main playbook
├── README.md # ← You are here
├── templates/
│ ├── overrides/ # Ignored directory, put your own templates here
│ ├── check-password-expiry.ps1.j2 # PowerShell script template
│ ├── password-is-expiring.html.j2 # “Password expiring soon” email template
│ └── password-is-expired.html.j2 # “Password expired” email template
├── tasks/
│ └── send-email-if-required.yml # Included tasks to send mail & log results
└── vars/
├── config.yml.example # Example configuration (copy to config.yml)
└── README.md # Detailed variable explanations
-
Validation & Setup
- The first play (on
localhost
) ensures thatvars/config.yml
exists, that required variables are defined and valid (e.g. positive integers, valid email format), and that template files (script_template_path
,expiring_template_path
,expired_template_path
) actually exist. - It also creates (or ensures existence of) the log directory and touches a daily log file named
password-expiry-<today>.log
. Any old logs older thanlog_age
are purged.
- The first play (on
-
Gathering AD Data
-
The second play targets your domain controller(s).
-
It runs a PowerShell snippet (using
win_shell
+lookup('template', ...)
) that:-
Queries all user accounts matching
get_aduser_filter
. -
Calculates days until password expiry for each user.
-
Outputs a JSON array of objects, each containing at least:
{ "DisplayName": "John Doe", "SamAccountName": "jdoe", "EmailAddress": "jdoe@domain.com", "DaysToExpire": 5 }
-
-
-
Processing & Notification
-
Back on the control node, Ansible parses that JSON into the
expiring_user_list
variable. -
It loops over each user object and:
- Checks if
DaysToExpire
is less than or equal topassword_expiry_threshold
. - Verifies there’s an email address on file.
- Chooses either the “expiring soon” or “already expired” HTML template.
- Sends an email via Ansible’s
mail
module using the configured SMTP server. - Logs the outcome (success/failure/no-email/no-notice) to the daily log file.
- Checks if
-
This project is licensed under the GNU General Public License v3.0 (GPL-3.0). See the full text in the LICENSE file.