Link to home
Start Free TrialLog in
Avatar of Pete Long
Pete LongFlag for United Kingdom of Great Britain and Northern Ireland

asked on

More Script Requirements

Thanks to all the Expers who have helped this far. Next Item on the agenda

I need a vbscript that will output the following (from my Active Directory)

-------
The following user accounts are disabled

GivenName               LoginName
etc........


The following user accounts are locked out

GivenName              LoginName
etc.........

------

Pete
Avatar of Stephen Manderson
Stephen Manderson
Flag of United Kingdom of Great Britain and Northern Ireland image

Hi there I do not know what version of server you are running but this is a vb script to show disabled user accounts

strDomainDN = "<DomainDN>"    ' e.g. dc=rallencorp,dc=com


strBase   =  "<LDAP://" & strDomainDN & ">;"
strFilter = "(&(objectclass=user)(objectcategory=person)" & _
            "(useraccountcontrol:1.2.840.113556.1.4.803:=2));"
strAttrs  = "name;"
strScope  = "subtree"

set objConn = CreateObject("ADODB.Connection")
objConn.Provider = "ADsDSOObject"
objConn.Open "Active Directory Provider"


set objRS = objConn.Execute(strBase & strFilter & strAttrs & strScope)

objRS.MoveFirst

while Not objRS.EOF
    Wscript.Echo objRS.Fields(0).Value
    objRS.MoveNext
wend

and here is an example of a locked account

https://www.experts-exchange.com/questions/20578437/Query-locked-out-accounts-in-AD.html?query=vbscript+locked+user+account&clearTAFilter=true

Hope this points you in the right direction

Manderson
Avatar of Chris Dent
Hi Pete,

Kind of nice to be able to help you out ;)

Best run with cscript...


Option Explicit

' Global Constants

Const ADS_UF_LOCKOUT = &H10
Const ADS_UF_ACCOUNTDISABLE = &H2

' Global Variable Declaration

Dim objRootDSE, objDomainRoot, objArg, objItem
Dim strFirstName, strLastName, strFullName, strLogonName, strLine
Dim arrUserDisabled()
Dim arrUserLocked()
Dim intUserLocked, intUserDisabled, intUAC

'
' Subroutines
'

Sub ProcessUsers(objUsers)

' This routine takes an OU from OURecurse and checks the users to
' see if we find what we're looking for

    Dim objUser
    Dim strUserName, strTelephoneNumber, strFirstName, strInitials
    Dim strSurname, strFullName, strOutputLine

    objUsers.Filter = Array("user")

    For Each objUser in objUsers
        On Error Resume Next
        strFirstName = objUser.Get("givenName")
        strLastName = objUser.Get("sn")
        strFullName = strFirstName & " " & strLastName
        strLogonName = objUser.Get("sAMAccountName")
        On Error Goto 0

        intUAC = objUser.Get("userAccountControl")
        If ADS_UF_ACCOUNTDISABLE and intUAC Then
            intUserDisabled = intUserDisabled + 1
            ReDim Preserve arrUserDisabled(intUserDisabled)
            arrUserDisabled(intUserDisabled) = strFullName & Chr(9) & strLogonName
        End If
       
        If ADS_UF_LOCKOUT and intUAC Then
            intUserLocked = intUserLocked + 1
            ReDim Preserve arrUserLocked(intUserLocked)
            arrUserLocked(intUserLocked) = strFullName & Chr(9) & strLogonName
        End If
    Next
End Sub


Sub OURecurse(objFirst)

' This OU is responsible for going through the AD Structure

    Dim objOrgUnit, objItem

    Set objOrgUnit = GetObject(objFirst.ADSPath)
    For Each objItem in objOrgUnit
        If (objItem.Class = "organizationalUnit") Then
            ProcessUsers objItem
            OURecurse objItem
        End If
    Next
End Sub


'
' Main Code
'

' Connect to the Root of the Domain

Set objRootDSE = GetObject("LDAP://rootDSE")
Set objDomainRoot = GetObject("LDAP://" & objRootDSE.Get("defaultNamingContext"))

' These will be incremented to 0 the first time they are used
' They represent the size of the array of users

intUserDisabled = -1
intUserLocked = -1

' Objects like the Users OU is actually a container so it must be caught with the container check
' Other objects we're interested in are real OUs
' Not recursing through container objects (too messy, User Objects are containers)

For Each objItem in objDomainRoot
     If (objItem.Class = "container") Then
          ProcessUsers objItem
     End If
     If (objItem.Class = "organizationalUnit") Then
          ProcessUsers objItem
          OURecurse objItem
     End If
Next

wscript.echo "Disabled Accounts"
wscript.echo "Given Name" & Chr(9) & "Logon Name"
For Each strLine in arrUserDisabled
    wscript.echo strLine
Next

wscript.echo ""
wscript.echo "Locked Out Accounts"
wscript.echo "Given Name" & Chr(9) & "Logon Name"
For Each strLine in arrUserLocked
    wscript.echo strLine
Next



HTH

Chris
Avatar of Pete Long

ASKER

:) Hi Chris

Where is this outputting to? is it dynamically getting my domain details?

Screen at present although that's just because you didn't specify where you wanted it dumped to ;)

And yes it figures it's own way around your domain.
OIC its outputting to screen :)

Can It output to c:\disabled_locked.txt

and I sort of answered my own second question there :)
typing at the same time :)


Yep, give me a moment, I'll write in what it needs to output to file.

Currently tab delimited (that's what Chr(9) is) would you prefer something else?
Not fussy at the moment - as you can see Ive allready aked one script question then Ive got this one to do then one more and the last question will be how to knit all three together and produce one output file (in txt or csv)

Here we go... writes output to a file (as specified above). File Name / Path are set in the Constants section at the top of the script - it overwrites the file if it exists.

Option Explicit

' Global Constants

Const ADS_UF_LOCKOUT = &H10
Const ADS_UF_ACCOUNTDISABLE = &H2
Const FILE_NAME = "c:\disabled_locked.txt"

' Global Variable Declaration

Dim objRootDSE, objDomainRoot, objArg, objItem, objFileSystem, objFile
Dim strFirstName, strLastName, strFullName, strLogonName, strLine
Dim arrUserDisabled()
Dim arrUserLocked()
Dim intUserLocked, intUserDisabled, intUAC

'
' Subroutines
'

Sub ProcessUsers(objUsers)

' This routine takes an OU from OURecurse and checks the users to
' see if we find what we're looking for

    Dim objUser
    Dim strUserName, strTelephoneNumber, strFirstName, strInitials
    Dim strSurname, strFullName, strOutputLine

    objUsers.Filter = Array("user")

    For Each objUser in objUsers
        On Error Resume Next
        strFirstName = objUser.Get("givenName")
        strLastName = objUser.Get("sn")
        strFullName = strFirstName & " " & strLastName
        strLogonName = objUser.Get("sAMAccountName")
        On Error Goto 0

        intUAC = objUser.Get("userAccountControl")
        If ADS_UF_ACCOUNTDISABLE and intUAC Then
            intUserDisabled = intUserDisabled + 1
            ReDim Preserve arrUserDisabled(intUserDisabled)
            arrUserDisabled(intUserDisabled) = strFullName & Chr(9) & strLogonName
        End If
       
        If ADS_UF_LOCKOUT and intUAC Then
            intUserLocked = intUserLocked + 1
            ReDim Preserve arrUserLocked(intUserLocked)
            arrUserLocked(intUserLocked) = strFullName & Chr(9) & strLogonName
        End If
    Next
End Sub


Sub OURecurse(objFirst)

' This OU is responsible for going through the AD Structure

    Dim objOrgUnit, objItem

    Set objOrgUnit = GetObject(objFirst.ADSPath)
    For Each objItem in objOrgUnit
        If (objItem.Class = "organizationalUnit") Then
            ProcessUsers objItem
            OURecurse objItem
        End If
    Next
End Sub


'
' Main Code
'

' Connect to the Root of the Domain

Set objRootDSE = GetObject("LDAP://rootDSE")
Set objDomainRoot = GetObject("LDAP://" & objRootDSE.Get("defaultNamingContext"))

' These will be incremented to 0 the first time they are used
' They represent the size of the array of users

intUserDisabled = -1
intUserLocked = -1

' Objects like the Users OU is actually a container so it must be caught with the container check
' Other objects we're interested in are real OUs
' Not recursing through container objects (too messy, User Objects are containers)

For Each objItem in objDomainRoot
     If (objItem.Class = "container") Then
          ProcessUsers objItem
     End If
     If (objItem.Class = "organizationalUnit") Then
          ProcessUsers objItem
          OURecurse objItem
     End If
Next

' Initialize the File System Object and create a text file

Set objFileSystem = CreateObject("Scripting.FileSystemObject")
Set objFile = objFileSystem.CreateTextFile(FILE_NAME, True, False)

objFile.WriteLine "Disabled Accounts"
objFile.WriteLine "Given Name" & Chr(9) & "Logon Name"
For Each strLine in arrUserDisabled
    objFile.WriteLine strLine
Next

objFile.WriteLine ""
objFile.WriteLine "Locked Out Accounts"
objFile.WriteLine "Given Name" & Chr(9) & "Logon Name"
For Each strLine in arrUserLocked
    objFile.WriteLine strLine
Next

Although I really should remember to close off all these objects I'm using... ah well.. it's only a little script...
Works!

Is it possible to autodetect the domain details and domain controllers in the same way? if so can that be applied to
https://www.experts-exchange.com/questions/21410672/Script-Error-Please-Advise.html

Yes it should be.. give me a little time to rework it all.
OK

so in summary it should

give the following output
-----------------------------------------------------------------------------------------------------------
Accounts that have not logged on in the last 30 days

GivenName               LoginName                 LastLoginTime
etc.........

The following user accounts are disabled

GivenName               LoginName
etc........

The following user accounts are locked out

GivenName              LoginName
etc.........
------------------------------------------------------------------------------------------------------------

There will be another bit on the end too (But thats another Question)

Pete

Hope I didn't break it:

Option Explicit

' Global Constants

Const FILE_NAME = "C:\LastLogon.txt"

' Global Variables

Dim objDict, objRootDSE, objDomainControllers, objDomainController, objServer
Dim objUser, objFileSystem, objFile, objTemp
Dim strDomainController
Dim intDays, intCounter
Dim datLastLogon
Dim arrKeys, arrItems

'
' Functions
'
Function Pad(varString, intLength)

      Dim intSize

      intSize = Len(Trim(varString))
      Pad = Trim(varString) & Space(intLength - intSize)
End Function


'
' Main Code
'

' Read in the number of days from the command line

If Wscript.Arguments.Count > 0 Then
      intDays = CInt(Wscript.Arguments(0))
      If intDays = 0 Then
            intDays = 30
      End If
Else
      intDays = 30
End If

Set objDict = CreateObject("Scripting.Dictionary")

' Get the DC List - Assumes OU is still called Domain Controllers

Set objRootDSE = GetObject("LDAP://rootDSE")
Set objDomainControllers = GetObject("LDAP://ou=domain controllers," & objRootDSE.Get("defaultNamingContext"))
objDomainControllers.Filter = Array("computer")

' Reconnect to each DC and grab the Last Logon time

For Each objDomainController In objDomainControllers
      strDomainController = Mid(objDomainController.Name, 4)

      Wscript.Echo "Reading Domain Controller: " & strDomainController
      Set objServer = GetObject("WinNT://" & strDomainController)

      ' Get the User list

      objServer.Filter = Array("User")

      For Each objUser In objServer
            ' This is where Error Handling is needed
            On Error Resume Next
            datLastLogon = objUser.Get("lastLogon")
            On Error Goto 0

            If datLastLogon <> "" Then
                  If objDict.Exists(Trim(objUser.Name)) Then
                        If CDate(datLastLogon) > CDate(objDict.Item(Trim(objUser.Name))(1)) Then
                              objTemp = objDict.Item(Trim(objUser.Name))
                              objTemp(1) = datLastLogon
                              objDict.Item(Trim(objUser.Name)) = objTemp
                        End If
                  Else
                        objDict.Add Trim(objUser.Name), Array(objUser.Get("FullName"), datLastLogon)
                  End If
            End If
            Set objUser = Nothing
      Next
      Set objServer = Nothing
      Set objDomainController = Nothing
Next

Set objDomainControllers = Nothing

' Write the output to the file

Set objFileSystem = CreateObject("Scripting.FileSystemObject")
Set objFile = objFileSystem.CreateTextFile(FILE_NAME, True, False)
objFile.WriteLine "Accounts that have not logged in within the last " & intDays & " days." & vbCrLf


arrKeys = objDict.Keys
arrItems = objDict.Items
For intCounter = 0 To objDict.Count - 1
      If DateDiff("d", CDate(arrItems(intCounter)(1)), Now) >= intDays Then
            objFile.WriteLine Pad(arrItems(intCounter)(0), 30) & Pad(arrKeys(intCounter), 15) & arrItems(intCounter)(1)
      End If
Next

objFile.Close

Set objDict = Nothing
Set objFileSystem = Nothing
Set objFile = Nothing
Set objTemp = Nothing

Oops... one moment... brb with the full set.

Is the last bit particularly complicated?
As I dont know diddly squat about code I dont know?

basically the next bit returns anyone with administrative access either directly or through Group membership

with regards to this Q this is my output

-------------------------------------------
Disabled Accounts
Given Name      Logon Name
       MPCT-000344$
       MPCT-0010$
       MPCT-001022$
       MPCT-001715$
Greig Sharman      Guest
Kay Weatherell      weatherk
Kirsty White      krbtgt
Richard Gill      gillr
Sundaram Janakiraman      SUPPORT_388945a0

Locked Out Accounts
Given Name      Logon Name
----------------------------------------------
Grieg Sharmans account is NOT Disabled (and guest is listed as his logon name)
Guest account is disabled (as usuall)
Sundaram Janakiraman account is NOT disabled and (SUPPORT_388945a0 is listed as his login name)
SUPPORT_388945a0 is disabled (as usuall)

Also

Its not displaying any locked out accounts? Theres at least one (I locked a an account out to test it)
MPCT-000344$
MPCT-0010$
MPCT-001022$
MPCT-001715$

are computer accounts by the way but thats OK I can leave them in :)

Okay here we go... I've glued them all together. I hope I have't broken LastLogon - I never liked that attribute of AD anyway ;)



Option Explicit

' Global Constants

Const ADS_UF_LOCKOUT = &H10
Const ADS_UF_ACCOUNTDISABLE = &H2
Const FILE_NAME = "c:\disabled_locked.txt"
Const LOGON_PERIOD = "30"

' Global Variable Declaration

Dim objRootDSE, objDomainRoot, objArg, objItem, objFileSystem, objFile
Dim objDict, objDomainControllers, objDomainController, objServer
Dim strFirstName, strLastName, strFullName, strLogonName, strLine
Dim strDomainController
Dim arrKeys, arrItems
Dim arrUserDisabled()
Dim arrUserLocked()
Dim intUserLocked, intUserDisabled, intUAC, intCounter
 
'
' Subroutines
'

Sub ProcessUsers(objUsers)

' This routine takes an OU from OURecurse and checks the users to
' see if we find what we're looking for

    Dim objUser
    Dim strUserName, strTelephoneNumber, strFirstName, strInitials
    Dim strSurname, strFullName, strOutputLine

    objUsers.Filter = Array("user")

    For Each objUser in objUsers
        On Error Resume Next
        strFirstName = objUser.Get("givenName")
        strLastName = objUser.Get("sn")
        strFullName = strFirstName & " " & strLastName
        strLogonName = objUser.Get("sAMAccountName")
        strLastLogon = objUser.Get("lastLogin")
        On Error Goto 0

        intUAC = objUser.Get("userAccountControl")
        If ADS_UF_ACCOUNTDISABLE and intUAC Then
            intUserDisabled = intUserDisabled + 1
            ReDim Preserve arrUserDisabled(intUserDisabled)
            arrUserDisabled(intUserDisabled) = strFullName & Chr(9) & strLogonName
        End If
       
        If ADS_UF_LOCKOUT and intUAC Then
            intUserLocked = intUserLocked + 1
            ReDim Preserve arrUserLocked(intUserLocked)
            arrUserLocked(intUserLocked) = strFullName & Chr(9) & strLogonName
        End If
        Set objUser = Nothing
    Next
End Sub


Sub OURecurse(objFirst)

' This OU is responsible for going through the AD Structure

    Dim objOrgUnit, objItem

    Set objOrgUnit = GetObject(objFirst.ADSPath)
    For Each objItem in objOrgUnit
        If (objItem.Class = "organizationalUnit") Then
            ProcessUsers objItem
            OURecurse objItem
        End If
    Next
    Set objOrgUnit = Nothing
    Set objFirst = Nothing
End Sub


Sub GetLastLogon(strDCName)

' Retrieves the Last Logon Time from a DC

      Dim objServer, objUser, objTemp
      Dim datLastLogon

      Wscript.Echo "Reading Domain Controller: " & strDCName
      Set objServer = GetObject("WinNT://" & strDCName)
      objServer.Filter = Array("User")
      For Each objUser In objServer
      
            ' This is where Error Handling is needed
            
            On Error Resume Next
            datLastLogon = objUser.Get("lastLogon")
            On Error Goto 0

            If datLastLogon <> "" Then
                  If objDict.Exists(Trim(objUser.Name)) Then
                        If CDate(datLastLogon) > CDate(objDict.Item(Trim(objUser.Name))(1)) Then
                              objTemp = objDict.Item(Trim(objUser.Name))
                              objTemp(1) = datLastLogon
                              objDict.Item(Trim(objUser.Name)) = objTemp
                        End If
                  Else
                        objDict.Add Trim(objUser.Name), Array(objUser.Get("FullName"), datLastLogon)
                  End If
            End If
            Set objTemp = Nothing
            Set objUser = Nothing
      Next
      Set objServer = Nothing
End Sub

'
' Main Code
'

' Allow us to get the naming context

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

' These will be incremented to 0 the first time they are used

intUserDisabled = -1
intUserLocked = -1

' Get Accounts that haven't logged on recently

Set objDict = CreateObject("Scripting.Dictionary")

' Get the DC List

Set objDomainControllers = GetObject("LDAP://ou=domain controllers," & objRootDSE.Get("defaultNamingContext"))
objDomainControllers.Filter = Array("computer")

For Each objDomainController In objDomainControllers
      strDomainController = Mid(objDomainController.Name, 4)
      Set objDomainController = Nothing
      GetLastLogon(strDomainController)
Next

Set objDomainControllers = Nothing

' Get Accounts that are Disabled or Locked Out

Set objDomainRoot = GetObject("LDAP://" & objRootDSE.Get("defaultNamingContext"))

' Objects like the Users OU is actually a container so it must be caught with the container check
' Other objects we're interested in are real OUs
' Not recursing through container objects (too messy, User Objects are containers)

For Each objItem in objDomainRoot
     If (objItem.Class = "container") Then
          ProcessUsers objItem
     End If
     If (objItem.Class = "organizationalUnit") Then
          ProcessUsers objItem
          OURecurse objItem
     End If
Next

Set objDomainRoot = Nothing
Set objRootDSE = Nothing

' Initialize the File System Object, create a text file and write the report

Set objFileSystem = CreateObject("Scripting.FileSystemObject")
Set objFile = objFileSystem.CreateTextFile(FILE_NAME, True, False)

arrKeys = objDict.Keys
arrItems = objDict.Items

objFile.WriteLine "Accounts that have not logged on in the last 30 days"
objFile.WriteLine ""
objFile.WriteLine "Given Name" & Chr(9) & "Logon Name" & Chr(9) & "Last Logon Time"
objFile.WriteLine ""
For intCounter = 0 To objDict.Count - 1
      If DateDiff("d", CDate(arrItems(intCounter)(1)), Now) >= LOGON_PERIOD Then
            objFile.WriteLine arrItems(intCounter) & Chr(9) & arrKeys(intCounter) & Chr(9) & arrItems(intCounter)
      End If
Next

objFile.WriteLine ""
objFile.WriteLine "The following User Accounts are Disabled"
objFile.WriteLine ""
objFile.WriteLine "Given Name" & Chr(9) & "Logon Name"
For Each strLine in arrUserDisabled
    objFile.WriteLine strLine
Next

objFile.WriteLine ""
objFile.WriteLine "The following User Accounts are Locked Out"
objFile.WriteLine ""
objFile.WriteLine "Given Name" & Chr(9) & "Logon Name"
For Each strLine in arrUserLocked
    objFile.WriteLine strLine
Next

objFile.Close
Set objDict = Nothing
Set objFileSystem = Nothing

Ahh knew I should have hit refresh first...

Checking all the bits..
gives this

Accounts that have not logged on in the last 30 days

Given Name      Logon Name      Last Logon Time


The following User Accounts are Disabled

Given Name      Logon Name
       MPCT-000344$
       MPCT-0010$
       MPCT-001022$
       MPCT-001715$
Greig Sharman      Guest
Kay Weatherell      weatherk
Kirsty White      krbtgt
Richard Gill      gillr
Sundaram Janakiraman      SUPPORT_388945a0

The following User Accounts are Locked Out

Given Name      Logon Name



its listing no accounts in 30 days and the second bit (see comments above)
>>Ahh knew I should have hit refresh first...

LOL that makes two of us

I have a working base for the Last Logon attribute, I probably just broke the original.

Disabled and Lockout checks are now working correctly and I'll post the full thing in a minute or two when I've finished breaking it all some more.

Sorry for the wait... this lot works for me, let me know if it doesn't for you :)



Option Explicit

' Global Constants

Const ADS_UF_ACCOUNTDISABLE = &H2
Const FILE_NAME = "c:\disabled_locked.txt"
Const INACTIVE_PERIOD = 30

' Global Variable Declaration

Dim objRootDSE, objDomainRoot, objArg, objItem, objFileSystem, objFile
Dim objList, objDomainControllers, objDomainController, objServer
Dim objLockout
Dim strFirstName, strLastName, strFullName, strLogonName, strLine
Dim strLockedOut, strEntry
Dim arrKeys, arrItems
Dim arrUserDisabled()
Dim arrUserLocked()
Dim intUserLocked, intUserDisabled, intUAC, intCounter
 
'
' Subroutines
'

Sub ProcessUsers(objUsers)

' This routine takes an OU from OURecurse and checks the users to
' see if we find what we're looking for

      Dim objUser
      Dim strUserName, strTelephoneNumber, strFirstName, strInitials
      Dim strSurname, strFullName, strOutputLine

      objUsers.Filter = Array("user")

      For Each objUser in objUsers
            On Error Resume Next
            strFirstName = ""
            strFirstName = objUser.Get("givenName")
            strLastName = ""
            strLastName = objUser.Get("sn")
            strFullName = strFirstName & " " & strLastName
            strLogonName = ""
            strLogonName = objUser.Get("sAMAccountName")
            strLastLogon = ""
            strLastLogon = objUser.Get("lastLogin")
            On Error Goto 0

            intUAC = objUser.Get("userAccountControl")
            If ADS_UF_ACCOUNTDISABLE and intUAC Then
                  intUserDisabled = intUserDisabled + 1
                  ReDim Preserve arrUserDisabled(intUserDisabled)
                  arrUserDisabled(intUserDisabled) = strFullName & Chr(9) & strLogonName
            End If

            On Error Resume Next
            Set objLockOut = objUser.lockoutTime
            If Err.number <> 0 Then
                  ' Do Nothing
            Else
                  intUserLocked = intUserLocked + 1
                  ReDim Preserve arrUserLocked(intUserLocked)
                  arrUserLocked(intUserLocked) = strFullName & Chr(9) & strLogonName
            End If
            On Error Goto 0
            Set objUser = Nothing
      Next
End Sub


Sub OURecurse(objFirst)

' This OU is responsible for going through the AD Structure

      Dim objOrgUnit, objItem

      Set objOrgUnit = GetObject(objFirst.ADSPath)
      For Each objItem in objOrgUnit
            If (objItem.Class = "organizationalUnit") Then
                  ProcessUsers objItem
                  OURecurse objItem
            End If
      Next
      Set objOrgUnit = Nothing
      Set objFirst = Nothing
End Sub


Sub GetLastLogon(objDomainController)

' Retrieves the Last Logon Time from a DC

      Dim objUsers, objUser, objTemp
      Dim datLastLogon
      Dim strDCName, strUserName

      strDCName = Mid(objDomainController.Name, 4, Len(objDomainController.Name))

      Set objUsers = GetObject("WinNT://" & strDCName)
      objUsers.Filter = Array("user")
      For Each objUser In objUsers
      
            ' This is where Error Handling is needed
            
            On Error Resume Next
            strLogonName = ""
            strLogonName = objUser.Name
            strFullName = ""
            strFullName = objUser.FullName
            datLastLogon = ""
            datLastLogon = objUser.LastLogin
            On Error Goto 0

            If datLastLogon <> "" Then
                  If objList.Exists(strLogonName) Then
                        If datLastLogon > objList(strLogonName) Then
                              objList(strLogonName) = datLastLogon
                        End If
                  Else
                        objList.Add strLogonName, Array(strFullName, datLastLogon)
                  End If
            End If
            Set objUser = Nothing
      Next
      Set objServer = Nothing
End Sub

'
' Main Code
'

' Allow us to get the naming context

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

' These will be incremented to 0 the first time they are used

intUserDisabled = -1
intUserLocked = -1

' Get Accounts that haven't logged on recently

Set objList = CreateObject("Scripting.Dictionary")

' Get the DC List

Set objDomainControllers = GetObject("LDAP://ou=domain controllers," & objRootDSE.Get("defaultNamingContext"))
objDomainControllers.Filter = Array("computer")

For Each objDomainController In objDomainControllers
      GetLastLogon(objDomainController)
Next

Set objDomainControllers = Nothing

' Get Accounts that are Disabled or Locked Out

Set objDomainRoot = GetObject("LDAP://" & objRootDSE.Get("defaultNamingContext"))

' Objects like the Users OU is actually a container so it must be caught with the container check
' Other objects we're interested in are real OUs
' Not recursing through container objects (too messy, User Objects are containers)

For Each objItem in objDomainRoot
       If (objItem.Class = "container") Then
              ProcessUsers objItem
       End If
       If (objItem.Class = "organizationalUnit") Then
              ProcessUsers objItem
              OURecurse objItem
       End If
Next

Set objDomainRoot = Nothing
Set objRootDSE = Nothing

' Initialize the File System Object, create a text file and write the report

Set objFileSystem = CreateObject("Scripting.FileSystemObject")
Set objFile = objFileSystem.CreateTextFile(FILE_NAME, True, False)

objFile.WriteLine "Accounts that have not logged on in the last 30 days"
objFile.WriteLine ""
objFile.WriteLine "Given Name" & Chr(9) & "Logon Name" & Chr(9) & "Last Logon Time"
objFile.WriteLine ""
For Each strEntry In objList
      If (CDate(objList(strEntry)(1)) < (Date() - INACTIVE_PERIOD)) Then
            objFile.WriteLine objList(strEntry)(0) & Chr(9) & strEntry & Chr(9) & objList(strEntry)(1)
      End If
Next

objFile.WriteLine ""
objFile.WriteLine "The following User Accounts are Disabled"
objFile.WriteLine ""
objFile.WriteLine "Given Name" & Chr(9) & "Logon Name"
For Each strLine in arrUserDisabled
      objFile.WriteLine strLine
Next

objFile.WriteLine ""
objFile.WriteLine "The following User Accounts are Locked Out"
objFile.WriteLine ""
objFile.WriteLine "Given Name" & Chr(9) & "Logon Name"
For Each strLine in arrUserLocked
      objFile.WriteLine strLine
Next

objFile.Close
Set objList = Nothing
Set objFileSystem = Nothing

As a small side-note. If you did prefer CSV format just do a Find and Replace on the vbs file:

Find: Chr(9)
Replace with (including the quotes - but no spaces in either case): ","

And that should do it.

:)
Hi Chris - Im back at home now (17:49 hrs GMT) I wont get a chance to test it till tommorow (my home domain doesnt have the capacity to test it :)

Where did you learn vbscript? your an OS/Nethead like myself - writing code is a skill that has allways escaped me :(


Home... I remember that place... work is almost done then it's time for the pub (also GMT) ;)

I've been messing around with vbscript and perl on and off for about a year, most of the really useful bits in the last 6 months or so. I learnt the vast majority of it from Google ( :) ), but I was taught to program in Pascal during my brief stay at university which helps a lot with seeing what the hell is going on.

ADSI is quite a nice system though, once you know how to do one bit (like LDAP) then IIS and all the other services are dead easy - although it takes ages to get around the lack of any kind of predictable naming of values (WinNT's FullName doesn't exist in LDAP etc etc).

Hopefully there will be a nice Windows Scripting TA for me to hang around in when the site redesign is done :)

Going to the pub now :)

Chris
Have a stella for me...............................................
Line: 116
Char:21
Error: Type mismatch
Code: 800A000D
Source: Microsoft VBScript runtime error

Is all I get :(

Oops sorry... could you change line 116 to:

If datLastLogon > objList(strLogonName)(1) Then

Should have added, line 116 should currently have:

If datLastLogon > objList(strLogonName) Then

Unfortunately I don't have enough DCs to test that bit works correctly - rebuilding everything. So it may have to be rewritten slightly.
changed line 16 to

If datLastLogon > objList(strLogonName)(1) Then

error now reads

Line 116
Char 21
Error Type mismatch 'objlist(...)'
Code 800A00D
Source Microsoft VBScript Error

Hmm sorry about this...

Could you change line 111 to:

datLastLogon = CDate(objUser.LastLogin)

Now the two values it compares should be the same, anything that's blank has already been discarded.

I really need more DCs to test this on. 3 domains, but only one DC in each while I fix them all...
Hi Chris -

Ive been out on site most of today - hence the lack of movement on this, I'll give it a try in the morning - now get yer-sel down the pub :)

No problem, I should have a little more time to check into the bit that's breaking tomorrow anyway.

Fortunately the pub is only an hour away :)
Game on :)
Hi Chris

Sorry for the late follow up Ive changed line 111 qas recommended in  http:Q_21410743.html#13927854 still it dont like line 116

Line 116
Char 21
Error Type mismatch 'objlist(...)'
Code 800A00D
Source Microsoft VBScript Error
Hmm okay... I'll have another look, give me an hour or so :)
cheers Chris

Pete

Okay, finished playing with it now. Hopefully all the bugs ironed out - tested against 2057 user accounts over 2 DCs with no errors and what seems to be relatively sane output - although it's one of those "start it and get a coffee" type scripts.

To avoid having annoying popup boxes appear (well 4 of them) run it with:

cscript <script name>

And it'll dump the wscript.echo's to the command line instead of through a message box - they're only there so you can see it's actually doing stuff in broad terms anyway.

Constants at the top as usual for file name and account inactivity period - I can make it request these or have them as command line parameters if you prefer though.

As a minor point of interest... it's also possible to pull out the lockout time if you wanted it.

Chris



Option Explicit

' Global Constants

Const ADS_UF_ACCOUNTDISABLE = &H2
Const FILE_NAME = "c:\disabled_locked.txt"
Const INACTIVE_PERIOD = 30

' Global Variable Declaration

Dim objRootDSE, objDomainRoot, objArg, objItem, objFileSystem, objFile
Dim objList, objDomainControllers, objDomainController, objServer
Dim objLockout, objDate
Dim strFirstName, strLastName, strFullName, strLogonName, strLine
Dim strLockedOut, strEntry
Dim arrKeys, arrItems, arrTemp
Dim arrUserDisabled()
Dim arrUserLocked()
Dim intUserLocked, intUserDisabled, intUAC, intCounter
 
'
' Subroutines
'

Sub ProcessUsers(objUsers)

' This routine takes an OU from OURecurse and checks the users to
' see if we find what we're looking for

      Dim objUser
      Dim strUserName, strTelephoneNumber, strFirstName, strInitials
      Dim strSurname, strFullName, strOutputLine

      objUsers.Filter = Array("user")

      For Each objUser in objUsers
            On Error Resume Next
            strFirstName = ""
            strFirstName = objUser.Get("givenName")
            strLastName = ""
            strLastName = objUser.Get("sn")
            strFullName = strFirstName & " " & strLastName
            strLogonName = ""
            strLogonName = objUser.Get("sAMAccountName")
            strLastLogon = ""
            strLastLogon = objUser.Get("lastLogin")
            On Error Goto 0

            intUAC = objUser.Get("userAccountControl")
            If ADS_UF_ACCOUNTDISABLE and intUAC Then
                  intUserDisabled = intUserDisabled + 1
                  ReDim Preserve arrUserDisabled(intUserDisabled)
                  arrUserDisabled(intUserDisabled) = strFullName & Chr(9) & strLogonName
            End If

            On Error Resume Next
            Set objLockOut = objUser.lockoutTime
            If Err.number <> 0 Then
                  ' Do Nothing
            Else
                  intUserLocked = intUserLocked + 1
                  ReDim Preserve arrUserLocked(intUserLocked)
                  arrUserLocked(intUserLocked) = strFullName & Chr(9) & strLogonName
            End If
            On Error Goto 0
            Set objUser = Nothing
      Next
End Sub


Sub OURecurse(objFirst)

' This OU is responsible for going through the AD Structure

      Dim objOrgUnit, objItem

      Set objOrgUnit = GetObject(objFirst.ADSPath)
      For Each objItem in objOrgUnit
            If (objItem.Class = "organizationalUnit") Then
                  ProcessUsers objItem
                  OURecurse objItem
            End If
      Next
      Set objOrgUnit = Nothing
      Set objFirst = Nothing
End Sub


Sub GetLastLogon(objDomainController)

' Retrieves the Last Logon Time from a DC

      Dim objUsers, objUser, objTemp
      Dim datLastLogon
      Dim strDCName, strUserName

      strDCName = Mid(objDomainController.Name, 4, Len(objDomainController.Name))

      wscript.echo "Checking Last Logon for accounts on " & strDCName
      Set objUsers = GetObject("WinNT://" & strDCName)
      objUsers.Filter = Array("user")
      For Each objUser In objUsers
      
            ' This is where Error Handling is needed
            
            On Error Resume Next
            strLogonName = ""
            strLogonName = objUser.Name
            strFullName = ""
            strFullName = objUser.FullName
            datLastLogon = ""
            datLastLogon = objUser.LastLogin
            On Error Goto 0
            datLastLogon = Trim(datLastLogon)

            If datLastLogon <> "" Then
                  arrTemp = Split(datLastLogon, " ")
                  datLastLogon = arrTemp(0)
                  datLastLogon = CDate(datLastLogon)
                  If objList.Exists(strLogonName) Then
                        If datLastLogon > objList(strLogonName)(1) Then
                              objList(strLogonName)(1) = datLastLogon
                        End If
                  Else
                        objList.Add strLogonName, Array(strFullName, datLastLogon)
                  End If
                  Set objUser = Nothing
            End If
      Next
      Set objServer = Nothing
End Sub

'
' Main Code
'

' Allow us to get the naming context

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

' These will be incremented to 0 the first time they are used

intUserDisabled = -1
intUserLocked = -1

' Get Accounts that haven't logged on recently

Set objList = CreateObject("Scripting.Dictionary")

' Get the DC List

Set objDomainControllers = GetObject("LDAP://ou=domain controllers," & objRootDSE.Get("defaultNamingContext"))
objDomainControllers.Filter = Array("computer")

For Each objDomainController In objDomainControllers
      GetLastLogon(objDomainController)
Next

Set objDomainControllers = Nothing

' Get Accounts that are Disabled or Locked Out

Set objDomainRoot = GetObject("LDAP://" & objRootDSE.Get("defaultNamingContext"))

' Objects like the Users OU is actually a container so it must be caught with the container check
' Other objects we're interested in are real OUs
' Not recursing through container objects (too messy, User Objects are containers)

wscript.echo "Fetching Account status for all users - this may take some time"

For Each objItem in objDomainRoot
       If (objItem.Class = "container") Then
              ProcessUsers objItem
       End If
       If (objItem.Class = "organizationalUnit") Then
              ProcessUsers objItem
              OURecurse objItem
       End If
Next

Set objDomainRoot = Nothing
Set objRootDSE = Nothing

' Initialize the File System Object, create a text file and write the report

wscript.echo "Writing Report"

Set objFileSystem = CreateObject("Scripting.FileSystemObject")
Set objFile = objFileSystem.CreateTextFile(FILE_NAME, True, False)

objFile.WriteLine "Accounts that have not logged on in the last 30 days"
objFile.WriteLine ""
objFile.WriteLine "Given Name" & Chr(9) & "Logon Name" & Chr(9) & "Last Logon Time"
objFile.WriteLine ""
For Each strEntry In objList
      If objList(strEntry)(1) < (Date() - INACTIVE_PERIOD) Then
            objFile.WriteLine objList(strEntry)(0) & Chr(9) & strEntry & Chr(9) & objList(strEntry)(1)
      End If
Next

objFile.WriteLine ""
objFile.WriteLine "The following User Accounts are Disabled"
objFile.WriteLine ""
objFile.WriteLine "Given Name" & Chr(9) & "Logon Name"
For Each strLine in arrUserDisabled
      objFile.WriteLine strLine
Next

objFile.WriteLine ""
objFile.WriteLine "The following User Accounts are Locked Out"
objFile.WriteLine ""
objFile.WriteLine "Given Name" & Chr(9) & "Logon Name"
For Each strLine in arrUserLocked
      objFile.WriteLine strLine
Next

objFile.Close
Set objList = Nothing
Set objFileSystem = Nothing
trying now
Sweet Progress :)

not logged in  for =>30 days works hurrah!!!
disabled account bit works, tip top!

locked out account's bit returned a LOT so I went and investigated

its listing accounts that are NOT locked
and its also listing Mail anabled contacts (which is strange)


Fun stuff... if an account has ever been locked out it populates the LockoutTime field - so that one is no good as a test for it. Let me find another way to do that and update it all.

Mail enabled contacts is a bit of an odd one, I assume they're coming up as user class, let me have a look around and see if I can filter them out.

Chris
ASKER CERTIFIED SOLUTION
Avatar of Chris Dent
Chris Dent
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Tip top

I changed it to csv output and its running sweet as a nut - heres the full code just for PAQ Value

Option Explicit

' Global Constants

Const ADS_UF_ACCOUNTDISABLE = &H2
Const FILE_NAME = "c:\audit.csv"
Const INACTIVE_PERIOD = 30

' Global Variable Declaration

Dim objRootDSE, objDomainRoot, objArg, objItem, objFileSystem, objFile
Dim objList, objDomainControllers, objDomainController, objServer
Dim objLockout, objDate
Dim strFirstName, strLastName, strFullName, strLogonName, strLine
Dim strLockedOut, strEntry
Dim arrKeys, arrItems, arrTemp
Dim arrUserDisabled()
Dim arrUserLocked()
Dim intUserLocked, intUserDisabled, intUAC, intCounter
 
'
' Subroutines
'

Sub ProcessUsers(objUsers)

' This routine takes an OU from OURecurse and checks the users to
' see if we find what we're looking for

     Dim objUser
     Dim strUserName, strTelephoneNumber, strFirstName, strInitials
     Dim strSurname, strFullName, strOutputLine

     objUsers.Filter = Array("user")

     For Each objUser in objUsers
          On Error Resume Next
          strFirstName = ""
          strFirstName = objUser.Get("givenName")
          strLastName = ""
          strLastName = objUser.Get("sn")
          strFullName = strFirstName & " " & strLastName
          strLogonName = ""
          strLogonName = objUser.Get("sAMAccountName")
          strLastLogon = ""
          strLastLogon = objUser.Get("lastLogin")
          On Error Goto 0

          intUAC = objUser.Get("userAccountControl")
          If ADS_UF_ACCOUNTDISABLE and intUAC Then
               intUserDisabled = intUserDisabled + 1
               ReDim Preserve arrUserDisabled(intUserDisabled)
               arrUserDisabled(intUserDisabled) = strFullName & "," & strLogonName
          End If

          If objUser.IsAccountLocked = True Then
               intUserLocked = intUserLocked + 1
               ReDim Preserve arrUserLocked(intUserLocked)
               arrUserLocked(intUserLocked) = strFullName & "," & strLogonName
          End If

          Set objUser = Nothing
     Next
End Sub


Sub OURecurse(objFirst)

' This OU is responsible for going through the AD Structure

     Dim objOrgUnit, objItem

     Set objOrgUnit = GetObject(objFirst.ADSPath)
     For Each objItem in objOrgUnit
          If (objItem.Class = "organizationalUnit") Then
               ProcessUsers objItem
               OURecurse objItem
          End If
     Next
     Set objOrgUnit = Nothing
     Set objFirst = Nothing
End Sub


Sub GetLastLogon(objDomainController)

' Retrieves the Last Logon Time from a DC

     Dim objUsers, objUser, objTemp
     Dim datLastLogon
     Dim strDCName, strUserName

     strDCName = Mid(objDomainController.Name, 4, Len(objDomainController.Name))

     wscript.echo "Checking Last Logon for accounts on " & strDCName
     Set objUsers = GetObject("WinNT://" & strDCName)
     objUsers.Filter = Array("user")
     For Each objUser In objUsers
     
          ' This is where Error Handling is needed
         
          On Error Resume Next
          strLogonName = ""
          strLogonName = objUser.Name
          strFullName = ""
          strFullName = objUser.FullName
          datLastLogon = ""
          datLastLogon = objUser.LastLogin
          On Error Goto 0
          datLastLogon = Trim(datLastLogon)

          If datLastLogon <> "" Then
               arrTemp = Split(datLastLogon, " ")
               datLastLogon = arrTemp(0)
               datLastLogon = CDate(datLastLogon)
               If objList.Exists(strLogonName) Then
                    If datLastLogon > objList(strLogonName)(1) Then
                         objList(strLogonName)(1) = datLastLogon
                    End If
               Else
                    objList.Add strLogonName, Array(strFullName, datLastLogon)
               End If
               Set objUser = Nothing
          End If
     Next
     Set objServer = Nothing
End Sub

'
' Main Code
'

' Allow us to get the naming context

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

' These will be incremented to 0 the first time they are used

intUserDisabled = -1
intUserLocked = -1

' Get Accounts that haven't logged on recently

Set objList = CreateObject("Scripting.Dictionary")

' Get the DC List

Set objDomainControllers = GetObject("LDAP://ou=domain controllers," & objRootDSE.Get("defaultNamingContext"))
objDomainControllers.Filter = Array("computer")

For Each objDomainController In objDomainControllers
     GetLastLogon(objDomainController)
Next

Set objDomainControllers = Nothing

' Get Accounts that are Disabled or Locked Out

Set objDomainRoot = GetObject("LDAP://" & objRootDSE.Get("defaultNamingContext"))

' Objects like the Users OU is actually a container so it must be caught with the container check
' Other objects we're interested in are real OUs
' Not recursing through container objects (too messy, User Objects are containers)

wscript.echo "Fetching Account status for all users - this may take some time"

For Each objItem in objDomainRoot
      If (objItem.Class = "container") Then
            ProcessUsers objItem
      End If
      If (objItem.Class = "organizationalUnit") Then
            ProcessUsers objItem
            OURecurse objItem
      End If
Next

Set objDomainRoot = Nothing
Set objRootDSE = Nothing

' Initialize the File System Object, create a text file and write the report

wscript.echo "Writing Report"

Set objFileSystem = CreateObject("Scripting.FileSystemObject")
Set objFile = objFileSystem.CreateTextFile(FILE_NAME, True, False)

objFile.WriteLine "Accounts that have not logged on in the last 30 days"
objFile.WriteLine ""
objFile.WriteLine "Given Name" & "," & "Logon Name" & "," & "Last Logon Time"
objFile.WriteLine ""
For Each strEntry In objList
     If objList(strEntry)(1) < (Date() - INACTIVE_PERIOD) Then
          objFile.WriteLine objList(strEntry)(0) & "," & strEntry & "," & objList(strEntry)(1)
     End If
Next

objFile.WriteLine ""
objFile.WriteLine "The following User Accounts are Disabled"
objFile.WriteLine ""
objFile.WriteLine "Given Name" & "," & "Logon Name"
For Each strLine in arrUserDisabled
     objFile.WriteLine strLine
Next

objFile.WriteLine ""
objFile.WriteLine "The following User Accounts are Locked Out"
objFile.WriteLine ""
objFile.WriteLine "Given Name" & "," & "Logon Name"
For Each strLine in arrUserLocked
     objFile.WriteLine strLine
Next

objFile.Close
Set objList = Nothing
Set objFileSystem = Nothing


Many thanks for all your Help Chris - Ill repost here when I get round to asking the next Question :)

Pete

Pleasure Pete, glad it's all working :-D
Avatar of keynetics
keynetics

I loaded hyena v8.1 to pull reports, but I keep getting RPC server not available. I have checked my services and the correct ones seems to be working. Any clues as why? I am logged in the domain controller and trying to hit my local computer (windows 7).