|
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.
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
Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.GetFile("C:\boot.ini")
WScript.Echo "Initial attributes: " & GetAttributesAsString(file.Attributes)
file.Attributes = SetAttribute(file.Attributes, Archive, False)
WScript.Echo "Modified attributes: " & GetAttributesAsString(file.Attributes)
file.Attributes = SetAttribute(file.Attributes, Archive, True)
WScript.Echo "Reset attributes: " & GetAttributesAsString(file.Attributes)
Function GetAttributesAsString(attributes)
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
Function SetAttribute(attrCurrent, attrModified, remove)
If remove Then
SetAttribute = attrCurrent And Not attrModified
Else
SetAttribute = attrCurrent Or attrModified
End If
End Function
|
|