Jump to content

WMI "ASSOCIATORS OF" query ridiculously slower when running as Administrator than when running as a standard user


Recommended Posts

Never experienced anything like this before, so to you I come for help...

I'm in the process of rewriting my fork of JSThePatriot's excellent Computer Information library, and my current project is rewriting the User profiling function. One of the new features I've added in is gathering the list of groups a particular user profile belongs to, which uses an existing WMI object to run an "ASSOCIATORS OF" query targeting the specified domain and username. 

When I run the function under my own user account from within ISN/ScITE or compile it and run it via Windows Explorer, my test script collects the information in a fraction of a second. When I compile the script and Right Click -> "Run As Administrator," the script takes almost three and a half minutes to complete. It's literally the exact same code, but running under Administrator has a computed 6320x slowdown. Examining the script running in Process Monitor showed no activity during what I assume are these queries, which boggles my mind. 

If someone could tell me what I'm doing wrong, that'd be super awesome. Below is the code used, and below that is a log file that I generated to root out what in particular was causing the massive slowdown. It's worth mentioning that I'm currently running Windows 8.1 on this machine, under a user account that is part of both the Administrators and Users groups. 

; From CompInfoObject.au3
; =========================

#include <File.au3>
#include <Array.au3>
#include <Security.au3>

#region Global Variables and Constants
If Not(IsDeclared("$cI_CompName")) Then
    Global $cI_CompName = @ComputerName
EndIf
Global Const $cI_VersionInfo        = "00.03.08"
Global Const $cI_aName              = 0, _
             $cI_aDesc              = 4
Global  $wbemFlagReturnImmediately  = 0x10, _   ;DO NOT CHANGE
$wbemFlagForwardOnly        = 0x20              ;DO NOT CHANGE
Global  $ERR_NO_INFO                = "Array contains no information", _
        $ERR_NOT_OBJ                = "$colItems isnt an object"

Global Const $VERSION_CIO = "2.1.0.0"
#endregion Global Variables and Constants

; Test script
; ==========================

Global Const $logPath = "D:\logFile.txt"

If IsAdmin() Then 
    FileWriteLine($logPath, "============= RUNNING AS ADMINISTRATOR ============")
Else 
    FileWriteLine($logPath, "================= RUNNING AS USER =================")
EndIf
FileWriteLine($logPath, "")


FileWriteLine($logPath, "Starting user profiling...")
FileWriteLine($logPath, "")
Local $runtime = TimerInit()
Local $test = _ComputerGetUsers()
FileWriteLine($logPath, "User profiling complete after " & (TimerDiff($runtime) / 1000) & " seconds")
FileWriteLine($logPath, "")

MsgBox(0, "", "complete", 10)

; From CompInfoObject.au3

Func _ComputerGetUsers()
    Local $localSIDs[1]
    Local $i = 0, $x = 1, $sid
    
    While 1
        $sid = RegEnumKey("HKLM\Software\Microsoft\Windows NT\CurrentVersion\ProfileList", $x)
        If @error Then ExitLoop
        
        If StringInStr($sid, "S-1-5-21-") Then 
            If IsArray(_Security__LookupAccountSid($sid)) Then
                ReDim $localSIDs[$i + 1]
                $localSIDs[$i] = $sid
                
                $i += 1
            EndIf 
        EndIf
        
        $x += 1
    Wend
    
    $i = 0
    _ArraySort($localSIDs)
    
    Local $colItems, $objWMIService, $objItem, $wqlQuery
    Local $groups, $userProfile, $userProfileLoaded, $regMountPoint, $runtime
    Local $users[1] = [ObjCreate("Scripting.Dictionary")]
    
    $wqlQuery = "SELECT * FROM Win32_UserAccount"

    $objWMIService = ObjGet("winmgmts:\\" & $cI_Compname & "\root\CIMV2")
    $colItems = $objWMIService.ExecQuery($wqlQuery, "WQL", $wbemFlagReturnImmediately + $wbemFlagForwardOnly)

    If IsObj($colItems) Then
        For $objItem In $colItems
            ; Restrict users to actual users on the computer
            If _ArrayBinarySearch($localSIDs, $objItem.SID) <> -1 Then 
                FileWriteLine($logPath, "Collecting basic information for " & $objItem.Name & "...")
                $runtime = TimerInit()
                
                $regMountPoint = Null 
                $userProfileLoaded = False
                
                ReDim $users[$i + 1]
                $users[$i] = ObjCreate("Scripting.Dictionary")
                $users[$i].Add("Name", StringStripWS($objItem.Name, 3))
                $users[$i].Add("Domain", StringStripWS($objItem.Domain, 3))
                $users[$i].Add("Status", StringStripWS($objItem.Status, 3))
                $users[$i].Add("FullName", StringStripWS($objItem.FullName, 3))
                $users[$i].Add("Description", StringStripWS($objItem.Description, 3))
                $users[$i].Add("SID", StringStripWS($objItem.SID, 3))
                $users[$i].Add("Disabled", $objItem.Disabled)
                $users[$i].Add("Lockout", $objItem.Lockout)
                $users[$i].Add("PasswordChangeable", $objItem.PasswordChangeable)
                $users[$i].Add("PasswordExpires", $objItem.PasswordExpires)
                $users[$i].Add("PasswordRequired", $objItem.PasswordRequired)
                
                FileWriteLine($logPath, "Finished collecting basic information for " & $users[$i].Item("Name") & " in " & (TimerDiff($runtime) / 1000) & " seconds")
                FileWriteLine($logPath, "")
                
                ; Get user group membership
                FileWriteLine($logPath, "Collecting group information for " & $users[$i].Item("Name") & "...")
                $runtime = TimerInit()
                
                $groups = $objWMIService.ExecQuery("ASSOCIATORS OF {Win32_UserAccount.Domain='" & $objItem.Domain & "',Name='" & $objItem.Name & "'} WHERE ResultClass=Win32_Group", "WQL", $wbemFlagReturnImmediately + $wbemFlagForwardOnly)
                
                If IsObj($groups) Then 
                    Local $temp[1]
                    $x = 0
                    
                    For $group in $groups
                        ReDim $temp[$x + 1]
                        $temp[$x] = ObjCreate("Scripting.Dictionary")
                        
                        $temp[$x].Add("Name", $group.Name)
                        $temp[$x].Add("Domain", $group.Domain)
                        $temp[$x].Add("SID", $group.SID)
                        
                        $x += 1
                    Next
                    
                    $users[$i].Add("Groups", $temp)
                EndIf
                
                FileWriteLine($logPath, "Finished collecting group information for " & $users[$i].Item("Name") & " in " & (TimerDiff($runtime) / 1000) & " seconds")
                FileWriteLine($logPath, "")
                
                #cs 
                ; First, we need to mount the registry before anything else, or get the registry path if it already is
                $userProfile = $objWMIService.ExecQuery("SELECT * FROM Win32_UserProfile WHERE SID=""" & $users[$i].Item("SID") & """", "WQL", $wbemFlagReturnImmediately + $wbemFlagForwardOnly)
                
                If IsObj($userProfile) Then                 
                    For $profile in $userProfile
                        $users[$i].Add("ProfilePath", StringStripWS($profile.LocalPath, 3))
                        
                        If $profile.Loaded Then 
                            $userProfileLoaded = True 
                            $regMountPoint = "HKEY_USERS\" & $users[$i].Item("SID") & "\"
                        EndIf
                        ExitLoop
                    Next
                    
                    ; Profile isn't loaded. Load it manually. 
                    If $regMountPoint == Null Then
                        If RunWait("reg load HKU\TEMP-&" & $users[$i].Item("SID") & " " & $users[$i].Item("ProfilePath") & "\NTUSER.dat") == 0 Then 
                            MsgBox(0, "", "Loaded registry hive for " & $users[$i].Item("Name") & @CRLF, 3)
                            $regMountPoint = "HKEY_USERS\TEMP-" & $users[$i].Item("SID") & "\"
                        Else 
                            MsgBox(0, "", "Failed to load registry hive for " & $users[$i].Item("Name") & @CRLF, 3)
                        EndIf
                    EndIf
                EndIf
                
                ; Get user's mapped file shares
                ; HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\
                ; Run regex "\#\#(.+?)\#(.+?)" to get path, then read key _LabelFromReg for any custom drive label applied
                

                ; Get user's mapped printers
                ; https://support.microsoft.com/en-us/kb/102966
                
                
                ; Unload profile if it wasn't previously loaded
                If Not $userProfileLoaded And $regMountPoint <> Null Then 
                    If RunWait("reg unload " & StringRegExp($regMountPoint, "^(.*)\\$", 1)[0]) Then 
                        MsgBox(0, "", "Unloaded registry hive at " & $regMountPoint & @CRLF, 3)
                    Else 
                        MsgBox(0, "", "Error: Could not unload registry hive at " & $regMountPoint & @CRLF, 3)
                    EndIf
                EndIf
                #ce 
                
                $i += 1
            EndIf
        Next
        If $users[Ubound($users) - 1].Count < 1 Then
            ReDim $users[Ubound($users) - 1]
        EndIf
        If Ubound($users) < 1 Then
            Return SetError(1, 1, 0)
        EndIf
    Else 
        Return SetError(1, 2, 0)
    EndIf
    
    Return $users 
EndFunc

And here's the log file:

================= RUNNING AS USER =================

Starting user profiling...

Collecting basic information for Administrator...
Finished collecting basic information for Administrator in 0.000332286438607835 seconds

Collecting group information for Administrator...
Finished collecting group information for Administrator in 0.0347993066888362 seconds

Collecting basic information for <redacted>...
Finished collecting basic information for <redacted> in 0.00030855169299299 seconds

Collecting group information for <redacted>...
Finished collecting group information for <redacted> in 0.0321612217877267 seconds

User profiling complete after 0.1464664737096 seconds

============= RUNNING AS ADMINISTRATOR ============

Starting user profiling...

Collecting basic information for Administrator...
Finished collecting basic information for Administrator in 0.000270383656125874 seconds

Collecting group information for Administrator...
Finished collecting group information for Administrator in 27.3050492143158 seconds

Collecting basic information for <redacted>...
Finished collecting basic information for <redacted> in 0.000575407211257197 seconds

Collecting group information for <redacted>...
Finished collecting group information for <redacted> in 203.270754752081 seconds

User profiling complete after 230.621668475639 seconds

 

Edited by TheAppleFreak
Link to comment
Share on other sites

Many, many thanks for that link! While I can't see the link on its own being incredibly useful, it did remind me of the existence of Win32_GroupUser, which a quick Google search for yielded a wealth of information that looks to be incredibly useful. 

Once I get to my computer, I'll do some testing to see what performance is like; hopefully this should help significantly. 

Link to comment
Share on other sites

As an update for those who might stumble across this later on:

I haven't figured out why the Associators query runs so slowly under an Administrator account, but I did rework my queries to accomplish roughly the same task. As a warning, this method will perform more queries than you'd use otherwise, and it's likely not the fastest way to do this, but it does works properly under both a standard user and an administrator account (tested on Windows 7 and Windows 8.1).

The Win32_GroupUser class is composed of two properties, GroupComponent and PartComponent. GroupComponent is a link to the specified group, and PartComponent is a link to the specified user. Since I'm not good enough yet with my WMI-fu to just get the object at the link directly, I'm instead constructing a WMI query to find any entries that match the user I specify (identified by domain and username). Using the Win32_UserAccount object for the user I'm looking up, my query looks something like this:

Local $objGroupMaps = $objWMIService.ExecQuery("SELECT GroupComponent FROM Win32_GroupUser WHERE PartComponent=""Win32_UserAccount.Domain='" & $objItem.Domain & "',Name='" & $objItem.Name & "'""", "WQL", $wbemFlagReturnImmediately + $wbemFlagForwardOnly)

It's worth noting you can reverse this (search by GroupComponent instead of PartComponent) so you can enumerate all users in a group.

Following that, I iterate through the list of return group mappings and run a regular expression on the GroupComponent property to extract the domain and name of the group from the map string. If all you're looking for is the domain and name of the group, you can stop here, but I want to get a little more information from the group, so I run another WMI query to get that. 

For $objGroupMap in $objGroupMaps
    $aParams = StringRegExp($groupMap.GroupComponent, "^.*?(?<=Win32_Group\.)(.*?),(.*?)$", 1)
    
    ; It should return only one entry, so the name $groups probably isn't the best choice, but I
    ; iterate through it like any other object collection.
    ; If anyone has a better idea what to call this, I'm all ears
    Local $groups = $objWMIService.ExecQuery("SELECT * FROM Win32_Group WHERE " & $aParams[0] & " AND " & $aParams[1], "WQL", $wbemFlagReturnImmediately + $wbemFlagForwardOnly)
    
    If IsObj($groups) Then 
        For $group in $groups
            ...
        Next
    EndIf
Next

It's obviously not as performant as running the "ASSOCIATORS OF" query under a standard user, but execution time remains very similar when running as an administrator, which is what I want. I will admit I also had an issue with this query where on my development machine it wasn't returning the local administrator's profile information, though I think that might be an unrelated issue. Yep, completely unrelated issue.

I hope this helps somebody!

Edited by TheAppleFreak
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...