Home  |   French  |   About  |   Search  | mvps.org  

What's New
Table Of Contents
Credits
Netiquette
10 Commandments 
Bugs
Tables
Queries
Forms
Reports
Modules
APIs
Strings
Date/Time
General
Downloads
Resources
Search
Feedback
mvps.org

In Memoriam

Terms of Use


VB Petition

API: Enumerating user accounts in a NT Domain

Author(s)
Dev Ashish

To get a list of all users in a Windows NT/2000 domain, we can use the NetUserEnum API function.  

Although the API provides several levels of information associated with an user account, the user name is provided in the USER_INFO_0 structure which is the lowest set of information returned.  The USER_INFO_2 returns a great deal more of information pertaining to a specific user account.

A sample function for both level 0 and level 2 API calls is included below. Please note that these are NT/2000 only API function.

' ******** Code Start ********
'This code was originally written by Dev Ashish.
'It is not to be altered or distributed,
'except as part of an application.
'You are free to use it in any application,
'provided the copyright notice is left unchanged.
'
'Code Courtesy of
'Dev Ashish
'
' Level 0 - Return user account names.
' The bufptr parameter (NetUserEnum) points to
' an array of USER_INFO_0 structures.
Private Type USER_INFO_0
  usri0_name As Long
End Type

' structure contains information about a user account, including
' the account name, password data, privilege level, the path
' to the user's home directory, and other user-related
' network statistics. Level 2 call
Private Type USER_INFO_2
    usri2_name As Long
    usri2_password   As Long  ' Null, only settable
    usri2_password_age  As Long
    usri2_priv  As Long
    usri2_home_dir  As Long
    usri2_comment  As Long
    usri2_flags  As Long
    usri2_script_path  As Long
    usri2_auth_flags  As Long
    usri2_full_name As Long
    usri2_usr_comment  As Long
    usri2_parms  As Long
    usri2_workstations  As Long
    usri2_last_logon  As Long
    usri2_last_logoff  As Long
    usri2_acct_expires  As Long
    usri2_max_storage  As Long
    usri2_units_per_week  As Long
    usri2_logon_hours  As Long
    usri2_bad_pw_count  As Long
    usri2_num_logons  As Long
    usri2_logon_server  As Long
    usri2_country_code  As Long
    usri2_code_page  As Long
End Type

Private Type SYSTEMTIME
   wYear As Integer
   wMonth As Integer
   wDayOfWeek As Integer
   wDay As Integer
   wHour As Integer
   wMinute As Integer
   wSecond As Integer
   wMilliseconds As Integer
End Type

Private Type TIME_ZONE_INFORMATION
   Bias As Long
   StandardName(0 To 63) As Byte
   StandardDate As SYSTEMTIME
   StandardBias As Long
   DaylightName(0 To 63) As Byte
   DaylightDate As SYSTEMTIME
   DaylightBias As Long
End Type


' function provides information about all user
' accounts on a server
Private Declare Function apiNetUserEnum _
    Lib "netapi32.DLL" Alias "NetUserEnum" _
    (ByVal servername As Long, _
    ByVal level As Long, _
    ByVal filter As Long, _
    bufptr As Long, _
    ByVal prefmaxlen As Long, _
    entriesread As Long, _
    totalentries As Long, _
    resume_handle As Long) _
    As Long


' function frees the memory that the NetApiBufferAllocate
' function allocates.
Private Declare Function apiNetAPIBufferFree _
    Lib "netapi32.DLL" Alias "NetApiBufferFree" _
    (ByVal buffer As Long) _
    As Long

' Retrieves the length of the specified wide string.
Private Declare Function apilstrlenW _
    Lib "kernel32" Alias "lstrlenW" _
    (ByVal lpString As Long) _
    As Long

' moves memory either forward or backward, aligned or unaligned,
' in 4-byte blocks, followed by any remaining bytes
Private Declare Sub sapiCopyMem _
    Lib "kernel32" Alias "RtlMoveMemory" _
    (Destination As Any, _
    Source As Any, _
    ByVal Length As Long)

'function retrieves the current time-zone parameters. These
'parameters control the translations between Coordinated
'Universal Time (UTC) and local time.
Private Declare Function apiGetTZI Lib "kernel32" _
    Alias "GetTimeZoneInformation" _
    (lpTimeZoneInformation As TIME_ZONE_INFORMATION) _
    As Long

' Enumerates local user account data on a domain controller.
Private Const FILTER_TEMP_DUPLICATE_ACCOUNT = &H1&
' Enumerates global user account data on a computer.
Private Const FILTER_NORMAL_ACCOUNT = &H2&
' Enumerates domain trust account data on a domain controller.
Private Const FILTER_INTERDOMAIN_TRUST_ACCOUNT = &H8&
'  Enumerates workstation or member server account data on a
' domain controller.
Private Const FILTER_WORKSTATION_TRUST_ACCOUNT = &H10&
'  Enumerates domain controller account data on a domain controller.
Private Const FILTER_SERVER_TRUST_ACCOUNT = &H20&
Private Const MAX_PREFERRED_LENGTH = -1&
Private Const NERR_SUCCESS = 0
' More entries are available. Specify a large enough
' buffer to receive all entries.
Private Const ERROR_MORE_DATA = 234&
' Privileges 
Private Const USER_PRIV_GUEST = 0&
Private Const USER_PRIV_USER = 1&
Private Const USER_PRIV_ADMIN = 2&

'GetTimeZoneInformation Return values
Private Const TIME_ZONE_ID_INVALID = &HFFFFFFFF
Private Const TIME_ZONE_ID_UNKNOWN = 0
Private Const TIME_ZONE_ID_STANDARD = 1
Private Const TIME_ZONE_ID_DAYLIGHT = 2

' User Flags
Private Const UF_SCRIPT = &H1
Private Const UF_ACCOUNTDISABLE = &H2
Private Const UF_HOMEDIR_REQUIRED = &H8
Private Const UF_LOCKOUT = &H10
Private Const UF_PASSWD_NOTREQD = &H20
Private Const UF_PASSWD_CANT_CHANGE = &H40
Private Const UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = &H80
Private Const UF_DONT_EXPIRE_PASSWD = &H10000
Private Const UF_MNS_LOGON_ACCOUNT = &H20000
Private Const UF_SMARTCARD_REQUIRED = &H40000
Private Const UF_TRUSTED_FOR_DELEGATION = &H80000
Private Const UF_NOT_DELEGATED = &H100000
Private Const UF_USE_DES_KEY_ONLY = &H200000
Private Const UF_DONT_REQUIRE_PREAUTH = &H400000

Private Const UF_TEMP_DUPLICATE_ACCOUNT = &H100
Private Const UF_NORMAL_ACCOUNT = &H200
Private Const UF_INTERDOMAIN_TRUST_ACCOUNT = &H800
Private Const UF_WORKSTATION_TRUST_ACCOUNT = &H1000
Private Const UF_SERVER_TRUST_ACCOUNT = &H2000

Function fEnumDomainUsers_Level2( _
                    Optional strServerName As String) _
                    As Boolean
' -------------------------------
'         NT / WIN2000 ONLY
' -------------------------------
' Enumerates all Global accounts on a NT
' server, LEVEL_2
' if len(strServerName)=0,
' assume local machine
'
On Error GoTo ErrHandler
Dim abytServerName() As Byte
Dim pBuf As Long
Dim pTmpBuf As USER_INFO_2
Dim dwLevel As Long
Dim dwPrefMaxLen As Long
Dim dwEntriesRead As Long
Dim dwTotalEntries As Long
Dim dwResumeHandle As Long
Dim i As Long
Dim dwTotalCount As Long
Dim nStatus As Long

    ' assume MAX_PREFERRED_LENGTH
    dwPrefMaxLen = MAX_PREFERRED_LENGTH
    abytServerName = strServerName & vbNullChar
    dwLevel = 2 ' Level 2 call
    Do
        'only global users
        nStatus = apiNetUserEnum(VarPtr(abytServerName(0)), _
                            dwLevel, _
                            FILTER_NORMAL_ACCOUNT, _
                            pBuf, _
                            dwPrefMaxLen, _
                            dwEntriesRead, _
                            dwTotalEntries, _
                            dwResumeHandle)
        ' If the call succeeds,
        If ((nStatus = NERR_SUCCESS) Or (nStatus = ERROR_MORE_DATA)) Then
            ' Loop through the entries.
            For i = 0 To dwEntriesRead - 1
                Call sapiCopyMem( _
                                            pTmpBuf, _
                                            ByVal (pBuf + (i * 96)), _
                                            Len(pTmpBuf))   ' 96=len(USER_INFO_2)
                With pTmpBuf
                    ' Print the name of the user account.
                    Debug.Print "Name :  ", fStrFromPtrW(.usri2_name)
                
                    ' password is Null when reading the info, obviously!
                    
                    ' flags
                    Debug.Print "Account Info: "
                    Call sPrintAccountInfo(.usri2_flags)
                    
                    ' password_age
                    Debug.Print "Number of seconds since the " _
                        & "password was last changed: ", .usri2_password_age
                        
                    ' Privilege
                    Debug.Print "Level of Privilege:  ",
                    Select Case .usri2_priv
                        Case USER_PRIV_GUEST
                            Debug.Print "Guest"
                        Case USER_PRIV_USER
                            Debug.Print "User"
                        Case USER_PRIV_ADMIN
                            Debug.Print "Admin"
                    End Select
                    
                    ' Home Directory
                    Debug.Print "Home Directory:  ", fStrFromPtrW(.usri2_home_dir)
                    
                    ' Comment
                    Debug.Print "Comment:  ", fStrFromPtrW(.usri2_comment)
                    
                    ' Script Path
                    Debug.Print "Script Path:  ", fStrFromPtrW(.usri2_script_path)
                   
                   	' user's operator privileges
                    
                    ' Full Name
                    Debug.Print "Full Name:  ", fStrFromPtrW(.usri2_full_name)
                    
                    ' User Comment
                    Debug.Print "User Comment: ", fStrFromPtrW(.usri2_usr_comment)
                    
                    ' Parms for Apps
                    Debug.Print "Params for Apps: ", fStrFromPtrW(.usri2_parms)
                    
                    ' Workstations from which user can log on (max 8)
                    Debug.Print "Workstations from which user can log on: ", _
                                                    fStrFromPtrW(.usri2_workstations)

                    ' when the last logon occurred
                    Debug.Print "Last Logon occurred at: ", fGetTime(.usri2_last_logon)
                    
                    ' when the last logoff occurred
                    Debug.Print "Last Logoff occurred at: ", fGetTime(.usri2_last_logoff)
                    
                    ' when the account expires
                    Debug.Print "Account Expires on: ", fGetTime(.usri2_acct_expires)
                    
                    ' Max disk space user can use
                    Debug.Print "Max Disk Space allotted: ", .usri2_max_storage

                    ' Units per week
                    Debug.Print "Units per week: ", .usri2_units_per_week
    
                    ' Logon Hours 21-byte (168 bits) bit string
                    Debug.Print "Logon Hours: ", .usri2_logon_hours

                    ' Bad password count
                    Debug.Print "Bad password count: ", .usri2_bad_pw_count

                    ' Number of successful logons
                    Debug.Print "Number of successful logons: ", .usri2_num_logons

                    ' Logon Server
                    Debug.Print "Logon Server: ", fStrFromPtrW(.usri2_logon_server)
                
                    ' Country Code
                    Debug.Print "Country Code: ", .usri2_country_code

                    ' Code Page
                    Debug.Print "Code Page: ", .usri2_code_page
                    
                End With
                ' Keep a count of accounts enumerated
                dwTotalCount = dwTotalCount + 1
                Debug.Print "--------------------------------"
            Next
        End If
        ' free the associated memory
        Call apiNetAPIBufferFree(pBuf)
        pBuf = 0
    Loop While (nStatus = ERROR_MORE_DATA)
    If Not (pBuf = 0) Then Call apiNetAPIBufferFree(pBuf)

    fEnumDomainUsers_Level2 = True
ExitHere:
    Exit Function
ErrHandler:
    fEnumDomainUsers_Level2 = False
    Resume ExitHere
End Function

Function fEnumDomainUsers( _
        Optional strServerName As String) _
        As Boolean
' -------------------------------
'         NT / WIN2000 ONLY
' -------------------------------
' Enumerates all Global accounts on a NT
' server, LEVEL_0
' if len(strServerName)=0,
' assume local machine
'
On Error GoTo ErrHandler
Dim abytServerName() As Byte
Dim pBuf As Long
Dim pTmpBuf As USER_INFO_0
Dim dwLevel As Long
Dim dwPrefMaxLen As Long
Dim dwEntriesRead As Long
Dim dwTotalEntries As Long
Dim dwResumeHandle As Long
Dim i As Long
Dim dwTotalCount As Long
Dim nStatus As Long

    ' assume MAX_PREFERRED_LENGTH
    dwPrefMaxLen = MAX_PREFERRED_LENGTH
    abytServerName = strServerName & vbNullChar
    dwLevel = 0 ' Level 0 call
    Do
        'only global users
        nStatus = apiNetUserEnum(VarPtr(abytServerName(0)), _
                            dwLevel, _
                            FILTER_NORMAL_ACCOUNT, _
                            pBuf, _
                            dwPrefMaxLen, _
                            dwEntriesRead, _
                            dwTotalEntries, _
                            dwResumeHandle)
        ' If the call succeeds,
        If ((nStatus = NERR_SUCCESS) Or (nStatus = ERROR_MORE_DATA)) Then
            ' Loop through the entries.
            For i = 0 To dwEntriesRead - 1
                Call sapiCopyMem( _
                                            pTmpBuf, _
                                            ByVal (pBuf + (i * 4)), _
                                            Len(pTmpBuf)) ' 4=len(USER_INFO_0)
                ' Print the name of the user account.
                Debug.Print fStrFromPtrW(pTmpBuf.usri0_name)
                ' Keep a count of accounts enumerated
                dwTotalCount = dwTotalCount + 1
            Next
        End If
        ' free the associated memory
        Call apiNetAPIBufferFree(pBuf)
        pBuf = 0
    Loop While (nStatus = ERROR_MORE_DATA)
    If Not (pBuf = 0) Then Call apiNetAPIBufferFree(pBuf)

    fEnumDomainUsers = True
ExitHere:
    Exit Function
ErrHandler:
    fEnumDomainUsers = False
    Resume ExitHere
End Function

Private Sub sPrintAccountInfo(lngFlags As Long)
' Prints out details about an account

    If lngFlags And UF_ACCOUNTDISABLE Then
        Debug.Print Space$(5), "Account is disabled."
    End If
    
    If lngFlags And UF_HOMEDIR_REQUIRED Then
        Debug.Print Space$(5), "Home Direcotory is required."
    End If
    
    If lngFlags And UF_PASSWD_NOTREQD Then
        Debug.Print Space$(5), "No password is required."
    End If
    
    If lngFlags And UF_PASSWD_CANT_CHANGE Then
        Debug.Print Space$(5), "The user cannot change the password."
    End If
    
    If lngFlags And UF_LOCKOUT Then
        Debug.Print Space$(5), "The account is currently locked out."
    End If
    
    If lngFlags And UF_DONT_EXPIRE_PASSWD Then
        Debug.Print Space$(5), "The password should never expire on the account."
    End If
    
    If lngFlags And UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED Then
        Debug.Print Space$(5), "The User 's password is stored " _
                        & "under reversible encryption in the Active Directory."
    End If
    
    If lngFlags And UF_NOT_DELEGATED Then
        Debug.Print Space$(5), "Sensitive account; other " _
                & "users cannot act as delegates of this user account."
    End If
    
    If lngFlags And UF_SMARTCARD_REQUIRED Then
        Debug.Print Space$(5), "Smart card is required to log on."
    End If
    
    If lngFlags And UF_DONT_REQUIRE_PREAUTH Then
        Debug.Print Space$(5), "This account does not require " _
            & "Kerberos preauthentication for logon."
    End If
    
    If lngFlags And UF_TRUSTED_FOR_DELEGATION Then
        Debug.Print Space$(5), "The account is enabled for delegation."
    End If
    
    If lngFlags And UF_SCRIPT Then
        Debug.Print Space$(5), "The logon script executed. "
    End If
    
    If lngFlags And UF_NORMAL_ACCOUNT Then
        Debug.Print Space$(5), "This is a default account " _
            & "type that represents a typical user."
    End If
    
    If lngFlags And UF_TEMP_DUPLICATE_ACCOUNT Then
        Debug.Print Space$(5), "This is an account for users " _
            & "whose primary account is in another domain. "
    End If
    
    If lngFlags And UF_WORKSTATION_TRUST_ACCOUNT Then
        Debug.Print Space$(5), "This is a computer account for a " _
            & "Windows NT/Windows 2000 workstation or Windows " _
            & "NT/Windows 2000 server that is a member of this domain."
    End If
    
    If lngFlags And UF_SERVER_TRUST_ACCOUNT Then
        Debug.Print Space$(5), "This is a computer account for a " _
            & "backup domain controller that is a member of this domain."
    End If
    
    If lngFlags And UF_INTERDOMAIN_TRUST_ACCOUNT Then
        Debug.Print Space$(5), "This is a permit to trust account for " _
            & " a domain that trusts other domains."
    End If

End Sub

Private Function fGetTime(ByVal lngSeconds As Long) As Variant
Dim lpTZI As TIME_ZONE_INFORMATION
Dim lngRet As Long
Const START_DATE = "01/01/1970 00:00:00"

    ' Get the TimeZone Information
    lngRet = apiGetTZI(lpTZI)
    ' if the function succeeded
    If Not lngRet = TIME_ZONE_ID_INVALID Then
        With lpTZI
            ' calculate offset from START_DATE
            fGetTime = DateAdd("s", lngSeconds - (.Bias * 60) _
                                    - (.DaylightBias * 60), _
                                    CDate(START_DATE))
        End With
    End If
End Function

Private Function fStrFromPtrW(pBuf As Long) As String
Dim lngLen As Long
Dim abytBuf() As Byte
    
    ' Get the length of the string at the memory location
    lngLen = apilstrlenW(pBuf) * 2
    ' if it's not a ZLS
    If lngLen Then
        ReDim abytBuf(lngLen)
        ' then copy the memory contents
        ' into a temp buffer
        Call sapiCopyMem( _
                abytBuf(0), _
                ByVal pBuf, _
                lngLen)
        ' return the buffer
        fStrFromPtrW = abytBuf
    End If
End Function
' ********* Code End ***********

© 1998-2010, Dev Ashish & Arvin Meyer, All rights reserved. Optimized for Microsoft Internet Explorer