Trying to iterate multiple columns from security user logons to our 7 server environments [where an individual user could have mulriple logons - I decided to throw in columns from an ADSI lookup based on 'samaccountname' ... and for real fun asked the query to return the useracountcontrol... which in most cases gave 512 but in the few needed to be removed from privileges - 514....
and, I know you are now thinking "we already know that"
So, my question is simple: what is the difference between an enum, a map, an array, a list , a hashtable [as declared as such]... so that I can prove a return code of 514 is based on the B'or of 512/2 based on
SCRIPT = 1
ACCOUNTDISABLE = 2
HOMEDIR_REQUIRED = 8
LOCKOUT = 16
PASSWD_NOTREQD = 32
PASSWD_CANT_CHANGE = 64
ENCRYPTED_TEXT_PWD_ALLOWED = 128
TEMP_DUPLICATE_ACCOUNT = 256
NORMAL_ACCOUNT = 512
INTERDOMAIN_TRUST_ACCOUNT = 2048
WORKSTATION_TRUST_ACCOUNT = 4096
SERVER_TRUST_ACCOUNT = 8192
DONT_EXPIRE_PASSWORD = 65536
MNS_LOGON_ACCOUNT = 131072
SMARTCARD_REQUIRED = 262144
TRUSTED_FOR_DELEGATION = 524288
NOT_DELEGATED = 1048576
USE_DES_KEY_ONLY = 2097152
DONT_REQ_PREAUTH = 4194304
PASSWORD_EXPIRED = 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216
PARTIAL_SECRETS_ACCOUNT = 67108864
Based on what I Bing'd... calling the above an enum, is the best bet. NOTE: there is a lot on Tech Support about ADSI and the Extender so nothing against it.
But: retrieving a value of 514, how to determne the lookup(s) text for that value.
If you were working with C#, you'd declare an 'enum' type, possibly with an underlying 'uint' ['UInt32'] type, and apply the '[Flags]' attribute to it.
Combining flag bit values via bit-wise 'OR' operations is simple to produce a flags mask value.
Converting a flags mask value into individual bits and then deriving string name values is a bit more of a chore. In effect, you have to take the flags mask and perform a bit-wise 'AND' operation with each defined flag bit value in the enumerated type and test for that bit's value or just for a non-zero value. If there is a match, compute the necessary name value as a string and return it. Given that there can be multiple bits enabled in the flags mask, the return value could have multiple string values with names for each enabled bit so using some kind of container/collection of strings is advisable.
If you need to maintain an association between each flag bit and its corresponding name in your return value(s), you could utilize a map. The same map could also be utilized if just returning a name for each flag bit.
Thanks Chuck. Looking over old adsi code I wrote in 2010 to parse the useraccountcontrol value. Was looking at a CLR option of a way to construct an enum.
;Stan Littlefield, November 1, 2010
;/////////////////////////////////////////////////////////////////////////////////////////
oRoot = getobject("LDAP://RootDSE")
oLDAP = GetObject("LDAP://ou=admins,":oRoot.get("defaultnamingcontext")) ;substitute for your own value
ForEach user in olDAP
If user.name=="CN=myadmin" ;substitute for your own value
tags=""
iUser=user.Get("userAccountControl")
While iUser > 0
If iUser >= 134217728 && iUser <= 2147483647 ;Then - optional
iUser = iUser - 134217728
tags=tags:"USE_AES_KEYS":@CRLF
elseif iUser >= 67108864 && iUser < 134217728 ;Then - optional
iUser = iUser - 67108864
tags=tags:"PARTIAL_SECRETS_ACCOUNT":@CRLF
elseif iUser >= 16777216 && iUser < 67108864
iUser= iUser - 16777216
tags=tags:"TRUSTED_TO_AUTH_FOR_DELEGATION":@CRLF
elseif iUser >= 8388608 && iUser < 16777216
iUser= iUser - 8388608
tags=tags:"PASSWORD_EXPIRED":@CRLF
elseif iUser >= 4194304 && iUser < 8388608
iUser= iUser - 4194304
tags=tags:"DONT_REQ_PREAUTH":@CRLF
elseif iUser >= 2097152 && iUser < 4194304
iUser= iUser - 2097152
tags=tags:"USE_DES_KEY_ONLY":@CRLF
elseif iUser >= 1048576 && iUser < 2097152
iUser= iUser - 1048576
tags=tags:"NOT_DELEGATED":@CRLF
elseif iUser >= 524288 && iUser < 1048576
iUser= iUser - 524288
tags=tags:"TRUSTED_FOR_DELEGATION":@CRLF
elseif iUser >= 262144 && iUser < 524288
iUser= iUser - 262144
tags=tags:"SMARTCARD_REQUIRED":@CRLF
elseif iUser >= 131072 && iUser < 262144
iUser= iUser - 131072
tags=tags:"MNS_LOGON_ACCOUNT":@CRLF
elseif iUser >= 65536 && iUser < 131072
iUser= iUser - 65536
tags=tags:"DONT_EXPIRE_PASSWORD":@CRLF
elseif iUser >= 8192 && iUser < 65536
iUser= iUser - 8192
tags=tags:"SERVER_TRUST_ACCOUNT":@CRLF
elseif iUser >= 4096 && iUser < 8192
iUser= iUser - 4096
tags=tags:"WORKSTATION_TRUST_ACCOUNT":@CRLF
elseif iUser >= 2048 && iUser < 4096
iUser= iUser - 2048
tags=tags:"INTERDOMAIN_TRUST_ACCOUNT":@CRLF
elseif iUser >= 512 && iUser < 2048
iUser= iUser - 512
tags=tags:"NORMAL_ACCOUNT":@CRLF
elseif iUser >= 256 && iUser < 512
iUser= iUser - 256
tags=tags:"TEMP_DUPLICATE_ACCOUNT":@CRLF
elseif iUser >= 128 && iUser < 256
iUser= iUser - 128
tags=tags:"ENCRYPTED_TEXT_PWD_ALLOWED":@CRLF
elseif iUser >= 64 && iUser < 128
iUser= iUser - 64
tags=tags:"PASSWD_CANT_CHANGE":@CRLF
elseif iUser >= 32 && iUser < 64
iUser= iUser - 32
tags=tags:"PASSWD_NOTREQD":@CRLF
elseif iUser >= 16 && iUser < 32
iUser= iUser - 16
tags=tags:"LOCKOUT":@CRLF
elseif iUser >= 8 && iUser < 16
iUser= iUser - 8
tags=tags:"HOMEDIR_REQUIRED":@CRLF
elseif iUser >= 2 && iUser < 8
iUser= iUser - 2
tags=tags:"ACCOUNTDISABLE":@CRLF
elseif iUser >= 1 && iUser < 2
iUser= iUser - 1
tags=tags:"SCRIPT":@CRLF
EndIf
Endwhile
Message("",tags)
Endif
Next
iUser=0
oLDAP=0
Exit
;/////////////////////////////////////////////////////////////////////////////////////////
You could dynamically compile some C# code into an in-memory assembly where it defines the enumerated type and provides a class with methods to perform the conversions between flag bit values and enumerated value names. C# has support for getting the name of each value from an enum type and vice versa for providing a string name and getting the enum value.
Quote from: ChuckC on November 08, 2023, 06:41:50 AM
You could dynamically compile some C# code into an in-memory assembly where it defines the enumerated type and provides a class with methods to perform the conversions between flag bit values and enumerated value names. C# has support for getting the name of each value from an enum type and vice versa for providing a string name and getting the enum value.
Thanks again. I'm looking at playing with System.DirectoryServices Namespace for searching Active Directory. I found PS code for the enum - it returns one or more enum names based on bitwise or - i.e. if useraccountcontrol returns 512 it returns NORMAL_ACCOUNT, while 514 returns ACCOUNTDISABLE + NORMAL_ACCOUNT => which is what I am after. I'll take a look at C# but out of laziness will probably just call PS code from WB's CLR ::)
Another approach:
EnumPairs = $"1,SCRIPT
2,ACCOUNTDISABLE
8,HOMEDIR_REQUIRED
16,LOCKOUT
32,PASSWD_NOTREQD
64,PASSWD_CANT_CHANGE
128,ENCRYPTED_TEXT_PWD_ALLOWED
256,TEMP_DUPLICATE_ACCOUNT
512,NORMAL_ACCOUNT2
2048,INTERDOMAIN_TRUST_ACCOUNT
4096,WORKSTATION_TRUST_ACCOUNT
8192,SERVER_TRUST_ACCOUNT
65536,DONT_EXPIRE_PASSWORD
131072,MNS_LOGON_ACCOUNT
262144,SMARTCARD_REQUIRED
524288,TRUSTED_FOR_DELEGATION
1048576,NOT_DELEGATED
2097152,USE_DES_KEY_ONLY
4194304,DONT_REQ_PREAUTH
8388608,PASSWORD_EXPIRED
16777216,TRUSTED_TO_AUTH_FOR_DELEGATION
67108864,PARTIAL_SECRETS_ACCOUNT$"
EnumPairs = StrReplace(EnumPairs, @cr, "") ; Remove editor dependence
UacSettings = MapCreate(EnumPairs, ",", @lf)
UserVals = 514
SettingsText = ""
foreach Key in UacSettings
if UserVals & Key
if SettingsText != "" then SettingsText :="|"
SettingsText := UacSettings[Key]
UserVals &= (~Key)
endif
if !UserVals then break
next
Message("User UAC Settings", SettingsText)
exit
Damn. Pretty Slick.
EDIT:
So an enum could be treated as a Map by inverting the key|value. I now have several to test, and to return to my initial post where I stated:
So, my question is simple: what is the difference between an enum, a map, an array, a list , a hashtable [as declared as such].
I did modify the code you sent to use = instead of , for separator and, of course, still worked fine, and I appreciate making it simple w/out having to integrate PS code. ;)