'This VBScript will run aggainst the ActiveDirectory Domain the executing User is a Member of.
'The executing User does not need to be an Domain-Administrator of any kind, a simply User is able to read the necessary information from ActiveDirectory
'
'Please configure the following Options - the Script does not accept or expect any Start-Parameter
'
'Version 2.2 - 04/12/2017: Florian Rossmark
'---------------------------------- START OPTIONS ----------------------------------
Const strSMTPServer = "relay.domain.com" 'SMTP Mail-Server to use
Const intSMTPServerPort = 25 'SMTP Mail-Server connection Port Number
Const strFrom = "pwdscript@domain.com" 'SMTP Mail From Address
Const strToAdmin = "helpdesk@domain.com" 'SMTP Mail To Address for Administrator
Const strAdminMailSubject = "Company Name - User Password Script Results to Admin" 'Subject for Mail to Admin
Const strUserMailSubjectExpired = "Your password has expired!" 'Subject for Mail to User when Password is expired
Const strUserMailSubjectWillExpire = "Your password will expire in REPLACEWITHDAYS days" 'Subject for Mail to User when Password will expire - the exact word REPLACEWITHDAYS will be replaced by the days left value
Const strBodyURL = "file://C:/scripts/PWD/PWDExpire.html" 'URL or full file-path (HTML) to import for Body, the entire content of this URL/FILE will be imported to the Body of the email and should explain ways how to change the password
Const strAttachment = "" 'full File-Path to an attachment for the email to the users / leave empty if no attachment
Const strLDAPSortColumn = "pwdLastSet" 'per default: pwdLastSet / sort column for LDAP query
Const intStartWithPWexpiresInDays = 5 'If the Passwords expires in days N or less, the script will inform the user
Const bolIgnoreDisabledAccounts = True 'Disabled accounts should always be ignored
Const bolInformAdminAboutPWexpires = True 'This will inform the Admin about expiring passwords
Const bolInformAdminAboutPWisExpired = True 'This will inform the Admin about accounts with expired Passwords
Const bolInformAdminAboutPWneverExpires = True 'This will inform the Admin about accounts with password set to never expire
Const bolInformAdminAboutUserCantChangePW = True 'This will inform the Admin about users who are not allowed to change their password
Const bolInformAdminAboutAccountDisabled = True 'This will inform the Admin about disabled accounts found - this would have been done in ADS by an administrator
Const bolInformAdminAboutExpiredUserAccount = True 'This will inform the admin if the User Account has an expiration date and the account is expired
Const bolInformAdminAboutAccountWithoutEMail = True 'This will inform the Admin about accounts without a set email address
Const bolInformAdminAboutStillGoodPasswords = True 'This will inform the Admin about Users/Passwords that are still valid
Const bolInformAdminAboutIgnoredUsersExcludedByGroup = True 'This will inform the Admin about Users that have been ignored by the strGroupsExclude filter
'Please Note: Status Account Locked will not be checked, this should be corrected automatically by the Default Security GPO instead (will be in most cases by default)
'Filter Priority 1 - only Users in those OU Paths will be processed
'Use LDAP DN like: "OU=Folder,OU=Folder,DC=Domain,DC=local", you do not need to include the DC=Domain,DC=local - the Script will add this information if necessary
'Use | (pipe) if you want to add more then one LDAP DN Path
'Leave empty ("") to disable this filter
Const strSearchOUs = "" '"OU=Users,OU=Site,DC=domain,DC=local"
'Filter Priority 2 - if the User Object is still not excluded, this Group Exclude filter will be applied
'If the user is member of one of those groups (if multiple groups are defined), he will be ignored
'Use | (pipe) if you want to add more then one GroupName
'Leave empty ("") to disable this filter
'Example: "Group Number1|GroupNumber2"
Const strGroupsExclude = "No Password notification emails"
'Filter Priority 3 - if the User Object is still not excluded, this Group Include filter will be applied
'The user has to be a member of one of those groups (if multiple groups are defined)
'Use | (pipe) if you want to add more then one GroupName
'Leave empty ("") to disable this filter
'Example: "Group Number1|GroupNumber2"
Const strGroupsInclude = ""
Const bolDebug = True 'Set TRUE for Script-Output, highly recommended to execute the Script in CMD with CSCRIPT <ScriptName>
Const bolAttachDebugToAdminMail = False 'The Debug Output will be attached to the Admin-Mail (independent from bolDebug)
Const bolTestDebugOutputToConsoleOnly = False 'This will disable the Mail.Send - only output to the CMD will be generated, please enable bolDebug
Const bolRedirectMailToAdmin = True 'This will redirect all Mails to the Admin, instead of sending them to the User - the Subject line will include the User-Mail Address in this case
Const bolAdminMailOnly = False 'This will send the Admin-Mail only, no User Mail will be generated
'---------------------------------- END OPTIONS ----------------------------------
'---------------------------------- Script starts here ----------------------------------
'---------------------------------- please do not modify after this line ----------------------------------
'Reference: https://msdn.microsoft.com/en-us/library/aa772300(v=vs.85).aspx
Const ADS_UF_DONT_EXPIRE_PASSWD = 65536
Const ADS_UF_PASSWD_CANT_CHANGE = 64
Const ADS_UF_PASSWORD_EXPIRED = 8388608
Const ADS_ACETYPE_ACCESS_DENIED_OBJECT = 6
Const CHANGE_PASSWORD_GUID = "{ab721a53-1e2f-11d0-9819-00aa0040529b}"
'A few Global Variables for the Admin-Mail
Dim strAdminPWexpires, strAdminPWisExpired, strAdminPWneverExpires, strAdminUserCantChangePW, strAdminAccountDisabled, strAdminExpiredUserAccount, strAdminAccountWithoutEMail, strAdminPWstillGood, strAdminIgnoredUsersExcludedByGroup
Dim strAdminErrorUsers
Dim strAdminDebug
SearchADS() 'Let's Start the actual process
'starts the actual search procedure - in a sub so it is easier to keep overview of the script
Sub SearchADS()
Dim objADSRoot, objADSDomain, objADSConnection, objADSCommand, objMaxPWDage, objUser
Dim intMaxPWDage, intDaysLeft
Dim bolProcessUser
Dim cntArray
Dim arrSearchOUs, arrGroupsExclude, arrGroupsInclude
Dim iADSCommand
Dim strFirstFoundGroupMembership
arrGroupsExclude = Split(strGroupsExclude, "|")
arrGroupsInclude = Split(strGroupsInclude, "|")
'Connect to ADS and get Users
Set objADSRoot = GetObject("LDAP://rootDSE")
Set objADSDomain = GetObject("LDAP://" & objADSRoot.Get("defaultNamingContext"))
Set objMaxPWDage = objADSDomain.Get("maxPwdAge") 'please note, this script can't obey the latest ADS multible password GPO rules, it will only follow the standard with only one password rule
intMaxPWDage = CCur((objMaxPWDage.HighPart * 2 ^ 32) + objMaxPWDage.LowPart) / CCur(-864000000000)
If Not intMaxPWDage > 0 Then 'Passwords won't expire at all
ScriptDebug("intMaxPWDage is set to: " & intMaxPWDage & " - Script stopped")
SendEmailNotification True, "", True, 0, "intMaxPWDage is incorrect, please check ADS configuration. Script aborded" 'Inform that there is no expiration configuration in Active Directory
Exit Sub 'Stop the Sub / End the Search
End If
Set objADSConnection = CreateObject("ADODB.Connection")
objADSConnection.Open "Provider=ADsDSOObject;"
Set objADSCommand = CreateObject("ADODB.Command")
objADSCommand.ActiveConnection = objADSConnection
If Not Len(strSearchOUs) > 0 Then
ReDim arrSearchOUs(0) 'we still need an array to run the For Next
arrSearchOUs(0) = ""
Else
arrSearchOUs = Split(strSearchOUs, "|")
End If
For iADSCommand = 0 To UBound(arrSearchOUs) 'we already filter OUs if necessary - filter priority 1
If Len(strSearchOUs) > 0 Then 'we need everytime a new objADSDomain to set the Filter based on the OU
If Not Right(arrSearchOUs(iADSCommand),Len(objADSRoot.Get("defaultNamingContext"))) = objADSRoot.Get("defaultNamingContext") Then
If Not Right(arrSearchOUs(iADSCommand),1) = "'" Then
arrSearchOUs(iADSCommand) = arrSearchOUs(iADSCommand) & ","
End If
arrSearchOUs(iADSCommand) = arrSearchOUs(iADSCommand) & objADSRoot.Get("defaultNamingContext")
End If
ScriptDebug("Searching LDAP Path: " & arrSearchOUs(iADSCommand))
Set objADSDomain = GetObject("LDAP://" & arrSearchOUs(iADSCommand))
End If
'samAccountType=805306368 / this is the correct value for USERS only - a search for the ObjectClass USER will also list Contacts
objADSCommand.CommandText = "<" & objADSDomain.ADsPath & ">;(&(samAccountType=805306368));userAccountControl,sAMAccountName,givenName,sn,mail,distinguishedName,lockoutTime;subtree"
objADSCommand.Properties("Sort on") = strLDAPSortColumn
Set rs = objADSCommand.Execute
If Not rs.EOF Then
Do While Not rs.EOF
ScriptDebug("") 'Empty Line before each run for easier to read output
ScriptDebug("PROCESSING USER: " & NZ(rs("givenName"), "") & " " & NZ(rs("sn"), "") & " (" & NZ(rs("sAMAccountName"), "") & ")")
intDaysLeft = 0
bolProcessUser = True
'Priority 2 - Exclude the following groups
If Len(strGroupsExclude) > 0 Then
strFirstFoundGroupMembership = "" 'just to make sure, lets clean the variable
If IsUserInGroup(rs("distinguishedName"), arrGroupsExclude, strFirstFoundGroupMembership) Then 'User is Member of the Group, we ignore him
ScriptDebug("..USER IS MEMBER OF EXCLUDED GROUP: " & strFirstFoundGroupMembership & " - IGNORING USER")
bolProcessUser = False
'old info - 'We don't inform the Admin in this case, would be done over the Debug Info
If bolInformAdminAboutIgnoredUsersExcludedByGroup Then
strAdminIgnoredUsersExcludedByGroup = AddAdminInfoTo(strAdminIgnoredUsersExcludedByGroup, NZ(rs("givenName"), "") & "</td><td>" & NZ(rs("sn"), "") & "</td><td>" & NZ(rs("sAMAccountName"), "") & "</td><td class=""right"">" & AdminInfoDaysLeftHTMLFormat((GetPWDaysLeft(rs("distinguishedName"), NZ(rs("givenName"), ""), NZ(rs("sn"), ""), NZ(rs("sAMAccountName"), ""), intDaysLeft, intMaxPWDage) * -1)), True) 'Add to Admin Info
End If
End If
End If
'Priority 3 - Include the following groups
If bolProcessUser Then 'do we still go on with this User?
If Len(strGroupsInclude) > 0 Then
strFirstFoundGroupMembership = "" 'just to make sure, lets clean the variable
If Not IsUserInGroup(rs("distinguishedName"), arrGroupsInclude, strFirstFoundGroupMembership) Then 'User is Member of the Group, we ignore him
ScriptDebug("..USER IS NOT A MEMBER OF ANY OF THE INCLUDED GROUPS - IGNORING USER")
bolProcessUser = False
'old info - 'We don't inform the Admin in this case, would be done over the Debug Info
If bolInformAdminAboutIgnoredUsersExcludedByGroup Then
strAdminIgnoredUsersExcludedByGroup = AddAdminInfoTo(strAdminIgnoredUsersExcludedByGroup, NZ(rs("givenName"), "") & "</td><td>" & NZ(rs("sn"), "") & "</td><td>" & NZ(rs("sAMAccountName"), "") & "</td><td class=""right"">" & AdminInfoDaysLeftHTMLFormat((GetPWDaysLeft(rs("distinguishedName"), NZ(rs("givenName"), ""), NZ(rs("sn"), ""), NZ(rs("sAMAccountName"), ""), intDaysLeft, intMaxPWDage) * -1)), True) 'Add to Admin Info
End If
Else 'User will be noramlly proccessed, we still inform about it in the Script-Debug
ScriptDebug("..USER IS MEMBER OF INCLUDED GROUP: " & strFirstFoundGroupMembership & " - PROCESSING USER")
End If
End If
End If
'Priority 4 - disabled account? this would have been done by an administrator
If bolProcessUser Then 'do we still go on with this User?
If IsAccountDisabled(rs("distinguishedName")) Then 'Account is disabled
ScriptDebug("..DISABLED USER ACCOUNT FOUND")
If bolIgnoreDisabledAccounts Then 'do we ignore this Account?
bolProcessUser = False
End If
If bolInformAdminAboutAccountDisabled Then
'strAdminAccountDisabled = AddAdminInfoTo(strAdminAccountDisabled, NZ(rs("givenName"), "") & " " & NZ(rs("sn"), "") & " (" & NZ(rs("sAMAccountName"), "") & ")") 'Add to Admin Info
strAdminAccountDisabled = AddAdminInfoTo(strAdminAccountDisabled, NZ(rs("givenName"), "") & "</td><td>" & NZ(rs("sn"), "") & "</td><td>" & NZ(rs("sAMAccountName"), ""), True) 'Add to Admin Info
End If
End If
End If
'Priority 5 - Will User Account expire? (we only check if it is expired, if yes we take action)
If bolProcessUser Then 'do we still go on with this User?
If IsUserAccountExpired(rs("distinguishedName"), dtmExpiration) Then
bolProcessUser = False
ScriptDebug("..USER ACCOUNT IS EXPIRED SINCE: " & dtmExpiration)
If bolInformAdminAboutExpiredUserAccount Then
'strAdminExpiredUserAccount = AddAdminInfoTo(strAdminExpiredUserAccount, NZ(rs("givenName"), "") & " " & NZ(rs("sn"), "") & " (" & NZ(rs("sAMAccountName"), "") & ") - User expired since: " & dtmExpiration) 'Add to Admin Info
strAdminExpiredUserAccount = AddAdminInfoTo(strAdminExpiredUserAccount, NZ(rs("givenName"), "") & "</td><td>" & NZ(rs("sn"), "") & "</td><td>" & NZ(rs("sAMAccountName"), "") & "</td><td class=""right"">" & AdminInfoDaysLeftHTMLFormat(dtmExpiration), True) 'Add to Admin Info
End If
End If
End If
'Priority 6 - password never expires? this would have been done by an administrator
If bolProcessUser Then 'do we still go on with this User?
If PasswordDoesExpire(rs("userAccountControl")) Then 'Password does not expire
ScriptDebug("..USER WITH PASSWORD WILL NOT EXPIRE FOUND")
bolProcessUser = False
If bolInformAdminAboutPWneverExpires Then
'strAdminPWneverExpires = AddAdminInfoTo(strAdminPWneverExpires, NZ(rs("givenName"), "") & " " & NZ(rs("sn"), "") & " (" & NZ(rs("sAMAccountName"), "") & ")") 'Add to Admin Info
strAdminPWneverExpires = AddAdminInfoTo(strAdminPWneverExpires, NZ(rs("givenName"), "") & "</td><td>" & NZ(rs("sn"), "") & "</td><td>" & NZ(rs("sAMAccountName"), ""), True) 'Add to Admin Info
End If
End If
End If
'Priority 7 - User can't even change the password? this would have been done by an administrator
If bolProcessUser Then 'do we still go on with this User?
If UserCantChangePassword(rs("distinguishedName")) Then 'User can't change Password
ScriptDebug("..USER WITH CAN'T CHANGE PASSWORD FOUND")
bolProcessUser = False
If bolInformAdminAboutUserCantChangePW Then
'strAdminUserCantChangePW = AddAdminInfoTo(strAdminUserCantChangePW, NZ(rs("givenName"), "") & " " & NZ(rs("sn"), "") & " (" & NZ(rs("sAMAccountName"), "") & ")") 'Add to Admin Info
strAdminUserCantChangePW = AddAdminInfoTo(strAdminUserCantChangePW, NZ(rs("givenName"), "") & "</td><td>" & NZ(rs("sn"), "") & "</td><td>" & NZ(rs("sAMAccountName"), ""), True) 'Add to Admin Info
End If
End If
End If
'Priority 8 - User does not have an EMail Address? can't inform User if PW is about to expire what shall we do?
If bolProcessUser Then 'do we still go on with this User?
If Not Len(NZ(rs("mail"), "")) > 0 Then 'User is missing email address
ScriptDebug("..USER WITHOUT EMAIL ADDRESS FOUND")
bolProcessUser = False
If bolInformAdminAboutAccountWithoutEMail Then
'strAdminAccountWithoutEMail = AddAdminInfoTo(strAdminAccountWithoutEMail, NZ(rs("givenName"), "") & " " & NZ(rs("sn"), "") & " (" & NZ(rs("sAMAccountName"), "") & ") - Days Left: " & (GetPWDaysLeft(rs("distinguishedName"), NZ(rs("givenName"), ""), NZ(rs("sn"), ""), NZ(rs("sAMAccountName"), ""), intDaysLeft, intMaxPWDage) * -1)) 'Add to Admin Info
strAdminAccountWithoutEMail = AddAdminInfoTo(strAdminAccountWithoutEMail, NZ(rs("givenName"), "") & "</td><td>" & NZ(rs("sn"), "") & "</td><td>" & NZ(rs("sAMAccountName"), "") & "</td><td class=""right"">" & AdminInfoDaysLeftHTMLFormat((GetPWDaysLeft(rs("distinguishedName"), NZ(rs("givenName"), ""), NZ(rs("sn"), ""), NZ(rs("sAMAccountName"), ""), intDaysLeft, intMaxPWDage) * -1)), True) 'Add to Admin Info
End If
End If
End If
'Priority 9 - now we check the Password Age and take action
If bolProcessUser Then 'do we still go on with this User?
If PasswordIsExpired(rs("userAccountControl")) Then 'The Password is already expired? We need to take action / this will be double checked later on with a calculation
ScriptDebug("..PASSWORD IS EXPIRED")
SendEmailNotification False, NZ(rs("mail"), ""), True, 0, "" 'Inform the USER about the expired Password
If bolInformAdminAboutPWisExpired Then
'strAdminPWisExpired = AddAdminInfoTo(strAdminPWisExpired, NZ(rs("givenName"), "") & " " & NZ(rs("sn"), "") & " (" & NZ(rs("sAMAccountName"), "") & ")") 'Add to Admin Info
strAdminPWisExpired = AddAdminInfoTo(strAdminPWisExpired, NZ(rs("givenName"), "") & "</td><td>" & NZ(rs("sn"), "") & "</td><td>" & NZ(rs("sAMAccountName"), "") & "</td><td class=""right"">" & AdminInfoDaysLeftHTMLFormat((GetPWDaysLeft(rs("distinguishedName"), NZ(rs("givenName"), ""), NZ(rs("sn"), ""), NZ(rs("sAMAccountName"), ""), intDaysLeft, intMaxPWDage) * -1)), True) 'Add to Admin Info
End If
Else 'Password is not expired yet, how many days does the User have left?
Set objUser = GetObject("LDAP://" & rs("distinguishedName"))
intDaysLeft = GetPWDaysLeft(rs(