You not logged on | Log On | Register
     

Home > Steve's Scripting Corner > Enumerations and Bitwise Operations

Enumerations and Bitwise Operations
October 2004

Welcome to the very first (and if this goes over as well as I fear, the very last) entry to Steve's Scripting Corner (SSC).  The thought behind the SSC is to provide some timely and often overlooked scripting information that may help to fill in some of the blanks for those relatively new to scripting, as well as provide the old timers with some "Ah!  That's why it works that way..." moments.  Today in the SSC we're going to talk about enumerations and bitwise operations, as the title so subtly suggests.

Enumerations
Before we talk about how to use enumerations we should first define what they are and why they're useful to scripters.  Instead of actually looking up an erudite definition that no one really cares to hear anyway, in simplest terms an enumeration is a way to create a human readable string from a number.  Since computers work with numbers and we work with ideas, thoughts and words (i.e. strings) enumerations are just a way to make it easier for us to explain our intentions to the computer without having to translate all these numbers in our head.  Using enumerations forces the computer to translate our readable string to a number.  In short, we're shifting the string/number translation burden to the computer.

If you prefer to do all the dirty work yourself, then by all means go ahead and skip right over this article to next months mind-blowing topic.  But for the rest of us, productivity tends to increase the less you are forced to focus on the mundane details and enumerations can certainly help us in this regard.

The news isn't all rosy for the scripting community.  Unlike Visual Basic or C++, scripting languages like VBScript or KiXtart do not directly support the use of enumerated values. Rather, you must define as variables (or constants) the enumeration members that you use in your scripts.  In that regard, when writing scripts we aren't using enumerations in the strict sense.  For example, in VB, you could use:

VB:

If fsoDrive("d").DriveType = CDRom then
 . . .
End If

But in either VBScript or KiXtart you would have to define the number that corresponds to 'CDRom.'

Before we can do that, however, we need to determine what number actually corresponds to the 'CDRom' enumerated type.  There are several ways to do this, but I'll just provide two methods.  As you might have guessed from the sample, we are dealing with the FileSystemObject COM object.  If you don't know what that is, don't worry.  It's just a COM object that makes working with files and directories much easier.  The script samples don't show the creation of the fsoDrive object, it is assumed to already exist.  We're really more concerned with determining the number behind the enumerated type.  The easiest way to get this information is to launch your registered or trial version of AdminScriptEditor, expand the COM browser and select "Microsoft Scripting Runtime" from the dropdown box.  You will then see all the objects contained in that COM library. 

Of particular interest to us are the enumerations, denoted by the two yellow tags.  When opening the 'DriveTypeConst' enumeration, all of the enumeration members are displayed.  When selecting CDRom, the info pane at the bottom of the COM browser shows the number associated with the CDRom enumeration, which is 4.  Now, all that's left to do is assign the variable (or constant) so that we can use it in our script.  So, to rewrite the sample above for VBScript, we have the following:

VBScript:

Const CDRom = 4
If fsoDrive("d").DriveType = CDRom then
 . . .
End If

You may be asking yourself, "Is the only difference in using enumerations in a script that I have to define the enumerated value explicity?" In a word, yes! It does require that you take the extra step to find out what the value is and then assign it to a variable, but once that is done then the hard part is over and you can use those variables just as you would use an enumerated type. As KiXtart doesn't support a constant, you need to define these enumerations as standard variables. In VBScript, the only real difference between a CONST and a DIMed variable is that once assigned, you cannot change the value of a constant. If you try it, you will get an "illegal assignment" error. As long as you know not to change these values, then it really makes no difference whether or not you use a variable or a constant.

A second way to get this enumeration information is from an iTripoli freeware utility called the TypeLibrary Viewer.  This very handy utility allows you to open any COM object file (.exe, .ocx, .dll, .tlb, .olb) and query the objects it contains.  It's a terrific tool that belongs in every scripter's figurative toolbox. Of course, if you use/own ASE, then there is no need for this tool as it is built-in to the editor.

You might also be asking yourself is all of this necessary?  Well, yes and no.  You can always use the number in place of the enumerated value.  But you need to find the number that corresponds to the value you're looking for anyway.  Once you find it, assigning that value to a variable will only make your script that much more maintainable and readable.  This is especially true when you are called upon to update or fix someone else's script.  If they failed to use enumerations then you will immediately see their value.  Or worse yet, if you need to fix your own script because some genius you work with was "trying to make your script better."  In either case, their utility is immediately obvious.

Bitwise Operations

Although not directly related, bitwise operations are often associated with, and used alongside, enumerations.  Once again eschewing a Merriam-Webster-esque definition, bitwise operations are logical functions performed on binary numbers.  Unlike mathematical operands (+, -, *, /), bitwise operations are logical comparisons of two or more bit values using AND, OR, XOR, and NOT as the bitwise operands.

Before we go any further, here's just a quick review in converting binary numbers to decimal.  Our Base10 number system raises each digit placeholder by a power of 10.  1000 is ten times the value of 100, etc.  In a binary system, each digit is raised by a power of 2, hence the term binary, as shown in the following chart:

Since there are only two values in a binary number system, 0 and 1, each digit from right to left is twice the value of its neighbor (also known as "little-endian").  To evaluate the number 10011010 in our example, we simply look at the placeholder value for whichever bits are set to one, or those digits that are "switched on".  We see from the chart, that the bits representing 128, 16, 8 and 2 are switched on.  When added, these values total 154--and that is the decimal value for the binary number 10011010.

With that behind us we can now look at how to perform these logical bitwise operations on two binary numbers.  These operations are either of the AND, OR, or XOR variety.  You may have heard, or read of, bits being ANDed or ORed together.  Those are just references to these bitwise operations.  These comparisons are actually quite simple:

  • When ANDing, the resulting bit is one if and only if both comparison bits are set to one
  • When ORing, the resulting bit is one if either bit is set to one
  • When XORing, the resulting bit is one if and only if one of the bits is set to one

This is easiest to demonstrate through some examples.  Let's continue using our number 154 and we'll compare it with the number 49.  Following are examples of all three types of operations.  Take a close look at the results to make sure the concept is cemented in your mind.

Let's make sure we got this right.  Run the following script and see the results for yourself:

VBScript:

WScript.Echo "154 AND 49 = " & (154 And 49)
WScript.Echo "154 OR 49 = " & (154 Or 49)
WScript.Echo "154 XOR 49 = " & (154 Xor 49)

Putting It All Together

"OK, so the numbers work out right, but so what?  What has this got to do with anything?" you might be wondering.  Bitwise operations are used very commonly to compare a series of mutually exclusive options or to create series of flags that are assignable to a single number.  The more familiar you get with using enumerations and bitwise operators the more uses you'll find for them.  Soon you'll be wondering how you ever got along without them.

To illustrate the usefulness of all of this, let's look at a concrete example.  When determining what attributes a file contains, we again use the FileSystemObject.  The 'File' object contains a property called 'Attributes.'  This property is a bitmask flag that returns a number.  This number is the total of flagged enumeration members that are set on the file.  Without using an enumeration, then you would have to have a separate boolean property for each separate attribute.

Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.GetFile("C:\boot.ini")
WScript.Echo file.Attributes

Your results may vary, but in my case I get a '6' when running the preceding script.  What the heck is that supposed to mean?  First we need to define the enumeration members for the attributes.  Let's go back to our handy ASE COM viewer and look at the enumeration members for 'FileAttribute.'  Once we get them we should add them to our script like so:

Const Alias = 1024
Const Archive = 32
Const Compressed = 2048
Const Directory = 16
Const Hidden = 2
Const Normal = 0
Const ReadOnly = 1
Const System = 4
Const Volume = 8

To determine what that 6 means, we just look at the values and quickly deduce that in my case, boot.ini contains both the System and Hidden attribute (4 + 2 = 6). Usually, though, we are not interested in every attribute that a file may contain. Rather, we are typically checking if a file contains one or two specific attributes. This is where bitwise operations can really shine. If we want to determine if a file is hidden (value of 2), we just need to see if the second bit that represents the value of 2 is switched on. We do this by ANDing the actual attribute value (6 in my case) against the value of the hidden flag, which is 2. Remember that ANDing only carries the value if BOTH comparison bits are set. Since one of our comparison numbers only contains one switch, if the result of the ANDing is the value of the flag we're searching for then the attribute is set on the file:

If (file.Attributes And Hidden) > 0 Then
   WScript.Echo "boot.ini is hidden!"
Else
   WScript.Echo "boot.ini is not hidden"
End If

You may be wondering why the first line is checking to see if the value is greater than zero.  If we're only checking for one value, then anything greater than zero indicates success.  In a strict sense, though, it would be more correct to use: If (file.Attributes And Hidden) = Hidden Then, but either method will work.  When scripting, you'll commonly find the first method used more often, but you should be familiar with both expressions.  If we're looking for two values, though, then we'll have to include them explicitly:

If (file.Attributes And Hidden + ReadOnly) = Hidden + ReadOnly Then
   WScript.Echo "boot.ini is hidden and readonly!"
Else
   WScript.Echo "boot.ini is not hidden and readonly"
End If

In the previous example, if we had only checked that the result was greater than zero, then all we would know is that at least one of the values was present. 

Anytime you wish to see if a given value is present, the ANDing operation is the one to use.  If you want to set a value, then you need to use the OR operation.  Let's say we want to add the Archive attribute to our boot.ini file.  We need to assign the Attributes property it's existing value OR the value of the Archive bit (32).  Remember that the OR operation accepts the bit value if either comparison is set so this OR operation adds 32 to the number.  A real advantage here is that we don't have to bother checking whether or not the Archive bit is set.  If it is already set then the number will not change; if it is not set, then this Attributes property will increment by the value of the Archive variable.

file.Attributes = file.Attributes Or Archive   ' If the archive bit is not set, then it will be set here

What about turning a bit off? For that we need to set every other bit to one and the bit we want to turn off to zero. This can only be accomplished with the NOT keyword that we haven't said anything about yet. The NOT keyword provides the inverse result whether we're dealing with a comparison or a bit value itself. In this case, to turn off the archive bit we need to use the NOT keyword, but because every other bit is set to '1', we need to AND the results to make sure we only get results that are already included in the original value.

file.Attributes = file.Attributes And Not Archive

The following script samples (in VBScript and KiXtart) demonstrate both enumerations and bitwise operations to view the attributes of a file, modify the attributes, and then change them back to what they were originally.  More than being able to get and set file attributes, this should enable you to make the most of using bitwise operations generally in your own scripts.  You'll be surprised at how much cleaner and more efficient your scripts can become by using these techniques. Hopefully this very cursory look at these topics will get your mind thinking about the gajillion other ways they can be used as well.

Until next time, happy scripting.

' VBScript:
' Define the enumeration members
Const Alias = 1024
Const Archive = 32
Const Compressed = 2048
Const Directory = 16
Const Hidden = 2
Const Normal = 0
Const ReadOnly = 1
Const System = 4
Const Volume = 8

' Create our objects
Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.GetFile("C:\boot.ini")

' List the initial attributes of the boot.ini file
WScript.Echo "Initial attributes: " & GetAttributesAsString(file.Attributes)

' Modify the attributes and list the updated attributes
file.Attributes = SetAttribute(file.Attributes, Archive, False)
WScript.Echo "Modified attributes: " & GetAttributesAsString(file.Attributes)

' Reset the attributes and list the updated attributes
file.Attributes = SetAttribute(file.Attributes, Archive, True)
WScript.Echo "Reset attributes: " & GetAttributesAsString(file.Attributes)


' Simple function that writes out the attributes applied to a file as a string
Function GetAttributesAsString(attributes)   

   ' Only read/write attributes added
   Set attrs = CreateObject("Scripting.Dictionary")
   attrs.Add 1, "ReadOnly"
   attrs.Add 2, "Hidden"
   attrs.Add 4, "System"
   attrs.Add 32, "Archive"
   
   For Each Key In attrs.Keys
      If (attributes And Key) > 0 Then
         ret = ret & ", " & attrs.Item(Key)
      End If
   Next
   
   If Len(ret) > 0 Then
      ret = Mid(ret, 3, Len(ret) - 2)
   End If
   
   GetAttributesAsString = ret
   
End Function

' Simple function that handles the bitwise operations for setting attributes
Function SetAttribute(attrCurrent, attrModified, remove)
   ' remove flag determines if we're adding or removing the attribute
   If remove Then
      SetAttribute = attrCurrent And Not attrModified
   Else
      SetAttribute = attrCurrent Or attrModified
   End If
End Function
 

Feedback | Site Map | About | Privacy | Legal
© 2004, 2005 iTripoli, Inc.