How to Find Active Directory Accounts with Expiring Passwords

How to Find Active Directory Accounts with Expiring Passwords

In an organization with multiple employees, it becomes tedious for the administrators to track users who's passwords are about to expire. This is crucial because, if the users fails to reset their passwords, the phone at the helpdesk is bound to ring. This means that the users are locked out of their accounts, loose out on productivity, and if the helpdesk is outsourced to external organizations, these password expiry calls can end up costing the organizations dearly.

 

Commonly available PowerShell scripts can only be used to return the list of users and when their respective passwords will expire. However, if you'd like to know which of your users have a soon-to-expire password, that is, lets say, passwords that expire in 7 days or lesser, you'd have very few options. Luckily, admins can turn to VBScript to retrieve a list of Soon-to-expire password users  . Follow these steps:

 

  1. Identify the domain from which you want to retrieve the list of users and the necessary LDAP attributes.

  2. Identify the primary Domain Controller which houses the user base.

  3. Compile and execute the script.

The report will be exported in the given format. For a different format, the script is to be edited accordingly.

VBScript

  1. Option Explicit
  2. Dim adoCommand, adoConnection, strBase, strFilter, strAttributes
  3. Dim objRootDSE, strDNSDomain, strQuery, adoRecordset
  4. Dim dtmDate1, dtmDate2, intDays, strName, strEmail
  5. Dim lngSeconds1, str64Bit1, lngSeconds2, str64Bit2
  6. Dim objShell, lngBiasKey, lngBias, k
  7. Dim objDomain, objMaxPwdAge, lngHighAge, lngLowAge, sngMaxPwdAge
  8. Dim objDate, dtmPwdLastSet, dtmExpires
  9. Dim strItem, strPrefix, objFSO, objLogFile

  10. Const ForWriting = 2
  11. Set objFSO = CreateObject("Scripting.FileSystemObject")
  12. Set objLogFile = objFSO.CreateTextFile("C:\Scripts\PasswordExp.csv", ForWriting, True)

  13. objLogFile.Write "sAMAccountName,"
  14. objLogFile.Write "mail,"
  15. objLogFile.Write "passwordExpiresAt"
  16. objLogFile.Writeline

  17. ' Specify number of days. Any users whose password expires within
  18. this many days after today will be processed.'
  19. intDays = 14

  20. ' Determine domain maximum password age policy in days.
  21. Set objRootDSE = GetObject("LDAP://RootDSE")
  22. strDNSDomain = objRootDSE.Get("DefaultNamingContext")
  23. Set objDomain = GetObject("LDAP://" & strDNSDomain)
  24. Set objMaxPwdAge = objDomain.MaxPwdAge

  25. lngHighAge = objMaxPwdAge.HighPart
  26. lngLowAge = objMaxPwdAge.LowPart
  27. If (lngLowAge < 0) Then
  28. lngHighAge = lngHighAge + 1
  29. End If

  30. ' Convert from 100-nanosecond intervals into days.
  31. sngMaxPwdAge = -((lngHighAge * 2^32) _
  32. + lngLowAge)/(600000000 * 1440)

  33. ' Determine the password last changed date such that the password
  34. would just now be expired. We will not process users whose
  35. password has already expired.'
  36. dtmDate1 = DateAdd("d", - sngMaxPwdAge, Now())

  37. ' Determine the password last changed date such that the password
  38. 'will expire intDays in the future.'
  39. dtmDate2 = DateAdd("d", intDays - sngMaxPwdAge, Now())

  40. ' Obtain local Time Zone bias from machine registry.
  41. This bias changes with Daylight Savings Time.'
  42. Set objShell = CreateObject("Wscript.Shell")
  43. lngBiasKey = objShell.RegRead("HKLM\System\CurrentControlSet\Control\" _
  44. & "TimeZoneInformation\ActiveTimeBias")
  45. If (UCase(TypeName(lngBiasKey)) = "LONG") Then
  46. lngBias = lngBiasKey
  47. ElseIf (UCase(TypeName(lngBiasKey)) = "VARIANT()") Then
  48. lngBias = 0
  49. For k = 0 To UBound(lngBiasKey)
  50. lngBias = lngBias + (lngBiasKey(k) * 256^k)
  51. Next
  52. End If

  53. ' Convert the datetime values to UTC.
  54. dtmDate1 = DateAdd("n", lngBias, dtmDate1)
  55. dtmDate2 = DateAdd("n", lngBias, dtmDate2)

  56. ' Find number of seconds since 1/1/1601 for these dates.
  57. lngSeconds1 = DateDiff("s", #1/1/1601#, dtmDate1)
  58. lngSeconds2 = DateDiff("s", #1/1/1601#, dtmDate2)

  59. ' Convert the number of seconds to a string
  60. ' and convert to 100-nanosecond intervals.
  61. str64Bit1 = CStr(lngSeconds1) & "0000000"
  62. str64Bit2 = CStr(lngSeconds2) & "0000000"

  63. ' Setup ADO objects.
  64. Set adoCommand = CreateObject("ADODB.Command")
  65. Set adoConnection = CreateObject("ADODB.Connection")
  66. adoConnection.Provider = "ADsDSOObject"
  67. adoConnection.Open "Active Directory Provider"
  68. Set adoCommand.ActiveConnection = adoConnection

  69. ' Search entire Active Directory domain.
  70. strBase = "<LDAP://" & strDNSDomain & ">"
  71.  
  72. ' Filter on user objects where the password expires between the
  73. ' dates specified, the account is not disabled, password never
  74. ' expires is not set, password not required is not set,
  75. ' and password cannot change is not set.
  76. strFilter = "(&(objectCategory=person)(objectClass=user)" _
  77. & "(pwdLastSet>=" & str64Bit1 & ")" _
  78. & "(pwdLastSet<=" & str64Bit2 & ")" _
  79. & "(!userAccountControl:1.2.840.113556.1.4.803:=2)" _
  80. & "(!userAccountControl:1.2.840.113556.1.4.803:=65536)" _
  81. & "(!userAccountControl:1.2.840.113556.1.4.803:=32)" _
  82. & "(!userAccountControl:1.2.840.113556.1.4.803:=48))"
  83.  
  84. ' Comma delimited list of attribute values to retrieve.
  85. strAttributes = "sAMAccountName,mail,pwdLastSet"

  86. ' Construct the LDAP syntax query.
  87. strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
  88. adoCommand.CommandText = strQuery
  89. adoCommand.Properties("Page Size") = 100
  90. adoCommand.Properties("Timeout") = 30
  91. adoCommand.Properties("Cache Results") = False

  92. ' Run the query.
  93. Set adoRecordset = adoCommand.Execute

  94. ' Enumerate the resulting recordset.
  95. Do Until adoRecordset.EOF
  96. strName = adoRecordset.Fields("sAMAccountName").Value
  97. strEmail = adoRecordset.Fields("mail").Value & ""
  98. If (TypeName(adoRecordset.Fields("pwdLastSet").Value) = "Object") Then
  99. Set objDate = adoRecordset.Fields("pwdLastSet").Value
  100. dtmPwdLastSet = Integer8Date(objDate, lngBias)
  101. Else
  102. dtmPwdLastSet = #1/1/1601#
  103. End If
  104. dtmExpires = DateAdd("d", sngMaxPwdAge, dtmPwdLastSet)

  105. objLogFile.Write strName & ","
  106. objLogFile.Write strEmail & ","
  107. objLogFile.Write dtmExpires
  108. objLogFile.Writeline
  109. adoRecordset.MoveNext
  110. Loop

  111. ' Clean up.
  112. objLogFile.Close
  113. adoRecordset.Close
  114. adoConnection.Close

  115. Function Integer8Date(ByVal objDate, ByVal lngBias)
  116. ' Function to convert Integer8 (64-bit) value to a date, adjusted for
  117. ' local time zone bias.
  118. Dim lngAdjust, lngDate, lngHigh, lngLow
  119. lngAdjust = lngBias
  120. lngHigh = objDate.HighPart
  121. lngLow = objDate.LowPart
  122. ' Account for error in IADsLargeInteger property methods.
  123. If (lngLow < 0) Then
  124. lngHigh = lngHigh + 1
  125. End If
  126. If (lngHigh = 0) And (lngLow = 0) Then
  127. lngAdjust = 0
  128. End If
  129. lngDate = #1/1/1601# + (((lngHigh * (2 ^ 32)) _
  130. + lngLow) / 600000000 - lngAdjust) / 1440
  131. ' Trap error if lngDate is ridiculously huge.
  132. On Error Resume Next
  133. Integer8Date = CDate(lngDate)
  134. If (Err.Number <> 0) Then
  135. On Error GoTo 0
  136. Integer8Date = #1/1/1601#
  137. End If
  138. On Error GoTo 0

  139. End Function