howto decode Active Directory attribute UserAccountControl

#1
hello programmers,
I know there are several tools to extract informations from AD,
but I ask myself if I could decode the attribute UserAccountControl with TCC in-house means.
If I use ADFIND I get integer values as described here.
Code:
>adfind -f "objectcategory=person" -b ou=dv,ou=!ber,dc=company,dc=de useraccountcontrol -csv
"dn","useraccountcontrol"
"CN=test\, sap,OU=DV,OU=!BER,DC=company,DC=de","514"    (NORMAL_ACCOUNT + disabled)
"CN=Berlin\, Willi,OU=Users,OU=DV,OU=!BER,DC=company,DC=de","66048"    (NORMAL_ACCOUNT + DONT_EXPIRE_PASSWORD)
"CN=xxxxxxxxx\, Frank,OU=Users,OU=DV,OU=!BER,DC=company,DC=de","512"    (NORMAL_ACCOUNT)
"CN=Test-User\, Tony,OU=people,OU=DV,OU=!BER,DC=company,DC=de","514"    (NORMAL_ACCOUNT + disabled)
"CN=pwdtest\, frank,OU=DV,OU=!BER,DC=company,DC=de","514"      (NORMAL_ACCOUNT + disabled)
I would like to do something like this:
Code:
do      account in @adfind.out
        set    dn=%@word[",",0,%account]
        set    uac=%@unquote[%@word[",",1,%account]]
        gosub  check-uac
       echo%dn:%@if[PASSWD_NOTREQD eq 1,yes,no],%@if[ACCOUNTDISABLE eq 1,yes,no]...
enddo
This is a rough-and-ready example. I just want to create a list of accounts with special attributes (set or not set).
The challenge is to decode the integer field into the property flags.
Any help would be appreciated.

btw:
just listening to "Dean Brown, Here" :cool:
 
#3
Any help would be appreciated.
Code:
@ECHO OFF
SET IntegerField=100613115
IFF %IntegerField GE 67108864 THEN
    ECHO PARTIAL_SECRETS_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-67108864]
ENDIFF
IFF %IntegerField GE 16777216 THEN
    ECHO TRUSTED_TO_AUTH_FOR_DELEGATION
    SET IntegerField=%@EVAL[%IntegerField-16777216]
ENDIFF
IFF %IntegerField GE 8388608 THEN
    ECHO PASSWORD_EXPIRED
    SET IntegerField=%@EVAL[%IntegerField-8388608]
ENDIFF
IFF %IntegerField GE 4194304 THEN
    ECHO DONT_REQ_PREAUTH
    SET IntegerField=%@EVAL[%IntegerField-4194304]
ENDIFF
IFF %IntegerField GE 2097152 THEN
    ECHO USE_DES_KEY_ONLY
    SET IntegerField=%@EVAL[%IntegerField-2097152]
ENDIFF
IFF %IntegerField GE 1048576 THEN
    ECHO NOT_DELEGATED
    SET IntegerField=%@EVAL[%IntegerField-1048576]
ENDIFF
IFF %IntegerField GE 524288 THEN
    ECHO TRUSTED_FOR_DELEGATION
    SET IntegerField=%@EVAL[%IntegerField-524288]
ENDIFF
IFF %IntegerField GE 262144 THEN
    ECHO SMARTCARD_REQUIRED
    SET IntegerField=%@EVAL[%IntegerField-262144]
ENDIFF
IFF %IntegerField GE 131072 THEN
    ECHO MNS_LOGON_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-131072]
ENDIFF
IFF %IntegerField GE 65536 THEN
    ECHO DONT_EXPIRE_PASSWORD
    SET IntegerField=%@EVAL[%IntegerField-65536]
ENDIFF
IFF %IntegerField GE 8192 THEN
    ECHO SERVER_TRUST_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-8192]
ENDIFF
IFF %IntegerField GE 4096 THEN
    ECHO WORKSTATION_TRUST_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-4096]
ENDIFF
IFF %IntegerField GE 2048 THEN
    ECHO INTERDOMAIN_TRUST_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-2048]
ENDIFF
IFF %IntegerField GE 512 THEN
    ECHO NORMAL_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-512]
ENDIFF
IFF %IntegerField GE 256 THEN
    ECHO TEMP_DUPLICATE_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-256]
ENDIFF
IFF %IntegerField GE 128 THEN
    ECHO ENCRYPTED_TEXT_PWD_ALLOWED
    SET IntegerField=%@EVAL[%IntegerField-128]
ENDIFF
IFF %IntegerField GE 64 THEN
    ECHO PASSWD_CANT_CHANGE
    SET IntegerField=%@EVAL[%IntegerField-64]
ENDIFF
IFF %IntegerField GE 32 THEN
    ECHO PASSWD_NOTREQD
    SET IntegerField=%@EVAL[%IntegerField-32]
ENDIFF
IFF %IntegerField GE 16 THEN
    ECHO LOCKOUT
    SET IntegerField=%@EVAL[%IntegerField-16]
ENDIFF
IFF %IntegerField GE 8 THEN
    ECHO HOMEDIR_REQUIRED
    SET IntegerField=%@EVAL[%IntegerField-8]
ENDIFF
IFF %IntegerField GE 2 THEN
    ECHO ACCOUNTDISABLE
    SET IntegerField=%@EVAL[%IntegerField-2]
ENDIFF
IFF %IntegerField GE 1 THEN
    ECHO SCRIPT
    SET IntegerField=%@EVAL[%IntegerField-1]
ENDIFF
ECHO %IntegerField: should be zero now.
 
#4
Code:
@ECHO OFF
SET IntegerField=100613115
IFF %IntegerField GE 67108864 THEN
    ECHO PARTIAL_SECRETS_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-67108864]
ENDIFF
IFF %IntegerField GE 16777216 THEN
    ECHO TRUSTED_TO_AUTH_FOR_DELEGATION
    SET IntegerField=%@EVAL[%IntegerField-16777216]
ENDIFF
IFF %IntegerField GE 8388608 THEN
    ECHO PASSWORD_EXPIRED
    SET IntegerField=%@EVAL[%IntegerField-8388608]
ENDIFF
IFF %IntegerField GE 4194304 THEN
    ECHO DONT_REQ_PREAUTH
    SET IntegerField=%@EVAL[%IntegerField-4194304]
ENDIFF
IFF %IntegerField GE 2097152 THEN
    ECHO USE_DES_KEY_ONLY
    SET IntegerField=%@EVAL[%IntegerField-2097152]
ENDIFF
IFF %IntegerField GE 1048576 THEN
    ECHO NOT_DELEGATED
    SET IntegerField=%@EVAL[%IntegerField-1048576]
ENDIFF
IFF %IntegerField GE 524288 THEN
    ECHO TRUSTED_FOR_DELEGATION
    SET IntegerField=%@EVAL[%IntegerField-524288]
ENDIFF
IFF %IntegerField GE 262144 THEN
    ECHO SMARTCARD_REQUIRED
    SET IntegerField=%@EVAL[%IntegerField-262144]
ENDIFF
IFF %IntegerField GE 131072 THEN
    ECHO MNS_LOGON_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-131072]
ENDIFF
IFF %IntegerField GE 65536 THEN
    ECHO DONT_EXPIRE_PASSWORD
    SET IntegerField=%@EVAL[%IntegerField-65536]
ENDIFF
IFF %IntegerField GE 8192 THEN
    ECHO SERVER_TRUST_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-8192]
ENDIFF
IFF %IntegerField GE 4096 THEN
    ECHO WORKSTATION_TRUST_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-4096]
ENDIFF
IFF %IntegerField GE 2048 THEN
    ECHO INTERDOMAIN_TRUST_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-2048]
ENDIFF
IFF %IntegerField GE 512 THEN
    ECHO NORMAL_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-512]
ENDIFF
IFF %IntegerField GE 256 THEN
    ECHO TEMP_DUPLICATE_ACCOUNT
    SET IntegerField=%@EVAL[%IntegerField-256]
ENDIFF
IFF %IntegerField GE 128 THEN
    ECHO ENCRYPTED_TEXT_PWD_ALLOWED
    SET IntegerField=%@EVAL[%IntegerField-128]
ENDIFF
IFF %IntegerField GE 64 THEN
    ECHO PASSWD_CANT_CHANGE
    SET IntegerField=%@EVAL[%IntegerField-64]
ENDIFF
IFF %IntegerField GE 32 THEN
    ECHO PASSWD_NOTREQD
    SET IntegerField=%@EVAL[%IntegerField-32]
ENDIFF
IFF %IntegerField GE 16 THEN
    ECHO LOCKOUT
    SET IntegerField=%@EVAL[%IntegerField-16]
ENDIFF
IFF %IntegerField GE 8 THEN
    ECHO HOMEDIR_REQUIRED
    SET IntegerField=%@EVAL[%IntegerField-8]
ENDIFF
IFF %IntegerField GE 2 THEN
    ECHO ACCOUNTDISABLE
    SET IntegerField=%@EVAL[%IntegerField-2]
ENDIFF
IFF %IntegerField GE 1 THEN
    ECHO SCRIPT
    SET IntegerField=%@EVAL[%IntegerField-1]
ENDIFF
ECHO %IntegerField: should be zero now.
Hello John,

thank you for the help! That's cool.
I was thinking much too complicated with bit-comparision or something like that.
I think your solution is lean, clean and easy to understand. Thank you again.

regards
Frank
 
#5
I was thinking much too complicated with bit-comparision or something like that.
I think your solution is lean, clean and easy to understand. Thank you again.
Here is a version that does bit comparisons and uses a stored array to report the names of flags that are set:
Code:
@echo off
iff %@arrayinfo[active_dir_flags,0] LT 0 then
  setarray active_dir_flags[32]
  set active_dir_flags[26]=PARTIAL_SECRETS_ACCOUNT
  set active_dir_flags[24]=TRUSTED_TO_AUTH_FOR_DELEGATION
  set active_dir_flags[23]=PASSWORD_EXPIRED
  set active_dir_flags[22]=DONT_REQ_PREAUTH
  set active_dir_flags[21]=USE_DES_KEY_ONLY
  set active_dir_flags[20]=NOT_DELEGATED
  set active_dir_flags[19]=TRUSTED_FOR_DELEGATION
  set active_dir_flags[18]=SMARTCARD_REQUIRED
  set active_dir_flags[17]=MNS_LOGON_ACCOUNT
  set active_dir_flags[16]=DONT_EXPIRE_PASSWORD
  set active_dir_flags[13]=SERVER_TRUST_ACCOUNT
  set active_dir_flags[12]=WORKSTATION_TRUST_ACCOUNT
  set active_dir_flags[11]=INTERDOMAIN_TRUST_ACCOUNT
  set active_dir_flags[9]=NORMAL_ACCOUNT
  set active_dir_flags[8]=TEMP_DUPLICATE_ACCOUNT
  set active_dir_flags[7]=ENCRYPTED_TEXT_PWD_ALLOWED
  set active_dir_flags[6]=PASSWD_CANT_CHANGE
  set active_dir_flags[5]=PASSWD_NOTREQD
  set active_dir_flags[4]=LOCKOUT
  set active_dir_flags[3]=HOMEDIR_REQUIRED
  set active_dir_flags[1]=ACCOUNTDISABLE
  set active_dir_flags[0]=SCRIPT
endiff
set /qa mask=0x8000000
do n = 31 to 0 by -1
  set v=%active_dir_flags[%n]
  if defined v if %@eval[ %mask & %1] GT 0 echo %v
  set /qa mask=%mask SHR 1
enddo
Notes:
1/ If the above is stored as a batchfile ACTIVE_FLAGS.BTM, invoke it as
call ACTIVE_FLAGS flag_word
2/ It makes the array name ACTIVE_DIR_FLAGS reserved (to avoid multiple initializations). One could instead UNSETARRAY ACTIVE_DIR_FLAGS at the end.
3/ the "set v..." statement is used because "if defined array[index]" is FALSE even if the array element is initialized to a non-empty string. Alternately one could use the single statement:
if "%active_dir_flags[%n]" NE "" if %@eval[ %mask & %1] GT 0 echo %active_dir_flags[%n]
In both forms I use "if test1 if test2 command" to avoid evaluating test2 if test1 is FALSE. Combining the tests with .and. would result in evaluating test2 regardless of the value of test1, which - though harmless in this instance - often results in error when test1 is FALSE.
 
#6
Here is a version that does bit comparisons and uses a stored array to report the names of flags that are set:
Wow, you're using functions that I've noticed in the doc eventually but never really thought about their meaning.
Now that you gave me such an excellent example, I will try to occupy myself with them.
Thank you.
 
#7
Here is a version that does bit comparisons and uses a stored array to report the names of flags that are set:
Code:
@echo off
iff %@arrayinfo[active_dir_flags,0] LT 0 then
  setarray active_dir_flags[32]
  set active_dir_flags[26]=PARTIAL_SECRETS_ACCOUNT
  set active_dir_flags[24]=TRUSTED_TO_AUTH_FOR_DELEGATION
  set active_dir_flags[23]=PASSWORD_EXPIRED
  set active_dir_flags[22]=DONT_REQ_PREAUTH
  set active_dir_flags[21]=USE_DES_KEY_ONLY
  set active_dir_flags[20]=NOT_DELEGATED
  set active_dir_flags[19]=TRUSTED_FOR_DELEGATION
  set active_dir_flags[18]=SMARTCARD_REQUIRED
  set active_dir_flags[17]=MNS_LOGON_ACCOUNT
  set active_dir_flags[16]=DONT_EXPIRE_PASSWORD
  set active_dir_flags[13]=SERVER_TRUST_ACCOUNT
  set active_dir_flags[12]=WORKSTATION_TRUST_ACCOUNT
  set active_dir_flags[11]=INTERDOMAIN_TRUST_ACCOUNT
  set active_dir_flags[9]=NORMAL_ACCOUNT
  set active_dir_flags[8]=TEMP_DUPLICATE_ACCOUNT
  set active_dir_flags[7]=ENCRYPTED_TEXT_PWD_ALLOWED
  set active_dir_flags[6]=PASSWD_CANT_CHANGE
  set active_dir_flags[5]=PASSWD_NOTREQD
  set active_dir_flags[4]=LOCKOUT
  set active_dir_flags[3]=HOMEDIR_REQUIRED
  set active_dir_flags[1]=ACCOUNTDISABLE
  set active_dir_flags[0]=SCRIPT
endiff
set /qa mask=0x8000000
do n = 31 to 0 by -1
  set v=%active_dir_flags[%n]
  if defined v if %@eval[ %mask & %1] GT 0 echo %v
  set /qa mask=%mask SHR 1
enddo
Notes:
1/ If the above is stored as a batchfile ACTIVE_FLAGS.BTM, invoke it as
call ACTIVE_FLAGS flag_word
2/ It makes the array name ACTIVE_DIR_FLAGS reserved (to avoid multiple initializations). One could instead UNSETARRAY ACTIVE_DIR_FLAGS at the end.
3/ the "set v..." statement is used because "if defined array[index]" is FALSE even if the array element is initialized to a non-empty string. Alternately one could use the single statement:
if "%active_dir_flags[%n]" NE "" if %@eval[ %mask & %1] GT 0 echo %active_dir_flags[%n]
In both forms I use "if test1 if test2 command" to avoid evaluating test2 if test1 is FALSE. Combining the tests with .and. would result in evaluating test2 regardless of the value of test1, which - though harmless in this instance - often results in error when test1 is FALSE.
Hi Steve,

please give me a private lesson :)

I (try to) understand your example as following:
  • you're filling an array with 27 (of max. 32) strings
  • you define a bit-mask with 29 bits which is sufficient for the 27 flags and the highest bit is set to on ?
  • instead of using 0x8000000 I could also use 0x80000000 (32 bits) ?
  • now we do a loop to check all bits from left/highest to right/lowest ?
  • if we have a valid array element at all, the bit is checked with "AND" and if set the string is echoed ?
  • at the end of each iteration we shift the bit to the right ?
    But I don't really understand the doc for "@eval SHR":
    "arithmetic right shift of the first parameter, truncated toward zero to an integer, by the number of bits specified by the second parameter"
Please notice the ? , because I just try to understand.
And please give me an advice if I am wrong.
Thank you in advance.
 
#8
Hi Steve,

please give me a private lesson :)

I (try to) understand your example as following:
  • you're filling an array with 27 (of max. 32) strings
yes

  • you define a bit-mask with 29 bits which is sufficient for the 27 flags and the highest bit is set to on ?
    [*]instead of using 0x8000000 I could also use 0x80000000 (32 bits) ?
there is a copying error here - the last 0 was lost somewhere, the starting mask is supposed to be 2^31

  • now we do a loop to check all bits from left/highest to right/lowest ?
yes
  • if we have a valid array element at all, the bit is checked with "AND" and if set the string is echoed ?
yes (bits for which there is no initialized array element are not significant and thus ignored)
yes
  • But I don't really understand the doc for "@eval SHR":
    "arithmetic right shift of the first parameter, truncated toward zero to an integer, by the number of bits specified by the second parameter"[/quote]
"the first parameter, truncated toward zero to an integer" means that if the first parameter is not an integer, its fractional part is discarded, so the absolute value of the parameter becomes smaller - it becomes closer to zero; the resulting value used. "shifting by the number of bits" implies the value of the parameter is represented in binary, and the bit pattern is shifted to the right - commonly understood to mean toward smaller values. "Arithmetic shift" is NOT defined, but is commonly understood to mean that the binary representation is conisdered to be in 2's complement mode, so the MSB is the sign, 1 for negative and 0 for positive values. In an "arithmetic right shift" under these conditions the MSB is copied into the 2nd MSB for each bit by which the value is shifted. In effect each bit by which the parameter is shifted divides it by 2, shifting by "n" bits divides it by 2^n. The least defined issue is the size of the word for the arithmetic shift. On my 32-bit system running WinXP SP3 home, @EVAL uses 64-bit integers, so the operation is correct.
--
HTH, Steve
 
#9
Yes, this helped me very much (although I will have to read the explanation even some more times).
Thank you.