Solved

How can i disable inactive user accounts and move them to specified OU?

Posted on 2009-07-06
19
1,536 Views
Last Modified: 2012-05-07
Hello,

I need a vbscript that can disable inactive AD user accounts that hasn't logged on for more than 90 days and move them to a specified OU after disabling them.

Appreciate your fast response.

Yassein
0
Comment
Question by:amyassein
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 8
  • 8
  • 3
19 Comments
 
LVL 57

Expert Comment

by:Mike Kline
ID: 24791914
Yassein,
Does this have to be done using vbscript?   The reason I ask is because there is a great tool called old computer by MVP Joe Richards.
...don't worry it also works on users
http://www.joeware.net/freetools/tools/oldcmp/index.htm
To look at a quick report run
oldcmp -report -users -llts
that -llts is for lastlogontimestamp which is available in a w2k3 domain which it looks like you are.
It can disable and move too
Thanks
Mike
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 24792432

Because it's sometimes useful to know how... VbScript version below :)

Chris
' Inactivity period
 
Const PERIOD_TO_REMOVE = 90
 
' Create a connection to the OU which holds disabled accounts
 
Set objOU = GetObject("LDAP://OU=Disabled Users,DC=yourdomain,DC=com")
 
' Create a value to represent the date
 
Dim dblInt8 : dblInt8 = CDbl(DateDiff("s", CDate("01/01/1601 00:00:00"), Now - PERIOD_TO_REMOVE))
 
' Create a filter to return all user accounts which have been inactive for PERIOD_TO_REMOVE and
' and are still enabled.
 
Dim strLdapFilter
strLdapFilter = "(&(objectClass=user)(objectCategory=person)(lastLogonTimeStamp<=" & _
  CStr(dblInt8) & "0000000)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
 
' Search the current AD domain
 
Dim objConnection : Set objConnection = CreateObject("ADODB.Connection")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
 
Dim objCommand : Set objCommand = Createobject("ADODB.Command")
objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 1000
 
Dim objRootDSE : Set objRootDSE = GetObject("LDAP://RootDSE")
 
objCommand.CommandText = "<LDAP://" & objRootDSE.Get("defaultNamingContext") & ">;" & _
  strLdapFilter & ";distinguishedName,userAccountControl;subtree"
 
Dim objRecordSet : Set objRecordSet = objCommand.Execute
 
Do Until objRecordSet.EOF
  Set objUser = GetObject("LDAP://" & objRecordSet.Fields("distinguishedName").Value)
 
  ' Debugging / Testing
  WScript.Echo "Disable: " & objRecordSet.Fields("distinguishedName").Value
 
  ' Disable this account - Note: these are commented out to allow testing prior to activation
  ' objUser.AccountDisabled = True
  ' objUser.SetInfo
 
  Set objUser = Nothing
 
  ' Debugging / Testing
  WScript.Echo "Moving: " & objRecrodSet.Fields("distinguishedName").Value  
 
  ' Move the account to a new OU - Note: this is commented out to allow testing prior to activation
 
  ' objOU.MoveHere "LDAP://" & objRecordSet.Fields("distinguishedName").Value, VbNullString
 
  objRecordSet.MoveNext
Loop

Open in new window

0
 
LVL 1

Author Comment

by:amyassein
ID: 24793023
Chris,Mkline

Thanks for the quick response and i appreciate it.

Chris,

I have a question, Which date shall i insert in this function ? CDate("01/01/1601 00:00:00" ..... Sorry but i don't do scripting too much.

Thank You again.
0
Free NetCrunch network monitor licenses!

Only on Experts-Exchange: Sign-up for a free-trial and we'll send you your permanent license!

Here is what you get: 30 Nodes | Unlimited Sensors | No Time Restrictions | Absolutely FREE!

Act now. This offer ends July 14, 2017.

 
LVL 71

Expert Comment

by:Chris Dent
ID: 24793042

The lastLogonTimeStamp attribute is the number of 100-nanosecond intervals since 01/01/1601 00:00:00. That means it has to be calculated to fit into the script.

The script does that by taking the value you set for "PERIOD_TO_REMOVE" and figuring out the difference between the date above (01/01/1601) and todays date (Now) minus the specified number of days.

In short, you only need to set a value for "PERIOD_TO_REMOVE", it's 90 days at the moment.

Chris
0
 
LVL 1

Author Comment

by:amyassein
ID: 24793091
Chris,

I am very sorry that i didn't mention this from the beginning but instead of searching the whole AD domain , i need only to look for inactive accounts in "People" OU since all user accounts are placed inside one OU.

My bad :)

Please can you help in this?

Thanks
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 24793594

Sure :)

Fill in the value for SEARCH_ROOT here.

Chris
' Inactivity period
 
Const PERIOD_TO_REMOVE = 90
 
' Search base - The AD path you wish to find users in (and beneath)
 
Const SEARCH_ROOT = "OU=People,DC=yourdomain,DC=com"
 
' Create a connection to the OU which holds disabled accounts
 
Set objOU = GetObject("LDAP://OU=Disabled Users,DC=yourdomain,DC=com")
 
' Create a value to represent the date
 
Dim dblInt8 : dblInt8 = CDbl(DateDiff("s", CDate("01/01/1601 00:00:00"), Now - PERIOD_TO_REMOVE))
 
' Create a filter to return all user accounts which have been inactive for PERIOD_TO_REMOVE and
' and are still enabled.
 
Dim strLdapFilter
strLdapFilter = "(&(objectClass=user)(objectCategory=person)(lastLogonTimeStamp<=" & _
  CStr(dblInt8) & "0000000)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
 
' Search the current AD domain
 
Dim objConnection : Set objConnection = CreateObject("ADODB.Connection")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
 
Dim objCommand : Set objCommand = Createobject("ADODB.Command")
objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 1000
 
objCommand.CommandText = "<LDAP://" & SEARCH_ROOT & ">;" & _
  strLdapFilter & ";distinguishedName,userAccountControl;subtree"
 
Dim objRecordSet : Set objRecordSet = objCommand.Execute
 
Do Until objRecordSet.EOF
  Set objUser = GetObject("LDAP://" & objRecordSet.Fields("distinguishedName").Value)
 
  ' Debugging / Testing
  WScript.Echo "Disable: " & objRecordSet.Fields("distinguishedName").Value
 
  ' Disable this account - Note: these are commented out to allow testing prior to activation
  ' objUser.AccountDisabled = True
  ' objUser.SetInfo
 
  Set objUser = Nothing
 
  ' Debugging / Testing
  WScript.Echo "Moving: " & objRecrodSet.Fields("distinguishedName").Value  
 
  ' Move the account to a new OU - Note: this is commented out to allow testing prior to activation
 
  ' objOU.MoveHere "LDAP://" & objRecordSet.Fields("distinguishedName").Value, VbNullString
 
  objRecordSet.MoveNext
Loop

Open in new window

0
 
LVL 57

Expert Comment

by:Mike Kline
ID: 24795018
Just to follow up with oldcmp, you can also change the base where it searches
...for that you use the -b switch then your OU
so
oldcmp -b "DN of your OU here" -report -users -llts
That report gives you 90 days but you can change the age by using the -age switch.  If you want to check for 120 days for example
oldcmp -b "DN of your OU here" -report -age 120 -users -llts  
Thanks
Mike
0
 
LVL 1

Author Comment

by:amyassein
ID: 24795458
Chris,

Thank you so much for the valuable information. Tomorrow i will test it and will update you.

Mkline,

Fantastic follow up !!! i promise i will give it a try later. The reason why i love scripting because it's flexible, less CPU consuming and give you results in terms of visible outputs such as MsgBox.

Thanks again all.
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 24795464

We should have had a bit of PowerShell as well, just to round things off nicely :)

Chris
0
 
LVL 57

Expert Comment

by:Mike Kline
ID: 24795495
One thing that I also like about oldcmp is that you can send those html reports to the managers.  Can make you look good.
...even managers can understand them :)
0
 
LVL 1

Author Comment

by:amyassein
ID: 24800847
Chris,

I tried your final code but it generated a run time error "Object required "objRecordSet".

Any Suggestions?
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 24801661

That confused me for a while, but there's a typo, it complains about "objRecrodSet" being required instead :)

Fixed here.

Chris
' Inactivity period
 
Const PERIOD_TO_REMOVE = 90
 
' Search base - The AD path you wish to find users in (and beneath)
 
Const SEARCH_ROOT = "OU=People,DC=yourdomain,DC=com"
 
' Create a connection to the OU which holds disabled accounts
 
Set objOU = GetObject("LDAP://OU=Disabled Users,DC=yourdomain,DC=com")
 
' Create a value to represent the date
 
Dim dblInt8 : dblInt8 = CDbl(DateDiff("s", CDate("01/01/1601 00:00:00"), Now - PERIOD_TO_REMOVE))
 
' Create a filter to return all user accounts which have been inactive for PERIOD_TO_REMOVE and
' and are still enabled.
 
Dim strLdapFilter
strLdapFilter = "(&(objectClass=user)(objectCategory=person)(lastLogonTimeStamp<=" & _
  CStr(dblInt8) & "0000000)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
 
' Search the current AD domain
 
Dim objConnection : Set objConnection = CreateObject("ADODB.Connection")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
 
Dim objCommand : Set objCommand = Createobject("ADODB.Command")
objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 1000
 
objCommand.CommandText = "<LDAP://" & SEARCH_ROOT & ">;" & _
  strLdapFilter & ";distinguishedName,userAccountControl;subtree"
 
Dim objRecordSet : Set objRecordSet = objCommand.Execute
 
Do Until objRecordSet.EOF
  Set objUser = GetObject("LDAP://" & objRecordSet.Fields("distinguishedName").Value)
 
  ' Debugging / Testing
  WScript.Echo "Disable: " & objRecordSet.Fields("distinguishedName").Value
 
  ' Disable this account - Note: these are commented out to allow testing prior to activation
  ' objUser.AccountDisabled = True
  ' objUser.SetInfo
 
  Set objUser = Nothing
 
  ' Debugging / Testing
  WScript.Echo "Moving: " & objRecordSet.Fields("distinguishedName").Value  
 
  ' Move the account to a new OU - Note: this is commented out to allow testing prior to activation
 
  ' objOU.MoveHere "LDAP://" & objRecordSet.Fields("distinguishedName").Value, VbNullString
 
  objRecordSet.MoveNext
Loop

Open in new window

0
 
LVL 1

Author Comment

by:amyassein
ID: 24810969
Chris,

Thank you so much for fixing that error. Well, when i run the script, it works fine by showing pop up messages for disabling and moving the intended accounts but the thing is, when i checked the accounts, i noticed that they still not disabled and not moved to the specified OU. I am sure that i mentioned the name of the destination OU correctly (where all disabled accounts will be placed). Actually, i got many DCs in my environment so i waited for 30 minutes which is the DC replication interval because i doubted the script ran against a specified DC not all DCs.

Anyways, how can i tell the script to run against all the DCs in my environment?

Thank you again man.
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 24811506

Hey :)

It's because the lines that make the changes are commented out. I wanted to give you a chance to test it before it starting actually making changes.

You should see two sections for this:

  ' Disable this account - Note: these are commented out to allow testing prior to activation
  ' objUser.AccountDisabled = True
  ' objUser.SetInfo

And:

  ' Move the account to a new OU - Note: this is commented out to allow testing prior to activation

  ' objOU.MoveHere "LDAP://" & objRecordSet.Fields("distinguishedName").Value, VbNullString

Removing the ' will activate them (don't remove it from the actual comment though :)).

Chris
0
 
LVL 1

Author Comment

by:amyassein
ID: 24812692
Chris,

Yeah Yeah, now it's working man after removing these things  :)

But, there is still minor issue. Moving to the new OU is not performed.

It's moving to the same OU where i perform the search. For example, I perform the search in People OU and my destination folder is Decommissioned OU. After i configure these OUs in the script, the accoun is disabled properly but not moving to the destination OU. It also says "The server is unwilling to process the request"

Any ideas my friend?
0
 
LVL 1

Author Comment

by:amyassein
ID: 24834865
Chris,

Ok let me clarify things more.

After running the script, it says:

"Disable: CN=Smith,OU=People,DC=Domain,DC=com"

I click "Ok", then the second message appears:

"Moving: CN=Smith,OU=People,DC=Domain,DC=com"

Well, it should say:

"Moving: CN=Smith,OU=Decommissioned,DC=Domain,DC=com"

The this runtime error appears:

"The server is unwilling to process the request"

Therefore, the result is that the account is disabled successfully but remains in the source OU. I checked my permissions in the destination OU and i can move to and from this OU.

Hope this was clear.
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 24838037

Hey sorry dude, bit distracted.

> Well, it should say:

No, it shouldn't. It's telling you which object is being moved, not what the object will be after the move. That's only there for debugging.

> "The server is unwilling to process the request"

There aren't any other accounts in the target OU with the same name are there?

Chris
0
 
LVL 1

Author Comment

by:amyassein
ID: 24846467
Chris,

You know what! you are right! .... your script now is working like charm. I was access denied on the target OU when some administrator was doing maintenance for the OU permissions.

I know i ask too many questions but i have one last request which i beleive it's very easy to you.
I need the script to silently do this operation without poping up messages to me because it's headache to keep clicking "OK" after each message prompt. I think you need to remove those "Echo" lines. i don't want to do it as i am afraid i ruin something.

Many Thanks.
0
 
LVL 71

Accepted Solution

by:
Chris Dent earned 125 total points
ID: 24847123

No worries, all we need to do is comment out or remove the lines beginning WScript.Echo. As in the version below.

Chris
' Inactivity period
 
Const PERIOD_TO_REMOVE = 90
 
' Search base - The AD path you wish to find users in (and beneath)
 
Const SEARCH_ROOT = "OU=People,DC=yourdomain,DC=com"
 
' Create a connection to the OU which holds disabled accounts
 
Set objOU = GetObject("LDAP://OU=Disabled Users,DC=yourdomain,DC=com")
 
' Create a value to represent the date
 
Dim dblInt8 : dblInt8 = CDbl(DateDiff("s", CDate("01/01/1601 00:00:00"), Now - PERIOD_TO_REMOVE))
 
' Create a filter to return all user accounts which have been inactive for PERIOD_TO_REMOVE and
' and are still enabled.
 
Dim strLdapFilter
strLdapFilter = "(&(objectClass=user)(objectCategory=person)(lastLogonTimeStamp<=" & _
  CStr(dblInt8) & "0000000)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
 
' Search the current AD domain
 
Dim objConnection : Set objConnection = CreateObject("ADODB.Connection")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
 
Dim objCommand : Set objCommand = Createobject("ADODB.Command")
objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 1000
 
objCommand.CommandText = "<LDAP://" & SEARCH_ROOT & ">;" & _
  strLdapFilter & ";distinguishedName,userAccountControl;subtree"
 
Dim objRecordSet : Set objRecordSet = objCommand.Execute
 
Do Until objRecordSet.EOF
  Set objUser = GetObject("LDAP://" & objRecordSet.Fields("distinguishedName").Value)
 
  ' Debugging / Testing
  ' WScript.Echo "Disable: " & objRecordSet.Fields("distinguishedName").Value
 
  ' Disable this account - Note: these are commented out to allow testing prior to activation
  objUser.AccountDisabled = True
  objUser.SetInfo
 
  Set objUser = Nothing
 
  ' Debugging / Testing
  ' WScript.Echo "Moving: " & objRecordSet.Fields("distinguishedName").Value  
 
  ' Move the account to a new OU - Note: this is commented out to allow testing prior to activation
 
  objOU.MoveHere "LDAP://" & objRecordSet.Fields("distinguishedName").Value, VbNullString
 
  objRecordSet.MoveNext
Loop

Open in new window

0

Featured Post

Online Training Solution

Drastically shorten your training time with WalkMe's advanced online training solution that Guides your trainees to action. Forget about retraining and skyrocket knowledge retention rates.

Question has a verified solution.

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

This article demonstrates probably the easiest way to configure domain-wide tier isolation within Active Directory. If you do not know tier isolation read https://technet.microsoft.com/en-us/windows-server-docs/security/securing-privileged-access/s…
Group policies can be applied selectively to specific devices with the help of groups. Utilising this, it is possible to phase-in group policies, over a period of time, by randomly adding non-members user or computers at a set interval, to a group f…
Attackers love to prey on accounts that have privileges. Reducing privileged accounts and protecting privileged accounts therefore is paramount. Users, groups, and service accounts need to be protected to help protect the entire Active Directory …
Sometimes it takes a new vantage point, apart from our everyday security practices, to truly see our Active Directory (AD) vulnerabilities. We get used to implementing the same techniques and checking the same areas for a breach. This pattern can re…
Suggested Courses

623 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