Automatic Disabling of unused user account in AD

  • Thread starter Thread starter Sudeep Batra
  • Start date Start date
S

Sudeep Batra

We intend to deploy a policy such that if any user is not accessing his
userid for 3 months or more, it should automatically get disabled and
thereafter we might delete it .

Any hep would be highly appreciated,

regards,


Sudeep
 
Sudeep said:
We intend to deploy a policy such that if any user is not accessing his
userid for 3 months or more, it should automatically get disabled and
thereafter we might delete it .

Any hep would be highly appreciated,
This cann't be done by policy settings but simple script wich will
gather data about last logon time form all of the DC's and then decide
if the user account has to be disabled will do this for You perfectly
 
This can become a big headache if you have a lot of DC's,
so you will want to first tackle gathering the "last sign
on" data for the accounts in an automated manner. This
data is stored on the server that provided authentication
to the user last. Each user can have multiple DC's
approving it's logon credentials.

If you have a small 1-3 DC LAN you won't have too much
trouble. I just imagine making that operational policy
here and then the need for adding bandwidth just to gather
all the data. :)

With a lot of DC's, I would try to run a script on each DC
to "trim" the data of duplicates so it ONLY provides the
last sign on time in a weekly or even monthly report.
 
Yeah I wrote a script that went and looked at each dc and found the latest
signon, this was then output to a csv and I can look at in Excel. Doing on
each dc would be a real head ache even with two dc's.

--

Paul Bergson MCT, MCSE, MCSA, CNE, CNA, CCA

This posting is provided "AS IS" with no warranties, and confers no rights.
 
We intend to deploy a policy such that if any user is not accessing his
userid for 3 months or more, it should automatically get disabled and
thereafter we might delete it .

Any hep would be highly appreciated,

regards,


Sudeep
For a single domain controller, see tip 7358 in the 'Tips & Tricks' at
http://www.jsiinc.com

See tip 8244


Jerold Schulman
Windows: General MVP
JSI, Inc.
http://www.jsiinc.com
 
-----Original Message-----
Yeah I wrote a script that went and looked at each dc and found the latest
signon, this was then output to a csv and I can look at in Excel. Doing on
each dc would be a real head ache even with two dc's.

No need to run the script on each DC if you have few
enough to do it quickly from one machine. What I am
thinking is a WAN with more than a couple dozen DC's to be
done regularly. Running the script at a given time, then
collecting the output file remotely on another schedule
would reduce bandwidth time in compiling the list.

Imagine collecting the data from your HQ in New York on 5
DC's in 5 other sites. Say these sites are in Colorodo,
California, Florida, Wyoming, New Hampshire. Depending on
connection between the sites it could take a considerable
time to compile each list.

If you are talking the 5 DC's on a single LAN (where they
should have 10mbit to 100mbit connections between servers)
then it's easy enough to run from one admin workstation.
I was just picturing a script running for hours on end in
the above scenario. So instead of taking say 10 minutes
per server remotely we let each server run a script that
takes it 1 minute (or less) and we spend less than a
minute grabbing that output file. Much faster :)

If anyone can't tell yet, I work in a very large AD
enterprise. We are just about a "test case" for
Microsoft. :)
 
Yeah I used to work for a major airline with worldwide connections, it could
be a real pain, nothing was ever simple.

My script runs on a separate machine and polls all the dc's via a nightly
scheduled task. Totally hands off and unobtrusive. It needs no
intervention and it reads ad to find out the dc names. You can add and
remove and it makes the adjustments.

--

Paul Bergson MCT, MCSE, MCSA, CNE, CNA, CCA

This posting is provided "AS IS" with no warranties, and confers no rights.
 
For what it is worth - Change the "YourDomain" to the domain name in
question. Feedback (Fixes always welcome)



'
' Author Paul Bergson
' Date Written June 24, 2003
' Description Rips out all users and their context from AD and writes
them to a ssv file (semi-colon seperated)
' Modified December 3, 2003 Fixed sAMAccountName
reference bug
' January 2, 2004 Added display name to the csv
output file
'
'

Option Explicit

Dim aryContainer(500) ' Track the name of all containers
Dim aryCN(9999,10) ' Track the name of all users
Dim aryWhen() ' When Created
Dim arySvCN() ' Original context
Dim aryHomeDirectory() ' Location of home folder
Dim aryScriptPath() ' Location of script path
Dim arydisplayName() ' Display Name
Dim cntLen, cntComma, cntRight
Dim cntX, cntY, cntZ
Dim cntL, cntM, cntO
Dim cntColumns
Dim flgLoop
Dim flgContainerFnd
Dim txtName, txtCN, txtSvName
Dim usr
Dim flag
Dim objConnection
Dim objRecordSet
Dim objCommand
Dim objUser
Dim fso
Dim ts
Dim strHomeDirectory
Dim Output

Dim fsoHTA
Dim tsHTA


Dim oRoot, sConfig, oConnection, oCommand, sQuery
Dim oResults, oDC, sDNSDomain, oShell, nBiasKey
Dim nBias, k, sDCs(), sAdsPath, oDate, nDate
Dim oComputer, nLatestDate

Const ADS_UF_PASSWD_NOTREQD = &H0020
Const ADS_UF_DONT_EXPIRE_PASSWD = &H10000
Const ADS_UF_PASSWORD_EXPIRED = &H800000
Const ADS_UF_ACCOUNTDISABLE = &H0002


Sub AvailableDCs


' Obtain local Time Zone bias from machine registry.
' Watch for line wrapping.
Set oShell = CreateObject("Wscript.Shell")
nBiasKey =
oShell.RegRead("HKLM\System\CurrentControlSet\Control\TimeZoneInformation\Ac
tiveTimeBias")
If UCase(TypeName(nBiasKey)) = "LONG" Then
nBias = nBiasKey
ElseIf UCase(TypeName(nBiasKey)) = "VARIANT()" Then
nBias = 0
For k = 0 To UBound(nBiasKey)
nBias = nBias + (nBiasKey(k) * 256^k)
Next
End If

' Determine configuration context and
' DNS domain from RootDSE object.
Set oRoot = GetObject("LDAP://RootDSE")
sConfig = oRoot.Get("ConfigurationNamingContext")
sDNSDomain = oRoot.Get("DefaultNamingContext")

' Use ADO to search Active Directory for
' ObjectClass nTDSDSA.
' This will identify all Domain Controllers.
Set oCommand = CreateObject("ADODB.Command")
Set oConnection = CreateObject("ADODB.Connection")
oConnection.Provider = "ADsDSOObject"
oConnection.Open = "Active Directory Provider"
oCommand.ActiveConnection = oConnection

sQuery = "<LDAP://" & sConfig _
& ">;(ObjectClass=nTDSDSA);AdsPath;subtree"

oCommand.CommandText = sQuery
oCommand.Properties("Page Size") = 100
oCommand.Properties("Timeout") = 30
oCommand.Properties("Searchscope") = 2
oCommand.Properties("Cache Results") = False

Set oResults = oCommand.Execute

' Enumerate parent objects of class nTDSDSA. Save
' Domain Controller names in dynamic array sDCs.
k = 0
Do Until oResults.EOF
Set oDC = _
GetObject(GetObject(oResults.Fields("AdsPath")).Parent)

If Not UCASE(Left(oDC.DNSHostName,3)) = "VDC" and Not
UCASE(Left(oDC.DNSHostName,5)) = "DCSRE" Then ' don't include virtual
servers or SRE
ReDim Preserve sDCs(k)
sDCs(k) = oDC.DNSHostName
k = k + 1
End If
oResults.MoveNext
Loop

End Sub



Sub LastTime

' Hard code LDAP AdsPath of user.
sAdsPath = arySvCN(cntL)

' Retrieve LastLogon attribute for computer on
' each Domain Controller.
nLatestDate = #1/1/1601#
oDate = #1/1/1601#


For k = 0 To Ubound(sDCs)
On Error Resume Next
Set oComputer = GetObject("LDAP://" & sDCs(k) & "/" & sAdsPath)

' Trap error in case LastLogon is null.
Set oDate = oComputer.LastLogon
On Error Resume Next

If Err.Number <> 0 Then
Err.Clear
nDate = #1/1/1601#
Else
If (oDate.HighPart = 0) And (oDate.LowPart = 0 ) Then
nDate = #1/1/1601#
Else
nDate = #1/1/1601# + (((oDate.HighPart * (2 ^ 32)) _
+ oDate.LowPart)/600000000 - nBias)/1440
End If
End If

Err.Clear
If nDate > nLatestDate Then
nLatestDate = nDate
End If

Next

End Sub

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Routine to strip out all containers and place in the aryContainer '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub Stripper

Do
cntLen = Len(txtName)-3
txtName = Right(txtName,cntLen) ' Drops the Container
Type Name
cntComma = Instr(txtName,",") - 1 ' Find the end of the
name

If cntComma < 0 Then ' Last Name to process
there is no comma
cntComma = Len(txtName)
End If

txtCN = Left(txtName,cntComma) ' Pull the Name out of
the Distinguished Name

flgContainerFnd = "N"


For cntL = 0 to 500 ' Does this current
Container exist?
If aryContainer(cntL) = "" Then ' If current location is
blank then add it to the table
aryContainer(cntL) = txtCN
Exit For
End If

If aryContainer(cntL) = txtCN Then ' OU found
Exit For
End if
Next

aryCN(cntX, cntY) = cntL ' Store location of OU
cntY = cntY + 1 ' increment counter

cntComma = Instr(txtName,",") ' If equal to zero then
all done

If cntComma > 0 Then
cntRight = Len(txtName) - (cntComma) ' Get to start of next
part of name
txtName = Right(txtName, cntRight) ' Purge CN name
flgLoop = "Y"
Else
flgLoop = "N" ' Set all done flag
End If

Loop Until flgLoop = "N"

End Sub

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Main Code of Program '
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Set objConnection = CreateObject("ADODB.Connection") ' Create a Connection
object in memory
objConnection.Open "Provider=ADsDSOObject;" ' Open the Connection
object using the ADSI OLE DB provider

Set objCommand = CreateObject("ADODB.Command") 'Create an ADO Command
object in memory, and assign the Command _
objCommand.ActiveConnection = objConnection ' object's
ActiveConnection property to the Connection object

objCommand.Properties("Page Size") = 100
objCommand.Properties("Size Limit") = 3000

objCommand.CommandText = _

"<LDAP://dc=YourDomain,dc=com>;(objectCategory=user);sAMAccountName,distingu
ishedName,name,whenCreated,homeDirectory,scriptPath,displayName;subtree"
'
"<LDAP://dc=YourDomain,dc=com>;(objectCategory=user);distinguishedName,name,
whenCreated,homeDirectory,scriptPath;subtree"

Set objRecordSet = objCommand.Execute ' Run the query by
calling the Execute method of the Command object

cntX = 0
cntY = 0

While Not objRecordSet.EOF
txtName = lcase(objRecordSet.Fields("distinguishedName")) '
Access each record in objRecordSet
txtSvName = txtName

ReDim Preserve arySvCN(cntX)
ReDim Preserve aryWhen(cntX) ' Save when
account created
ReDim Preserve aryHomeDirectory(cntX)
ReDim Preserve aryScriptPath(cntX)
ReDim Preserve arydisplayName(cntX)
aryWhen(cntX) = lcase(objRecordSet.Fields("whenCreated"))
aryHomeDirectory(cntX) = lcase(objRecordSet.Fields("homeDirectory"))
aryScriptPath(cntX) = lcase(objRecordSet.Fields("scriptPath"))
arydisplayName(cntX) = lcase(objRecordSet.Fields("displayName"))

cntLen = Len(txtName)
txtName = Left(txtName,(cntLen-18)) ' Provides the name w/o
dc=Your Domain,dc=com
cntLen = Len(txtName)-3
txtName = Right(txtName,cntLen) ' Drops the Container
Type Name
cntComma = Instr(txtName,",") - 1 ' Find the end of the
name
txtCN = Left(txtName,cntComma) ' Pull the Name out of
the Distinguished Name

aryCN(cntX, cntY) = lcase(objRecordSet.Fields("sAMAccountName")) '
Save Name
arySvCN(cntX) = txtSvName ' Save Distinguished
Name
cntY = cntY + 1 ' increment table
counter
cntRight = Len(txtName) - (cntComma + 1) ' Get to start of next
part of name
txtName = Right(txtName,cntRight) ' Purge CN name

Call Stripper ' Build table of the
context of the user

cntX = cntX + 1
cntY = 0

objRecordSet.MoveNext
Wend


Call AvailableDCs ' Go get the DC's in the
domain

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Create a file on the p drive '
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Set fso = CreateObject("Scripting.FileSystemObject")
set ts = fso.CreateTextFile("c:\temp\users.txt", True)

For cntL = 0 to 500 ' Find out how many
columns used
If aryContainer(cntL) = "" Then
Exit For
End If
Next

cntColumns = cntL

ts.write("Logon Name; Context")
'For cntO = 1 to cntColumns - 2 ' used to add headers for
ou
' ts.write("; OU")
'Next
ts.write("; Header OU") ' Remove for multiple
OU's
ts.writeline("; Last Logon Date; Creation Date; Home Folder; Script; Display
Name; Password Not Needed; Password Does Not Expire; Expired Password;
Account Is Disabled")

For cntL = 0 to 9999 ' Cycle through all
users found
If aryCN(cntL,0) = "" Then ' Quit once end found
Exit For
End If

ts.write(aryCN(cntL,0)) & "; " ' Output users logon
name
ts.write(arySvCN(cntL)) & "; " ' Output full context of
user

ts.write(aryContainer(aryCN(cntL,1))) ' Output users Home OU


' code will enumurate all the individual columns from within the context
' For cntM = 1 to 11
' If aryCN(cntL,cntM) > "" Then
' ts.write ("; ")
' ts.write(aryContainer(aryCN(cntL,cntM))) ' Output the context of
this user
' Else
' Exit For ' No records left to
process
' End If
' Next
'
' If cntColumns > cntM Then ' Align Columns
' For cntO = 1 to cntColumns - cntM
' ts.write("; ")
' Next
' End If

Call LastTime ' Sniff Out Last Time
logged on

If nLatestDate <> "1/1/1601" Then
ts.write("; " & nLatestDate)
Else
ts.write("; ")
End If

ts.write("; ") ' Date Account Was
Created
ts.write(aryWhen(cntL))


ts.write("; ") ' Home folder location
If aryHomeDirectory(cntL) > "" Then
ts.write(aryHomeDirectory(cntL))
Else
ts.write(" ")
End If

ts.write("; ")

If aryScriptPath(cntL) > "" Then
ts.write(aryScriptPath(cntL))
Else
ts.write(" ")
End If

ts.write("; ")

If arydisplayName(cntL) > "" Then
ts.write(arydisplayName(cntL))
Else
ts.write(" ")
End If

If aryContainer(aryCN(cntL,1)) <> "ServiceAccounts" Then
on error resume next
Set usr = GetObject("WinNT://gob/" & aryCN(cntL,0))
flag = usr.Get("UserFlags")

If flag AND ADS_UF_PASSWD_NOTREQD Then
ts.write("; y")
Else
ts.write("; ")
End If

If flag AND ADS_UF_DONT_EXPIRE_PASSWD Then
ts.write("; y")
Else
ts.write("; ")
End If

If flag AND ADS_UF_PASSWORD_EXPIRED Then
ts.write("; y")
Else
ts.write("; ")
End If

If flag AND ADS_UF_ACCOUNTDISABLE Then
ts.write("; y")
Else
ts.write("; ")
End If
End If

ts.writeline() ' Start a newline

Next

objConnection.Close


--

Paul Bergson MCT, MCSE, MCSA, CNE, CNA, CCA

This posting is provided "AS IS" with no warranties, and confers no rights.
 
Great script sample. I'll spend the day looking over it,
so I probably won't post again for a while, ROFL. I might
be able to use it as a template for a script we need
here. Thanks a ton for the contribution.
 
Back
Top