By
t0nZ
Today I want to share this little project made to check and notify the expiration of domain users password, in a Microsoft domain.
Briefly, the script check users domain password expiration and takes actions.
The script can work on multiple domain groups, taking different actions for every group, there is an .ini file with some options.
Groups to be checked are defined in the .ini, and the groups must contain only users no other groups.
The list of users of every group is obtained and if the password expiration in (remaining) days is matched (two possibilities) an email is sent.
It can be a mail sent directly to the user (ini file : tomail=user) or it can be a mail sent to only one address (ini file : tomail=the@mail.it) (like domain admins...) and in this case the mail contains a report with the users approaching expiration.
An operation log is always generated.
In the ini (also the posted one) you can set to have no mail sent (for testing) and/or to have a GUI, but also the GUI is intended only for test, this script is scheduled on a server not logged in, so normally no GUI .
Update 2018/03/16 : added switch to reset the password expiration, useful if you have for example an user (or 500) with psw expiration withing 3 days and you want to restore expiration within 90 days WITHOUT changing password.
Used the way as advised by Microsoft (see the link), but with sth AD.au3 , the fantastic Active Directory UDF
# First change the pwdlastset to 0 because Microsoft wants it this way
$todouser.pwdLastSet = 0
Set-ADUser -Instance $todouser
# Change the pwdlastset to the current date/time of the associate DC
$todouser.pwdLastSet = -1
Set-ADUser -Instance $todouser
Why you should act this way ? Big companies have strange policies listen to me ...
The code:
#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Icon=Icone\Faenza\117.ico
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
; PEG
; Password Expiration Guardian
; (C) NSC 2018
; check user domain password expiration and takes actions
; the script can work on multiple domain groups, taking differente actions for every group.
; the groups must contains only users no other groups
; the list of users of every group is obtained and if the password expiration in day is matched (two possibilities) an email is sent.
; It can be a mail sent directly to the user (ini file : tomail=user)
; or it can be a mail sent to only one address (ini file : tomail=the@mail.it)
; and in this case the mail contains a report with the users approaching expiration
; V.0.5 check based on one domain group
; V.1.0 ini file and check based on multiple domain groups
; V.1.5 ini file with general section to activate "test" GUI, and to enable disable mail send
; V.1.6 march 2018 italian "home made" translation of days and months in date
; V.1.7 added flag pwdLastSet to reset pass expiration - intended to use like a one time on/off switch to reset psw expiration
#include <AD.au3>
#include <File.au3>
#include <GuiEdit.au3>
#include <_zip.au3>
#include <Date.au3>
#include <Inet.au3>
#include <GUIConstantsEx.au3>
#include <GuiEdit.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <Debug.au3>
Global $appname = "PEG", $appver = "V.1.7"
Global $inifile = @ScriptDir & "\" & $appname & ".ini"
Global $geleft = 5, $getop = 5, $gewidth = 790, $geheight = 540
Global $gollogcount = 0, $lastlog = "sicrlf", $cachelog = "", $guititle = "PEG " & $appver, $Gollogedit, $logfile = @ScriptDir & "\" & $appname & "_LOG_", $months2NOTzip = 3
Global $INIgroup, $INItomail, $INImailsubject, $INIsmpt, $INIfromname, $INIfromaddress, $INIdays1, $INIdays2, $INItosend, $arrayINIsections, $guiactive, $flagITA, $flagpwdLastSet
; START program
GOLLOG(">>>>>> " & $appname & " " & $appver & " START >>>>>>")
CFGctrl()
If $guiactive = 1 Then GUI()
$groupnumber = 0
While $groupnumber < $arrayINIsections[0]
$groupnumber += 1
If $arrayINIsections[$groupnumber] <> "general" Then
CFGload($arrayINIsections[$groupnumber])
loaduserS()
EndIf
WEnd
If $guiactive = 1 Then
While 1
$nMsg = GUIGetMsg()
Switch $nMsg
Case $GUI_EVENT_CLOSE
GOLLOG("<<<< STOP <<<<")
Exit
EndSwitch
WEnd
EndIf
GOLLOG("<<<<<< PEG STOP <<<<<<<")
Exit
;STOP program
Func GUI()
GUICreate($guititle, 800, 560, 100, 200, -1)
GUISetBkColor(0x693F54) ; will change background color
$Gollogedit = GUICtrlCreateEdit("", $geleft, $getop, $gewidth, $geheight, BitOR($ES_AUTOVSCROLL, $ES_AUTOHSCROLL, $ES_WANTRETURN, $WS_BORDER, $WS_VSCROLL))
GUICtrlSetBkColor(-1, 0xC7BBC1)
GUICtrlSetData(-1, "" & @CRLF)
GUICtrlSetFont(-1, 9, 800, 0, "consolas")
GUICtrlSetColor(-1, 0x090608)
GUISetState(@SW_SHOW)
GOLLOG("PEG " & $appver & " gui STARTED")
EndFunc ;==>GUI
Func loaduserS()
GOLLOG("workin on group: " & $INIgroup)
Local $Nscad = 0
Dim $report[1] = ["Report:"]
Local $singlereport = ""
Local $usermail = ""
Local $username = ""
Local $datediff = ""
Local $arrayuserpsw
Local $iErr
_AD_Open()
$search1 = _AD_GetGroupMembers($INIgroup)
;$search1 = _AD_RecursiveGetGroupMembers($INIgroup); testing recursive .. in the future maybe
If @error = 0 Then
Local $conta1 = 0
While $search1[0] > $conta1
$conta1 += 1
$arrayuserpsw = _AD_GetPasswordInfo($search1[$conta1])
$datediff = _DateDiff("D", _NowCalc(), $arrayuserpsw[9])
GOLLOG("USER: " & $search1[$conta1])
GOLLOG("Password expires on: " & $arrayuserpsw[9] & " in " & $datediff & " days")
If $datediff = $INIdays1 Or $datediff = $INIdays2 Then
GOLLOG("expiration match !")
If $INItomail = "user" Then ; this IF is relative to .ini file parameter TOSEND
$usermail = _AD_GetObjectAttribute($search1[$conta1], "mail")
GOLLOG("sending mail to: " & $usermail)
If $flagITA = 1 Then
$dataITA = dataITA($arrayuserpsw[9])
Else
$dataITA = _DateTimeFormat($arrayuserpsw[9], 1)
EndIf
Dim $report[1] = ["La tua password scadra' " & $dataITA & ", entro " & $datediff & " giorni."]
_ArrayAdd($report, "Modificala per tempo !")
If $INItosend = 0 Then
GOLLOG("Not sent mail " & $Nscad & ": ")
GOLLOG("from :" & $INIfromname & " | " & $INIfromaddress)
GOLLOG("to :" & $usermail & " | subject: " & $INImailsubject)
Local $reporttext = _ArrayToString($report)
GOLLOG("text :" & $reporttext)
Else
Local $iResponse = _INetSmtpMail($INIsmpt, $INIfromname, $INIfromaddress, $usermail, $INImailsubject, $report, "EHLO " & @ComputerName, "-1") ; perla pearl mail send HS smtp (ehlo required)
$iErr = @error
If $iResponse = 1 Then
GOLLOG("Success! " & "Mail to user sent")
Else
GOLLOG("Error! " & "Mail failed with error code " & $iErr)
EndIf
EndIf
Else
$username = _AD_GetObjectAttribute($search1[$conta1], "displayname")
_ArrayAdd($report, "USER: " & $username)
_ArrayAdd($report, "Password expires on: " & $arrayuserpsw[9] & " in " & $datediff & " days")
$Nscad += 1
If $flagpwdLastSet = 1 Then ; warning : auto pass set
GOLLOG("Re-set password expiration for " & $search1[$conta1])
If _AD_ModifyAttribute($search1[$conta1], "pwdLastSet", "0") Then
GOLLOG("pwdLastSet to 0 - OK")
Else
GOLLOG("pwdLastSet to 0 - ERROR " & @error)
EndIf
If _AD_ModifyAttribute($search1[$conta1], "pwdLastSet", "-1") Then
GOLLOG("pwdLastSet to -1 - OK")
Else
GOLLOG("pwdLastSet to -1 - ERROR " & @error)
EndIf
EndIf
EndIf
EndIf
WEnd
If $Nscad > 0 And $INItomail <> "user" Then
_ArrayAdd($report, $Nscad & " user passwords near expiration")
If $INItosend = 0 Then
GOLLOG("Not sent mail " & $Nscad & ": ")
GOLLOG("from :" & $INIfromname & " | " & $INIfromaddress)
GOLLOG("to :" & $INItomail & " | subject: " & $INImailsubject)
Local $reporttext = _ArrayToString($report)
GOLLOG("text :" & $reporttext)
Else
Local $iResponse = _INetSmtpMail($INIsmpt, $INIfromname, $INIfromaddress, $INItomail, $INImailsubject, $report, "EHLO " & @ComputerName, "-1") ; perla pearl mail send HS smtp (ehlo required)
Local $iErr = @error
If $iResponse = 1 Then
GOLLOG("Success! " & "Mail sent")
Else
GOLLOG("Error! " & "Mail failed with error code " & $iErr)
EndIf
EndIf
EndIf
GOLLOG("checked n° " & $conta1 & " users")
Else
GOLLOG("error in user search " & @error)
EndIf
_AD_Close()
EndFunc ;==>loaduserS
Func dataITA($inputdate) ; Input date in the format "YYYY/MM/DD[ HH:MM:SS]", and translates Tuesday 8 May 2018 -> Martedì 8 maggio 2018 - perla pearl
Local $stringaDATAita = _DateTimeFormat($inputdate, 1)
Select
Case StringInStr($stringaDATAita, "Monday")
$stringaDATAita = StringReplace($stringaDATAita, "Monday", "lunedi'")
Case StringInStr($stringaDATAita, "Tuesday")
$stringaDATAita = StringReplace($stringaDATAita, "Tuesday", "martedi'")
Case StringInStr($stringaDATAita, "Wednesday")
$stringaDATAita = StringReplace($stringaDATAita, "Wednesday", "mercoledi'")
Case StringInStr($stringaDATAita, "Thursday")
$stringaDATAita = StringReplace($stringaDATAita, "Thursday", "giovedi'")
Case StringInStr($stringaDATAita, "Friday")
$stringaDATAita = StringReplace($stringaDATAita, "Friday", "venerdi'")
Case StringInStr($stringaDATAita, "Saturday")
$stringaDATAita = StringReplace($stringaDATAita, "Saturday", "sabato")
Case StringInStr($stringaDATAita, "Sunday")
$stringaDATAita = StringReplace($stringaDATAita, "Sunday", "Domenica")
EndSelect
Select
Case StringInStr($stringaDATAita, "January")
$stringaDATAita = StringReplace($stringaDATAita, "January", "gennaio")
Case StringInStr($stringaDATAita, "February")
$stringaDATAita = StringReplace($stringaDATAita, "February", "febbraio")
Case StringInStr($stringaDATAita, "March")
$stringaDATAita = StringReplace($stringaDATAita, "March", "marzo")
Case StringInStr($stringaDATAita, "April")
$stringaDATAita = StringReplace($stringaDATAita, "April", "aprile")
Case StringInStr($stringaDATAita, "May")
$stringaDATAita = StringReplace($stringaDATAita, "May", "maggio")
Case StringInStr($stringaDATAita, "June")
$stringaDATAita = StringReplace($stringaDATAita, "June", "giugno")
Case StringInStr($stringaDATAita, "July")
$stringaDATAita = StringReplace($stringaDATAita, "July", "luglio")
Case StringInStr($stringaDATAita, "August")
$stringaDATAita = StringReplace($stringaDATAita, "August", "agosto")
Case StringInStr($stringaDATAita, "September")
$stringaDATAita = StringReplace($stringaDATAita, "September", "settembre")
Case StringInStr($stringaDATAita, "October")
$stringaDATAita = StringReplace($stringaDATAita, "October", "ottobre")
Case StringInStr($stringaDATAita, "November")
$stringaDATAita = StringReplace($stringaDATAita, "November", "novembre")
Case StringInStr($stringaDATAita, "December")
$stringaDATAita = StringReplace($stringaDATAita, "December", "dicembre")
EndSelect
Return ($stringaDATAita)
EndFunc ;==>dataITA
Func GOLLOG($logtext) ; Gollog V.2.3 gestione CRLF si o no ; gestione a capo automatico oltre i xx caratteri; gestione pulitura ogni totmila char Perla pearl
; basta aggiungere |nocrlf50 a fine stringa, dove 50 sono gli xx caratteri, conta la prima riga dove si supera quel limite.
; to declare $gollogcount = 0,$lastlog="sicrlf",$cachelog="",$guititle = "nomegui",$Gollogedit,$logfile = @ScriptDir & "\GOLLOG_LOG_", $months2NOTzip = 3
; e anche le misure dell'edit: $geleft = 32, $getop = 32, $gewidth = 553, $geheight = 377
; #include <File.au3> #include <GuiEdit.au3> #include <_zip.au3>
; to insert FUNCs: GOLLOG CLEANEDIT GOLzipZIP
$gollogcount += StringLen($logtext)
;Local $logfile = @ScriptDir & "\GOLLOG_LOG_" ; now global
Local $logfiletimerange = @YEAR & @MON
Local $linelimit = StringRight($logtext, 2)
If StringRight($logtext, 9) = "|nocrlf" & $linelimit Then
$logtext = StringTrimRight($logtext, 9)
Local $acapo = "no"
Else
Local $acapo = "si"
$gollogcount += 4
If $gollogcount > 13000 Then
Sleep(3000)
cleanedit()
; MsgBox(64, "debug", $conta)
$gollogcount = 0
EndIf
EndIf
If $acapo = "no" And (StringLen($cachelog) <= $linelimit) Then ;pearl perla non a capo se
If $lastlog = "nocrlf" Then
If WinExists($guititle) Then ; per non scrivere in gui se questa non esiste
_GUICtrlEdit_AppendText($Gollogedit, $logtext)
EndIf
Else
If WinExists($guititle) Then ; per non scrivere in gui se questa non esiste
_GUICtrlEdit_AppendText($Gollogedit, @MDAY & "/" & @MON & "_" & @HOUR & ":" & @MIN & " " & $logtext)
EndIf
EndIf
$cachelog = $cachelog & $logtext
$lastlog = "nocrlf"
Else
If $lastlog = "nocrlf" Then
If WinExists($guititle) Then ; per non scrivere in gui se questa non esiste
_GUICtrlEdit_AppendText($Gollogedit, $logtext & @CRLF)
EndIf
$cachelog = $cachelog & $logtext
_FileWriteLog($logfile & $logfiletimerange & ".txt", $cachelog)
$cachelog = ""
Else
If WinExists($guititle) Then ; per non scrivere in gui se questa non esiste
_GUICtrlEdit_AppendText($Gollogedit, @MDAY & "/" & @MON & "_" & @HOUR & ":" & @MIN & " " & $logtext & @CRLF)
EndIf
_FileWriteLog($logfile & $logfiletimerange & ".txt", $logtext)
EndIf
$lastlog = "sicrlf"
EndIf
EndFunc ;==>GOLLOG
Func cleanedit() ; cleaning of edit every n° lines (in program put if $nlines > xlines then this function)
GUICtrlDelete($Gollogedit)
$Gollogedit = GUICtrlCreateEdit("", $geleft, $getop, $gewidth, $geheight) ;, BitOR($ES_AUTOVSCROLL, $ES_AUTOHSCROLL, $ES_WANTRETURN, $WS_BORDER))
GUICtrlSetData(-1, "" & @CRLF)
GUICtrlSetFont(-1, 9, 800, 0, "consolas")
GUICtrlSetColor(-1, 0090608)
GUICtrlSetBkColor(-1, 0xF0DAE5)
GUICtrlSetCursor(-1, 3)
EndFunc ;==>cleanedit
Func GOLzipLOG($months2NOTzip) ; zipping old log leaving unzipped only n months
GOLLOG("Starting old logs zipping..")
; path extraction zone
Local $logfiletimerange = @YEAR & @MON
Local $sDrive = "", $sDir = "", $sFileName = "", $sExtension = ""
Local $arraylogpath = _PathSplit($logfile & $logfiletimerange & ".txt", $sDrive, $sDir, $sFileName, $sExtension)
Local $logpath = $arraylogpath[1] & $arraylogpath[2]
Local $hSearch = FileFindFirstFile($logfile & "*.txt") ; searching for logs
Local $logconta = 0
While 1 ; single file processing cycle
Local $sFileName = FileFindNextFile($hSearch)
; If there is no more file matching the search.
If @error Then ExitLoop
Local $stringtime = StringTrimRight(StringRight($sFileName, 10), 4) ;obtaining year-month like 201609
If $logfiletimerange - $stringtime > $months2NOTzip Then ;zipping
If Not FileExists($logfile & ".zip") Then
If Not _Zip_Create($logfile & ".zip", 1) Then
GOLLOG("Error " & @error & " creating " & $logfile & ".zip")
Else
GOLLOG("Created new log archive: " & $logfile & ".zip")
EndIf
Else
GOLLOG("adding to archive: " & $logfile & ".zip")
EndIf
If Not _zip_additem($logfile & ".zip", $logpath & $sFileName) Then
GOLLOG("Error " & @error & " zipping: " & $logpath & $sFileName)
Else
GOLLOG("Added: " & $logpath & $sFileName)
$logconta += 1
If Not FileDelete($logpath & $sFileName) Then
GOLLOG("ERROR - Unable to DELETE log file " & $logpath & $sFileName)
EndIf
EndIf
EndIf
WEnd
GOLLOG("Finished = " & $logconta & " log files zipped")
EndFunc ;==>GOLzipLOG
Func CFGctrl()
; check ini files and load section names
GOLLOG("checkin' INI file..|nocrlf50")
If FileExists($inifile) Then
$guiactive = IniRead($inifile, "general", "GUI", "?")
If $guiactive = "?" Then
GOLLOG("INI incomplete, missing section 'general', value GUI")
ExitwithError()
EndIf
$flagITA = IniRead($inifile, "general", "dataITA", "?")
If $flagITA = "?" Then
GOLLOG("INI incomplete, missing section 'general', value dataITA")
ExitwithError()
EndIf
$flagpwdLastSet = IniRead($inifile, "general", "pwdLastSet", "?")
If $flagpwdLastSet = "?" Then
GOLLOG("INI incomplete, missing section 'general', value pwdLastSet")
ExitwithError()
EndIf
GOLLOG("reading section names...|nocrlf50")
$arrayINIsections = IniReadSectionNames($inifile)
GOLLOG("N°" & $arrayINIsections[0] - 1 & " groups to process")
Else
$message1 = "error: no saved settings !?"
GOLLOG($message1)
ExitwithError()
EndIf
GOLLOG("..completed")
EndFunc ;==>CFGctrl
Func CFGload($section) ; load single ini file section values
$INIgroup = IniRead($inifile, $section, "group", "?")
$INItomail = IniRead($inifile, $section, "tomail", "?")
$INItosend = IniRead($inifile, $section, "tosend", "?")
$INIdays1 = IniRead($inifile, $section, "days1", "?")
$INIdays2 = IniRead($inifile, $section, "days2", "?")
$INImailsubject = IniRead($inifile, $section, "mailsubject", "?")
$INIsmpt = IniRead($inifile, $section, "smtp", "?")
$INIfromname = IniRead($inifile, $section, "fromname", "?")
$INIfromaddress = IniRead($inifile, $section, "fromaddress", "?")
EndFunc ;==>CFGload
Func ExitwithError()
GOLLOG("**********ERROR and STOP****************")
Exit
EndFunc ;==>ExitwithError
The example .ini:
[group1]
group=G_IT_PASSWORD_MONITORED
days1=5
days2=2
tomail=yourgroup@yourdomain.it
;tosend=user; send mails to the domain user mail address, otherwise send to specified address
tosend=0
;tosend ;1 send mails, 0 disable mails for testing
mailsubject=Domain users going to expire passwords
smtp=smtp.your.own.server
fromname=Password Expiration Guardian
fromaddress=PEG@NSC.it
[group2]
group=G_IT_PASSWORD_NOTIFIED
days1=5
days2=2
tomail=user
;tosend=user; send mails to the domain user mail address, otherwise send to specified address
tosend=0
;tosend ;1 send mails, 0 disable mails for testing
mailsubject=Password is expiring !
smtp=smtp.your.own.server
fromname=Password Expiration Guardian
fromaddress=PEG@NSC.it
[general]
GUI=1
;1 gui ON for testing, 0 gui disabled
dataITA = 1
;1 translates datetime in italian, 0 for ENG
pwdLastSet = 0
;1 tries to reset the 'pwdLastSet' attribute (you must have permissions), 0 do nothing