Tracking local user accounts for security audit

The Quick and Dirty Way

You'll need PsExec from Microsoft for this script to work. I'm assuming that you can connect to the non-domain member and domain member computers with the same username and password. If that's not possible, let me know and I'll change up the script a little bit.

Put a list of machine names into machines.txt and run:

@echo off

for /F "delims=" %%i in (machines.txt) do (
 psexec \\%%i NET USER > %%i.txt
)

You'll end up with a bunch of text files of the format computer-name.txt with the output of "NET USER" on each computer.

The Fancy(tm) Way

That's pretty quick-and-dirty and the output would be fairly painful to parse. Here's a fancier script in VBScript:

Option Explicit

Dim dictGroupsToIgnore, dictUsersToIgnore, objNetwork, strComputer
Dim colUsers, colGroups, objGroup, objUser

' Debugging
Const DEBUGGING = True

' Constants for comparison of accounts to ignore list
Const MATCH_EXACT = 1
Const MATCH_LEFT = 2

Set dictGroupsToIgnore = CreateObject("Scripting.Dictionary")
' dictGroupsToIgnore.Add "Name of group you want to ignore (matching left only)", MATCH_LEFT
' dictGroupsToIgnore.Add "Name of group you want to ignore", MATCH_EXACT

' Accounts to ignore during copying
Set dictUsersToIgnore = CreateObject("Scripting.Dictionary")
' dictUsersToIgnore.Add "Name of user you want to ignore (matching left only)", MATCH_LEFT
' dictUsersToIgnore.Add "Name of user you want to ignore", MATCH_EXACT

' Should this account be ignored
Function IgnoreObject(Name, dictNames)
    Dim strToIgnore

    IgnoreObject = False

    For Each strToIgnore in dictNames

        ' Match Exact
        If (dictNames.Item(strToIgnore) = MATCH_EXACT) and (UCase(Name) = UCase(strToIgnore)) Then
            IgnoreObject = True
            Exit Function
        End If

        ' Match left
        If (dictNames.Item(strToIgnore) = MATCH_LEFT) and (Left(UCase(Name), Len(strToIgnore)) = UCase(strToIgnore)) Then
            IgnoreObject = True
            Exit Function
        End If

    Next' strToIgnore
End Function

' Main

Set objNetwork = CreateObject("Wscript.Network")

While NOT WScript.StdIn.AtEndOfStream

    strComputer = WScript.StdIn.ReadLine

    ' Get accounts on source computer and loop through them, copying as necessary
    Set colUsers = GetObject("WinNT://" & strComputer)
    colUsers.Filter = Array("user")

    For Each objUser In colUsers
        If IgnoreObject(objUser.Name, dictUsersToIgnore) = False Then
            WScript.Echo strComputer & Chr(9) & "user" & Chr(9) & objUser.Name
        End If
    Next ' objUser

    ' Get groups on source computer and loop through them, copying as necessary
    Set colGroups = GetObject("WinNT://" & strComputer)
    colGroups.Filter = Array("group")

    ' Put user into destination groups
    For Each objGroup In colGroups
        If IgnoreObject(objGroup.Name, dictGroupsToIgnore) = False then
            For Each objUser In objGroup.Members
                WScript.Echo strComputer & Chr(9) & "group" & Chr(9) & objGroup.Name & Chr(9) & "member" & Chr(9) & objUser.Name
            Next ' objUser
        End If
    Next 'objGroup
Wend ' WScript.StdIn.AtEndOfStream

I've included some functionality to "ignore" groups or users, too.

  • Add any group names that should not be reported to the dictGroupsToIgnore list (as shown in the script). MATCH_EXACT means that the name of the group is matched exactly. MATCH_LEFT means that only the leftmost portion of the group name will be matched (i.e. imagine that the name match has a "*" after it).

  • Add any user names that should not be reported to the dictUsersToIgnore list (as shown in the script, too). MATCH_EXACT and MATCH_LEFT have the same meanings as with the dictGroupsToIgnores list (i.e. "IUSR_" with MATCH_LEFT means that any user account starting with "IUSR_" will not be reported).

Call this script redirecting input from a text file and output to a text file (i.e. "cscript script-name.vbs < machines.txt > report.txt") and you'll get a TAB delimited output of the format:

computer_name   user   username
computer_name   group  groupname   member   member_1_name
computer_name   group  groupname   member   member_2_name
computer_name   group  groupname   member   member_3_name
...

You might not need the group information but it'll be easy to filter away later.

If you need to connect to each machine with different credentials let me know and I'll change up the script a bit.


I remember working on something similar a few years ago. There is probably 5 ways that you could easily have this done via a script however recently I was introduced to SYDI and I recommend that you check it out, it might serve more benefits then just the audit tracking of users localy.

http://sydiproject.com/tools/sydi-audit-localgroups/

Snippit from site :Usage Scenarios You might want to track how many local administrators you have in your organization, perhaps some users have been placed in the local administrators group “temporarily” but have now settled in with all the privileges it provides. Even if your organization doesn’t yet disallow local administrative access you still might want to be able to see in black and white which users have been granted this access. The Power Users group can be another group you want to monitor.

If you have a standardized environment your group structure on your clients should all look the same way. You can use the tool to find any additional groups which shouldn’t be there.

Using the Script Like many other SYDI tools this script is written in vbscript and intended to be run from cscript.exe. To use it you provide an argument with the path to your SYDI Server output files:

Cscript.exe sydi-audit-localgroups.vbs -xN:\SYDI\Output

Let me know how it works out

Best, Nick