Wednesday, March 21, 2012

VBScript Bit-wise Operators

Working with Active Directory  userAccountControl  and VBScript (.vbs files) to check for a disabled account. This is a much better alternative to checking if the account is equal to "514"or "546".  Read on to understand why!

(Full VBScript included at the very bottom) 

ADS_UF_ACCOUNTDISABLE = 2
UserAccountControl = 514
If (UserAccountControl AND ADS_UF_ACCOUNTDISABLE) = ADS_UF_ACCOUNTDISABLE Then
MsgBox UserAccountControl & " = Is Disabled!"
Else
MsgBox UserAccountControl & " = Is NOT Disabled!"
End If
The "AND" operator has two meanings in VBScript, depending on how it's used.  "AND" is normally used for Boolean Logic, such as True AND False; however in VBScript, when used between two numbers, "AND"/"OR" is used as a "Bit-wise Logical Operator", which is essential for comparing 1's and 0's underlying your decimal values.   The tricky part was knowing to use the parenthesis around the "AND".   statement. Without it, order of operations will evaluate the Equals sign first, and the AND statement second.    Keep reading to understand more.

Since userAccountControl  in AD is represented as a long ugly hexadecimal number, i.e. "0x00000002", finding out if a user is disabled is a very unintuitive task that requires expanding the value to binary and checking for a "1" or a "0" in the 2nd most right position (thanks to Bit-wise operators, we'll never have to do this, but rather it's provided as an explanation).
Decimal: 512
Binary:  1000000000
    *Note:  Red "0" indicates enabled account.
Decimal: 514
Binary:  1000000010
    *Note:  Red "1 "indicates disabled account.
Since expanding decimal to binary is a lot of work, computers have native support for these operations known as "Bit-wise Operators", sometimes referred to as "DWORD" calculations.

The bitwise operator known as a "Logical OR" treats a number, such as "512", "514" or "2" as binary and returns only the binary matches between the two numbers being compared.

Decimal: 2
Binary: 0000000010---------------------------
Decimal: 512
Binary: 1000000010
    *Note:  0 "OR" 1 is always "0".
---------------------------
Result: 000000001

  • So, 514 "OR" 2 is always 2!
  • This means 512 "OR" 2 is always 0!
  • This works for any number stored for user account control!
My colleague originally wanted to check if the account was equal to 514 or 546, but this is very bad, since different settings on the account could break this logic.

I had a hard time finding how to do logical "AND" logical "OR" in VBScript.  These operators are usually represented with "&" (AND) "|" (OR) in C based languages such as Java, C++, but VBScript simply uses the word "AND" and the word "OR" in-between numbers.

More on bitwise "OR" can be found here:  http://en.wikipedia.org/wiki/Bitwise_operation#OR

Test this out with 512, 544, etc. This is the RIGHT way to check account status.   For more account status codes, look here (Scroll down to remarks):  http://msdn.microsoft.com/en-us/library/windows/desktop/ms680832(v=vs.85).aspx


On Error Resume Next
Const ADS_SCOPE_SUBTREE = 2
Const ADS_UF_ACCOUNTDISABLE = 2
Set objConnection = CreateObject("ADODB.Connection")

Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection
Set fso = CreateObject("Scripting.FileSystemObject")
Set oStream = fso.CreateTextFile("DisabledUsers.csv", True)
objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommandProperties("Sort On") = "Name"
objCommand.CommandText = _    "SELECT 'Name', 'userAccountControl' FROM 'LDAP://dc=AD_DOMAIN,dc=YOURCOMPANY,dc=COM_NET_ETC' WHERE objectCategory='user'"
Set objRecordSet = objCommand.Execute
objRecordSet.MoveFirst
Do Until objRecordSet.EOF  
   'oStream.Write(objRecordSet.Fields("Name").Value & ",")  
   'objRecordSet.MoveNext  
   If (objRecordSet.Fields("userAccountControl").Value AND ADS_UF_ACCOUNTDISABLE) = ADS_UF_ACCOUNTDISABLE Then       oStream.WriteLine(objRecordSet.Fields("Name").Value & ",")
   End If
   objRecordSet.MoveNext
Loop
WScript.Quit 




-Tres




No comments: