Solved

Enumerating User Accounts in AD for Password Age

Posted on 2009-07-10
9
2,934 Views
Last Modified: 2012-06-27
My scripting skills are weak so I need some help on this one. I have a script that would search the Users container in AD and then create an output text file of the password age for each user. Last Name, First Name and password age. We have since divided our AD by Practice and within each Practice container there are sub containers which have user accounts. I am trying to adjust my existing script to enumerate all users from the root of the domain down but am having no success. I have found reference to 'subtree' but am unfamiliar  with its use. The script that I used in the past is pasted below.

Thanks
Option Explicit
 

Dim objDSE, strDefaultDN, strDN, objContainer, objChild, dtmValue, intAge, strFileName

dim objFSO, objFile, objTextFile, strReportName

Const ForAppending = 8
 

'**** OPEN OBJECTS

Set objDSE = GetObject("LDAP://rootDSE") 'open Active Directory

strDefaultDN = "OU=Users,OU=Insurance,OU=SABC," & objDSE.Get("defaultNamingContext") 'full path to users folder

'strDefaultDN = "OU=Users," & objDSE.Get("defaultNamingContext") 'full path to users folder
 

'**** ARE YOU SURE?

strDN = 	InputBox("Enter the distinguished name of a container" & _

	vbCrLf & "(e.g. " & strDefaultDN & ")", , strDefaultDN) 'allow user to change folder
 

If strDN = "" Then WScript.Quit(1) 'user clicked Cancel = exit script
 

'******** NAME REPORT

strReportName = InputBox("Enter Report File Name:", , "PasswordReport") 'allow user to name report
 

'***** CREATE AND OPEN TEXT FILE

Set objContainer = GetObject("LDAP://" & strDN) 'open users folder

objContainer.Filter = Array("user") 'only read users

strFileName = "C:\Documents and Settings\maunw\Desktop\Password Age Checker\Report\" & strReportName & ".txt"

Set objFSO = CreateObject("Scripting.FileSystemObject") 'create file system object

Set objFile = objFSO.CreateTextFile(strFileName) 'create report file

objFile.close 'close report file

Set objTextFile = objFSO.OpenTextFile(strFileName, ForAppending, True) 'open report file to edit
 

'**** PROCESS

on error resume next 'if there is a bad record, move to the next

For Each objChild In objContainer 'process each user

	dtmValue = objChild.PasswordLastChanged 'date password was changed

	intAge = Int(Now - dtmValue) 'Today - password last changed = age

	objTextFile.WriteLine(objChild.FullName & vbtab & intAge) 'write line to file

Next

objTextFile.Close 'close file

WScript.Echo "END" 'notify that processing is done

Open in new window

0
Comment
Question by:wtm
  • 5
  • 3
9 Comments
 
LVL 31

Expert Comment

by:Henrik Johansson
ID: 24828667
Open a recordset with objRS.Open as showed in the snippet below and loop through the recordset.
strQuery has three parts separated by ; (from, criteria and fields)

I see I missed to save output to file, but it can either be done by using scripting.filesystemobject as used in question or by executing the script with the following command

cscript //NoLogo script.vbs > filename.txt
Option Explicit

On Error Resume Next
 

Dim objDSE,strDomain,strDN,strFields,strFrom,strWhere,strQuery,objRS,objConn,fld,strSeparator,strLine,objUser
 

Set objDSE=GetObject("LDAP://rootDSE") 

strDomain=objDSE.Get("defaultNamingContext")
 

strDN=InputBox("Enter OU in current domain")

If strDN<>"" And Right(strDN,1)<>"," Then strDN=strDN & ","

strFields="samAccountName,sn,givenName,pwdLastSet,adsPath"

strFrom="<LDAP://" & strDN & objDSE.Get("defaultNamingContext") &">"

strWhere="(&(objectClass=user)(objectClass=Person)(samAccountName=*)(userprincipalname=*))"

strQuery= strFrom & ";" & strWhere & ";" & strFields
 

Set objRS=CreateObject("ADODB.RecordSet")

Set objConn=CreateObject("ADODB.Connection")

Call objConn.Open("Provider=ADSDSOObject")

Call objRS.Open(strQuery,objConn)
 

strLine=""

strSeparator=vbTab

For Each fld in objRS.Fields

	Select Case fld.Name

		Case "sn" strLine=strLine & "Last name" & strSeparator

		Case "givenName" strLine=strLine & "First name" & strSeparator

		Case "samAccountName" strLine=strLine & "Username" & strSeparator

		Case "pwdLastSet" strLine=strLine & "Password age" & strSeparator

	End Select

Next

wscript.echo strLine
 

Do While Not objRS.EOF

	strLine=""

	Set objUser=GetObject(objRS("adsPath"))

	For Each fld in objRS.Fields

		If fld.Name="pwdLastSet" Then

			If Not IsNull(objUser.PasswordLastChanged) Then 

				strLine=strLine & CStr(DateDiff("h",objUser.PasswordLastChanged,Now))

			End IF

			strLine=strLine & strSeparator

		ElseIf fld.Name<>"adsPath" Then

			If Not isNull(fld) Then strLine=strLine & CStr(fld) 

			strLine=strLIne & strSeparator

		End IF

	Next

	Set objUser=Nothing

	wscript.echo strLine

	objRS.moveNext

Loop

objRS.Close: Set objRS=Nothing

objConn.Close: Set objConn=Nothing

Open in new window

0
 
LVL 70

Expert Comment

by:Chris Dent
ID: 24829863
wtm,

If you have no existing experience with VbScript you might consider PowerShell for these kind of tasks. It needs these two installed:

http://www.microsoft.com/windowsserver2003/technologies/management/powershell/default.mspx
http://www.quest.com/activeroles-server/arms.aspx

But once you have those open up the version from the Quest folder in the start menu and all you need to do to generate this report is this:

Get-QADUser -IncludedProperties PwdLastSet | `
  Select-Object FirstName, LastName, sAMAccountName, PwdLastSet

If you want to pop that into a file it's:

Get-QADUser -IncludedProperties PwdLastSet | `
  Select-Object FirstName, LastName, sAMAccountName, PwdLastSet | `
  Export-CSV "SomeFile.csv"

The ` above just lets it carry the command onto the next line. Easier to read. It could just be written on a single line.

It can also sort, or easily include any other attributes. To see what could be included run:

Get-QADUser "You" | Format-List *

Sorting would be:

Get-QADUser -IncludedProperties PwdLastSet | `
  Select-Object FirstName, LastName, sAMAccountName, PwdLastSet | `
  Sort-Object PwdLastSet | `
  Export-CSV "SomeFile.csv"

Those tools can be installed wherever you like, it doesn't have to be on your Domain Controller or anywhere silly like that.

Chris
0
 

Author Comment

by:wtm
ID: 24841715
henjoh09:

Thank you for your help. Hoping you can point me in the right direction. I don't need the pop up for each user account info. I just need the information dumped into a text file. I am not able to follow what you did or how you did it. Why the case statement?
0
 

Author Comment

by:wtm
ID: 24845183
What I am looking for is just the code to allow the present script to enumerate through my AD.

Thanks
0
Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

 
LVL 31

Accepted Solution

by:
Henrik Johansson earned 300 total points
ID: 24847558
Looking on the local timestamp of my previous post, and realize I nead to learn myself to avoid coding at 3AM and be better in commenting... :)

The 'case' section is giving a more user friendly labels ('last name' instead of field name sn) when looping through the field names.

The following part executes the query.

Set objRS=CreateObject("ADODB.RecordSet")
Set objConn=CreateObject("ADODB.Connection")
Call objConn.Open("Provider=ADSDSOObject")
Call objRS.Open(strQuery,objConn)

The data is in the objRS recordset, and is accessed by looping through the recordset with moveNext

Do While Not objRS.EOF
  ... handle current record ...
  objRS.moveNext
Loop

I see that I used Datediff("h",...) instead of DateDiff("d",...) in previous sample making the list to be the difference of hours instead of days

You can as said use 'cscript //NoLogo scriptname.vbs > filename.txt' for letting previous sample write the output to file instead of getting the popups for each line. Rewrote the sample to also include the filesystemobject handling from original question, so script is saving the textfile.

Option Explicit

'On Error Resume Next

 

Dim objDSE,strDomain,strDN,strFields,strFrom,strWhere,strQuery,objRS,objConn,fld,strSeparator,strLine,objUser

 

Set objDSE=GetObject("LDAP://rootDSE") 

strDomain=objDSE.Get("defaultNamingContext")

 

strDN=InputBox("Enter OU in current domain")

If strDN<>"" And Right(strDN,1)<>"," Then strDN=strDN & ","

strFields="samAccountName,sn,givenName,pwdLastSet,adsPath"

strFrom="<LDAP://" & strDN & strDomain &">"

strWhere="(&(objectClass=user)(objectClass=Person)(samAccountName=*)(userprincipalname=*))"

strQuery= strFrom & ";" & strWhere & ";" & strFields
 

dim objFSO, objFile, objTextFile, strReportName, strFileName

Const ForAppending = 8
 

' Open connection and execute query 

Set objRS=CreateObject("ADODB.RecordSet")

Set objConn=CreateObject("ADODB.Connection")

Call objConn.Open("Provider=ADSDSOObject")

Call objRS.Open(strQuery,objConn)

 

strLine=""

strSeparator=vbTab
 
 

strReportName = InputBox("Enter Report File Name:", , "PasswordReport_" & Replace(Replace(strDN,"=","-"),",","_")) 'allow user to name report

strFileName = "C:\temp\" & strReportName & ".txt"

Set objFSO = CreateObject("Scripting.FileSystemObject") 'create file system object

Set objFile = objFSO.CreateTextFile(strFileName) 'create report file

objFile.close: Set objFile=Nothing 'close report file

Set objTextFile = objFSO.OpenTextFile(strFileName, ForAppending, True) 'open report file for append
 
 

' Column labels

For Each fld in objRS.Fields

	Select Case fld.Name

		Case "sn" strLine=strLine & "Last name" & strSeparator

		Case "givenName" strLine=strLine & "First name" & strSeparator

		Case "samAccountName" strLine=strLine & "Username" & strSeparator

		Case "pwdLastSet" strLine=strLine & "Password age" & strSeparator

	End Select

Next

'wscript.echo strLine

objTextFile.WriteLIne strLIne
 

' Loop through data 

Do While Not objRS.EOF

	strLine=""

	Set objUser=GetObject(objRS("adsPath"))

	For Each fld in objRS.Fields

		If fld.Name="pwdLastSet" Then

			If Not IsNull(objUser.PasswordLastChanged) Then 

				strLine=strLine & CStr(DateDiff("d",objUser.PasswordLastChanged,Now))

			End IF

			strLine=strLine & strSeparator
 

		ElseIf fld.Name<>"adsPath" Then ' exclude DN

			If Not isNull(fld) Then strLine=strLine & CStr(fld) 

			strLine=strLIne & strSeparator

		End IF

	Next

	Set objUser=Nothing

'	wscript.echo strLine

	objTextFile.WriteLIne strLIne
 

	objRS.moveNext

Loop
 

objRS.Close: Set objRS=Nothing

objConn.Close: Set objConn=Nothing

objTextFile.Close: Set objTextFile=Nothing

Set objFSO=Nothing

Open in new window

0
 

Author Closing Comment

by:wtm
ID: 31602250
Thank you henjoh09 for the redue. Works as expected.
0
 

Author Comment

by:wtm
ID: 24848177
henjoh09,

One last thing. After testing, the scrip works great with the exception of enumerating through one of my OU's. If I give the entire path to the OU i.e OU=Commercial A and A,OU=SFACTS, it works fine. But if I only give the root OU i.e. OU=SFACTS, I get a script failure on line 55 char 4 source (null). Under the SFACTS OU there are several sub OU's with OU's under that. Under my SABC OU I have only one sub layer of OU's. Enumerating through the SABC OU works Fine. Any Ideas?

Thanks Again
0
 
LVL 31

Expert Comment

by:Henrik Johansson
ID: 24848259
I commented out the 'On Error Resume Next' on line 2 for debugging and forgot to change it back when posting.
0
 

Author Comment

by:wtm
ID: 24848613
That did it. Thanks for all your help.
0

Featured Post

Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

Join & Write a Comment

Installing a printer using group policy preferences is not that hard let’s take a look at it. First lets open up your group policy console and edit the policy you want to add it to. I recommend creating a new policy for each printer makes it a l…
If you need to start windows update installation remotely or as a scheduled task you will find this very helpful.
This tutorial will walk an individual through the process of transferring the five major, necessary Active Directory Roles, commonly referred to as the FSMO roles from a Windows Server 2008 domain controller to a Windows Server 2012 domain controlle…
This tutorial will walk an individual through the process of configuring their Windows Server 2012 domain controller to synchronize its time with a trusted, external resource. Use Google, Bing, or other preferred search engine to locate trusted NTP …

706 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now