Migration ; Stage Two – Active Directory Cleanup

Why migrate unused service accounts, user objects and computer accounts from your source domains to the target? A migration is an ideal opportunity to cleanup all of the disused and legacy objects from your environment.

A simple way to achieve a user purge to is to query the lastLogonTimeStamp attribute on all user objects. This will immediately provide a reliable picture of recent account utilisation across the domain. The lastLogonTimeStamp attribute is new to Windows Server 2003, as opposed to the Windows 2000 lastLogon attribute which will vary on each Domain Controller within the domain, is replicated to all Domain Controllers.

The following vbScript will perform the query, creating a report on recent account usage:

Sub ListUsers( strDomain )
Set objComputer = GetObject(“WinNT://” & strNetBIOSDomain )
objComputer.Filter = Array( “User” )
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set objFile = fso.CreateTextFile(fileUserLst, True)
Dim strUserName, objUser, objLogon, intLogonTime

‘ Constants for the NameTranslate object.
Const ADS_NAME_TYPE_1779 = 1

For Each objUser In objComputer
On Error Resume Next

‘ Specify the NT name of the user.
strNTName = objUser.Name

‘ Use the NameTranslate object to convert the NT user name to the
‘ Distinguished Name required for the LDAP provider.
Set objTrans = CreateObject(“NameTranslate”)

‘ Initialize NameTranslate by locating the Global Catalog.
objTrans.Init ADS_NAME_INITTYPE_GC, “”
‘ Use the Set method to specify the NT format of the object name.
objTrans.Set ADS_NAME_TYPE_NT4, strNetBIOSDomain & “” & strNTName

‘ Use the Get method to retrieve the RPC 1779 Distinguished Name.
strUserDN = objTrans.Get(ADS_NAME_TYPE_1779)

‘ Bind to the user object in Active Directory with the LDAP provider.
Set objUser = GetObject(“LDAP://” & strUserDN)

‘MsgBox(“DN = ” & strUserDN)
‘ This code prints the last logon timestamp for a user.
set objLogon = objUser.Get(“lastLogonTimestamp”)
intLogonTime = objLogon.HighPart * (2^32) + objLogon.LowPart
intLogonTime = intLogonTime / (60 * 10000000)
intLogonTime = intLogonTime / 1440

‘…and writes to file specified by user
objFile.WriteLine( objUser.Name & “;” & intLogonTime + #1/1/1601#)

End Sub
‘ Main
‘ ****************************************************************************
  strNetBIOSDomain = inputbox(“Please enter the NetBios domainname. example; mydomain”, “NetBIOS domain name” )
  fileUserLst = inputbox(“Please enter a file name and path for the output file.”, “File path”, “C:” & strNetBIOSDomain & ” users.csv”)
Loop until strNetBIOSDomain “”

ListUsers( strDomain )

With regards to computer accounts, don’t worry too much about the cleanup of these objects as you will be migrating all of the active computer objects within your environment individually duringthe migration process. Therefore what is left is likely to be the disused / legacy objects.

I would also suggest reviewing security group memberships and useage. This can be achieved by looking at logon scripts for any group based functions, group policy assignment and file share permission auditing. DSQUESRY can be used to return these values for each group individually:

dsquery * “” -attr name createTimestamp modifyTimestamp

Or for all security groups (well all objects in the case of this command) in an OU:

dsquery * “OU=Security Groups,DC=Domain,DC=local” -attr name modifyTimeStamp

This helps to further justify normalising your OU structure prior to the migration process (see here.) Without normalisation of the Security Gorups into a particlar OU it would be more complex to return the required information.

Perform the same steps with your Distribution Lists to ensure you’re not wasting time an effort migrating objects which are no longer in use.

Finally, if you are using the Quest tools identify which customAttributes are in use within your environment.These attributes are essential during the migration and each source pair will require 2 free sttributes for the duration of the migration.


Migration ; Stage One – Active Directory Normlisation

One of the first steps towards simplifying your Active Directory migration is directory normalisation. This process involves re-designing each of your Organisational Unit structures in order that they are identicle in each source domain. This allows you to locate service accounts, generic accounts and resource mailboxes in seperate OUs, thus allowing identification of these resources in advance of the migration. Numbers are important during a migration, so when this process is finished you’ll be able to identify:

1) Number of non-adminisitrative user and computer accounts in each source domain
2) Number of service accounts in the domain
3) Number of generic logon accounts in the domain
4) Number of resource mailboxes in the domain

 An example of a normalised OU strufture is as follows:


You can clearly see the division of user and computer objects, Exchange objects (excluding User Mailboxes), service accounts and server objects. This structure allows for simple Group Policy management as well as simplified administration.

By performing the above process on each of your source environments this will speed up the process of migration as the administrators do not necessariliy need to be familiar with the source environments to perform a stream-lined and well-structured migration. Just be careful when moving objects into new OUs as this can change Group Policy inheritance!


VBScript; List Exchange Mailbox DACL / ACE / ACL

The below VBScript will return all user mailboxes on your environment and display the DACL for the mailbox. This helps to easily identify custom ACEs during an Exchange migration enabling you to pre-configure permissions in your target environment. The script is useful in identifying groups of users that work together and thus need access to each others mailboxes.

The script will create an Excel file, although I have stopped the script form closing the Excel file as this will cause issues in the event of you running Office versons earlier than 2003.


On Error Resume Next

sXLS = “C:\mbx-access-rights-export.xls”   ‘excel file must be created before script is ran

 Set objRootDSE = GetObject(“LDAP://rootDSE”)
 strDNSDomain = objRootDSE.Get(“defaultNamingContext”)

 ‘Start the ADO connection
 Set objCommand = CreateObject(“ADODB.Command”)
 Set objConnection = CreateObject(“ADODB.Connection”)
 objConnection.Provider = “ADsDSOObject”
 objConnection.Open “Active Directory Provider”
 objCommand.ActiveConnection = objConnection

 ‘Set the ADO connection query strings
 StartNode = strDNSDomain
 SearchScope = “subtree”

 FilterString = “(&(objectCategory=person)(objectClass=user)” _
             & “(description=*)” _
              & “(mail=*))” _
                 ‘& “(!(userAccountControl:1.2.840.113556.1.4.803:=2)))”
 Attributes = “adspath”

 ‘Create the LDAP-Query
 LDAPQuery = “;” & FilterString & “;” _
                & Attributes & “;” & SearchScope

 objCommand.CommandText = LDAPQuery
 objCommand.Properties(“Page Size”) = 100
 objCommand.Properties(“Timeout”) = 30
 objCommand.Properties(“Cache Results”) = False

 Set objRecordSet = objCommand.Execute

 Set objExcel = CreateObject(“Excel.Application”)
    objExcel.Application.DisplayAlerts = False
    objExcel.Visible = True

     ‘Set objWorkbook = objExcel.Workbooks.Open(sXLS)

        objExcel.Cells(1,1).Value = “Logon Name”
        objExcel.Cells(1,2).Value = “Display Name”
        objExcel.Cells(1,3).Value = “Email Address”
        objExcel.Cells(1,4).Value = “Mailbox Rights”

          xRow = 1
          yColumn = 1

       Do Until yColumn = 5
               objExcel.Cells(xRow,yColumn).Font.Bold = True
            objExcel.Cells(xRow,yColumn).Font.Size = 11
            objExcel.Cells(xRow,yColumn).Interior.ColorIndex = 11
            objExcel.Cells(xRow,yColumn).Interior.Pattern = 1
            objExcel.Cells(xRow,yColumn).Font.ColorIndex = 2
            objExcel.Cells(xRow,yColumn).Borders.LineStyle = 1
            objExcel.Cells(xRow,yColumn).WrapText = True
    yColumn = yColumn + 1

    x = 2
    y = 1

     If NOT objRecordSet.eof Then
          While Not objRecordset.EOF
            Set objUser = GetObject(objRecordSet.Fields(“AdsPath”).Value)
            y1 = y
                      objExcel.Cells(x,y1).Value = objUser.sAMAccountName
                      y1 = y1 + 1
                      objExcel.Cells(x,y1).Value = objUser.displayName
                y1 = y1 + 1
                      objExcel.Cells(x,y1).Value = objUser.mail
                y1 = y1 + 1
                    Set oSecurityDescriptor = objuser.Get(“msExchMailboxSecurityDescriptor”)
                    Set dacl = oSecurityDescriptor.DiscretionaryAcl
                    Set ace = CreateObject(“AccessControlEntry”)
                      For Each ace In dacl
                        mystring = ace.Trustee
                        If (ace.AceType = ADS_ACETYPE_ACCESS_ALLOWED) Then
                            x = x + 1
                                  objExcel.Cells(x,y1).Value = mystring & ” has access”
                        ElseIf (ace.AceType = ADS_ACETYPE_ACCESS_DENIED) Then
                            x = x + 1
                                  objExcel.Cells(x,y1).Value = mystring & ” is denied access”
                        End If
                      x = x + 1 ‘go to the next Row
     End If

 objExcel.Selection.HorizontalAlignment = 3     ‘center all data
 objExcel.Selection.Borders.LineStyle = 1     ‘apply borders
 objExcel.Columns(“A:AH”).EntireColumn.AutoFit  ‘autofit all columns

 appVerInt = split(objExcel.Version, “.”)(0)
    If appVerInt-Excel2007 >=0 Then
          objExcel.ActiveWorkbook.SaveAs(sXLS), 56  ‘office 2007
          objExcel.ActiveWorkbook.SaveAs(sXLS), 43  ‘office 2003
    End If


 set objExcel = Nothing
 Set objUser = Nothing

msgbox “Done!”