TheAppleFreak

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

5 posts in this topic

#1 ·  Posted (edited)

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

Your welcome.  Glad it help.  

 

Adam

Share this post


Link to post
Share on other sites

#5 ·  Posted (edited)

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

    • ModemJunki
      By ModemJunki
      Edit: I found out from looking into the post from coffeeturtle that not all motherboards support WMI temperature reading. You have to explore the WMI namespaces for your system!
      I (stupidly) fried a computer. It was an old Zotac small form factor device that was junked at work, so I turned it into a home theater PC.
      But I had put it in my "audio rack", which has glass doors and not very good ventilation. Normally I would open the doors while it was running and I use a media remote which would hibernate the system. At some point, it woke up by itself (probably Windows Update) and did not go to sleep again. Unfortunately this happened during the summer months while I was away on a small holiday and of course the glass doors were closed.
      When I finally noticed the system was on, it was super hot and the fan was at full blast. Dead. No POST, no green light on the mainboard even with a new power supply, old mechanical laptop hard drive gives errors when connected to another device. My next home project will be to make some appropriate ventilation in the audio cabinet.
      For the replacement I bought a used HP ultra small form factor machine and decided to start working on an app to monitor the ambient temperature sensor on the motherboard and shutdown the system if needed. I used some code for smooth label updates from here (needed updating to work with latest AutoIT) and temperature conversion from here (not really needed, only if you want temperatures in something other than C).
      Maybe I will work on this some more and make it configurable with an .INI file or even storing the settings in registry, but since I probably won't I thought I'd put it up as an example of what a non-expert can do with an afternoon of coding and ideas from the community here.
      This uses an ambient temperature sensor populated on HP machines in HP-specific WMI extensions but the WMI query can easily be changed to any available sensor including CPU or GPU.
      The watchdog monitors temperature, warns with S.O.S beeping if the set point is exceeded, and either shuts down if a timeout is reached while the temperatures are high or goes back to monitoring if the temperature goes lower then the set point, and logs events to the Windows application event logs.
      The GUI it puts up is very small at the top center of the screen (very small on a 4k display anyway).
      #requireadmin because of the WMI query.
      A timer for the WMI query because of prior experience using similar functions in WinPE to get model infos from HP WMI bios extensions.
      If you play around with this, please post your fixes/improvements/changes back to this thread.
      Have fun!
      Edit: Updated to show "Unable to query temperature" if the WMI query returns a null
      #AutoIt3Wrapper_Icon=temperature-2-multi-size.ico #RequireAdmin #NoTrayIcon #include <Temperature.au3> #include <FFLabels.au3> #include <GUIConstantsEx.au3> #include <StaticConstants.au3> #include <WindowsConstants.au3> Global $winLogger = "C:\WINDOWS\system32\eventcreate.exe" ; application to use for event logging Global $MessageSource = "HP TempMon" Global $width = 185 Global $height = 15 Global $FontSize = 8 Global $FontFamily = 'Microsoft Sans Serif' Global $normClr = 0xFF000000 Global $warnClr = 0xFF0000 Global $WMInameSpace = "HP_BIOSNumericSensor" ;~ Global $WMIReadSensor = "Chassis Thermal Index" ; HP 8300 Global $WMIReadSensor = "System Ambient Temperature" ; HP Z400 Global $warnTmp = 46.1111111111 ; degrees C ;~ Global $warnTmp = 24 ; degrees C for testing Global $ovrtmpTime = 5 ; in minutes Global $hGUI = GUICreate("Board Temperature", $width, $height, -1, 0, BitOR($WS_SYSMENU, $WS_POPUP), BitOR($WS_EX_TOPMOST, $WS_EX_WINDOWEDGE, $WS_EX_TOOLWINDOW)) Global $lb1 = _GUICtrlFFLabel_Create($hGUI, "", -1, -1, $width, $height, 8, -1, 0, 1, $normClr) GUISetState(@SW_SHOW) _TempSenseLoop() Func _TempSenseLoop() While 1 Sleep(250) $nMsg = GUIGetMsg() Switch $nMsg Case $GUI_EVENT_CLOSE GUIDelete($hGUI) Return Case Else $s_ambTempC = _doQueryHPNumSens(5, $WMInameSpace, $WMIReadSensor, "100") ; + 100 Select Case $s_ambTempC = "" _GUICtrlFFLabel_SetTextColor($lb1, $normClr) _GUICtrlFFLabel_SetData($lb1, "Unable to query temperature") Case $s_ambTempC < $warnTmp _GUICtrlFFLabel_SetTextColor($lb1, $normClr) _GUICtrlFFLabel_SetData($lb1, "Board temperature: " & $s_ambTempC & "C" & "/" & Round(_CelsiusToFahrenheit($s_ambTempC), 0) & "F") Case Else _GUICtrlFFLabel_SetTextColor($lb1, $warnClr) _GUICtrlFFLabel_SetData($lb1, "Board temperature: " & $s_ambTempC & "C" & "/" & Round(_CelsiusToFahrenheit($s_ambTempC), 0) & "F") _doLog("System ambient temperature has exceeded " & $warnTmp & "C.", "WARN", $MessageSource) _AlarmMonitor($ovrtmpTime, $warnTmp) EndSelect EndSwitch WEnd EndFunc ;==>_TempSenseLoop Func _AlarmMonitor($min, $tmp) Local $Shutdown = 1 Local $s_ambTemp Local $freq = 3500 Local $shrt = 200 Local $long = 500 Local $timer = TimerInit() Local $wait = 1000 * ($min * 60) While TimerDiff($timer) < $wait $s_ambTemp = _doQueryHPNumSens(5, $WMInameSpace, $WMIReadSensor, "100") ; + 100 If $s_ambTemp >= $tmp Then _GUICtrlFFLabel_SetData($lb1, "WARNING! OVERTEMP!") Beep($freq, $shrt) Beep($freq, $shrt) Beep($freq, $shrt) Sleep(250) Beep($freq, $long) Beep($freq, $long) Beep($freq, $long) Sleep(250) Beep($freq, $shrt) Beep($freq, $shrt) Beep($freq, $shrt) _GUICtrlFFLabel_SetData($lb1, "Board temperature: " & $s_ambTemp & "C" & "/" & _CelsiusToFahrenheit($s_ambTemp) & "F") Sleep(2000) ElseIf $s_ambTemp < $tmp Then $Shutdown = 0 ExitLoop EndIf WEnd If $Shutdown = 1 Then _doLog("The system is shutting down due to overtemperature conditions. The temperature at the time of shutdown was " & $s_ambTemp & "C" & "/" & _CelsiusToFahrenheit($s_ambTemp) & "F", "ERRR", $MessageSource) Shutdown(24) ElseIf $Shutdown = 0 Then _doLog("System ambient temperature has been reduced below " & $warnTmp & "C.", "INFO", $MessageSource) EndIf EndFunc ;==>_AlarmMonitor Func _doQueryHPNumSens($min, $class, $name, $sleep) ; $sleep = milliseconds Local $s_tempReading = "" Local $s_HPBiosWMIService = "winmgmts:\\" & @ComputerName & "\Root\HP\InstrumentedBIOS" Local $objHPBiosWMIService = ObjGet($s_HPBiosWMIService) Local $s_biosQuery = $objHPBiosWMIService.ExecQuery("select * from " & $class & " where Name='" & $name & "'") $timer = TimerInit() ; we may need to wait for the HP WMI extensions to enumerate in WMI, in WinPE this takes some minutes. $wait = 1000 * ($min * 60) If Not IsObj($s_biosQuery) Then ; we do need to wait, put up a splash screen $s_WMISplash = 1 SplashTextOn("WMI", "Probing WMI (up to 10 minutes)...", 300, 50, -1, -1, 1) Sleep(1000) While TimerDiff($timer) < $wait Sleep(500) $objHPBiosWMIService = ObjGet($s_HPBiosWMIService) If IsObj($objHPBiosWMIService) Then Sleep(500) $colProdName = $s_biosQuery If IsObj($colProdName) Then For $oItem In $colProdName $s_tempReading = $oItem.CurrentReading Next EndIf EndIf WEnd ElseIf IsObj($s_biosQuery) Then ConsoleWrite($s_biosQuery & @CRLF) For $oItem In $s_biosQuery $s_tempReading = $oItem.CurrentReading Next Else $s_tempReading = 0 EndIf Sleep($sleep) Return $s_tempReading EndFunc ;==>_doQueryHPNumSens Func _doLog($message, $type, $msgsrc) Select Case $type = "INFO" $logTyp = "INFORMATION" Case $type = "WARN" $logTyp = "WARNING" Case $type = "ERRR" $logTyp = "ERROR" EndSelect If FileExists($winLogger) Then RunWait(@ComSpec & " /c " & $winLogger & " /L Application /T " & $logTyp & " /SO " & Chr(34) & $msgsrc & Chr(34) & " /ID 1000 /D " & Chr(34) & $message & Chr(34), "", @SW_HIDE) EndIf EndFunc ;==>_doLog  
       
       
      Temperature.au3
      FFLabels.au3
      HP_Temp.au3
    • ModemJunki
      By ModemJunki
      Hello,
      In Windows 10 PowerShell, one can do this to change the metric for a NIC in Windows 10:
      Get-NetAdapter | Where-Object -FilterScript {$_.InterfaceAlias -Eq "Ethernet 2"} | Set-NetIPInterface -InterfaceMetric 2 I know I can script the above PowerShell line (and it works!), but I wanted to try something I hadn't done before after looking into jguinch's most excellent Network configuration UDF. I wanted to make use of the SetIPConnectionMetric method in the WMI classes. There is an example VBscript here but this is not for Windows 10. Using AutoIT would also give better control over capturing error return codes than with PowerShell.
      But I cannot get my script to work! The return from SetIPConnectionMetric() is 0, which would indicate success. Yet the change does not happen. I also tried WMI methods using .put_ but this fails.
      Anyone more experienced than I have ideas to make this work?
      #RequireAdmin _SetNicInterfaceMetric2("Ethernet 2", "2") Func _SetNicInterfaceMetric2($NIC_NAME, $METRIC) Local $s_setIndx = 0 $objWMIService = ObjGet("winmgmts:{impersonationLevel = impersonate}!\\" & "." & "\root\cimv2") $colNICItems = $objWMIService.ExecQuery("SELECT * FROM Win32_NetworkAdapter WHERE NetConnectionID = '" & $NIC_NAME & "'", "WQL") If IsObj($colNICItems) Then For $objItem In $colNICItems $s_nicIndex = $objItem.Index Next ConsoleWrite("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE Index = '" & $s_nicIndex & "'" & @CRLF) $colNAC = $objWMIService.ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE Index = '" & $s_nicIndex & "'", "WQL") If IsObj($colNAC) Then For $objNetCard In $colNAC If $METRIC <> $objNetCard.IPConnectionMetric Then ConsoleWrite("Metric was set to " & $objNetCard.IPConnectionMetric & ". Setting to " & $METRIC & "." & @CRLF) $s_isSet = $objNetCard.SetIPConnectionMetric($METRIC) ConsoleWrite("SetIPConnectionMetric Result = " & $s_isSet & @CRLF) Else ConsoleWrite("Metric is already set to " & $METRIC & @CRLF) EndIf Next EndIf EndIf EndFunc ;==>_SetNicInterfaceMetric2  
    • AndyS19
      By AndyS19
      I have code that does a WMI SQL query to find all defined printers, and I want to parse the returned object in several places.  However, after parsing it the first time, all other times fail to find any printer objects.
      Here is my test code:
      test() Func test() Local $oPrinters, $oPrinter, $err, $cnt, $oP, $query $query = "SELECT * FROM Win32_Printer" $oPrinters = doQuery($query) $err = @error LogMsg("+++: $err = " & $err & ", isObj($oPrinters) = " & IsObj($oPrinters)) If ($err == 0) Then LogMsg("FIRST LOOP") ; <=== FIRST LOOP $cnt = 0 $oP = $oPrinters LogMsg("+++: isObj($oP) = " & IsObj($oP)) For $oPrinter In $oP $cnt += 1 LogMsg("+++: isObj($oPrinter): " & IsObj($oPrinter) & ", $oPrinter.Name ==>" & $oPrinter.Name & "<==") Next LogMsg("+++: Found " & $cnt & " printers") LogMsg("SECOND LOOP") ; <== SECOND LOOP $cnt = 0 $oP = $oPrinters LogMsg("+++: isObj($oP) = " & IsObj($oP)) For $oPrinter In $oP $cnt += 1 LogMsg("+++: isObj($oPrinter): " & IsObj($oPrinter) & ", $oPrinter.Name ==>" & $oPrinter.Name & "<==") Next LogMsg("+++: Found " & $cnt & " printers") EndIf EndFunc ;==>test Func doQuery($sQuery, $lnum = @ScriptLineNumber) #forceref $lnum LogMsg("+++:" & $lnum & ": doQuery(" & '"' & $sQuery & '"' & ") entered") Local $oWMIService, $oResults, $errstr Local $wbemFlags = BitOR(0x20, 0x10) ; $wbemFlagReturnImmediately and wbemFlagForwardOnly $oWMIService = ObjGet("winmgmts:\\" & "localhost" & "\root\CIMV2") If (IsObj($oWMIService)) Then $oResults = $oWMIService.ExecQuery($sQuery, "WQL", $wbemFlags) If (IsObj($oResults)) Then LogMsg("+++: doQuery() returns @error = 0, Good: returning the object") Return (SetError(0, 0, $oResults)) ;;; Good: return the object Else $errstr = "" _ & "WMI Query failed." & @CRLF _ & "This is the query:" & @CRLF _ & " " & $sQuery LogMsg("+++: ====>" & $errstr & "<===") LogMsg("+++: doQuery() returns @error = 1") Return (SetError(1, 0, $errstr)) ; Error: Query faled EndIf Else $errstr = "" _ & "WMI Output" & @CRLF _ & "No WMI Objects Found for class: " & @CRLF _ & "Win32_PrinterDriver" & @CRLF _ & "using this query:" & @CRLF _ & " " & $sQuery LogMsg("+++: ====>" & $errstr & "<===") MsgBox(0, "ERROR", $errstr) ; Error: Cannot get $oWMIService object Exit (1) EndIf EndFunc ;==>doQuery Func LogMsg($msg, $lnum = @ScriptLineNumber) ConsoleWrite("+++:" & $lnum & ": " & $msg & @CRLF) EndFunc ;==>LogMsg Parsing the returned $oPrinters object shows 5 printers:
      +++:15: FIRST LOOP +++:18: +++: isObj($oP) = 1 +++:22: +++: isObj($oPrinter): 1, $oPrinter.Name ==>Microsoft XPS Document Writer<== +++:22: +++: isObj($oPrinter): 1, $oPrinter.Name ==>Microsoft Office Document Image Writer<== +++:22: +++: isObj($oPrinter): 1, $oPrinter.Name ==>Fax<== +++:22: +++: isObj($oPrinter): 1, $oPrinter.Name ==>Canon MG7100 series Printer WS<== +++:22: +++: isObj($oPrinter): 1, $oPrinter.Name ==>Canon MG6100 series Printer WS<== +++:24: +++: Found 5 printers Parsing it again, shows no printers:
      +++:26: SECOND LOOP +++:29: +++: isObj($oP) = 1 +++:35: +++: Found 0 printers  
    • jguinch
      By jguinch
      Hello.
      I did create these few functions several months ago. I post here, if it can interest someone.
      These functions based on WMI queries allow you to manage printers : add / delete printer, driver, port, or obtain configuration, set default printer ... I let you discover it with the code.

       
      Here is the list of the available functions :
      _Printmgr_AddLocalPort
      _Printmgr_AddLPRPort
      _PrintMgr_AddPrinter
      _PrintMgr_AddPrinterDriver
      _PrintMgr_AddTCPIPPrinterPort
      _PrintMgr_AddWindowsPrinterConnection
      _PrintMgr_CancelAllJobs
      _Printmgr_EnumPorts
      _PrintMgr_EnumPrinter
      _PrintMgr_EnumPrinterConfiguration
      _PrintMgr_EnumPrinterDriver
      _PrintMgr_EnumPrinterProperties
      _PrintMgr_EnumTCPIPPrinterPort
      _Printmgr_Pause
      _Printmgr_PortExists
      _Printmgr_PrinterExists
      _Printmgr_PrinterSetComment
      _Printmgr_PrinterSetDriver
      _Printmgr_PrinterSetPort
      _Printmgr_PrinterShare
      _Printmgr_PrintTestPage
      _PrintMgr_RemoveLocalPort
      _PrintMgr_RemoveLPRPort
      _PrintMgr_RemovePrinter
      _PrintMgr_RemovePrinterDriver
      _PrintMgr_RemoveTCPIPPrinterPort
      _PrintMgr_RenamePrinter
      _Printmgr_Resume
      _PrintMgr_SetDefaultPrinter
       
      And some examples :
      #Include "PrintMgr.au3" ; Remove a printer called "My old Lexmark printer" : _PrintMgr_RemovePrinter("My old Lexmark printer") ; Remove the driver called "Lexmark T640" : _PrintMgr_RemovePrinterDriver("Lexmark T640") ; Remove the TCP/IP printer port called "TCP/IP" _PrintMgr_RemoveTCPIPPrinterPort("MyOLDPrinterPort") ; Add a driver, called "Samsung ML-451x 501x Series", and driver inf file is ".\Samsung5010\sse2m.inf" _PrintMgr_AddPrinterDriver("Samsung ML-451x 501x Series", "Windows NT x86", @scriptDir & "\Samsung5010", @scriptDir & "\Samsung5010\sse2m.inf") ; Add a TCP/IP printer port, called "MyTCPIPPrinterPort", with IPAddress = 192.168.1.10 and Port = 9100 _PrintMgr_AddTCPIPPrinterPort("MyTCPIPPrinterPort", "192.168.1.10", 9100) ; Add a printer, give it the name "My Printer", use the driver called "Samsung ML-451x 501x Series" and the port called "MyTCPIPPrinterPort" _PrintMgr_AddPrinter("My Printer", "Samsung ML-451x 501x Series", "MyTCPIPPrinterPort") ; Set the printer called "My Printer" as default printer _PrintMgr_SetDefaultPrinter("My Printer") ; Connect to the shared printer "\\192.168.1.1\HPDeskjetColor") _PrintMgr_AddWindowsPrinterConnection("\\192.168.1.1\HPDeskjetColor") ; List all installed printers #Include <Array.au3> $aPrinterList = _PrintMgr_EnumPrinter() _ArrayDisplay($aPrinterList) ; List all printers configuration #Include <Array.au3> $aPrinterConfig = _PrintMgr_EnumPrinterConfiguration() _ArrayDisplay($aPrinterConfig) ; List all installed printer drivers #Include <Array.au3> $aDriverList = _EnumPrinterDriver() _ArrayDisplay($aDriverList) ; Retrieve the printer configuration for the printer called "Lexmark T640" #Include <Array.au3> $aPrinterConfig = _PrintMgr_EnumPrinterConfiguration("Lexmark T640") _ArrayDisplay($aPrinterConfig) ; Add a local printer port (for a file output) _AddLocalPrinterPort("c:\temp\output.pcl") ; Remove the local port _RemoveLocalPrinterPort("c:\temp\output.pcl") Download link :  PrintMgr.au3
    • jguinch
      By jguinch
      Here is an UDF for managing printers.
      Features are :
       - add, remove or rename a printer
       - add or remove a driver
       - add or remove a TCP/IP printer port
       - add or remove a LPR printer port
       - connect to a remote printer
       - enum printers and there configuration and properties
       - pause resume or cancel all jobs of a printer
       - checks if a printer exists
       - print a test page
       - set the default printer