#Requires -Version 3.0
#Requires -Modules ActiveDirectory

    .VERSION 1.6.1
    .GUID 4ff55e9c-f6ca-4549-be4c-92ff07b085e4
    .AUTHOR Peter Wawa
    .COPYRIGHT (c) 2019 Peter Wawa. All rights reserved.
    .TAGS password, password, e-mail, email, notification, Windows, PSEdition_Desktop, PSEdition_Core

        Sends e-mail notification to users whose password is about to expire.
        Script is meant to send e-mail notifications about expiring passwords.
        The notification is sent only to users who:
            1. are enabled
            2. can change their password
            3. has an e-mail address
            4. has been logged on at least once
            5. their password expires in time
            6. password is not yet expired
        The script uses config file, that contains information nessesary to send e-mail.
        Script requires ActiveDirectory module on the computer where script runs. Script also
        requires AD WS (or AD GMS) service on any domain controller.
        Shows what would happen if the script runs.
        The script is not run.
    .PARAMETER Confirm
        Prompts you for confirmation before sending out e-mail messages
        PS C:\> Send-PasswordNotification.ps1 -DaysBefore 5,1
        Sends e-mail to users, whose password expires within 5 or 1 days.
        PS C:\> Send-PasswordNotification.ps1 7 -configFile my.config
        Sends notification 7 days before password expires. Uses custom configuration file.
        List of days before password expiration
        The script requires a config file with similar to following content:
        <?xml version="1.0" encoding="utf-8" ?>
            <ou></ou> <!-- Search base OU, if needed -->
                <!-- If the next element exist, the script refers to user as domain\samAccountName -->
                <!-- Otherwise, the script refers to user as userPrincipalName -->
                <!-- <useSamAccountName /> -->
                <!-- If the next element exist, the script takes e-mail from user's manager account, when available -->
                <!-- Otherwise, the user own mail attribute is used -->
                <!-- <useManagerMail /> -->
                <subject>Your password will expire soon </subject>
                <body>Dear User,
        Password of Your user account ({0}) expires in {1} days.
        Please change Your password ASAP.

    SupportsShouldProcess = $True,
    DefaultParameterSetName = 'Action'
param (
            Position = 0,
            Mandatory = $true,
            HelpMessage = 'Specify number of days before password expiration',
            ValueFromPipeLine = $true,
            ParameterSetName = 'Action'
        # Number of days before password expiration, when to send warning. Can contain more than one number.
            ParameterSetName = 'Action'
            if (test-path -Path $_) {$true}
            else {
                throw 'Config file not found'
        [PSDefaultValue(Help = '<scriptname>.config in the same folder as script')]
        # Configuration file to read. By default the config file is in the same directory as script and has the same name with .config extension.
    $ConfigFile = $(Join-Path -Path $PSScriptRoot -ChildPath ((get-item $PSCommandPath).BaseName + '.config')),
            Mandatory = $true,
            ParameterSetName = 'Version'
        # Returns script version.

    # Script version
Set-Variable -Name Ver -Option Constant -Scope Script -Value '1.6.0' -WhatIf:$false -Confirm:$false

if ($PSCmdlet.ParameterSetName -like 'Version') {
    "Version $Ver"
    exit 3

Write-Verbose -Message "Loading Config file: $ConfigFile"
$conf = [xml](Get-Content -Path $ConfigFile)

    # get Max Password age from Domain Policy
$MaxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
Write-Debug -Message ('Max Password Age: {0}' -f $MaxPasswordAge)

    # Get Domain functional level.
$AdDomain = Get-ADDomain

$mailSettings = @{
    Subject    = $conf.config.mail.subject
    From       = $conf.config.mail.from
    SmtpServer = $conf.config.server
    Encoding   = [text.encoding]::UTF8
    To         = ''
    Body       = ''

    # get users
$searchProperties = @{
    Filter     = 'Enabled -eq $true -and PasswordNeverExpires -eq $false -and logonCount -ge 1 -and mail -like "*"'
    Properties = 'CannotChangePassword', 'mail', 'manager', 'PasswordExpired', 'PasswordLastSet'
if ($conf.config.ou) {
    $searchProperties.SearchBase = $conf.config.ou
    #$searchProperties.SearchScope = 'Subtree'
Get-ADUser @searchProperties |
    Where-Object { -not ($_.CannotChangePassword -or $_.PasswordExpired) } |
    ForEach-Object {
        $PasswordAge = $MaxPasswordAge
        if ($AdDomain.DomainMode -ge 3) {
                # [Microsoft.ActiveDirectory.Management.ADDomainMode]::Windows2008Domain
            $accountFGPP = Get-ADUserResultantPasswordPolicy -Identity $_
            if ($accountFGPP) {
                $PasswordAge = $accountFGPP.MaxPasswordAge
        Write-Debug -Message "pwdAge = $PasswordAge"

        $PasswordDays = ($_.PasswordLastSet.Add($PasswordAge) - [datetime]::Now).days
        Write-Debug -Message "pwdDays = $PasswordDays"

        $userName = if ($conf.config.user.item('useSamAccountName')) {
            '{0}\{1}' -f $AdDomain.NetBIOSName, $_.SamAccountName
        } else {

        $userMail = if ($conf.config.user.item('useManagerMail')) {
                # use manager's e-mail instead of user's, if available
            $managerMail = (Get-ADUser -Identity $_.manager -Properties mail).mail
            if ($managerMail) {
            } else { $_.mail }
        } else {

        if ($PasswordDays -ge 1) {
            foreach ($day in $DaysBefore) {
                Write-Debug -Message "Processing day $day, user $userName"
                if ($PasswordDays -eq $day) {
                    $mailSettings.To = $userMail
                    $mailSettings.Body = ($conf.config.mail.body -f $userName, $day)
                    if ($PSCmdLet.ShouldProcess($userMail, 'Send e-mail message')) {
                        Send-MailMessage @mailSettings
                    # Write-Debug ('Message body: {0}' -f $mailSettings.Body)
                    Write-Verbose -Message "User $username ($userMail), password expires in $day days."