Jump to content

Password Expiration Guardian


t0nZ
 Share

Recommended Posts

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 :shifty:...

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

 

 

Edited by t0nZ
Added password expiration reset
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...