AD Password Expiry Notice Script Need Help!

Hello Powershell Experts!

I am new to this form and I am looking for some help with this script below. This isn’t something that I wrote but found in other community posts that seems to fit my need.

Background:

I am trying to create a Powershell script that will notify my AD users 15 days before their AD Account Passwords expire. Using Task Scheduler, I created a daily task to run this script to notify the user each day. The GPO policy that is in place is set to force AD password changes every 90 days.

Here is the issue:

The Powershell scripts works but what I am seeing is that its not alerting users from 15 days before their AD password is about to expire. What its doing is it seems to get anyone in the 90 days policy and sends the alert to let them know that in XX days left before their password expires and not within the last 15 days that we wanted.

One example of this is a user AD account has the following attributes listed:

whenChanged: 10/21/2025 10:28 AM Easter Daylight

PwdLastSet: 9/14/2025 6:44 AM Eastern Daylight

When the script was executed, what it did was sent the user the email notification saying that their password was going to expire in 45 days (from today) on December 13, 2025. However, our GPO policy regarding password expirations is set to 90 days which in my calculations this should be expiring on January 13, 2026 from the whenChanged date listed in AD. Not sure how to fix this in the script and looking for some guidance.

Here is the script in question:

##############################
#.SYNOPSIS
#Send email to Users whose password will expired in given days.

#.DESCRIPTION
#This script connect and fetches users list whose password is going to expired in the after mentioned days.

#.PARAMETER DaysAfterPasswordExpire
#Provide a value for a configured policy after how many days password will expire.

#.PARAMETER DaysBeforeAlert
#This is a parameter to set days alerts before user password should get email notification, Provide a number value.

#.PARAMETER SearchBase
#Provide distingushed name for domain/out to search users.

#.PARAMETER SMTPServer
#Email server FQDN or IP.

#.PARAMETER SMTPPort
#Email server SMTP port for submission dfault value is 587.

#.EXAMPLE
#Send-PasswordExpiryNotification -DaysBeforeAlert 15 -SearchBase ‘DC=EXAMPLE,DC=com’ -From ‘no-reply@example.com’ -SMTPServer ‘emailexchange.example.com’ -SMTPPort 587

#Finds users with expiring password in Active Directory and send notification email.

#.NOTES
#Written using powershell version 5
#Script code version 1.0
###############################

[CmdletBinding()]
param(
[Parameter(Position=0)]
[Int]$DaysAfterPasswordExpire = 90,
[Parameter(Position=1)]
[Int]$DaysBeforeAlert = 15,
[Parameter(Position=2)]
[System.String]$SearchBase = ‘ENTER SEARCH BASE’,
[Parameter(Position=3)]
[System.String]$From = ‘ENTER FROM EMAIL ADDRESS’,
[Parameter(Position=4)]
[System.String]$SMTPServer = ‘ENTER SMTP SERVER’,
[Parameter(Position=5)]
[Int]$SMTPPort = 25
)
Begin {
if (-not(Get-Module ActiveDirectory)) {
Import-Module -Name ActiveDirectory
}
}
Process {
#$DaysBeforeAlert = 15
#$searchBase = “ENTER SEARCH BASE”
#$from = “ENTER FROM EMAIL ADDRESS”
#$smtpServer = “ENTER SMTP SERVER”
#$smtpPort = “25”
#$backDate = (Get-Date).AddDays($days)

$alertDays = $DaysAfterPasswordExpire - $DaysBeforeAlert

$dateNow = [datetime]::Now
$expiryDate = $dateNow.AddDays(-$alertDays) #.ToFileTime()

$filter = {(Enabled -eq $True) -and (PasswordNeverExpires -eq $False) -and (PasswordLastSet -gt $expiryDate)} #-and (PasswordLastSet -gt $rawBackDate)} #-and (PasswordLastSet -gt $backDate) #name -eq 'user1' -and -and (msDS-UserPasswordExpiryTimeComputed -lt $expirtyAlertDate)
$adProperties = @('PasswordLastSet', 'pwdLastSet', 'msDS-UserPasswordExpiryTimeComputed', 'EmailAddress')

$users = Get-ADUser -SearchBase $SearchBase -Filter $filter -properties $adProperties 
$nearExpiryUsers = $users | Select-Object -Property Name, UserPrincipalName, SamAccountName, EmailAddress, 
        GivenName, Surname, PasswordLastSet, pwdLastSet, 'msDS-UserPasswordExpiryTimeComputed', 
        @{Name="PasswordExpirtyTimeComputed"; Expression={[datetime]::FromFileTime($_.'msDS-UserPasswordExpiryTimeComputed')}},
        DistinguishedName

foreach ($user in $nearExpiryUsers)
{
    $remainingDays = New-TimeSpan -Start $dateNow -End $user.PasswordExpirtyTimeComputed
    $to = $user.EmailAddress
    if ([string]::IsNullOrEmpty($to))
    {
        $to = $user.UserPrincipalName
    }

    $subject = "Your Network Active Directory Password expires in $($remainingDays.Days) Days"

    $body = @"
        <style>
            p {
                margin: auto;
                width: 75%;
                border: 1px solid coral;
                padding: 10px;        
                border-width: thin;
                font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            }
        </style>
        <p style='background-color: coral; text-align: center; color: white; font-size: large;'>
            <strong>Password Expiration Notice!</strong></strong>
        </p>
        <p>
            <br>
            <strong>Automated message system
            <br><br>
            Hi $($user.GivenName),
            <br><br>
            You are receiving this email because your password for user account ''<b>$($user.SamAccountName)</b>''
            will expire in <b>$($remainingDays.Days)</b> days(s) on date <b>$($user.PasswordExpirtyTimeComputed.ToLongDateString())</b>. 
            Consider changing your password as earliy as possible to avoid logon problems.
            <br><br>
            To change your password, Press ''CTRL+ ALT + DEL'' together and select ''Change Password''.
            <br><br>
            Passwords must contain:
            <br><br>
            A minimum of 8 characters,
            A minimum of 1 lower case letter [a-z],<br>
            A minimum of 1 upper case letter [A-Z],<br>
            A minimum of 1 numeric character [0-9],<br>
            and a minimum of 1 special character: [~ ! @  # $ % ^ & * ( ) _ + = { } | ; : < > / ?].
            <br><br>
            If you are working remotely, please be sure you are on the VPN and do a ''CTRL+ ALT + DEL'' together to display the ''Password Change'' option. Once the password has been successfully changed, do a ''CTRL+ ALT + DEL'' and then select "LOCK" to lock your computer.  Do another "CTRL+ ALT + DEL" and login using the new password you created.
            <br><br>
            Further, passwords should not contain your name or any other easily
            guessable/researchable information about you or your family, e.g.
            birthdates, SINs, pets or childrens names, etc. Also, please do not
            use a password that you have used in the last 4 previous password changes.
            <br><br>
            Finally, please do not use passwords that you also use on other online accounts,
            such as your online bank password, your Amazon/eBay password, your UberEats
            password, etc. Your company password should be unique and stay that way.
            Otherwise, if a threat actor manages to get one password, they get the rest, too.
            <br><br>
            If you need any help, please contact the IT Support team via email: EMAIL ADDRESS HERE
            <br><br>
            Sincerely,
            <br>
            YCU IT Support Team
            <br>
            <i><strong>Phone No:</strong> PHONE NUMBER HERE</i>
            <br>
            <i><strong>Email us:</strong> EMAIL ADDRESS HERE</i>
            <br><br>
            </p>

"@
try
{
Send-MailMessage -From $From -to $to -Subject $Subject -Body $Body -BodyAsHtml -SmtpServer $SMTPServer -Port $SMTPPort -ErrorAction Stop #-UseSsl -Credential (Get-Credential) #-Attachments $Attachment <#-Cc $Cc#>
Write-Host “$($user.Name): Email notification sent” -BackgroundColor DarkGreen
}
catch
{
Write-Host “$($user.Name): $Error[0].Exception.Message” -BackgroundColor DarkRed
}
}
}
end{}

I would be greatly appreciated if someone could assist me in finding out what is wrong the Powershell script.

You know there is a GPO setting that will notify them right?

1 Like

Justin,

Thanks for the reply, if you are referring to the interactive login then I am aware. The challenge with that is many say they never see it while others say they do. The email notification method will allow us to trace and used as proof of the notice was received for auditing purposes.

Luc

While I haven’t worked in an AD environment in quite some time, it seems this could be a lot simpler.

msDS-UserPasswordExpiryTimeComputed should be a computed value of AD when a users password changes.

Seems easier if you just filter if
msDS-UserPasswordExpiryTimeComputed is >= [DateTime]::Now and < Now+DaysBeforeAlert

Then just have the script run once per day from a server and it will send out notifications to everyone

i’m with @neemobeer on this one. Firstly, there is a baked in notification within AD/GPO that lets users know their password is expiring in two weeks. People at my work get them all the time.

Secondly, you gotta format code when you’re sharing it online. There’s a “preformatted text” button in the toolbar within the plus symbol that lets you create a code block to put code in. Guide to Posting Code . Please use that.

Lastly, on the line where you define your filter for Get-AdUser you’re comparing PasswordLastSet to a variable named $expirydate containing a datetime object. Theoretically this should work right? Comparing two datetime objects. However, PasswordLastSet is a computed property that I don’t believe can be leveraged within a filter argument, but maybe I’m wrong on that. What I’ve done in the past is get the users from AD with the msds-userpasswordexpirytimecomputed property so I can get their literal expiration date without doing any calculations myself, then filter with Where-Object based on that property.
Something like this for the setup

$GetADUserArgs = [ordered]@{
    Filter = "Enabled -eq 'TRUE' -and PasswordNeverExpires -eq 'FALSE'"
    Server = $DomainName
    Properties = @('Displayname','Passwordlastset','msDS-userpasswordexpirytimecomputed','PasswordNeverExpires','PasswordExpired')
    ErrorAction = "Stop"
    SearchBase = $null
}

$SelObjArgs = [ordered]@{
    Property = @(@{Name="User";Expression={$_.UserPrincipalName}},
                "DisplayName",
                "PasswordLastSet",
                @{Name="ExpiryDate";Expression={[datetime]::fromfiletime($_."msds-userpasswordexpirytimecomputed")}},
                'PasswordNeverExpires',
                'PasswordExpired',
                'Enabled'
    )
}

And then with those defined we can go out to AD and get users. Here $OUs is defined beforehand as an array of specific places to check.

$UsersToCheck = Foreach ($OU in $OUs) {
    $GetADUserArgs.SearchBase = $OU
    try {
        Get-ADUser @GetADUserArgs | Select-Object @SelObjArgs
    } catch {
        Write-Error $Error[0]
    }
}

$UsersToNotify = Foreach ($User in $UsersToCheck) {
    if ($null -ne $User.PasswordLastSet -and $User.PasswordExpired -eq $false) {
        if ($User.ExpiryDate -le (Get-Date).AddDays($Threshold)) {
            $User
        }
    }
}
1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.