Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 53 You must be signed in to star a gist
  • Fork 17 You must be signed in to fork a gist
  • Save meoso/3488ef8e9c77d2beccfd921f991faa64 to your computer and use it in GitHub Desktop.
Save meoso/3488ef8e9c77d2beccfd921f991faa64 to your computer and use it in GitHub Desktop.
PowerShell Active Directory Password Expiration Email Notification

Password-Expiration-Notifications.ps1 is a powerShell script designed to be run on a schedule to automatically email Active Directory users of soon-to-expire and recently-expired passwords.

This version is a highly modified fork of the original v1.4 by Robert Pearman from https://gallery.technet.microsoft.com/Password-Expiry-Email-177c3e27. Pearman's 2.x version was completely re-written.

New in this version:

  • A SearchBase is required.
  • OU ExcludeList [Commented out in code]
  • When logging, the CSV will always be overwritten. (unless you specify a variable file name such as "c:\PS-pwd-expiry-$(Get-Date -format yyyyMMdd-HHmmss).csv")
  • Accounts with recently-expired passwords can be notified by specifying a "negativedays" value.
  • Email attempts will handle basic errors, but nothing more. This script does not account for SMTP credentials. [Please review July 2022 comments for SMTP auth.]
  • Accounts with MaxPasswordAge 00:00:00 (never) are skipped. (Same as PasswordNeverExpires.)
  • Testing-mode will allow a specified number of sample notifications to be emailed to the Administrator(s). (Rather than defaulting to all users' expiration emails.)
  • Processing information and basic statistics are written to console.
  • When logging, the CSV file and basic statistics will be emailed to the specified Administrator(s).

Self-Help Resources:

Prefer scheduling with a Service account

#################################################################################################################
#
# Password-Expiration-Notifications v20220823
# Highly Modified fork. https://gist.github.com/meoso/3488ef8e9c77d2beccfd921f991faa64
#
# Originally from v1.4 @ https://gallery.technet.microsoft.com/Password-Expiry-Email-177c3e27
# https://windowspoweressentials.com/2017/02/21/powershell-password-reminder-script-updated/
# https://github.com/titlerequired/public
# Robert Pearman (WSSMB MVP)
# TitleRequired.com
# Script to Automated Email Reminders when Users Passwords due to Expire.
#
# Requires: Windows PowerShell Module for Active Directory
#
##################################################################################################################
# Please Configure the following variables....
$testing = $true # Set to $false to Email Users. $true to email samples to administrators only (see $sampleEmails below)
$SearchBase="DC=EXAMPLE,DC=COM"
### PURGING this option; seems to cause issue. # $ExcludeList="'New Employees'|'Separated Employees'" #in the form of "SubOU1|SubOU2|SubOU3" -- possibly needing single quote for OU's with spaces, separate OU's with pipe and double-quote the list.
$smtpServer="smtp.example.com"
$expireindays = 7 #number of days of soon-to-expire paswords. i.e. notify for expiring in X days (and every day until $negativedays)
$negativedays = -3 #negative number of days (days already-expired). i.e. notify for expired X days ago
$from = "Administrator <administrator@example.com>"
$logging = $true # Set to $false to Disable Logging
$logNonExpiring = $false
$logFile = "c:\PS-pwd-expiry.csv" # ie. c:\mylog.csv
$adminEmailAddr = "Admin1@example.com","Admin2@example.com","Admin3@example.com" #multiple addr allowed but MUST be independent strings separated by comma
$sampleEmails = 3 #number of sample email to send to adminEmailAddr when testing ; in the form $sampleEmails="ALL" or $sampleEmails=[0..X] e.g. $sampleEmails=0 or $sampleEmails=3 or $sampleEmails="all" are all valid.
# please edit $body variable within the code
###################################################################################################################
# System Settings
$textEncoding = [System.Text.Encoding]::UTF8
$date = Get-Date -format yyyy-MM-dd #for logfile only
$starttime=Get-Date #need time also; don't use date from above
Write-Host "Processing `"$SearchBase`" for Password-Expiration-Notifications"
Write-Host "Testing Mode: $testing"
# Get Users From AD who are Enabled, Passwords Expire
Import-Module ActiveDirectory
Write-Host "Gathering User List"
$users = get-aduser -SearchBase $SearchBase -Filter {(enabled -eq $true) -and (passwordNeverExpires -eq $false)} -properties sAMAccountName, displayName, PasswordNeverExpires, PasswordExpired, PasswordLastSet, EmailAddress, lastLogon, whenCreated
Write-Host "Filtering User List"
### PURGING this option; seems to cause issue. # $users = $users | Where-Object {$_.DistinguishedName -notlike $ExcludeList} ##also try -notmatch, needs heavy testing
$DefaultmaxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
$countprocessed=${users}.Count
$samplesSent=0
$countsent=0
$countnotsent=0
$countfailed=0
$nonexpiring=0
Write-Host "${countprocessed} user-accounts selected to iterate."
#set max sampleEmails to send to $adminEmailAddr
if ( $sampleEmails -isNot [int]) {
if ( $sampleEmails.ToLower() -eq "all") {
$sampleEmails=$users.Count
} #else use the value given
}
if (($testing -eq $true) -and ($sampleEmails -ge 0)) {
Write-Host "Testing only; $sampleEmails email samples will be sent to $adminEmailAddr"
} elseif (($testing -eq $true) -and ($sampleEmails -eq 0)) {
Write-Host "Testing only; emails will NOT be sent"
}
# Create CSV Log
if ($logging -eq $true) {
#Always purge old CSV file
Out-File $logfile
Add-Content $logfile "`"Date`",`"SAMAccountName`",`"DisplayName`",`"Created`",`"PasswordSet`",`"DaystoExpire`",`"ExpiresOn`",`"EmailAddress`",`"Notified`""
}
# Process Each User for Password Expiry
foreach ($user in $users) {
$dName = $user.displayName
$sName = $user.sAMAccountName
$emailaddress = $user.emailaddress
$whencreated = $user.whencreated
$passwordSetDate = $user.PasswordLastSet
$sent = "" # Reset Sent Flag
$PasswordPol = (Get-AduserResultantPasswordPolicy $user)
# Check for Fine Grained Password
if (($PasswordPol) -ne $null) {
$maxPasswordAge = ($PasswordPol).MaxPasswordAge
} else {
# No FGPP set to Domain Default
$maxPasswordAge = $DefaultmaxPasswordAge
}
#If maxPasswordAge=0 then same as passwordNeverExpires, but PasswordCannotExpire bit is not set
if ($maxPasswordAge -eq 0) {
Write-Host "$sName : MaxPasswordAge = $maxPasswordAge (i.e. PasswordNeverExpires) but bit not set -- User not selected to receive email."
}
$expiresOn = $passwordsetdate + $maxPasswordAge
$today = (get-date)
if ( ($user.passwordexpired -eq $false) -and ($maxPasswordAge -ne 0) ) { #not Expired and not PasswordNeverExpires
$daystoexpire = (New-TimeSpan -Start $today -End $expiresOn).Days
} elseif ( ($user.passwordexpired -eq $true) -and ($passwordSetDate -ne $null) -and ($maxPasswordAge -ne 0) ) { #if expired and passwordSetDate exists and not PasswordNeverExpires
# i.e. already expired
$daystoexpire = -((New-TimeSpan -Start $expiresOn -End $today).Days)
} else {
# i.e. (passwordSetDate = never) OR (maxPasswordAge = 0)
$daystoexpire="NA"
$nonexpiring += 1
#continue #"continue" would skip user, but bypass any non-expiry logging
}
#Write-Host "$sName : DaysToExpire: $daystoexpire MaxPasswordAge: $maxPasswordAge" #debug
# Set verbiage based on Number of Days to Expiry.
Switch ($daystoexpire) {
{$_ -ge $negativedays -and $_ -le "-1"} {$messageDays = "has expired"}
"0" {$messageDays = "will expire today"}
"1" {$messageDays = "will expire in 1 day"}
default {$messageDays = "will expire in " + "$daystoexpire" + " days"}
}
# Email Subject Set Here
$subject="Your password $messageDays"
# Email Body Set Here, Note You can use HTML, including Images.
$body="
<p>Your Active Directory password for your <b>$sName</b> account $messageDays. After expired, you will not be able to login until your password is changed.</p>
<p>Please visit selfservice.example.com to change your password. Alternatively, on a Windows machine, you may press Ctrl-Alt-Del and select `"Change Password`".</p>
<p>If you do not know your current password, <a href='https://selfservice.example.com/?action=sendtoken'>click here to email a password reset link</a>.</p>
Example.com Administrator<br>
Administrator@example.com<br>
www.example.com/support/<br>
</p>
"
# If testing-enabled and send-samples, then set recipient to adminEmailAddr else user's EmailAddress
if (($testing -eq $true) -and ($samplesSent -le $sampleEmails)) {
$recipient = $adminEmailAddr
} else {
$recipient = $emailaddress
}
#if in trigger range, send email
if ( ($daystoexpire -ge $negativedays) -and ($daystoexpire -le $expireindays) -and ($daystoexpire -ne "NA") ) {
Write-Host "$sName : Selected to receive email: password ${messageDays}"
# Send Email Message
if (($emailaddress) -ne $null) {
if ( ($testing -eq $false) -or (($testing -eq $true) -and ($samplesSent -lt $sampleEmails)) ) {
try {
Send-Mailmessage -smtpServer $smtpServer -from $from -to $recipient -subject $subject -body $body -bodyasHTML -priority High -Encoding $textEncoding -ErrorAction Stop -ErrorVariable err
} catch {
write-host "Error: Could not send email to $recipient via $smtpServer"
$sent = "Send fail"
$countfailed++
} finally {
if ($err.Count -eq 0) {
write-host "Sent email for $sName to $recipient"
$countsent++
if ($testing -eq $true) {
$samplesSent++
$sent = "toAdmin"
} else { $sent = "Yes" }
}
}
} else {
Write-Host "Testing mode: skipping email to $recipient"
$sent = "No"
$countnotsent++
}
} else {
Write-Host "$dName ($sName) has no email address."
$sent = "No addr"
$countnotsent++
}
# If Logging is Enabled Log Details
if ($logging -eq $true) {
Add-Content $logfile "`"$date`",`"$sName`",`"$dName`",`"$whencreated`",`"$passwordSetDate`",`"$daystoExpire`",`"$expireson`",`"$emailaddress`",`"$sent`""
}
} else {
#if ( ($daystoexpire -eq "NA") -and ($maxPasswordAge -eq 0) ) { Write-Host "$sName PasswordNeverExpires" } elseif ($daystoexpire -eq "NA") { Write-Host "$sName PasswordNeverSet" } #debug
# Log Non Expiring Password
if ( ($logging -eq $true) -and ($logNonExpiring -eq $true) ) {
if ($maxPasswordAge -eq 0 ) {
$sent = "NeverExp"
} else {
$sent = "No"
}
Add-Content $logfile "`"$date`",`"$sName`",`"$dName`",`"$whencreated`",`"$passwordSetDate`",`"$daystoExpire`",`"$expireson`",`"$emailaddress`",`"$sent`""
}
}
} # End User Processing
$endtime=Get-Date
$totaltime=($endtime-$starttime).TotalSeconds
$minutes="{0:N0}" -f ($totaltime/60)
$seconds="{0:N0}" -f ($totaltime%60)
Write-Host "$countprocessed Users from `"$SearchBase`" Processed in $minutes minutes $seconds seconds."
Write-Host "Email trigger range from $negativedays (past) to $expireindays (upcoming) days of user's password expiry date."
Write-Host "$nonexpiring Non-Expiring accounts."
Write-Host "$countsent Emails Sent."
Write-Host "$countnotsent Emails skipped."
Write-Host "$countfailed Emails failed."
# sort the CSV file
if ($logging -eq $true) {
Rename-Item $logfile "$logfile.old"
import-csv "$logfile.old" | sort ExpiresOn | export-csv $logfile -NoTypeInformation
Remove-Item "$logFile.old"
Write-Host "CSV File created at ${logfile}."
if ($testing -eq $true) {
$body="<b><i>Testing Mode.</i></b><br>"
} else {
$body=""
}
$body+="
CSV Attached for $date<br>
$countprocessed Users from `"$SearchBase`" Processed in $minutes minutes $seconds seconds.<br>
Email trigger range from $negativedays (past) to $expireindays (upcoming) days of user's password expiry date.<br>
$nonexpiring Non-Expiring accounts.<br>
$countsent Emails Sent.<br>
$countnotsent Emails skipped.<br>
$countfailed Emails failed.
"
try {
Send-Mailmessage -smtpServer $smtpServer -from $from -to $adminEmailAddr -subject "Password Expiry Logs" -body $body -bodyasHTML -Attachments "$logFile" -priority High -Encoding $textEncoding -ErrorAction Stop -ErrorVariable err
} catch {
write-host "Error: Failed to email CSV log to $adminEmailAddr via $smtpServer"
} finally {
if ($err.Count -eq 0) {
write-host "CSV emailed to $adminEmailAddr"
}
}
}
# End
@Veenhof03
Copy link

Is there a way to mail with this script to users that have no "DaystoExpire" value and have "PasswordNeverExpires" value enabled?
Will the script email users if either one is configured? Or is there an option to add an email to neverexpires users?

@meoso
Copy link
Author

meoso commented Jun 2, 2022

Is there a way to mail with this script to users that have no "DaystoExpire" value and have "PasswordNeverExpires" value enabled? Will the script email users if either one is configured? Or is there an option to add an email to neverexpires users?

such does not make logical sense, TBH. BUT you could easily modify the script. look at the $users = get-aduser ... near line 42 and modify as needed. look at the area that calculates DaysToExpire, then possibly modify the switch statement coptions near line 113.

i have another script that we never put into production that would tell NEW accounts they needed to change there initial password. maybe this is something you would like. please see https://gist.github.com/meoso/f17307bb852930960c1c7fb2d55e967f and review lines 48 & 49 for potential modification. again the powershell $users = get-aduser ... is how you filter users. see cmdlet get-aduser.

@Veenhof03
Copy link

Is there a way to mail with this script to users that have no "DaystoExpire" value and have "PasswordNeverExpires" value enabled? Will the script email users if either one is configured? Or is there an option to add an email to neverexpires users?

such does not make logical sense, TBH. BUT you could easily modify the script. look at the $users = get-aduser ... near line 42 and modify as needed. look at the area that calculates DaysToExpire, then possibly modify the switch statement coptions near line 113.

i have another script that we never put into production that would tell NEW accounts they needed to change there initial password. maybe this is something you would like. please see https://gist.github.com/meoso/f17307bb852930960c1c7fb2d55e967f and review lines 48 & 49 for potential modification. again the powershell $users = get-aduser ... is how you filter users. see cmdlet get-aduser.

I know it doesn't make sense. But thats the wonderfull world of IT-Customers for you ;)
The suggested script works like a charm in this situation, Thnx!

@Robert-Loveless
Copy link

NewTimeSpan Error_LI

Anyone else getting these errors? The script was working when I tested a couple days ago but now I'm getting this error.

@meoso
Copy link
Author

meoso commented Jun 13, 2022

Anyone else getting these errors? The script was working when I tested a couple days ago but now I'm getting this error.

I'm not getting those errors -- i copied the example and set my searchbase.
Might be something with rights to powershell, or rights to AD objects 🤷‍♂️

@shujanajmee
Copy link

Anyone had any luck with SMTP authentications, credentials and etc? I got stuck with email going out. The log says "send fail"

I added following to the script to for SMTP credentials:

$smtpCredentialsUsername = "noreply@example.com"
$smtpCredentialsPassword = ConvertTo-SecureString -String ************ -AsPlainText -Force

$smtpServer="smtp.office365.com"
$Creds = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $smtpCredentialsUsername, $smtpCredentialsPassword

Send-Mailmessage -from $from -to $recipient -subject $subject -smtpServer $smtpServer -Credential $creds -UseSsl -Port 587 -body $body -bodyasHTML -priority High -Encoding $textEncoding -ErrorAction Stop -ErrorVariable err

@meoso
Copy link
Author

meoso commented Jul 1, 2022

Anyone had any luck

skimming over it, seems it would work, the only thing i'd say is to check if -usessl is correct, some searching mentions TLS; otherwise nothing comes to mind of why it fails. i have not personally had the displeasure of needing credentials (yet).

@shujanajmee
Copy link

shujanajmee commented Jul 5, 2022

@meoso I was able to get this working with PowerShell 7. Installed the MSI for PS7 and it worked in Win Server 2012 R2. Now I will move on to to get this on a schedule using task scheduler with Powershell 7.

Also, enabled TLS 1.2 on this server, after TLS 1.2 was enabled. I was still getting error message:

Send-Mailmessage : Unable to read data from the transport connection: net_io_connectionclosed.
At line:1 char:1
+ Send-Mailmessage -from $from -to $recipient -subject $subject -smtpSe ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.Mail.SmtpClient:SmtpClient) [Send-MailMessage], SmtpExcept
   ion
    + FullyQualifiedErrorId : SmtpException,Microsoft.PowerShell.Commands.SendMailMessage

Thanks, Shuja

@meoso
Copy link
Author

meoso commented Jul 5, 2022

@shujanajmee
Copy link

@meoso

this line, in PowerShell, did the job:

[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;

@Fauzyx
Copy link

Fauzyx commented Jul 14, 2022

This is great and works as a charm! Thanks for the input.

@0xC
Copy link

0xC commented Aug 10, 2022

Like some others here, this isn't working for me for some reason. The script runs without error and I receive the admin email, but when testing using an account that expires in 4 days, no user notification email is generated and while I can see the script is looking in the correct OU and correctly sees two user accounts, it fails to detect that one of the two has a password that expires in 4 days. I have the script set to 14 days before and the default 3 days after. Since it's not erroring at all, it's hard to troubleshoot.

@meoso
Copy link
Author

meoso commented Aug 10, 2022

Like some others here, this isn't working for me for some reason. The script runs without error and I receive the admin email, but when testing using an account that expires in 4 days, no user notification email is generated and while I can see the script is looking in the correct OU and correctly sees two user accounts, it fails to detect that one of the two has a password that expires in 4 days. I have the script set to 14 days before and the default 3 days after. Since it's not erroring at all, it's hard to troubleshoot.

no clues in the .csv log? i can investigate additional write-host commands in the for each user loop some day soon if you like. or you can add your own for debugging.... look at all the if-then statements to see if something stands out.... user's password must be set for triggers.

@meoso
Copy link
Author

meoso commented Aug 23, 2022

Like some others here, this isn't working for me for some reason.

I've found two instances that were set for -lt (less-than) that i believe should have been -le (less-than-or-equal). i've also added a couple write-host lines for console logging.
image

image

image

You may also un-comment this line for additional debugging:
image

@meoso
Copy link
Author

meoso commented Aug 23, 2022

New "Non-Expiring" statistic added into the script.
please see last few revisions here: https://gist.github.com/meoso/3488ef8e9c77d2beccfd921f991faa64/revisions
(3 today -- 4 if you count version change)
image

@jeremytbeau
Copy link

I have had this setup for quite some time and receive the admin emails with .csv, although my users are not receiving the emails. I have verified that the email properties in AD are correct. Does this line in the CSV give any tips? (9:45:56 AM","Skipped - Interval")

@meoso
Copy link
Author

meoso commented Sep 9, 2022

Does this line in the CSV give any tips? (9:45:56 AM","Skipped - Interval")

please tell me "version" from the .ps1 file ... i dont have AM/PM specified anywhere as well as don't remember Skipped - Interval text.

@meoso
Copy link
Author

meoso commented Sep 9, 2022

@jeremytbeau , i found the text Skipped - Interval, it exists in Robert Pearman's original script. You'll have to get his support for his script. However the link-back to the Microsoft Original seems "404". i found the script elsewhere (linked below) in which there is an if/then/else which outputs the text in question.

https://windowspoweressentials.com/2017/02/21/powershell-password-reminder-script-updated/ --> https://github.com/titlerequired/public --> PasswordChangeNotification.ps1

look for if(($interval) -Contains($daysToExpire)).

@jeremytbeau
Copy link

jeremytbeau commented Sep 12, 2022 via email

@jeremytbeau
Copy link

@jeremytbeau , i found the text Skipped - Interval, it exists in Robert Pearman's original script. You'll have to get his support for his script. However the link-back to the Microsoft Original seems "404". i found the script elsewhere (linked below) in which there is an if/then/else which outputs the text in question.

https://windowspoweressentials.com/2017/02/21/powershell-password-reminder-script-updated/ --> https://github.com/titlerequired/public --> PasswordChangeNotification.ps1

look for if(($interval) -Contains($daysToExpire)).

Ahh, sorry about the confusion. Perhaps I'll implement yours since it's newer.

@AndreOrlando
Copy link

Hello all, on my AD i have two users OU, how can i set these two searchbases? i tried using $SearchBase="OU=First,DC=EXAMPLE,DC=COM", "OU=Second,DC=EXAMPLE,DC=COM", but i got the error "Get-ADUser : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'SearchBase'. Specified method is not supported."

The paramater Searchbase only accepts one value.

@meoso
Copy link
Author

meoso commented Oct 31, 2022

Hello all, on my AD i have two users OU, how can i set these two searchbases? i tried using $SearchBase="OU=First,DC=EXAMPLE,DC=COM", "OU=Second,DC=EXAMPLE,DC=COM", but i got the error "Get-ADUser : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'SearchBase'. Specified method is not supported."

The paramater Searchbase only accepts one value.

$SearchBase1="OU=First,DC=EXAMPLE,DC=COM"
$SearchBase2="OU=Second,DC=EXAMPLE,DC=COM"
$users1 = get-aduser -SearchBase $SearchBase1 ...
$users2 = get-aduser -SearchBase $SearchBase2 ...
$users = $users1 + $users2

@Tolcheen
Copy link

This script works great. Thank you.
Does anyone know the minimum rights needed to run this script as a scheduled task? Could you do it with a managed service account?

@meoso
Copy link
Author

meoso commented Nov 16, 2022

This script works great. Thank you. Does anyone know the minimum rights needed to run this script as a scheduled task? Could you do it with a managed service account?

the account is up to you. read-only account should work if desired. most user-accounts have AD read access.

examples (set program/arguments):

C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe
-ExecutionPolicy Bypass -WindowStyle Hidden -File "\path\to\script.ps1"

C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe
-ExecutionPolicy RemoteSigned -WindowStyle Hidden -File "\path\to\script.ps1"

C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe
-ExecutionPolicy RemoteSigned -NoProfile -NonInteractive -File "\path\to\script.ps1"

image
image

@jeremytbeau
Copy link

jeremytbeau commented Nov 22, 2022 via email

@zuku81
Copy link

zuku81 commented Jan 18, 2023

How could I use this script for standalone workgroup windows server?

@meoso
Copy link
Author

meoso commented Jan 18, 2023

How could I use this script for standalone workgroup windows server?

I don't honestly know the commends to get user-list from non-Active-Directory groups. If you can figure that out, then you may have a direction to follow. You'd have to remove the Password-Policy related code in it as well. any of the commands Get-AD**** is AD related.

@DavidKelly97
Copy link

Has anyone been successful making this work with Outlook? I'm currently having issues with authentication. I'm struggling to get around the following error message:
Send-Mailmessage : Error in processing. The server response was: 5.7.3 STARTTLS is required to send mail

Any ideas would be amazing, thank you.

@meoso
Copy link
Author

meoso commented Feb 16, 2023

Has anyone been successful making this work with Outlook? I'm currently having issues with authentication. I'm struggling to get around the following error message: Send-Mailmessage : Error in processing. The server response was: 5.7.3 STARTTLS is required to send mail

Any ideas would be amazing, thank you.

please see the conversations/comments above, specifically July-2022.

@Odakolo
Copy link

Odakolo commented Feb 28, 2023

Hi, How can I modify this code to send out notification to a security group?

@jerick70
Copy link

Is there a way to set this up with a CSV of emails for the each user name? We have multiple domains that don't have email addresses assigned to the username.

@meoso
Copy link
Author

meoso commented Apr 17, 2023

Is there a way to set this up with a CSV of emails for the each user name? We have multiple domains that don't have email addresses assigned to the username.

would have to program it yourself. powershell is rather easy to get started. good luck.

@dfranken
Copy link

dfranken commented Sep 6, 2023

is it also possible to set an interval? For example set a interval like on day 21,14,7,6,5,4,3,2,1 to send emails to the user if the expiry date matches one of the interval days?

@meoso
Copy link
Author

meoso commented Sep 7, 2023

is it also possible to set an interval? For example set a interval like on day 21,14,7,6,5,4,3,2,1 to send emails to the user if the expiry date matches one of the interval days?

i'm sure it would be possible with some clever if/then statement. look at line 150.
then maybe this resource: https://devblogs.microsoft.com/scripting/powertip-does-powershell-array-contain-a-value/
so i presume you could set an array of days, then compare $array.Contains($daystoexpire), but i'll leave this to you as it may need some testing, troubleshooting, light modifications overall.

@dfranken
Copy link

dfranken commented Sep 13, 2023

is it also possible to set an interval? For example set a interval like on day 21,14,7,6,5,4,3,2,1 to send emails to the user if the expiry date matches one of the interval days?

i'm sure it would be possible with some clever if/then statement. look at line 150. then maybe this resource: https://devblogs.microsoft.com/scripting/powertip-does-powershell-array-contain-a-value/ so i presume you could set an array of days, then compare $array.Contains($daystoexpire), but i'll leave this to you as it may need some testing, troubleshooting, light modifications overall.

i've got it working with help of your pointer and the newly rebuilt script of Robert Pearman.

On line 22 i added this:
$expireinterval = 239,230,21,14,7,6,5,4,3,2,1,0 #how many days before of soon-to-expire paswords. i.e. notify for expiring on certain number of days (and every day until $negativedays)

On line 41 i added:
[array]$expireinterval | Out-Null

On line 150 i added -and ($expireinterval -contains $daystoexpire) and thus changed in to this:
if ( ($daystoexpire -ge $negativedays) -and ($expireinterval -contains $daystoexpire) -and ($daystoexpire -ne "NA") ) {

@bspropro
Copy link

Has anyone tested this on Windows server 2019?

@meoso
Copy link
Author

meoso commented Dec 20, 2023

Has anyone tested this on Windows server 2019?

yes, running it for years now on a 2019 DC.

@bspropro
Copy link

bspropro commented Jan 9, 2024

I'm sure I am missing something simple but the script is saying all our users have "non-expiring accounts" aside from us admins, which is not the case.

@meoso
Copy link
Author

meoso commented Jan 9, 2024

I'm sure I am missing something simple but the script is saying all our users have "non-expiring accounts" aside from us admins, which is not the case.

such is an attribute of each AD account. You will need to inspect/modify your AD accounts.
each account may have one or the other of passwordSetDate = never OR maxPasswordAge = 0.

probably: image

@bspropro
Copy link

bspropro commented Jan 9, 2024

I'm sure I am missing something simple but the script is saying all our users have "non-expiring accounts" aside from us admins, which is not the case.

such is an attribute of each AD account. You will need to inspect/modify your AD accounts. each account may have one or the other of passwordSetDate = never OR maxPasswordAge = 0.

probably: image

As for maxPasswordAge, that is set to 90 and the password never expires is not checked but I am unable to find the passwordSetDate part.

@meoso
Copy link
Author

meoso commented Jan 9, 2024

sorry, i needed to review the code.
line https://gist.github.com/meoso/3488ef8e9c77d2beccfd921f991faa64#file-example-com-password-expiration-notifications-ps1-L84: $passwordSetDate = $user.PasswordLastSet.
Later an else is triggered when such is null (never set): https://gist.github.com/meoso/3488ef8e9c77d2beccfd921f991faa64#file-example-com-password-expiration-notifications-ps1-L109

so the AD's user attribute is PasswordLastSet.

you could run get-aduser -filter * -properties Name, passwordlastset, passwordneverexpires | Where-Object {$_.passwordlastset -eq $null} | ft Name, passwordlastset, Passwordneverexpires to list all the null PasswordLastSet's.

typically when accounts are created, the passwordLastSet is updated when the user changes their own password. I believe if a script creates an account and sets a password at such time, then it does not count; however, i could be mistaken.

@bspropro
Copy link

sorry, i needed to review the code. line https://gist.github.com/meoso/3488ef8e9c77d2beccfd921f991faa64#file-example-com-password-expiration-notifications-ps1-L84: $passwordSetDate = $user.PasswordLastSet. Later an else is triggered when such is null (never set): https://gist.github.com/meoso/3488ef8e9c77d2beccfd921f991faa64#file-example-com-password-expiration-notifications-ps1-L109

so the AD's user attribute is PasswordLastSet.

you could run get-aduser -filter * -properties Name, passwordlastset, passwordneverexpires | Where-Object {$_.passwordlastset -eq $null} | ft Name, passwordlastset, Passwordneverexpires to list all the null PasswordLastSet's.

typically when accounts are created, the passwordLastSet is updated when the user changes their own password. I believe if a script creates an account and sets a password at such time, then it does not count; however, i could be mistaken.

thank you for the help but I'm just dumb and needed to run as admin

@smaligetgroupcom
Copy link

what rights needed in Active Directory to extract the information

@meoso
Copy link
Author

meoso commented Feb 9, 2024

what rights needed in Active Directory to extract the information

you may have to do some deeper research on your own, but essentially make a new user-account, assign it to the root of the domain and set the security tab for only READ, disabling anything WRITE/DELETE/ADD,etc. Also set it for password-never-expires and cannot-change-password. You can also setup a GPO at root level denying any interactive login for that user. This is what we call a "service-account". some service accounts need write access depending on the usage.

[this comment edited several times]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment