Jump to content
TheAppleFreak

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

Share this post


Link to post
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. 

Share this post


Link to post
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

Share this post


Link to post
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

  • Similar Content

    • By jresine
      Hello, is it possible to know via a script or command, to have the percentage of disk usage of a process? thank you in advance.
      ps: see image

    • By Dwalfware
      HI
      Another head scratched
      Anyone solve how to get the IPV6 from WMI?
      I followed the usual SELECT * FROM Win32_NetworkAdapter WHERE NetConnectionID='" & GUICtrlRead($cboAdapters) & "'
      get the index 
      port it to 
      Local $query = $objWMIService.ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE Index = " & _GetSelectedNIC(), "WQL", 0x30 )
      All i can see is IPv4 addresses
      My question is is there another place the IPv6 information is hiding? I configured my LAN with IPV6 and my wireless with IPv4 so I can see what shows.
      Thanks in Advance.
      PS. I did try google
       
    • By Dwalfware
      Hi Folks
      Can not get my head around this one.
      This function gets the DNS object s
      If you have two DNS entries fine. but if you have only one! Trouble
      $objItem.DNSServerSearchOrder[0] = Primary DNS
      $objItem.DNSServerSearchOrder[1] = Secondry DNS not always there. I need a way to return 0 if not there or the IP if its there.
      Or is there a way to tell how many objects there are in the "[?]"
      I'm terrible at WMI errors, thanks for help in advance.
      Func _GETcurrentNICinfo2() $oErrors = ObjEvent("AutoIt.Error", "Error_Handle") Local $DnsSecondry Local $objWMIService = ObjGet( "winmgmts:\\" & @ComputerName & "\root\CIMV2" ) Local $query = $objWMIService.ExecQuery("SELECT DNSServerSearchOrder FROM Win32_NetworkAdapterConfiguration WHERE Index = " & _GetSelectedNIC(), "WQL", 0x30 ) If @error Then Return 0 If NOT IsObj($query) Then Return 0 Else For $objItem In $query Return $objItem.DNSServerSearchOrder[1] next Endif EndFunc  
    • By nitron
      Hy All,
       
      again I got a question.
      I try to convert
      wmic PATH Win32_videocontroller GET adapterram
       
      i tried to to it like in this example but my knoledge is not good enough
         Local $oWMI = ObjGet("winmgmts:root\CIMV2")
         Local $oDisks = $oWMI.ExecQuery("select * from WIN32_DiskDrive")
       
      So i tired 
         Local $oWMI = ObjGet("winmgmts:root\CIMV2")
         Local $oGMRam= $oWMI.ExecQuery("Win32_videocontroller")
         $Memory=$oGMRam.adapterram
      but this does not work.
      I like to understand what i need to do?
       
    • By morion84
      Hi, when I call a MsgBox function I can use a "$MB_SERVICE_NOTIFICATION" flag so it will be displayed "on the current active desktop, even if there is no user logged on to the computer.". That works just great. Is there a way to do the same to Autoit GUI Form so it will be visible on desktop even if running from system account and user is not logged?
×
×
  • Create New...