?
Solved

Disable active directory user account

Posted on 2005-05-17
31
Medium Priority
?
935 Views
Last Modified: 2008-05-30
I have 5 domain controllers (Win2000) and I developed Active Directory Service Interfaces (ADSI) script that reads the value of each user's lastLogon attribute from Active Directory (AD) user accounts and disables the user account if that value exceeds 3 days. The problem is AD doesn't replicate the lastLogon attribute. I searched this in google, but I didn't get any satisfactory result.

Please give me a solution to disable Active Directory (AD) user accounts after 3 days of inactivity.
0
Comment
Question by:anoop_chekkattu
  • 13
  • 9
  • 8
  • +1
31 Comments
 
LVL 22

Expert Comment

by:kristinaw
ID: 14018308
you've run into a common issue with win2k. as you've found, the lastlogon attribute doesn't replicate. the only thing you can do if you wish to use this attribute is to manually compare all 5 logons to find the most recent one. this isn't terribly easy, i've seen a few scripts out on the web that attempt to do this, but haven't found any that work well enough to use in production in my domain. i have 5 dc's as well in two different sites. so, i have some users that have only logged into dc's in one site. therefore, their lastlogon attribute on all dc's in the other site is null. lots of the examples out there don't account for situations such as this, and i've never had time to try and modify it so it would work right for me.

anyways, a 2003 domain WILL replicate this value, so, you can upgrade, write something to do the manual compare, or find a different way to do what you seek. some people use the pwdlastset value. for example, if users are required to change their pass every 30 days, you can look for pwdlastset values that exceed 45 days. not as neat as what you're trying to do, but that's the best info i can give you.

hth,
Kris.
0
 
LVL 12

Expert Comment

by:Heem14
ID: 14018635
The easiest thing to do would be to have your script check each DC, if one of them shows that the user has logged in, then move on to the next user, if all DCs show that the user has not logged in within the specified threshold, then on to your disable subroutine.
0
 
LVL 22

Expert Comment

by:kristinaw
ID: 14018661
this is basically the same thing i'm saying. since the attribute doesn't replicate, it's up to you check each one.
0
Has Powershell sent you back into the Stone Age?

If managing Active Directory using Windows Powershell® is making you feel like you stepped back in time, you are not alone.  For nearly 20 years, AD admins around the world have used one tool for day-to-day AD management: Hyena. Discover why.

 
LVL 71

Expert Comment

by:Chris Dent
ID: 14018702

There are quite a few scripts around to pull out the LastLogon time from a domain. This is the last one I was involved in:

http://www.experts-exchange.com/Programming/Programming_Languages/Visual_Basic/Q_21410743.html

As Kristinaw has pointed out the value doesn't replicate so the script retrieves the list of domain controllers from the Domain Controllers OU and cycles through each to check the LastLogon time.

If that's the kind of thing you're looking for the changes required would be fairly trivial.

Chris
0
 
LVL 22

Expert Comment

by:kristinaw
ID: 14019331
Chris,

i haven't had time to go through all your logic yet, but it definitely isn't working right for my domain. i have 5 dc's, and it seems to be recording any account that hasn't logged into the first dc in the list (active1) in the past 30 days, even though some of the other dc's have much more recent values for the lastlogin attribute. it's probably pretty easy to compary the dates in a domain with only 1 or 2 dc's, but when you have 5 dc's, it gets kind of complicated, especially when some of your dc's may have null entries for this value. i'm using a very simple script to run through each dc and display the lastlogin value for a specified user to compare against the entries that your script wrote in the csv file.

as i've said, it sounds like it should be much easier to do than it actually is. i'll try to look at your logic more later to figure out why it's only writing that one dc's value (if it's more than 30 days old). it doesn't look like you were gearing this for a one dc domain, but what's the most dc's this has ever successfully run against?

kris.
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 14019564

Hi Kris,

You'd have to ask Pete I'm afraid, it was written for his domain, the domains I have here are web hosting environments and don't have a large number of DCs in each to test it against (2 at the most).

The selection process for LastLogon is pretty simple, first it resets the value to "" to flush out whatever was there before. This is to protect against those irritating null values as null doesn't overwrite whatever is stored in the variant. If it doesn't find anything in the attribute it doesn't do anything else for the account on the server.

If it does find a value it tries to split off the date section, date format is assumed to be DD/MM/YYYY HH:MM:SS, which may potentially cause problems in some cases. That is then converted to date format which is required for any simple comparison. And finally, if that's newer it overwrites the value we have stored.

Practically speaking it shouldn't make any difference if there are 2 or 100 DCs; Retrieving the value, comparing it and changing a stored value if necessary are the key stages.

I'd say the two most likely points of failure in that are retrieving the value and getting the date format right.

Chris
0
 
LVL 22

Expert Comment

by:kristinaw
ID: 14019766
i've tried similar scripting in the past with no luck, but i'll take at look what you're trying and see what i can do with that. perhaps our combined efforts will shed some new light on it.

kris.
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 14019851

I feel there's no practical reason why it shouldn't be possible and it'd certainly be worth a try.

Chris
0
 

Author Comment

by:anoop_chekkattu
ID: 14020057
Hi all.

  I am trying Chris's logic. Please give me some time to test this..  I will get back to you  for clarifications

Anoop
0
 
LVL 22

Expert Comment

by:kristinaw
ID: 14021215
Chris,

the flaw in your code doesn't look like it has anything to do with the # of dc's, it may just be more apparent in that case. let me share some of my results with you. i have a user, ALCLARK, your script recorded 6/4/2003 in the Last Logon Time column under "Accounts that have not logged on in 30 days" in the CSV file it created.

i ran this particular user name through my query to get actual lastlogin values for all my dc's, and here's what i got. i'm leaving off the timestamp since it isn't relelvant and just using date

DC1 = 6/4/2003
DC2 = 2/20/2005
DC3 = 1/16/2005

as i said i have 5 dc's, in this case the values for the other two DC's were null. As you can see, your script should have recorded 2/20/2005 as the lastlogin value. it looks like you're doing your date compare here (see below) to record the value, where datLastLogon should be the value of the most recent logon:

               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

This is where we run into the problem. As a test, let's throw together a little vbscript to and assign values to the dates I quoted above:

DC1 = "6/4/2003"
DC2 = "2/20/2005"
DC3 = "1/16/2005"

datLastLogon = ""
If DC1 > datlastlogon Then
      datLastLogon = DC1
End If

If DC2 > datLastLogon Then
      datLastLogon = DC2
End If

If DC3 > datLastLogon Then
      datLastLogon = DC3
End If

wscript.Echo datLastLogon

the value that's echoed back is 6/4/2003.

If I change the date values to the following:

DC1 = "8/4/2003"
DC2 = "2/20/2005"
DC3 = "12/16/2004"

what we get is 8/4/2003. figure that one out. i thought that one would ring back 12/16/2004, but it looks like what's happening is this:

in so far as the > is concerned, it looks at the very first character of the string, in this case 8 for DC1, 2 for DC2, and 1 for DC3. So, as far as this comparison is concerned, 8 wins.

So, in smaller domains where the DC's get hit frequently, i can see where this might seem like it's working, but it's really not. it just looks like it's working because so far the values have probably been close together, and sequential. for example, it might have only January and February logins to deal with (1 and 2). But as you can see, in a large domain like mine the vast number of values that i have stored are quick to point out the flaw.

This is the part i wasn't able to get past last time. Hopefully you haven't farmed this script out to too many places, if you double check me i think you'll see what i'm talking about.

hth,
kris.
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 14021286

Hi Kris,

Close but not quite :)

First of all you're comparing Strings not Dates. As I'm sure you know VBScript doesn't have type declaration for it's variables - they're all variant class. But it will complain if you try and compare two completely different types.

datLastLogon "" never gets compared to the date, there's no point as there's nothing to compare. That section is skipped by an If statement for "" before the comparison.

But the most important point is the conversion of any string held in LastLogon to a Date type (CDate).

Try the modification and you get 2/20/2005 back:

TestDate = CDate("5/4/2003")

DC1 = "6/4/2003"
DC2 = "2/20/2005"
DC3 = "1/16/2005"

DC1 = CDate(DC1)
DC2 = CDate(DC2)
DC3 = CDate(DC3)

datLastLogon = TestDate
If DC1 > datlastlogon Then
     datLastLogon = DC1
End If

If DC2 > datLastLogon Then
     datLastLogon = DC2
End If

If DC3 > datLastLogon Then
     datLastLogon = DC3
End If

wscript.Echo datLastLogon
0
 
LVL 22

Expert Comment

by:kristinaw
ID: 14021319
ok, let me check it out.
0
 
LVL 22

Expert Comment

by:kristinaw
ID: 14021384
ok, i've got that part. converting them all to dates causes it to work as it should in the test, but for some reason it is writing the wrong value in (often the oldest value) in the login date box. i'll keep going through it to see if i can find what's causing it...

kris.

0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 14021797

I'm still wondering if there's a descrepancy in the date format - it may just be that the use of CDate is fundamentally flawed. I'll also have another look but I think it's going to have to wait until tomorrow - time for dinner ;)
0
 
LVL 22

Expert Comment

by:kristinaw
ID: 14021862
ok, i tried adding cdate below:

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

i think when it adds it to the array it loses the date format, and perhaps converts it to a string? that would explain my results, and i think i'm getting better results. i'll check back with you on it tomorrow...

kris.
0
 
LVL 22

Expert Comment

by:kristinaw
ID: 14021870
and i think i'm getting better results - now that i've added the extra 'cdate'. i'll check back with you on it tomorrow...

0
 

Author Comment

by:anoop_chekkattu
ID: 14028929
Chris,
   
        I want to disable Active Directory (AD) user accounts for particular OU after 3 days of inactivity. For that what are the changes I have to do in the functions GetLastLogon, OURecurse.

For Each objItem in objOrgUnit
          If (objItem.Class = "organizationalUnit") Then

'  ************************
'  is it possible to check the conditions here.. then how..?
'  ************************

               ProcessUsers objItem
'               OURecurse objItem
          End If
     Next

PLease help
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 14029115

OURecurse is only responsible for working its way around the AD structure, so except for the potential pattern match against an OU name it wouldn't do much.

Really though if you're only running it for a specific OU it would be better to define that statically rather than go through the entire AD structure.

ProcessUsers has (up to now at least) been landed with any changes or reporting required for users and all I'd do is add the following code to that, the sub should only be called if a matching OU is found, and if (datLastLogon > (Date() - 3)):

Const ADS_UF_ACCOUNTDISABLE = 2

intUAC = objUser.Get("userAccountControl")
objUser.Put "userAccountControl", intUAC OR ADS_UF_ACCOUNTDISABLE
objUser.SetInfo

I'll rewrite the script to do what you're looking for in a few hours - afraid I have to head off home now so can't do it immediately - but I will add it in for you.

Chris
0
 

Author Comment

by:anoop_chekkattu
ID: 14029413
Hi,

Presently I am using this code... This is for your reference only.
This code is disabling the account in one DC.

Const Dont_Know = "1/1/1601 5:30:00 AM"
Const ADS_UF_ACCOUNTDISABLE = 2
Set objConnection = CreateObject("ADODB.Connection")
objConnection.Open "Provider=ADsDSOObject;"
Set objCommand = CreateObject("ADODB.Command")
objCommand.ActiveConnection = objConnection
objCommand.CommandText = "<GC://OU=OU123,OU=Staff,dc=company,dc=com>;(objectCategory=User);userAccountControl,distinguishedName,Name;subtree"
Set objRecordSet = objCommand.Execute

'Now check for inactive users and take action
'F is a text file object
F.Write "Username" &","& "Last login date" &","& "Not Logged in since (in Days)" &","& "Action Taken"&vbCrlf

While Not objRecordset.EOF
      DN = objRecordset.Fields("distinguishedName")
      Set objUser = GetObject("LDAP://"&DN)
            Diff = DateDiff("d",objUser.LastLogin,Now)
            If Diff > 4 Then
                  If DateDiff("y",Dont_Know,objUser.LastLogin) = 0 Then
                              If objUser.AccountDisabled = Flase Then
                                    intUAC = objUser.Get("userAccountControl")
                                    objUser.Put "userAccountControl", intUAC OR ADS_UF_ACCOUNTDISABLE
                                    objUser.SetInfo      
                                    F.Write objUser.cn &","& objUser.LastLogin & ","& Diff &","& " can not determine the last login time of user. We have disabled the account."&vbCrlf
                                    Count = Count + 1
                              Else
                                    F.Write objUser.cn &","& objUser.LastLogin & ","& Diff &","& " can not determine the last login time of user. The account is already disabled"&vbCrlf
                                    Count = Count + 1
                              End IF
                  End IF

                  If DateDiff("y",Dont_Know,objUser.LastLogin) <> 0 and objUser.AccountDisabled = False Then
                              intUAC = objUser.Get("userAccountControl")
                              objUser.Put "userAccountControl", intUAC OR ADS_UF_ACCOUNTDISABLE
                              objUser.SetInfo      
                              F.Write objUser.cn &","& objUser.LastLogin & ","& Diff &","& " We have disabled the account. Please delete this account."&vbCrlf
                              Count = Count + 1
                        Else If DateDiff("y",Dont_Know,objUser.LastLogin) <> 0 and objUser.AccountDisabled = True Then
                              F.Write objUser.cn &","& objUser.LastLogin & ","& Diff &","& " The account is already disabled. Please delete this account."&vbCrlf
                              Count = Count + 1
                        End If
                  End If
            End IF
            
            If Diff > 4 and Diff <= 90 Then            
                  If objUser.AccountDisabled = False Then
                        intUAC = objUser.Get("userAccountControl")
                        objUser.Put "userAccountControl", intUAC OR ADS_UF_ACCOUNTDISABLE
                        objUser.SetInfo
                        F.Write objUser.cn &","& objUser.LastLogin & ","& Diff &","& " We have disabled the account."&vbCrlf
                        Count = Count + 1      
                  Else
                        F.Write objUser.cn &","& objUser.LastLogin & ","& Diff &","& " The account is already disabled."&vbCrlf
                        Count = Count + 1      
                  End IF
            End IF
                  objRecordset.MoveNext
Wend
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 14034613

Sorry for the delay with this... this will (I hope) do what you're looking for.

I've left it as a statically defined OU (which I tested it against as a sample) rather than recursing through all OUs. This is certainly quicker unless you really want to avoid having to manually define the path.

Let me know if you have any problems.


Option Explicit

' Global Constants

Const ADS_UF_ACCOUNTDISABLE = &H2
Const INACTIVE_PERIOD = 3

' Global Variable Declaration

Dim objRootDSE, objList, objDomainControllers, objDomainController
Dim objUser, objUsers
Dim strFirstName, strLastName, strFullName, strLogonName, strOU
Dim arrTemp
Dim datLastLogon
Dim intUAC

' The OU with Users to be disabled

strOU = "ou=Cancelled Accounts,ou=Web Environment,"
 
'
' Subroutines
'

Sub GetLastLogon(objDomainController)

' Retrieves the Last Logon Time from a DC

      Dim objTemp
      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
End Sub

'
' Main Code
'

' Allow us to get the naming context

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

' This gets the LastLogon time for every user in the domain and adds it to the collection (or hash) objList

Set objList = CreateObject("Scripting.Dictionary")
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

' Disables accounts in a specific OU that haven't logged in for INACTIVE_PERIOD days

Set objUsers = GetObject("LDAP://" & strOU & objRootDSE.Get("defaultNamingContext"))
objUsers.Filter = Array("user")
For Each objUser in objUsers
      strLogonName = objUser.Get("sAMAccountName")
      datLastLogon = objList(strLogonName)(1)
      If datLastLogon < (Date() - INACTIVE_PERIOD) Then
            wscript.echo "Disabling " & strLogonName
            intUAC = objUser.Get("userAccountControl")
            objUser.Put "userAccountControl", intUAC or ADS_UF_ACCOUNTDISABLE
            objUser.SetInfo
      End If
Next
0
 

Author Comment

by:anoop_chekkattu
ID: 14034775
Chris,

 Thanks for the help you are giving.. I will check this script and get back to you..?

Anoop
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 14034944

No problem, let me know if it doesn't work as you expect :)
0
 

Author Comment

by:anoop_chekkattu
ID: 14035743
Chris,

   I tested this.. its taking more than one 45 minutes to come out of following loop

For Each objDomainController In objDomainControllers
     GetLastLogon(objDomainController)
Next

I think its checking all users in the 5 DCs.  Any code to faster this process. I want to check  particular OU only.

Also
last I got error message in

                   datLastLogon = objList(strLogonName)(1)

That means some logon names are not existing in objList. any way I am going to include a condition

                   If objList.Exists(strLogonName) Then

before this statement.

I will test it and get back to you






0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 14036110

The condition is a good idea - although since it should be pulling data for every single account there shouldn't be a mismatch between objUser.Name and objUser.Get("sAMAccountName"), all should exist in objList - unless there's a problem with case-sensitivity of course (or something else odd that isn't quite working).

This may speed up the process... see what you think:


Const INACTIVE_PERIOD = 30
Const ADS_UF_ACCOUNTDISABLE = &H2

strOU = "ou=DepartmentName,ou=Company,"

' Must start at -1, incremented to 0 on first use

intCount = -1

Set objUsers = GetObject("LDAP://" & strOU & objRootDSE.Get("defaultNamingContext"))
objUsers.Filter = Array("user")

For Each objUser in objUsers
      For Each objDomainController In objDomainControllers
            intCount = intCount + 1
            ReDim Preserve arrADSList(intCount)
            arrADSList(intCount) = strDCName & "/" & objUser.Get("sAMAccountName") & ",user"
      Next
Next

Set objDomainController = Nothing
Set objDomainControllers = Nothing
Set objUser = Nothing

For Each strADSPath in arrADSList
      objUser = GetObject("WinNT://" & strADSPath)
          
      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
      End If
      Set objUser = Nothing
Next

' Need to reconnect to the user account. strUserName is not the container name

For Each objUser in objUsers
      datLastLogon = objList(objUser.Get("sAMAccountName"))(1)
      If datLastLogon < (Date() - INACTIVE_PERIOD) Then
            wscript.echo "Disabling " & objUser.Get("sAMAccountName")
            intUAC = objUser.Get("userAccountControl")
            objUser.Put "userAccountControl", intUAC or ADS_UF_ACCOUNTDISABLE
            objUser.SetInfo
      End If
Next

Set objUser = Nothing
Set objUsers = Nothing
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 14036174

I managed to forget to include the two lines to get the list of DCs in the code above:

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

Author Comment

by:anoop_chekkattu
ID: 14047322
Chris,

     I made couple of updates in the above script and I moved the results to a CSV file. But I am getting computer account name along with user accounts. How we can avoid this problem.  

I think the following statement moves all the accounts.
                                          objUser = GetObject("WinNT://" & strADSPath)

Please see the updated script...


Const INACTIVE_PERIOD = 30
Const ADS_UF_ACCOUNTDISABLE = &H2

strOU = "ou=OU115,ou=Tech Staff,"

' Must start at -1, incremented to 0 on first use

intCount = -1
Dim objRootDSE, objList, objDomainControllers, objDomainController
Dim objUser, objUsers
Dim strFirstName, strLastName, strFullName, strLogonName, strOU
Dim arrTemp
Dim datLastLogon
Dim intUAC
dim fso
dim fName
Set fso = CreateObject("Scripting.FileSystemObject")
Set fName = fso.CreateTextFile("c:\Accounts.csv",True)

'Set arrADSList = CreateObject("Scripting.Dictionary")
Set objList = CreateObject("Scripting.Dictionary")
Set objRootDSE = GetObject("LDAP://rootDSE")

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

Set objUsers = GetObject("LDAP://" & strOU & objRootDSE.Get("defaultNamingContext"))
objUsers.Filter = Array("user")

For Each objUser in objUsers
     For Each objDomainController In objDomainControllers
            strDCName = Mid(objDomainController.Name, 4, Len(objDomainController.Name))
          intCount = intCount + 1
          ReDim Preserve arrADSList(intCount)
          arrADSList(intCount) = strDCName & "/" & objUser.Get("sAMAccountName") & ",user"
     Next
Next

Set objDomainController = Nothing
Set objDomainControllers = Nothing
Set objUser = Nothing

For Each strADSPath in arrADSList
     set objUser = GetObject("WinNT://" & strADSPath)
         
     On Error Resume Next
     strLogonName = ""
     strLogonName = objUser.Name
     strFullName = ""
     strFullName = objUser.FullName
     datLastLogon = ""
     datLastLogon = objUser.LastLogin
     'Moving the information to csv file
       fName.Write strLogonName & "," & strFullName & "," & datLastLogon & vbCrlf

     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
     End If
     Set objUser = Nothing
Next

' Need to reconnect to the user account. strUserName is not the container name

fName.Write "Disabled Users" & vbCrlf

For Each objUser in objUsers
     If objList.Exists(objUser.Get("sAMAccountName")) Then
           datLastLogon = objList(objUser.Get("sAMAccountName"))(1)

             'Format funtion is for avoiding date format problem  
         If datLastLogon < (format(Date(),"MM/DD/YY HH:MM") - INACTIVE_PERIOD) Then
                strLogonName = ""
              strLogonName = objUser.Name
                    'user not logged in for last n days
              fName.Write strLogonName & vbCrlf

          'wscript.echo "Disabling " & objUser.Get("sAMAccountName")
          'intUAC = objUser.Get("userAccountControl")
          'objUser.Put "userAccountControl", intUAC or ADS_UF_ACCOUNTDISABLE
          'objUser.SetInfo
          End If
        End if
Next
wscript.echo "Ended"
Set objUser = Nothing
Set objUsers = Nothing
fName.close
set fName=nothing



0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 14049171

I take it you're getting computer accounts listing in the output file from the OU we're looking at?

This is supposed to filter out any computer accounts from the input list:

objUsers.Filter = Array("user")

This bit is only responsible for creating a connection to a user account based on a value stored in the array - generated from the list of DCs and the list of Users:

objUser = GetObject("WinNT://" & strADSPath)

strADSPath should always be in the form "DCName/UserName, user". This makes the connection string "WinNT://DCName/UserName, user" where objUser.Get("sAMAccountName") via LDAP is the same as objUser.Name via WinNT.

Where you do objUser.Name in the last section; what you're ending up with there is the Container Name in AD. This doesn't necessarily have to be the same as the user name: objUser.Get("sAMAccountName"). Interface differences make life interesting.
0
 

Author Comment

by:anoop_chekkattu
ID: 14049366
Chris,

    I made some updation in your prvious logic. Its working. I am doing some custom changes in this script. I am adding the latest script for your reference. Please share if you have other better logic.

Option Explicit

' Global Constants

Const ADS_UF_ACCOUNTDISABLE = &H2
Const INACTIVE_PERIOD = 3

' Global Variable Declaration

Dim objRootDSE, objList, objDomainControllers, objDomainController
Dim objUser, objUsers
Dim strFirstName, strLastName, strFullName, strLogonName, strOU
Dim arrTemp
Dim datLastLogon
Dim intUAC
dim fso
dim fName
dim fDecFile
Set fso = CreateObject("Scripting.FileSystemObject")
Set fName = fso.CreateTextFile("c:\AccountInfo.csv",True)

' The OU with Users to be disabled
strOU = "ou=OU15,ou=Operation Staff,"

'
' Subroutines
'

Sub GetLastLogon(objDomainController)

' Retrieves the Last Logon Time from a DC

     Dim objTemp
     Dim strDCName, strUserName
     strDCName = Mid(objDomainController.Name, 4, Len(objDomainController.Name))
       fName.Write "Checking Last Logon for accounts on " & strDCName & vbCrlf
     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)
          fName.Write strLogonName & "," & strFullName & "," & datLastLogon & vbCrlf
          If isdate(datLastLogon) Then
               datLastLogon = CDate(datLastLogon)
                     If objList.Exists(strLogonName) Then
                          if datediff("d", datLastLogon, objList(strLogonName)) < 0 then
                       objList(strLogonName) = datLastLogon
                              End If
               Else
                  if isdate(datLastLogon) then  
                              'objList.Add strLogonName, Array(strFullName, datLastLogon)
                              objList.Add strLogonName, datLastLogon
                          end if
               End If
               Set objUser = Nothing
          End If
     Next
     
End Sub

'
' Main Code
'

' Allow us to get the naming context

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

' This gets the LastLogon time for every user in the domain and adds it to the collection (or hash) objList

Set objList = CreateObject("Scripting.Dictionary")

Set objDomainControllers = GetObject("LDAP://ou=domain controllers," & objRootDSE.Get("defaultNamingContext"))
objDomainControllers.Filter = Array("computer")
fName.Write "Login Name,Full Name,Last Login" & vbCrlf
For Each objDomainController In objDomainControllers
     GetLastLogon(objDomainController)
Next
Set objDomainControllers = Nothing

' Disables accounts in a specific OU that haven't logged in for INACTIVE_PERIOD days
Set objUsers = GetObject("LDAP://" & strOU & objRootDSE.Get("defaultNamingContext"))
objUsers.Filter = Array("user")
fName.write "Users not logged in for last 3 days"  
For Each objUser in objUsers
     strLogonName = objUser.Get("sAMAccountName")
        
       If objList.Exists(strLogonName) Then
           fDecFile.Write strLogonName & "," & objList(strLogonName)
         datLastLogon = objList(strLogonName)
           if DateDiff("d",datLastLogon,Now) > 4 then 'can replace 4 with 'INACTIVE_PERIOD+1'
             fName.write strLogonName & "," & datLastLogon & vbCrlf
                  
                   ' uncomment folloeing fields to disable user
             'intUAC = objUser.Get("userAccountControl")
             'objUser.Put "userAccountControl", intUAC or ADS_UF_ACCOUNTDISABLE
             'objUser.SetInfo 'For disabling user
         End If
     End If
Next
fName.write "End" & vbCrlf
fName.close
set fName=nothing
0
 
LVL 71

Accepted Solution

by:
Chris Dent earned 600 total points
ID: 14049395

It all looks pretty good.

Curious why you use:

fName.Write "Checking Last Logon for accounts on " & strDCName & vbCrlf

Instead of:

fName.WriteLine "Checking Last Logon for accounts on " & strDCName

But that's such a minor point ;)
0
 

Author Comment

by:anoop_chekkattu
ID: 14051161
Chris,
    Thanks for your logic. Why kris is not getting reult for this..?

Thanks,
Anoop
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 14051181

Not sure, would appear to be an error in how it's getting and comparing the dates, I'll have to do more testing on it to figure that one out.
0

Featured Post

Problems using Powershell and Active Directory?

Managing Active Directory does not always have to be complicated.  If you are spending more time trying instead of doing, then it's time to look at something else. For nearly 20 years, AD admins around the world have used one tool for day-to-day AD management: Hyena. Discover why

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Unable to change the program that handles the scan event from a network attached Canon/Brother printer/scanner. This means you'll always have to choose which program handles this action, e.g. ControlCenter4 (in the case of a Brother).
In this article I will be showing you how to subnet the easiest way possible for IPv4 (Internet Protocol version 4). This article does not cover IPv6. Keep in mind that subnetting requires lots of practice and time.
Michael from AdRem Software explains how to view the most utilized and worst performing nodes in your network, by accessing the Top Charts view in NetCrunch network monitor (https://www.adremsoft.com/). Top Charts is a view in which you can set seve…
In this brief tutorial Pawel from AdRem Software explains how you can quickly find out which services are running on your network, or what are the IP addresses of servers responsible for each service. Software used is freeware NetCrunch Tools (https…

807 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