Mar 18 2010

Reduce the size of ntds.dit

On this post we will describe how to do a spring clean on your active directory database file ntds.dit.
The first step will be to search for stale objects in your domain, if after collecting those objects you don’t find many of them, do not hope to gain some space on your database. For example the size of a user object is at the minimum 4Ko, the size may vary depending on the number of attributes the account has. Check this article for more information on objects size.

We will check with a script  for useless computer and user accounts. This only works if your domain functional level is Windows 2003 because LastLogonTimeStamp is used to determine if a user account is stale or not, this attribute is replicated on all your DCs, unlike the LastLogon attribute. If LastLogonTimeStamp is void then the user never logged on to the domain, otherwise we can retrieve last access to domain with the algorithm used in the script.
For computer accounts we will check the value of PwdLastSet attribute, which represents the last time the computer account renewed its password with the DC. For more information on how to get stale computer and user accounts you can check this post. So here is the script:

On Error Resume Next
'Input domain's distinguishedname
DomainDN = "dc=ldap389,dc=info"
Const ForAppending = 8
'We search for objects stale since 6 months
dtmDateValue = DateAdd("m", -6, now)
Set objShell = CreateObject("Wscript.Shell")
lngBiasKey = objShell.RegRead("HKLMSystemCurrentControlSetControl" _
    & "TimeZoneInformationActiveTimeBias")
If (UCase(TypeName(lngBiasKey)) = "LONG") Then
    lngBias = lngBiasKey
ElseIf (UCase(TypeName(lngBiasKey)) = "VARIANT()") Then
    lngBias = 0
    For k = 0 To UBound(lngBiasKey)
        lngBias = lngBias + (lngBiasKey(k) * 256^k)
End If
' Convert datetime value to UTC.
dtmAdjusted = DateAdd("n", lngBias, dtmDateValue)
' Find number of seconds since 1/1/1601.
lngSeconds = DateDiff("s", #1/1/1601#, dtmAdjusted)
' Convert the number of seconds to a string
' and convert to 100-nanosecond intervals.
str64Bit = CStr(lngSeconds) & "0000000"
Set fso = CreateObject("Scripting.FileSystemObject")
CibleFileOU = "Resultsusers.csv"
CibleFileOU2 = "Resultscpu.csv"
Set df3 = fso.OpenTextFile(CibleFileOU,ForAppending,True)
Set df4 = fso.OpenTextFile(CibleFileOU2,ForAppending,True)
Set objConnection = CreateObject("ADODB.Connection")
Set objCommand =   CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 
objCommand.CommandText =_
    "SELECT DistinguishedName,sAMAccountName,lastLogonTimestamp FROM 'LDAP://"&DomainDN&"' WHERE objectCategory='user' AND lastLogonTimestamp < '"&str64Bit&"'"
Set objRecordSet = objCommand.Execute
Do Until objRecordSet.EOF
disting = objRecordSet.Fields("DistinguishedName").Value
SAM = objRecordSet.Fields("sAMAccountName").Value
Set StrFlagMCP = objRecordSet.Fields("lastLogonTimestamp").Value
           intLastLogonTime0 = StrFlagMCP.HighPart * (2^32) + StrFlagMCP.LowPart
           intLastLogonTime0 = intLastLogonTime0 / (60 * 10000000)
           intLastLogonTime0 = intLastLogonTime0 / 1440
           'Convert lastlogontimestamp in date format
           StrFlagPwd = intLastLogonTime0 + #1/1/1601#
objCommand.CommandText =_
    "SELECT DistinguishedName,sAMAccountName,lastLogonTimestamp,WhenCreated FROM 'LDAP://"&DomainDN&"' WHERE objectCategory='user' AND NOT lastLogonTimestamp = '*'"
Set objRecordSet2 = objCommand.Execute
Do Until objRecordSet2.EOF
disting = objRecordSet2.Fields("DistinguishedName").Value
SAM = objRecordSet2.Fields("sAMAccountName").Value
LastLOG = objRecordSet2.Fields("lastLogonTimestamp").Value
Whencreate = objRecordSet2.Fields("WhenCreated").Value
df3.writeline(SAM&";NEVER LOGGED;"&disting&";"&Whencreate)
objCommand.CommandText =_
    "SELECT DistinguishedName,sAMAccountName,lastLogonTimestamp FROM 'LDAP://"&DomainDN&"' WHERE objectCategory='computer' AND PwdLastSet < '"&str64Bit&"'"  
Set objRecordSet3 = objCommand.Execute
Do Until objRecordSet3.EOF
disting = objRecordSet3.Fields("DistinguishedName").Value
SAM = objRecordSet3.Fields("sAMAccountName").Value
set StrFlagMCP = objRecordSet3.Fields("lastLogonTimestamp").Value
           intLastLogonTime0 = StrFlagMCP.HighPart * (2^32) + StrFlagMCP.LowPart
           intLastLogonTime0 = intLastLogonTime0 / (60 * 10000000)
           intLastLogonTime0 = intLastLogonTime0 / 1440
           StrFlagPwd = intLastLogonTime0 + #1/1/1601#
msgbox "done!

Download script here:

Just change DomainDN to your domain’s DistinguishedName,the threshold used is 6 months, you can change it by editing dtmDateValue.

Results concerning user accounts are logged into Resultsusers.csv. For user accounts with a void LastLogonTimeStamp,  the account creation date is also displayed, if the account was created a long a time ago and was never used,you can delete it. If the creation date is a few days ago, this just means that the user did not use his account yet, so do not delete it.  For other user accounts you can first disable them, wait a few days or weeks then delete them.

Results concerning computer accounts are logged into Resultscpu.csv , be aware that some computer objects which do not have a Windows Operating System (CIFS servers, OS X…) might not renew their password. You should exclude those objects from your list if you declared them in Active Directory. Exclude Microsoft Cluster Server Virtual Server computer objects as well.

We will now remove useless GPOs from our domain, for this just use sample scripts shipped in the GPMC tool. They are located in %programfiles%gpmcscripts. We will find unliked and disabled GPOs using FindDisabledGPOs.wsf and FindUnlinkedGPOs.wsf scripts. If you don’t need anymore GPOs retrieved by those scripts you can delete them.

Now, after you have deleted all these objects the size of your ntds.dit hasn’t changed yet, there are two reasons for this:

The first is that your objects are not really deleted, they are tombstoned, they will be deleted for good 60 or 180 days later, this period is called the tombstone lifetime. The purpose of tombstone is to allow you to easily restore objects in case of accidental deletion, when an object is tombstoned only a few attributes are really deleted. To know more about saved and deleted attributes in the tombstone you can read this post. By the way to restore tombstoned objects you can use ADRestore.NET which is like ADRestore but with a Graphical User Interface. Of course if you have recycle bin enabled on your Windows 2008 domain you do not need these tools.

If you are in a hurry to get rid of your deleted objects for good you can reduce the tombstone lifetime using adsiedit.msc. Just edit the number of days in tombstonelifetime value which is located in cn=directory service,cn=windows nt,cn=services,cn=configuration,dc=<forestDN>. Be aware that this modification can be dangerous, tombstone lifetime should never be smaller than time needed for an object to replicate across the forest. Once deleted objects are removed for good do not forget to roll back to previous settings. Do not forget that if there is an accidental deletion while tombstone lifetime is reduced, you might not be able to reanimate object, and will have to use a backup. So my advice is do not mess with these settings, they are described for information purposes.

The second reason is that you need to perform an offline defragmentation of your ntds.dit file to really shrink its size. Before that we will audit our database file in order to know what effects defragmentation will have on its size. For this we need to activate garbage collection diagnosis. Once 6 Garbage Collection value is switched to one, under HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesNTDSDiagnostics, an EventID 1646 is generated each time an online defragmentation occurs (every 12 hours):

Free hard disk space shows you how much space you can gain by performing an offline defragmentation. In our example it is 584Mo which is a huge value, do not expect to gain this much space unless you have at least tens of thousands of stale objects. Now you just have to follow this procedure to perform offline defragmentation.

Offline defragmentation process is just a few minutes long, if you do not count reboot time in DSRM under Windows 2003, if you are on a Windows 2008 DC you just need to stop AD service. One last thing you need to perform this operation on every DC because changed data is replicated between domain controllers, not the database itself.

This post is also available in: French

No Comments

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment


WordPress Themes

Blossom Icon Set

Software Top Blogs