Jump to content

Is there a way to optimize performance?


Recommended Posts

Hi Professionals...

This is designed to remove the local user's attribute "Password never expires" and is meant to be run after each login by a "Run"-Entry in Registry:

; Hide the Tray-Icon
; ==================================================================================================
#NoTrayIcon


; Define the include files
; ==================================================================================================
#include <Array.au3>


; Variables
; ==================================================================================================
Global Const $USER_NOCHANGEPASSWD       = 0x40 ; Password change possible yes/no
Global Const $USER_NOEXPIREPASSWD       = 0x10000 ; Password expiration yes/no

Global $h_NewBits                       = BitOR($USER_NOCHANGEPASSWD, $USER_NOEXPIREPASSWD)
Global $s_ComputerName                  = @COMPUTERNAME


; main ;) 
; ==================================================================================================
$a_LocalUsers   = _GetUserNames()

For $i_i = 1 To UBound($a_LocalUsers) -1
    _UserCtrlAttribs($s_ComputerName & "/" & $a_LocalUsers[$i_i], "Clear", $h_NewBits)
Next


; Function _GetUserNames to find all local User-Accounts
; ==============================================================================================
Func _GetUserNames()
    Local $o_CollectedUsers, $a_Tmp[1], $a_ArrayOfUsers[1] = ["user"]
    $o_CollectedUsers = ObjGet("WinNT://" & $s_ComputerName)
    $o_CollectedUsers.Filter = $a_ArrayOfUsers
    For $o_UserObjects In $o_CollectedUsers
        _ArrayAdd($a_Tmp, $o_UserObjects.Name)
    Next
    Return $a_Tmp
EndFunc   ;==>_GetUserNames


; Function _UserCtrlAttribs to set certain Attributes for local user Accounts
; ==============================================================================================
Func _UserCtrlAttribs($s_UserName, $s_Mode, $i_AttributeBitsToChange)
    Local $o_UserObjects = ObjGet("WinNT://" & $s_UserName & ", User")
    $h_CurrentBits = $o_UserObjects.get("UserFlags")
    $i_Return = BitAND($h_CurrentBits, BitNOT($i_AttributeBitsToChange))
    $o_UserObjects.Put("UserFlags", $i_Return)
    $o_UserObjects.SetInfo
EndFunc   ;==>_UserCtrlAttribs

The code is functional and feature-complete (for our purposes). But can anyone of you imagine any way to further speed things up? On my system the .exe takes about 3,3 seconds to remove the attribute for about 25 users. It would be perfect if that was done in about 1,5 to 2,0 seconds...any ideas?

Best Regards,

Chris

p.s.: Also "size matters". :D You know, I know and probably she knows too - how to further lower the executable's size?

Edited by cherdeg
Link to comment
Share on other sites

To cut the size, take out the include for array, as you only use 1 function, and write the function straight in.

For $o_UserObjects In $o_CollectedUsers

ReDim $aTmp[uBound ($aTmp) + 1]

$aTmp[uBound ($aTmp) - 1] = $o_UserObjects.Name

Next

The other thing is to combine the 2 functions, so you only create half the objects.

Func _UserCtrlAllAttribs ($s_Mode, $i_AttributeBitsToChange)
    Local $o_CollectedUsers, $a_Tmp[1], $a_ArrayOfUsers[1] = ["user"]
    Local $o_UserObjects = ObjGet("WinNT://" & $s_UserName & ", User")
    $o_CollectedUsers = ObjGet("WinNT://" & $s_ComputerName)
    $o_CollectedUsers.Filter = $a_ArrayOfUsers
    For $o_UserObjects In $o_CollectedUsers
        $h_CurrentBits = $o_UserObjects.get ("UserFlags")
        $i_Return = BitAND($h_CurrentBits, BitNOT($i_AttributeBitsToChange))
        $o_UserObjects.Put("UserFlags", $i_Return)
        $o_UserObjects.SetInfo
    Next
    Return 1
EndFunc; ==> _UserCtrlAllAttribs

I'm not sure about the code though... It's not been tested but its the idea that counts :D

MDiesel

Link to comment
Share on other sites

To cut the size, take out the include for array, as you only use 1 function, and write the function straight in.

Okay, that's cool. It reduced the size from 292 Kbyte to, 284 Kbyte. That's way less than I awaited...but not your fault of course; great idea never the less!

The other thing is to combine the 2 functions, so you only create half the objects.

Yep, I thought about that idea too, but I can't get it to work properly; "Variable must be of type "Object"":

$h_CurrentBits = $o_UserObject.get("UserFlags")

$h_CurrentBits = $o_UserObject^ ERROR

Any idea what would be wrong with the line before? I assume the line:

$o_UserObject = ObjGet("WinNT://" & $o_UserObject.Name & ", User")

...doesn't get an object. So I also tried:

$o_CurrentUserName = $o_UserObject.Name

$o_UserObject = ObjGet("WinNT://" & $o_CurrentUserName & ", User")

...which also won't do it. Why? The only approach I can imagine is that items in a for...next loop are read-only (maybe even their child-objects?). But that's a shot into the blue...

Here my current version (which is at least way shorter than before):

; Hide the Tray-Icon
; ==================================================================================================
#NoTrayIcon


; Declaration of variables
; ==================================================================================================
Global Const $USER_NOCHANGEPASSWD       = 0x40 ; Password change possible yes/no
Global Const $USER_NOEXPIREPASSWD       = 0x10000 ; Password expiration yes/no

Global $h_NewBits                       = BitOR($USER_NOCHANGEPASSWD, $USER_NOEXPIREPASSWD)
Global $s_ComputerName                  = @COMPUTERNAME


; main()
; ==================================================================================================
_DisablePasswordNeverExpires($h_NewBits) ; $s_ComputerName & "/" & $a_LocalUsers[$i_i]


; Function _DisablePasswordNeverExpires to ... now what would you think?
; ==============================================================================================
Func _DisablePasswordNeverExpires ($i_AttributeBitsToChange)
    Local $o_CollectedUsers, $a_Tmp[1], $a_ArrayOfUsers[1] = ["user"]
    $o_CollectedUsers = ObjGet("WinNT://" & $s_ComputerName)
    $o_CollectedUsers.Filter = $a_ArrayOfUsers
    For $o_UserObject In $o_CollectedUsers
        $o_UserObject = ObjGet("WinNT://" & $o_UserObject.Name & ", User")
        $h_CurrentBits = $o_UserObject.get("UserFlags")
        $i_Return = BitAND($h_CurrentBits, BitNOT($i_AttributeBitsToChange))
        $o_UserObject.Put("UserFlags", $i_Return)
        $o_UserObject.SetInfo
    Next
EndFunc; ==> _DisablePasswordNeverExpires
Edited by cherdeg
Link to comment
Share on other sites

Okay - done!

The result is even shorter than awaited - and to nothing has to be paid as much attention as I thought before; Microsoft seems to try making it foolproof (even more than I like). The now again working code is:

; Hide the Tray-Icon
; ==================================================================================================
#NoTrayIcon


; Declaration of variables
; ==================================================================================================
Global Const $USER_NOCHANGEPASSWD       = 0x40 ; Password change possible yes/no
Global Const $USER_NOEXPIREPASSWD       = 0x10000 ; Password expiration yes/no

Global $h_NewBits                       = BitOR($USER_NOCHANGEPASSWD, $USER_NOEXPIREPASSWD)
Global $s_ComputerName                  = @COMPUTERNAME


; main()
; ==================================================================================================
_DisablePasswordNeverExpires($h_NewBits)


; Function _DisablePasswordNeverExpires to find all local User-Accounts
; ==============================================================================================
Func _DisablePasswordNeverExpires ($i_AttributeBitsToClear)
    Local $o_Collection, $a_FilterArray[1] = ["user"]
    $o_Collection = ObjGet("WinNT://" & $s_ComputerName)
    $o_Collection.Filter = $a_FilterArray
    For $o_UserObject In $o_Collection
        $h_AttributeSetCurrent = $o_UserObject.get("UserFlags")
        $i_AttributeSetNew = BitAND($h_AttributeSetCurrent, BitNOT($i_AttributeBitsToClear))
        $o_UserObject.Put("UserFlags", $i_AttributeSetNew)
        $o_UserObject.SetInfo
    Next
EndFunc; ==> _DisablePasswordNeverExpires

My mistake: I thought one had to fish out the "full user object" after getting "only" all the account names. But you don't get "only" the account names, you already get the full object in the 1st place. So no second ObjGet is needed, just .Put the changes and .SetInfo - done. The resulting code now takes only 2.8 instead of 3.3 seconds to process the 25 users and the .exe-File is 284 Kbytes in size.

Does anybody have something else to offer?

Edited by cherdeg
Link to comment
Share on other sites

One way to lower the size is to override the icon with a nice small one. I just did a little test :

UPXed with default icon "pack" : 284kb

UPXed with custom 48x48 (3,68kb) icon : 263kb

It's because AutoIt uses more than one icon. The difference is bigger for non-upxed cimpiled exes.

Edited by Inverted
Link to comment
Share on other sites

Local $aF[1] = ["user"], $oC = ObjGet("WinNT://" & @ComputerName)
$oC.Filter = $aF
For $oU In $oC
    $oU.Put("UserFlags", BitAND($oU.get("UserFlags"), -65601))
    $oU.SetInfo
Next

I very much doubt you will see a performance difference though, not with that method anyhow.

Why does it need to be so fast though? Surely you are executing a 1 off change?!

MDiesel

edit: even shorter code, and smaller variables.

Edited by mdiesel
Link to comment
Share on other sites

One way to lower the size is to override the icon with a nice small one.

Great...could you please upload the one you used? Don't have an icon generator here...and, btw.: how "to override"?

I very much doubt you will see a performance difference though, not with that method anyhow. Why does it need to be so fast though? Surely you are executing a 1 off change?!

Me too :D But one has to try...!

As I wrote in my OP, the .exe is meant to be started by a "Run"-entry in the registry of several machines. These machines are used for quite important driver builds and the developers leasing them are not willing to comply our security policies, which explicitly prohibit the use of the "lazy bum"-Attribute "Password never expires". If we manually click off the check, they will set it back the next day. The machines aren't domain members, so GPO is no option also; and local Security offers no option to do this afaik. But these guys are real-life developers; they have no clue of anything except their C++ compilers. So if a utility isn't located in the StartUp-folder, they won't find it. And if it isn't even noticeable (as in: "runs very fast") they won't start digging...

Edited by cherdeg
Link to comment
Share on other sites

The executable file won't get much smaller. The entire AutoIt interpreter is included, so there's nothing you can do about that. Even an empty script file will be almost that size.

What about writing an AutoIt compiler?

Just joking, keep cool :D

Edited by cherdeg
Link to comment
Share on other sites

The code is functional and feature-complete (for our purposes). But can anyone of you imagine any way to further speed things up? On my system the .exe takes about 3,3 seconds to remove the attribute for about 25 users. It would be perfect if that was done in about 1,5 to 2,0 seconds...any ideas?

@cherdeg

if using the slow COM method you should add a COM error handler and use IsObj() to check returned objects

NetUserEnum API is much faster.

NetUserEnum retrieves usernames and flags of all accounts

NetUserSetInfo sets flags

I assume you have policy set to not allow blank passwords?

example modified from this post by Ghost1987:

MakeMeAdmin (In AutoIt)

Edit: correction to second dllcall error handling, cleaned up variable declarations

Global $g_eventerror = 0; to be checked to know if com error occurs. Must be reset after handling.
Global $oMyError = ObjEvent("AutoIt.Error","ComErrorHandler")

Func ComErrorHandler()
    Dim $oMyError, $fDebugFlag = True
    Local $sHexNumber = Hex($oMyError.number,8)
    Local $sDesc = StringStripWS($oMyError.windescription, 2) 
    If $fDebugFlag Then
        ConsoleWrite("! AutoItCOM Test: We intercepted a COM Error !"       & @CRLF & @CRLF & _
                 "err.description is: " & @TAB & $oMyError.description     & @CRLF & _
                 "err.windescription:"   & @TAB & $oMyError.windescription  & @CRLF & _
                 "err.number is: "       & @TAB & $sHexNumber                 & @CRLF & _
                 "err.scriptline is: "   & @TAB & $oMyError.scriptline      & @CRLF)
             EndIf
    $oMyError.clear
    $g_eventerror = 1; something to check for when this function returns 
Endfunc

#NoTrayIcon
#include <Array.au3>
Opt('MustDeclareVars', 1)
Local $aUsers1 = _NetUserSetPasswordChangeExpireFlags(@ComputerName)
$aUsers1[0] &= " - " & @ComputerName
_ArrayDisplay($aUsers1)
Exit

;returns number of accounts with flags changed in returned array [0] element
;and names of user accounts changed in following elements

Func _NetUserSetPasswordChangeExpireFlags($sServer = "", $fNoDisabledAccounts = True)
;Author: rover, based on _NetUserEnum() code from here:
;MakeMeAdmin (In AutoIt) - Author: Ghost1987
;http://www.autoitscript.com/forum/index.php?showtopic=92252
    Local Const $UF_ACCOUNTDISABLE = 0x2
    Local Const $UF_PASSWD_CANT_CHANGE = 0x40
    Local Const $UF_DONT_EXPIRE_PASSWD = 0x10000
    Local Const $FILTER_NORMAL_ACCOUNT = 0x2
    Local Const $UF_NORMAL_ACCOUNT = 0x200
    Local Const $UF_SCRIPT = 0x1
    Local Const $MAX_PREFERRED_LENGTH = -1
    Local Const $NERR_Success = 0

    Local $i_AttributeBitsToClear = BitOR($UF_PASSWD_CANT_CHANGE, $UF_DONT_EXPIRE_PASSWD)
    Local $iUnread = 0, $aRet, $iError = 0, $tUSER_INFO_1008, $pBuf, $tName, $tFlag, _
    $iEntriesRead, $tTotalEntries, $tUserInfo1, $zUserInfo1, $aUserEnum[1] = [0]

    $aRet = DllCall("Netapi32.dll", "int", "NetUserEnum", "wstr", $sServer, "dword", 1, "dword", _
            $FILTER_NORMAL_ACCOUNT, "ptr*", 0, "dword", $MAX_PREFERRED_LENGTH, "dword*", 0, "dword*", 0, "ptr", 0)
    $iError = @error
    If @error Or $aRet[0] <> $NERR_Success Then
        If UBound($aRet) = 9 Then
            $iError = $aRet[0]
            DllCall("Netapi32.dll", "int", "NetApiBufferFree", "ptr", $aRet[4])
        EndIf
        Return SetError($iError, 1, $aUserEnum)
    EndIf

    $pBuf = $aRet[4]
    $iEntriesRead = $aRet[6]
    $tTotalEntries = $aRet[7]
    If $iEntriesRead <> $tTotalEntries Then $iUnread = $tTotalEntries - $iEntriesRead

    $tUserInfo1 = DllStructCreate("ptr;ptr;dword;dword;ptr;ptr;dword;ptr")
    $zUserInfo1 = DllStructGetSize($tUserInfo1)

    For $i = 1 To $iEntriesRead
        $tUserInfo1 = DllStructCreate("ptr;ptr;dword;dword;ptr;ptr;dword;ptr", $pBuf + ($i - 1) * $zUserInfo1)
        $tName = DllStructCreate("wchar[256]", DllStructGetData($tUserInfo1, 1))
        $tFlag = DllStructGetData($tUserInfo1, 7)
;ConsoleWrite('! User: ' & DllStructGetData($tName,1) & ' - Flags: ' &  $tFlag & @crlf)
        If BitAND($tFlag, $UF_ACCOUNTDISABLE) = 0 Or $fNoDisabledAccounts = False Then
            If BitAND($tFlag, $UF_PASSWD_CANT_CHANGE) = $UF_PASSWD_CANT_CHANGE Or _
                    BitAND($tFlag, $UF_DONT_EXPIRE_PASSWD) = $UF_DONT_EXPIRE_PASSWD Then
                $aUserEnum[0] += 1
                ReDim $aUserEnum[$aUserEnum[0] + 1]
                $aUserEnum[$aUserEnum[0]] = DllStructGetData($tName, 1)

                $tUSER_INFO_1008 = DllStructCreate("dword usri1008flags")
                $tFlag = BitAND($tFlag, BitNOT($i_AttributeBitsToClear))
                DllStructSetData($tUSER_INFO_1008, "usri1008flags", $tFlag)
                $aRet = DllCall("Netapi32.dll", "int", "NetUserSetInfo", "wstr", $sServer, _
                        "wstr", $aUserEnum[$aUserEnum[0]], "dword", 1008, _
                        "ptr", DllStructGetPtr($tUSER_INFO_1008), "dword*", 0)
                $iError = @error
                If @error Or $aRet[0] <> $NERR_Success Then
                    If UBound($aRet) = 6 Then $iError = $aRet[0]
                    DllCall("Netapi32.dll", "int", "NetApiBufferFree", "ptr", $pBuf)
                    Return SetError($iError, 2, $aUserEnum)
                EndIf
            EndIf
        ElseIf $tFlag < BitOR($UF_NORMAL_ACCOUNT, $UF_SCRIPT) Then
            $aUserEnum[0] += 1
            ReDim $aUserEnum[$aUserEnum[0] + 2]
            $aUserEnum[$aUserEnum[0] + 1] = "User account flag error: " & _
                    $tFlag & " -- User: " & DllStructGetData($tName, 1)
        EndIf
    Next

    If $iUnread <> 0 Then
        ReDim $aUserEnum[UBound($aUserEnum) + 1]
        $aUserEnum[UBound($aUserEnum) - 1] = "Accounts unread: " & $iUnread
    EndIf

    DllCall("Netapi32.dll", "int", "NetApiBufferFree", "ptr", $pBuf)
    Return SetError(0, 0, $aUserEnum)
EndFunc ;==>_NetUserSetPasswordChangeExpireFlags

Edit: correction to second dllcall error handling, cleaned up variable declarations

Edited by rover

I see fascists...

Link to comment
Share on other sites

Hi Rover,

nice to have another alternative: GREAT! But on my system it's not a bit faster that the "slow" COM version: 2.8 seconds. But of course you're generally right...if I had a slow computer with a lot of users it would probably play off its advantages.

But concerning code clarity, complexity and manageability...to tweak your version one would need a lot more skills. Never the less I'll try to understand... :D

Best Regards,

Chris

Edited by cherdeg
Link to comment
Share on other sites

Hi Rover,

nice to have another alternative: GREAT! But on my system it's not a bit faster that the "slow" COM version: 2.8 seconds. But of course you're generally right...if I had a slow computer with a lot of users it would probably play off its advantages.

But concerning code clarity, complexity and manageability...to tweak your version one would need a lot more skills. Never the less I'll try to understand... :D

Best Regards,

Chris

interesting

on one slow machine on my small 3 machine network

clearing target flags of 6 of 11 accounts, I get 3-6 seconds using COM, and about 1 second max using API

stick with the COM method and add the error handling

cheers

I see fascists...

Link to comment
Share on other sites

Rover, how do you measure? With any debugging / profiling tool? I just run the code from SciTe - maybe that's the problem?

ConsoleWrites and TimerInit/TimerDiff

Edit: It would help if I didn't post a quickly modified existing ConsoleWrite with @error/@extended reporting with a tacked on TimerDiff

and promptly wreck error reporting by forgetting to buffer the returned error codes before calling TimerDiff

#NoTrayIcon
    #include <Array.au3>
    Global $iTimer = TimerInit()
    Local $aUsers1 = _NetUserSetPasswordChangeExpireFlags(@ComputerName)
    Global $iErr = @error, $iExt = @extended
    ConsoleWrite("Time: " & Round(TimerDiff($iTimer)) & " - Error: " & $iErr & " - Extended: " & $iExt & @CRLF)
    $aUsers1[0] &= " - " & @ComputerName
    _ArrayDisplay($aUsers1)
    Exit

I also use SysInternals DebugView with OutputDebugString API for compiled scripts

#AutoIt3Wrapper_run_debug_mode=Y

help file has a few debug functions as well

haven't used them but there are debuggers on the forum as well

Edited by rover

I see fascists...

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

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...