locked
VB script for lastlogon dnsserver. RRS feed

  • Question

  • I have a script for polling all the dns server in a domain to find the lastlogon, but the script I have doesn't store the name of the dns server that had the most current last logon.  Is that data stored in the user class somewhere.  Or do I need to take the most current time I found and verify it against evey dns server with a If most current time = last logon time on this dns server  echo the name?  I am not real sure how to go about verifying it either.  I got alot of this script from Richard Mueller's site http://www.rlmueller.net  This script is great for auditing user accounts, so feel free to use it if you need it.  Thanks for your help in advance.

    Johnny

    Option Explicit
    Dim objRootDSE, strConfig, objConnection, objCommand, strQuery
    Dim objRecordSet, objDC
    Dim strDNSDomain, objShell, lngBiasKey, lngBias, k, arrstrDCs()
    Dim strDN, dtmDate, objDate, lngDate, objList, strUser
    Dim strBase, strFilter, strAttributes, lngHigh, lngLow

     


    ' Create dictionary to keep track of
    ' users and login times
    Set objList = CreateObject("Scripting.Dictionary")
    objList.CompareMode = vbTextCompare
    ' rmueller's script
    ' Obtain local Time Zone bias from machine registry.
    Set objShell = CreateObject("Wscript.Shell")
    lngBiasKey = objShell.RegRead("HKLM\System\CurrentControlSet\Control\" _
    & "TimeZoneInformation\ActiveTimeBias")
    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)
    Next
    End If
    ' Determine configuration context and DNS domain from RootDSE object.
    Set objRootDSE = GetObject("LDAP://RootDSE")
    strConfig = objRootDSE.Get("configurationNamingContext")
    strDNSDomain = objRootDSE.Get("defaultNamingContext")
    ' Use ADO to search Active Directory for ObjectClass nTDSDSA.
    ' This will identify all Domain Controllers.
    Set objCommand = CreateObject("ADODB.Command")
    Set objConnection = CreateObject("ADODB.Connection")
    objConnection.Provider = "ADsDSOObject"
    objConnection.Open "Active Directory Provider"
    objCommand.ActiveConnection = objConnection
    strBase = "<LDAP://" & strConfig & ">"
    strFilter = "(objectClass=nTDSDSA)"
    strAttributes = "AdsPath"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
    objCommand.CommandText = strQuery
    objCommand.Properties("Page Size") = 100
    objCommand.Properties("Timeout") = 60
    objCommand.Properties("Cache Results") = False
    Set objRecordSet = objCommand.Execute
    ' Enumerate parent objects of class nTDSDSA. Save Domain Controller
    ' AdsPaths in dynamic array arrstrDCs.
    k = 0
    Do Until objRecordSet.EOF
    Set objDC = _
       GetObject(GetObject(objRecordSet.Fields("AdsPath")).Parent)
    ReDim Preserve arrstrDCs(k)
    arrstrDCs(k) = objDC.DNSHostName
    k = k + 1
    objRecordSet.MoveNext
    Loop
    ' Retrieve lastLogon attribute for each user on each Domain Controller.
    For k = 0 To Ubound(arrstrDCs)
    strBase = "<LDAP://" & arrstrDCs(k) & "/" & strDNSDomain & ">"
    strFilter = "(&(objectCategory=person)(objectClass=user))"
     
    strAttributes = "distinguishedName,lastLogon"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes _
       & ";subtree"
    objCommand.CommandText = strQuery
    On Error Resume Next
    Set objRecordSet = objCommand.Execute
    If Err.Number <> 0 Then
       On Error GoTo 0
       Wscript.Echo "Domain Controller not available: " & arrstrDCs(k)
    Else
       On Error GoTo 0
       Do Until objRecordSet.EOF
          strDN = objRecordSet.Fields("distinguishedName")
          lngDate = objRecordSet.Fields("lastLogon")
        
         On Error Resume Next
         Set objDate = lngDate
         If Err.Number <> 0 Then
           On Error GoTo 0
           dtmDate = #1/1/1601#
         Else
           On Error GoTo 0
           'stored as a 64 bit integer that VBScript cannot handle, must
           'seperate it to work with it.
           lngHigh = objDate.HighPart
           lngLow = objDate.LowPart
           If lngLow < 0 Then
             lngHigh = lngHigh + 1
           End If
           If (lngHigh = 0) And (lngLow = 0 ) Then
             dtmDate = #1/1/1601#
           Else
             dtmDate = #1/1/1601# + (((lngHigh * (2 ^ 32)) _
               + lngLow)/600000000 - lngBias)/1440
           End If
         End If
         If objList.Exists(strDN) Then
           If dtmDate > objList(strDN) Then
             objList(strDN) = dtmDate
           End If
         Else
           objList.Add strDN, dtmDate
         End If
         objRecordSet.MoveNext
       Loop
    End If
    Next
    Dim objNetwork,objExcel
    dim intRow
    intRow = 2
    Set objNetwork = Wscript.CreateObject("Wscript.Network")
    Set objExcel = CreateObject("Excel.Application")
    objExcel.Visible = True
    objExcel.Workbooks.Add

    objExcel.Cells(1,1).value = "First Name"
    objExcel.Cells(1,2).value = "Last Name"
    objExcel.Cells(1,3).value = "User ID"
    objExcel.Cells(1,4).value = "Last Logon Date"
    objExcel.Cells(1,5).value = "Last Logon Time"
    objExcel.Cells(1,6).value = "Last Logon Server"
    objExcel.Cells(1,7).value = "Account Status"

    ' Output latest lastLogon date for each user.
    Dim easydate1
    Dim easydate2
    Dim objUser

    For Each strUser In objList
    'easydate1 is the date stamp
    'easydate2 is the time stamp
    easydate1 = objList(strUser)
    easydate1 = FormatDateTime(easydate1,2)
    easydate2 = objList(strUser)
    easydate2 = FormatDateTime(easydate2,3)
    'output username and last login date and last login time
    ' change 1/1/1601 to Never for Never logged in
    If easydate1 = "1/1/1601" Then
     easydate1 = "Never"
    End If 
    ' change 12:00:00 AM to Never for Never logged in
    If easydate2 = "12:00:00 AM" Then
     easydate2 = "Never"
    End If

    Set objUser = GetObject _
        ("LDAP://" & struser & "")

    objExcel.Cells(intRow,1).value = objuser.givenname
    objExcel.Cells(intRow,2).value = objuser.sn
    objExcel.Cells(intRow,3).value = objuser.sAMAccountName
    objExcel.Cells(intRow,4).value = easydate1
    objExcel.Cells(intRow,5).value = easydate2


    '*Problem area Start*
    'objExcel.Cells(intRow,6).value = lastlogon dns server name
    'populated disabled column
    '*Problem area End*


    if objuser.AccountDisabled = True Then
     objExcel.Cells(intRow,7).value = "DISABLED"
    Else
     objExcel.Cells(intRow,7).value = "ACTIVE"
    End if
    intRow = intRow + 1

    Next

    'sort by login Date
    Dim obrange, obrange2
    Set obrange = objExcel.Range("A:K")
    Set obrange2 = objExcel.Range("A2")
    obrange.Sort obrange2,1,,,,,,1


    ' Clean up.
    objConnection.Close
    Set objRootDSE = Nothing
    Set objConnection = Nothing
    Set objCommand = Nothing
    Set objRecordSet = Nothing
    Set objDC = Nothing
    Set objDate = Nothing
    Set objList = Nothing
    Set objShell = Nothing
    Set obrange = Nothing
    Set obrange2 = Nothing
    Set objExcel = Nothing
    WScript.Quit


    Johnny
    Tuesday, June 30, 2009 6:51 PM

Answers

  • I would recommend the following.

    1) You should consider dropping the use of the Dictionary Object....and change to an ADO Recordset, so you can store a 3rd, 4th, 5th, values as needed using ADO Recordsets easier.

    2) Add AdsPath to the list of user attibutes to return....this contains the ServerName that you want.
    strAttributes = "distinguishedName,lastLogon,Adspath"

    3) During your test for a more current logon date you need to store the matching AdsPath to the ADORecordset as well as the updated Logon Date/Time
    'Retrieve the fields here
          strDN = objRecordSet.Fields("distinguishedName")
          lngDate = objRecordSet.Fields("lastLogon") 
           strADSPath = objRecordSet.Fields("ADSPath")
    ....
    'Save the appropriate fields your Recordset/Dictionary Object here.
           If dtmDate > objList(strDN) Then 
            objNewRS.Fields("lastLogon") = dtmDate
            objNewRS.Fields("ADSPath") = strADSPath 
           End If


    Another option would be to keep the Dictionary Object and use a semicolon (or other special character) to deliminate multiple values.
           If dtmDate > objList(strDN) Then
             objList(strDN) = dtmDate & ";" & strAdsPath
           End If

    • Marked as answer by Johnny Joe Friday, November 20, 2009 7:22 PM
    Friday, November 20, 2009 6:03 PM
  • Here is how I would code the script. After retrieving the last logon dates (and the DC's) as before, this script then uses ADO to query AD for the remaining attributes of interest. It writes the values to a spreadsheet.

    I notice that your copy of my LastLogon.vbs program appears old (I changed some of the variable names some years ago). In particular, you don't ever close the recordsets, which seems risky to me. Other than that I don't believe my code below is much different, but I started from my latest version to be sure.

    Option Explicit
    
    Dim objRootDSE, strConfig, adoConnection, adoCommand, strQuery
    Dim adoRecordset, objDC
    Dim strDNSDomain, objShell, lngBiasKey, lngBias, k, arrstrDCs()
    Dim strDN, dtmDate, objDate, objList
    Dim strBase, strFilter, strAttributes, lngHigh, lngLow
    Dim objList2, objExcel, objSheet, intRow, lngFlag, strExcelFile
    
    Const ADS_UF_ACCOUNTDISABLE = &H02
    Const ADS_UF_DONT_EXPIRE_PASSWD = &H10000
    
    ' Specify spreadsheet file to be created.
    strExcelFile = "c:\Scripts\users.xls"
    
    ' Use a dictionary object to track latest lastLogon for each user.
    Set objList = CreateObject("Scripting.Dictionary")
    objList.CompareMode = vbTextCompare
    
    ' User a dictionary object to track the DC the user last
    ' authenticated to.
    Set objList2 = CreateObject("Scripting.Dictionary")
    objList2.CompareMode = vbTextCompare
    
    ' Obtain local Time Zone bias from machine registry.
    ' This bias changes with Daylight Savings Time.
    Set objShell = CreateObject("Wscript.Shell")
    lngBiasKey = objShell.RegRead("HKLM\System\CurrentControlSet\Control\" _
        & "TimeZoneInformation\ActiveTimeBias")
    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)
        Next
    End If
    
    ' Determine configuration context and DNS domain from RootDSE object.
    Set objRootDSE = GetObject("LDAP://RootDSE")
    strConfig = objRootDSE.Get("configurationNamingContext")
    strDNSDomain = objRootDSE.Get("defaultNamingContext")
    
    ' Use ADO to search Active Directory for ObjectClass nTDSDSA.
    ' This will identify all Domain Controllers.
    Set adoCommand = CreateObject("ADODB.Command")
    Set adoConnection = CreateObject("ADODB.Connection")
    adoConnection.Provider = "ADsDSOObject"
    adoConnection.Open "Active Directory Provider"
    adoCommand.ActiveConnection = adoConnection
    
    strBase = "<LDAP://" & strConfig & ">"
    strFilter = "(objectClass=nTDSDSA)"
    strAttributes = "AdsPath"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
    
    adoCommand.CommandText = strQuery
    adoCommand.Properties("Page Size") = 100
    adoCommand.Properties("Timeout") = 60
    adoCommand.Properties("Cache Results") = False
    
    Set adoRecordset = adoCommand.Execute
    
    ' Enumerate parent objects of class nTDSDSA. Save Domain Controller
    ' AdsPaths in dynamic array arrstrDCs.
    k = 0
    Do Until adoRecordset.EOF
        Set objDC = _
            GetObject(GetObject(adoRecordset.Fields("AdsPath").Value).Parent)
        ReDim Preserve arrstrDCs(k)
        arrstrDCs(k) = objDC.DNSHostName
        k = k + 1
        adoRecordset.MoveNext
    Loop
    adoRecordset.Close
    
    ' Retrieve lastLogon attribute for each user on each Domain Controller.
    For k = 0 To Ubound(arrstrDCs)
        strBase = "<LDAP://" & arrstrDCs(k) & "/" & strDNSDomain & ">"
        strFilter = "(&(objectCategory=person)(objectClass=user))"
        strAttributes = "distinguishedName,lastLogon"
        strQuery = strBase & ";" & strFilter & ";" & strAttributes _
            & ";subtree"
        adoCommand.CommandText = strQuery
        On Error Resume Next
        Set adoRecordset = adoCommand.Execute
        If (Err.Number <> 0) Then
            On Error GoTo 0
            Wscript.Echo "Domain Controller not available: " & arrstrDCs(k)
        Else
            On Error GoTo 0
            Do Until adoRecordset.EOF
                strDN = adoRecordset.Fields("distinguishedName").Value
                On Error Resume Next
                Set objDate = adoRecordset.Fields("lastLogon").Value
                If (Err.Number <> 0) Then
                    On Error GoTo 0
                    dtmDate = #1/1/1601#
                Else
                    On Error GoTo 0
                    lngHigh = objDate.HighPart
                    lngLow = objDate.LowPart
                    If (lngLow < 0) Then
                        lngHigh = lngHigh + 1
                    End If
                    If (lngHigh = 0) And (lngLow = 0) Then
                        dtmDate = #1/1/1601#
                    Else
                        dtmDate = #1/1/1601# + (((lngHigh * (2 ^ 32)) _
                            + lngLow)/600000000 - lngBias)/1440
                    End If
                End If
                If (objList.Exists(strDN) = True) Then
                    If (dtmDate > objList(strDN)) Then
                        objList.Item(strDN) = dtmDate
                        objList2.Item(strDN) = arrstrDCs(k)
                    End If
                Else
                    objList.Add strDN, dtmDate
                    objList2.Add strDN, arrstrDCs(k)
                End If
                adoRecordset.MoveNext
            Loop
            adoRecordset.Close
        End If
    Next
    
    ' Setup spreadsheet.
    Set objExcel = CreateObject("Excel.Application")
    objExcel.Workbooks.Add
    Set objSheet = objExcel.ActiveWorkbooks.Worksheets(1)
    
    ' Write header line to first row.
    objSheet.Cells(1, 1).Value = "First Name"
    objSheet.Cells(1, 2).Value = "Last Name"
    objSheet.Cells(1, 3).Value = "User ID"
    objSheet.Cells(1, 4).Value = "Last Logon Date"
    objSheet.Cells(1, 5).Value = "Last Logon Time"
    objSheet.Cells(1, 6).Value = "Last Logon Server"
    objSheet.Cells(1, 7).Value = "Account Status"
    objSheet.Cells(1, 8).Value = "Password Does Not Expire"
    
    ' Query for remaining user attributes.
    ' No need to query a specific DC.
    strBase = "<LDAP://" & strDNSDomain & ">"
    strFilter = "(&(objectCategory=person)(objectClass=user))"
    strAttributes = "sn,givenName,sAMAccountname,userAccountControl,distinguishedName"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
    adoCommand.CommandText = strQuery
    
    Set adoRecordset = adoCommand.Execute
    intRow = 2
    Do Until adoRecordset.EOF
        strDN = adoRecordset.Fields("distinguishedName").Value
        objSheet.Cells(intRow, 1).Value = adoRecordset.Fields("givenName").Value & ""
        objSheet.Cells(intRow, 2).Value = adoRecordset.Fields("sn").Value & ""
        objSheet.Cells(intRow, 3).Value = adoRecordset.Fields("sAMAccountName").Value
        dtmDate = objList(strDN)
        If (dtmDate = #1/1/1601#) Then
            objSheet.Cells(intRow, 4).Value = "Never"
            objSheet.Cells(intRow, 5).Value = "Never"
            objSheet.Cells(intRow, 6).Value = "Never"
        Else
            objSheet.Cells(intRow, 4).Value = FormatDateTime(dtmDate, vbShortDate)
            objSheet.Cells(intRow, 5).Value = FormatDateTime(dtmDate, vbLongTime)
            objSheet.Cells(intRow, 6).Value = objList2(strDN)
        End If
        lngFlag = adoRecordset.Fields("userAccountControl").Value
        If (lngFlag And ADS_UF_ACCOUNTDISABLE) <> 0 Then
            objSheet.Cells(intRow, 7).Value = "Disabled"
        Else
            objSheet.Cells(intRow, 7).Value = "Active"
        End If
        If (lngFlag And ADS_UF_DONT_EXPIRE_PASSWD) <> 0 Then
            objSheet.Cells(intRow, 8).Value = "True"
        End If
        intRow = intRow + 1
        adoRecordset.MoveNext
    Loop
    adoRecordset.Close
    
    ' Clean up.
    adoConnection.Close
    
    ' Save spreadsheet, close the workbook, and quit Excel.
    objExcel.ActiveWorkbook.SaveAs strExcelFile
    objExcel.ActiveWorkbook.Close
    objExcel.Application.Quit
    Richard Mueller
    MVP ADSI
    • Marked as answer by Johnny Joe Monday, November 23, 2009 9:45 PM
    Saturday, November 21, 2009 2:06 AM

All replies

  • I have modified this script again and added another field to check for accounts with passwords that don't expire.  I have still not came up with a good way to get the last logon server name though.

    -Johnny


    Option Explicit
    Dim objRootDSE, strConfig, objConnection, objCommand, strQuery
    Dim objRecordSet, objDC
    Dim strDNSDomain, objShell, lngBiasKey, lngBias, k, arrstrDCs()
    Dim strDN, dtmDate, objDate, lngDate, objList, strUser
    Dim strBase, strFilter, strAttributes, lngHigh, lngLow
    Dim intCurrentValue
    Const ADS_UF_DONT_EXPIRE_PASSWD = &h10000

     

     


    ' Create dictionary to keep track of
    ' users and login times
    Set objList = CreateObject("Scripting.Dictionary")
    objList.CompareMode = vbTextCompare
    ' rmueller's script
    ' Obtain local Time Zone bias from machine registry.
    Set objShell = CreateObject("Wscript.Shell")
    lngBiasKey = objShell.RegRead("HKLM\System\CurrentControlSet\Control\" _
    & "TimeZoneInformation\ActiveTimeBias")
    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)
    Next
    End If
    ' Determine configuration context and DNS domain from RootDSE object.
    Set objRootDSE = GetObject("LDAP://RootDSE")
    strConfig = objRootDSE.Get("configurationNamingContext")
    strDNSDomain = objRootDSE.Get("defaultNamingContext")
    ' Use ADO to search Active Directory for ObjectClass nTDSDSA.
    ' This will identify all Domain Controllers.
    Set objCommand = CreateObject("ADODB.Command")
    Set objConnection = CreateObject("ADODB.Connection")
    objConnection.Provider = "ADsDSOObject"
    objConnection.Open "Active Directory Provider"
    objCommand.ActiveConnection = objConnection
    strBase = "<LDAP://" & strConfig & ">"
    strFilter = "(objectClass=nTDSDSA)"
    strAttributes = "AdsPath"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
    objCommand.CommandText = strQuery
    objCommand.Properties("Page Size") = 100
    objCommand.Properties("Timeout") = 60
    objCommand.Properties("Cache Results") = False
    Set objRecordSet = objCommand.Execute
    ' Enumerate parent objects of class nTDSDSA. Save Domain Controller
    ' AdsPaths in dynamic array arrstrDCs.
    k = 0
    Do Until objRecordSet.EOF
    Set objDC = _
       GetObject(GetObject(objRecordSet.Fields("AdsPath")).Parent)
    ReDim Preserve arrstrDCs(k)
    arrstrDCs(k) = objDC.DNSHostName
    k = k + 1
    objRecordSet.MoveNext
    Loop
    ' Retrieve lastLogon attribute for each user on each Domain Controller.
    For k = 0 To Ubound(arrstrDCs)
    strBase = "<LDAP://" & arrstrDCs(k) & "/" & strDNSDomain & ">"
    strFilter = "(&(objectCategory=person)(objectClass=user))"
     
    strAttributes = "distinguishedName,lastLogon"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes _
       & ";subtree"
    objCommand.CommandText = strQuery
    On Error Resume Next
    Set objRecordSet = objCommand.Execute
    If Err.Number <> 0 Then
       On Error GoTo 0
       Wscript.Echo "Domain Controller not available: " & arrstrDCs(k)
    Else
       On Error GoTo 0
       Do Until objRecordSet.EOF
          strDN = objRecordSet.Fields("distinguishedName")
          lngDate = objRecordSet.Fields("lastLogon")
        
         On Error Resume Next
         Set objDate = lngDate
         If Err.Number <> 0 Then
           On Error GoTo 0
           dtmDate = #1/1/1601#
         Else
           On Error GoTo 0
           'stored as a 64 bit integer that VBScript cannot handle, must
           'seperate it to work with it.
           lngHigh = objDate.HighPart
           lngLow = objDate.LowPart
           If lngLow < 0 Then
             lngHigh = lngHigh + 1
           End If
           If (lngHigh = 0) And (lngLow = 0 ) Then
             dtmDate = #1/1/1601#
           Else
             dtmDate = #1/1/1601# + (((lngHigh * (2 ^ 32)) _
               + lngLow)/600000000 - lngBias)/1440
           End If
         End If
         If objList.Exists(strDN) Then
           If dtmDate > objList(strDN) Then
             objList(strDN) = dtmDate
           End If
         Else
           objList.Add strDN, dtmDate
         End If
         objRecordSet.MoveNext
       Loop
    End If
    Next
    Dim objNetwork,objExcel,objworkbook,objworksheet
    dim intRow
    intRow = 2
    Set objNetwork = Wscript.CreateObject("Wscript.Network")
    Set objExcel = CreateObject("Excel.Application")
    objExcel.Visible = True
    Set objWorkbook = objExcel.Workbooks.Add()
    Set objWorksheet = objWorkbook.Worksheets(1)


    objExcel.Cells(1,1).value = "First Name"
    objExcel.Cells(1,2).value = "Last Name"
    objExcel.Cells(1,3).value = "User ID"
    objExcel.Cells(1,4).value = "Last Logon Date"
    objExcel.Cells(1,5).value = "Last Logon Time"
    objExcel.Cells(1,6).value = "Last Logon Server"
    objExcel.Cells(1,7).value = "Account Status"
    objExcel.Cells(1,8).value = "Pswd Doesn't Expire"

    ' Output latest lastLogon date for each user.
    Dim easydate1
    Dim easydate2
    Dim easydate3
    Dim objUser
    Dim objAccount
    Dim objUser1
    Dim objDate3

    For Each strUser In objList
    'easydate1 is the date stamp
    'easydate2 is the time stamp
    easydate1 = objList(strUser)
    easydate1 = FormatDateTime(easydate1,2)
    easydate2 = objList(strUser)
    easydate2 = FormatDateTime(easydate2,3)
    easydate3 = objList(strUser)


    'output username and last login date and last login time
    ' change 1/1/1601 to Never for Never logged in
    If easydate1 = "1/1/1601" Then
     easydate1 = "Never"
    End If 
    ' change 12:00:00 AM to Never for Never logged in
    If easydate2 = "12:00:00 AM" Then
     easydate2 = "Never"
    End If

    Set objUser = GetObject _
        ("LDAP://" & struser & "")

    objExcel.Cells(intRow,1).value = objuser.givenname
    objExcel.Cells(intRow,2).value = objuser.sn
    objExcel.Cells(intRow,3).value = objuser.sAMAccountName
    objExcel.Cells(intRow,4).value = easydate1
    objExcel.Cells(intRow,5).value = easydate2

    objUser1 = objuser.cn

    '*Problem area Start*


    'If easydate1 = "Never" Then
    'objExcel.Cells(intRow,6).value = "Never"
    'Else
    'objExcel.Cells(intRow,6).value = arrstrDCs(k)
    'Next
    'End If

    'populated disabled column
    if objuser.AccountDisabled = True Then
     objExcel.Cells(intRow,7).value = "DISABLED"
    Else
     objExcel.Cells(intRow,7).value = "ACTIVE"
    End if


    intCurrentValue = objuser.Get("userAccountControl")

    If intCurrentValue and ADS_UF_DONT_EXPIRE_PASSWD Then
         objExcel.Cells(intRow,8).value = "True"
    End if

    intRow = intRow + 1

    Next

    'sort by login Date
    Dim obrange, obrange2
    Set obrange = objExcel.Range("A:K")
    Set obrange2 = objExcel.Range("A2")
    obrange.Sort obrange2,1,,,,,,1

    Set obRange = objWorksheet.UsedRange
    obRange.EntireColumn.Autofit()

     

     

    ' Clean up.
    objConnection.Close
    Set objRootDSE = Nothing
    Set objConnection = Nothing
    Set objCommand = Nothing
    Set objRecordSet = Nothing
    Set objDC = Nothing
    Set objDate = Nothing
    Set objList = Nothing
    Set objShell = Nothing
    Set obrange = Nothing
    Set obrange2 = Nothing
    Set objExcel = Nothing
    WScript.Quit


    Johnny
    Friday, November 20, 2009 4:11 PM
  • I would recommend the following.

    1) You should consider dropping the use of the Dictionary Object....and change to an ADO Recordset, so you can store a 3rd, 4th, 5th, values as needed using ADO Recordsets easier.

    2) Add AdsPath to the list of user attibutes to return....this contains the ServerName that you want.
    strAttributes = "distinguishedName,lastLogon,Adspath"

    3) During your test for a more current logon date you need to store the matching AdsPath to the ADORecordset as well as the updated Logon Date/Time
    'Retrieve the fields here
          strDN = objRecordSet.Fields("distinguishedName")
          lngDate = objRecordSet.Fields("lastLogon") 
           strADSPath = objRecordSet.Fields("ADSPath")
    ....
    'Save the appropriate fields your Recordset/Dictionary Object here.
           If dtmDate > objList(strDN) Then 
            objNewRS.Fields("lastLogon") = dtmDate
            objNewRS.Fields("ADSPath") = strADSPath 
           End If


    Another option would be to keep the Dictionary Object and use a semicolon (or other special character) to deliminate multiple values.
           If dtmDate > objList(strDN) Then
             objList(strDN) = dtmDate & ";" & strAdsPath
           End If

    • Marked as answer by Johnny Joe Friday, November 20, 2009 7:22 PM
    Friday, November 20, 2009 6:03 PM
  • I like your answer Gunner999.  I had already gotten the script to work by creating a second scripting dictionary that stores the DN and last logon server name, but an ADORecordset would probably be alot cleaner way to write the code.  I am still really new to scripting and I am trying to teach myself, so if you know of any good books on scripting for workstation management and AD I would be greatful for any suggestions.

    Thank you very much!
    Johnny

    Here is a copy of what I ended up with I will probably go back and rewrite it using an ADORecordset.

    Option Explicit
    Dim objRootDSE, strConfig, objConnection, objCommand, strQuery
    Dim objRecordSet, objDC
    Dim strDNSDomain, objShell, lngBiasKey, lngBias, k, arrstrDCs()
    Dim strDN, dtmDate, objDate, lngDate, objList, strUser, objlist2, strDC,strllsrv
    Dim strBase, strFilter, strAttributes, lngHigh, lngLow
    Dim intCurrentValue
    Const ADS_UF_DONT_EXPIRE_PASSWD = &h10000

     

     


    ' Create dictionary to keep track of
    ' users and login times
    Set objList = CreateObject("Scripting.Dictionary")
    objList.CompareMode = vbTextCompare

    Set objList2 = CreateObject("Scripting.Dictionary")
    objList.CompareMode = vbTextCompare

    ' rmueller's script
    ' Obtain local Time Zone bias from machine registry.
    Set objShell = CreateObject("Wscript.Shell")
    lngBiasKey = objShell.RegRead("HKLM\System\CurrentControlSet\Control\" _
    & "TimeZoneInformation\ActiveTimeBias")
    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)
    Next
    End If
    ' Determine configuration context and DNS domain from RootDSE object.
    Set objRootDSE = GetObject("LDAP://RootDSE")
    strConfig = objRootDSE.Get("configurationNamingContext")
    strDNSDomain = objRootDSE.Get("defaultNamingContext")
    ' Use ADO to search Active Directory for ObjectClass nTDSDSA.
    ' This will identify all Domain Controllers.
    Set objCommand = CreateObject("ADODB.Command")
    Set objConnection = CreateObject("ADODB.Connection")
    objConnection.Provider = "ADsDSOObject"
    objConnection.Open "Active Directory Provider"
    objCommand.ActiveConnection = objConnection
    strBase = "<LDAP://" & strConfig & ">"
    strFilter = "(objectClass=nTDSDSA)"
    strAttributes = "AdsPath"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
    objCommand.CommandText = strQuery
    objCommand.Properties("Page Size") = 100
    objCommand.Properties("Timeout") = 60
    objCommand.Properties("Cache Results") = False
    Set objRecordSet = objCommand.Execute
    ' Enumerate parent objects of class nTDSDSA. Save Domain Controller
    ' AdsPaths in dynamic array arrstrDCs.
    k = 0
    Do Until objRecordSet.EOF
    Set objDC = _
       GetObject(GetObject(objRecordSet.Fields("AdsPath")).Parent)
    ReDim Preserve arrstrDCs(k)
    arrstrDCs(k) = objDC.DNSHostName
    k = k + 1
    objRecordSet.MoveNext
    Loop
    ' Retrieve lastLogon attribute for each user on each Domain Controller.
    For k = 0 To Ubound(arrstrDCs)
    strBase = "<LDAP://" & arrstrDCs(k) & "/" & strDNSDomain & ">"
    strFilter = "(&(objectCategory=person)(objectClass=user))"
     
    strAttributes = "distinguishedName,lastLogon"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes _
       & ";subtree"
    objCommand.CommandText = strQuery
    On Error Resume Next
    Set objRecordSet = objCommand.Execute
    If Err.Number <> 0 Then
       On Error GoTo 0
       Wscript.Echo "Domain Controller not available: " & arrstrDCs(k)
    Else
       On Error GoTo 0
       Do Until objRecordSet.EOF
          strDN = objRecordSet.Fields("distinguishedName")
          lngDate = objRecordSet.Fields("lastLogon")
          strDC = arrstrDCs(k)
         On Error Resume Next
         Set objDate = lngDate
         If Err.Number <> 0 Then
           On Error GoTo 0
           dtmDate = #1/1/1601#
         Else
           On Error GoTo 0
           'stored as a 64 bit integer that VBScript cannot handle, must
           'seperate it to work with it.
           lngHigh = objDate.HighPart
           lngLow = objDate.LowPart
           If lngLow < 0 Then
             lngHigh = lngHigh + 1
           End If
           If (lngHigh = 0) And (lngLow = 0 ) Then
             dtmDate = #1/1/1601#
           Else
             dtmDate = #1/1/1601# + (((lngHigh * (2 ^ 32)) _
               + lngLow)/600000000 - lngBias)/1440
           End If
         End If
         If objList.Exists(strDN) Then
           If dtmDate > objList(strDN) Then
             objList(strDN) = dtmDate
             objList2(strDN) = arrstrDCs(k)
           End If
         Else
           objList.Add strDN, dtmDate
           objList2.Add strDN, strDC
         End If
         objRecordSet.MoveNext
       Loop
    End If
    Next
    Dim objNetwork,objExcel,objworkbook,objworksheet
    dim intRow
    intRow = 2
    Set objNetwork = Wscript.CreateObject("Wscript.Network")
    Set objExcel = CreateObject("Excel.Application")
    objExcel.Visible = True
    Set objWorkbook = objExcel.Workbooks.Add()
    Set objWorksheet = objWorkbook.Worksheets(1)


    objExcel.Cells(1,1).value = "First Name"
    objExcel.Cells(1,2).value = "Last Name"
    objExcel.Cells(1,3).value = "User ID"
    objExcel.Cells(1,4).value = "Last Logon Date"
    objExcel.Cells(1,5).value = "Last Logon Time"
    objExcel.Cells(1,6).value = "Last Logon Server"
    objExcel.Cells(1,7).value = "Account Status"
    objExcel.Cells(1,8).value = "Pswd Doesn't Expire"

    ' Output latest lastLogon date for each user.
    Dim easydate1
    Dim easydate2
    Dim easydate3
    Dim objUser
    Dim objAccount
    Dim objUser1
    Dim objDate3

    For Each strUser In objList
    'easydate1 is the date stamp
    'easydate2 is the time stamp
    easydate1 = objList(strUser)
    easydate1 = FormatDateTime(easydate1,2)
    easydate2 = objList(strUser)
    easydate2 = FormatDateTime(easydate2,3)
    easydate3 = objList(strUser)
    strllsrv = objlist2(strUser)

    'output username and last login date and last login time
    ' change 1/1/1601 to Never for Never logged in
    If easydate1 = "1/1/1601" Then
     easydate1 = "Never"
    End If 
    ' change 12:00:00 AM to Never for Never logged in
    If easydate2 = "12:00:00 AM" Then
     easydate2 = "Never"
    End If

    Set objUser = GetObject _
        ("LDAP://" & struser & "")

    objExcel.Cells(intRow,1).value = objuser.givenname
    objExcel.Cells(intRow,2).value = objuser.sn
    objExcel.Cells(intRow,3).value = objuser.sAMAccountName
    objExcel.Cells(intRow,4).value = easydate1
    objExcel.Cells(intRow,5).value = easydate2

    objUser1 = objuser.cn

    '*Problem area Start*


    If easydate1 = "Never" Then
    objExcel.Cells(intRow,6).value = "Never"
    Else
    objExcel.Cells(intRow,6).value = strllsrv
    End If

    'populated disabled column
    if objuser.AccountDisabled = True Then
     objExcel.Cells(intRow,7).value = "DISABLED"
    Else
     objExcel.Cells(intRow,7).value = "ACTIVE"
    End if


    intCurrentValue = objuser.Get("userAccountControl")

    If intCurrentValue and ADS_UF_DONT_EXPIRE_PASSWD Then
         objExcel.Cells(intRow,8).value = "True"
    End if

    intRow = intRow + 1

    Next

    'sort by login Date
    Dim obrange, obrange2
    Set obrange = objExcel.Range("A:K")
    Set obrange2 = objExcel.Range("A2")
    obrange.Sort obrange2,1,,,,,,1

    Set obRange = objWorksheet.UsedRange
    obRange.EntireColumn.Autofit()

     

     

    ' Clean up.
    objConnection.Close
    Set objRootDSE = Nothing
    Set objConnection = Nothing
    Set objCommand = Nothing
    Set objRecordSet = Nothing
    Set objDC = Nothing
    Set objDate = Nothing
    Set objList = Nothing
    Set objShell = Nothing
    Set obrange = Nothing
    Set obrange2 = Nothing
    Set objExcel = Nothing
    WScript.Quit


    Johnny
    Friday, November 20, 2009 7:22 PM
  • I like your solution with two dictionary objects. Some minor points. It should be:

    Set objList2 = CreateObject("Scripting.Dictionary")
    objList2.CompareMode = vbTextCompare

    You had objList.CompareMode at this point, which repeats what was above. Next, these lines:

    For Each strUser In objList
        'easydate1 is the date stamp
        'easydate2 is the time stamp
        easydate1 = objList(strUser)
        easydate1 = FormatDateTime(easydate1,2)
        easydate2 = objList(strUser)
        easydate2 = FormatDateTime(easydate2,3)
        easydate3 = objList(strUser)
        strllsrv = objlist2(strUser)

    can be replaced with these:

    For Each strUser In objList
        'easydate1 is the date stamp
        'easydate2 is the time stamp
        easydate3 = objList(strUser)
        easydate1 = FormatDateTime(easydate3,2)
        easydate2 = FormatDateTime(easydate3,3)
        strllsrv = objlist2(strUser)

    Pretty minor. Your script will be slower because you bind to all user objects at the end. Once you have queried for all lastLogon dates and the corresponding DC's (your two dictionary objects), you could use a singe ADO query for all users and retrieve distinguishedName, sAMAccountName, sn, givenName, userAccountControl, etc. in bulk. This would be much faster. Then in the loop where you enumerate this final recordset you could retrieve the distinguishedName and use this to retrieve the values from your two dictionary objects and populate cells in the spreadsheet. The only value you cannot retrieve directly is AccountDisabled, because that is a method (of the user object) not an attribute. However, you can deteremine this from userAccountControl and the proper bit mask, which is:

    Const ADS_UF_ACCOUNTDISABLE = &H2

    You would test this bit the same way you test the other. I hope this helps.

    Richard Mueller


    MVP ADSI
    Friday, November 20, 2009 11:59 PM
  • Here is how I would code the script. After retrieving the last logon dates (and the DC's) as before, this script then uses ADO to query AD for the remaining attributes of interest. It writes the values to a spreadsheet.

    I notice that your copy of my LastLogon.vbs program appears old (I changed some of the variable names some years ago). In particular, you don't ever close the recordsets, which seems risky to me. Other than that I don't believe my code below is much different, but I started from my latest version to be sure.

    Option Explicit
    
    Dim objRootDSE, strConfig, adoConnection, adoCommand, strQuery
    Dim adoRecordset, objDC
    Dim strDNSDomain, objShell, lngBiasKey, lngBias, k, arrstrDCs()
    Dim strDN, dtmDate, objDate, objList
    Dim strBase, strFilter, strAttributes, lngHigh, lngLow
    Dim objList2, objExcel, objSheet, intRow, lngFlag, strExcelFile
    
    Const ADS_UF_ACCOUNTDISABLE = &H02
    Const ADS_UF_DONT_EXPIRE_PASSWD = &H10000
    
    ' Specify spreadsheet file to be created.
    strExcelFile = "c:\Scripts\users.xls"
    
    ' Use a dictionary object to track latest lastLogon for each user.
    Set objList = CreateObject("Scripting.Dictionary")
    objList.CompareMode = vbTextCompare
    
    ' User a dictionary object to track the DC the user last
    ' authenticated to.
    Set objList2 = CreateObject("Scripting.Dictionary")
    objList2.CompareMode = vbTextCompare
    
    ' Obtain local Time Zone bias from machine registry.
    ' This bias changes with Daylight Savings Time.
    Set objShell = CreateObject("Wscript.Shell")
    lngBiasKey = objShell.RegRead("HKLM\System\CurrentControlSet\Control\" _
        & "TimeZoneInformation\ActiveTimeBias")
    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)
        Next
    End If
    
    ' Determine configuration context and DNS domain from RootDSE object.
    Set objRootDSE = GetObject("LDAP://RootDSE")
    strConfig = objRootDSE.Get("configurationNamingContext")
    strDNSDomain = objRootDSE.Get("defaultNamingContext")
    
    ' Use ADO to search Active Directory for ObjectClass nTDSDSA.
    ' This will identify all Domain Controllers.
    Set adoCommand = CreateObject("ADODB.Command")
    Set adoConnection = CreateObject("ADODB.Connection")
    adoConnection.Provider = "ADsDSOObject"
    adoConnection.Open "Active Directory Provider"
    adoCommand.ActiveConnection = adoConnection
    
    strBase = "<LDAP://" & strConfig & ">"
    strFilter = "(objectClass=nTDSDSA)"
    strAttributes = "AdsPath"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
    
    adoCommand.CommandText = strQuery
    adoCommand.Properties("Page Size") = 100
    adoCommand.Properties("Timeout") = 60
    adoCommand.Properties("Cache Results") = False
    
    Set adoRecordset = adoCommand.Execute
    
    ' Enumerate parent objects of class nTDSDSA. Save Domain Controller
    ' AdsPaths in dynamic array arrstrDCs.
    k = 0
    Do Until adoRecordset.EOF
        Set objDC = _
            GetObject(GetObject(adoRecordset.Fields("AdsPath").Value).Parent)
        ReDim Preserve arrstrDCs(k)
        arrstrDCs(k) = objDC.DNSHostName
        k = k + 1
        adoRecordset.MoveNext
    Loop
    adoRecordset.Close
    
    ' Retrieve lastLogon attribute for each user on each Domain Controller.
    For k = 0 To Ubound(arrstrDCs)
        strBase = "<LDAP://" & arrstrDCs(k) & "/" & strDNSDomain & ">"
        strFilter = "(&(objectCategory=person)(objectClass=user))"
        strAttributes = "distinguishedName,lastLogon"
        strQuery = strBase & ";" & strFilter & ";" & strAttributes _
            & ";subtree"
        adoCommand.CommandText = strQuery
        On Error Resume Next
        Set adoRecordset = adoCommand.Execute
        If (Err.Number <> 0) Then
            On Error GoTo 0
            Wscript.Echo "Domain Controller not available: " & arrstrDCs(k)
        Else
            On Error GoTo 0
            Do Until adoRecordset.EOF
                strDN = adoRecordset.Fields("distinguishedName").Value
                On Error Resume Next
                Set objDate = adoRecordset.Fields("lastLogon").Value
                If (Err.Number <> 0) Then
                    On Error GoTo 0
                    dtmDate = #1/1/1601#
                Else
                    On Error GoTo 0
                    lngHigh = objDate.HighPart
                    lngLow = objDate.LowPart
                    If (lngLow < 0) Then
                        lngHigh = lngHigh + 1
                    End If
                    If (lngHigh = 0) And (lngLow = 0) Then
                        dtmDate = #1/1/1601#
                    Else
                        dtmDate = #1/1/1601# + (((lngHigh * (2 ^ 32)) _
                            + lngLow)/600000000 - lngBias)/1440
                    End If
                End If
                If (objList.Exists(strDN) = True) Then
                    If (dtmDate > objList(strDN)) Then
                        objList.Item(strDN) = dtmDate
                        objList2.Item(strDN) = arrstrDCs(k)
                    End If
                Else
                    objList.Add strDN, dtmDate
                    objList2.Add strDN, arrstrDCs(k)
                End If
                adoRecordset.MoveNext
            Loop
            adoRecordset.Close
        End If
    Next
    
    ' Setup spreadsheet.
    Set objExcel = CreateObject("Excel.Application")
    objExcel.Workbooks.Add
    Set objSheet = objExcel.ActiveWorkbooks.Worksheets(1)
    
    ' Write header line to first row.
    objSheet.Cells(1, 1).Value = "First Name"
    objSheet.Cells(1, 2).Value = "Last Name"
    objSheet.Cells(1, 3).Value = "User ID"
    objSheet.Cells(1, 4).Value = "Last Logon Date"
    objSheet.Cells(1, 5).Value = "Last Logon Time"
    objSheet.Cells(1, 6).Value = "Last Logon Server"
    objSheet.Cells(1, 7).Value = "Account Status"
    objSheet.Cells(1, 8).Value = "Password Does Not Expire"
    
    ' Query for remaining user attributes.
    ' No need to query a specific DC.
    strBase = "<LDAP://" & strDNSDomain & ">"
    strFilter = "(&(objectCategory=person)(objectClass=user))"
    strAttributes = "sn,givenName,sAMAccountname,userAccountControl,distinguishedName"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
    adoCommand.CommandText = strQuery
    
    Set adoRecordset = adoCommand.Execute
    intRow = 2
    Do Until adoRecordset.EOF
        strDN = adoRecordset.Fields("distinguishedName").Value
        objSheet.Cells(intRow, 1).Value = adoRecordset.Fields("givenName").Value & ""
        objSheet.Cells(intRow, 2).Value = adoRecordset.Fields("sn").Value & ""
        objSheet.Cells(intRow, 3).Value = adoRecordset.Fields("sAMAccountName").Value
        dtmDate = objList(strDN)
        If (dtmDate = #1/1/1601#) Then
            objSheet.Cells(intRow, 4).Value = "Never"
            objSheet.Cells(intRow, 5).Value = "Never"
            objSheet.Cells(intRow, 6).Value = "Never"
        Else
            objSheet.Cells(intRow, 4).Value = FormatDateTime(dtmDate, vbShortDate)
            objSheet.Cells(intRow, 5).Value = FormatDateTime(dtmDate, vbLongTime)
            objSheet.Cells(intRow, 6).Value = objList2(strDN)
        End If
        lngFlag = adoRecordset.Fields("userAccountControl").Value
        If (lngFlag And ADS_UF_ACCOUNTDISABLE) <> 0 Then
            objSheet.Cells(intRow, 7).Value = "Disabled"
        Else
            objSheet.Cells(intRow, 7).Value = "Active"
        End If
        If (lngFlag And ADS_UF_DONT_EXPIRE_PASSWD) <> 0 Then
            objSheet.Cells(intRow, 8).Value = "True"
        End If
        intRow = intRow + 1
        adoRecordset.MoveNext
    Loop
    adoRecordset.Close
    
    ' Clean up.
    adoConnection.Close
    
    ' Save spreadsheet, close the workbook, and quit Excel.
    objExcel.ActiveWorkbook.SaveAs strExcelFile
    objExcel.ActiveWorkbook.Close
    objExcel.Application.Quit
    Richard Mueller
    MVP ADSI
    • Marked as answer by Johnny Joe Monday, November 23, 2009 9:45 PM
    Saturday, November 21, 2009 2:06 AM
  • Thank you very much for going to so much trouble to rewrite the script.  I am new to scripting and trying to teach myself so everything is twice as hard.  Would you by chance have any books that you would recommend to further my education?  I don't think I can convince my company to send me to any classes over scripting anytime soon, so I am on my own for at least a couple of years.  Appreciate your time and I hope I didn't make you look bad with my messy code.

    Thanks,
    Johnny Cliburn
    Johnny
    Monday, November 23, 2009 9:50 PM
  • No problem. I thought our good looked good.

    The best book I know of is still "Microsoft Windows 2000 Scripting Guide". I have the hardcopy text (very well worn), but it is also available online at:

    http://www.microsoft.com/technet/scriptcenter/guide/sagsas_overview.mspx?mfr=true

    Richard Mueller


    MVP ADSI
    Monday, November 23, 2009 10:36 PM
  • Just incase anyone else new to scripting runs into this, there is a slight typo in line 131 of Richards script. 

    It is currently
    Set objSheet = objExcel.ActiveWorkbooks.Worksheets(1)
    It should read
    Set objSheet = objExcel.ActiveWorkbook.Worksheets(1)

    Just an extra s.  Other than that it works great.  Just make sure you have the folder path created on your machine for the strExcelFile location.  The script will create the file for you, but it will not create new folders for you.  Also if you want to sort by a certain column in the sheet or have it auto fit the column width you will need some of the following code.

    'This sorts by first name and auto fits the column width
    Dim obrange, obrange2
    Set obrange = objExcel.Range("A:K")
    Set obrange2 = objExcel.Range("A2")
    obrange.Sort obrange2,1,,,,,,1

    Set obRange = objsheet.UsedRange
    obRange.EntireColumn.Autofit()

    If you want to change the sort by field, just change obrange2 = objExcel.Range("(Column?)(Row?)")
    Obrange specifies the length of the row so all the results that go with a certain user get sorted as a row and not individually otherwise your results would get jumbled between users.  Above obrange is set to columns A through K.  It could be set to a smaller range you just have to remember to change it if you add another column of data to your spreedsheet.

    Thanks,
    Johnny


    Johnny
    Tuesday, November 24, 2009 1:58 PM
  • Thanks for pointing out the typo. I manually copied the code when I should have pasted.

    Richard
    MVP ADSI
    Tuesday, November 24, 2009 4:04 PM