Finding 64-bit components in an MSI database

This blog entry provides an example of finding 64-bit components in an MSI database using VBScript.  It follows on from the previous blog post which provided a tutorial on editing MSI files in the windows installer cache using VBScript.

It forms part 17 of an 17-part series that explores how to use VBScript to manipulate MSI relational databases using the Windows Installer API.  Throughout this series of tutorials, we identify the common issues that we encounter and the best practises that we use to overcome them.

A couple of weeks ago, I needed to scan an MSI for 64-bit components. I knew each component had an ‘attribute’ value (which is actually a bit flag), but I needed to figure out a way to extract each bit flag so that I could find out how the component was configured and which attributes were set.

Here’s how I did it….

Firstly, let’s list down the component attributes and their corresponding bit flags:

Bit Flag Description Decimal Value
Component cannot be run from source 0
Component can only be run from source 1
Component can run locally or from source 2
Has a Registry Keypath 4
Increments Shared DLL Registry Count 8
Permanent Component (doesn’t remove on uninstall) 16
Has an ODBC Data Source Keypath 32
Re-evaluates Condition on Reinstall 64
Do Not Overwrite if Component Exists 128
64-Bit Component 256
Disable Registry Reflection 512
Prevent Leaving Orphan Components 1024
Share Component 2048

When we want to set multiple bit flags on a component, we add the decimal values together. For example, let’s say we have a component which contains a 64-bit DLL as the keypath, and we want to incrememnt the shared DLL reference count. The attribute of our component would be:

(Increments Shared DLL Registry Count + 64-Bit Component)
= 8 + 256
= 264

Now let’s reverse things. If we have a value of 264 in our attribute column, how do we work out which decimal values were added together to make this up?
The answer is that we need to convert the decimal value to a binary value. Here goes a quick binary lesson:

A 16-bit binary value would consist of 16 1’s and 0’s. For example, 0010101000101101. Each ‘bit’ flag has a corresponding decimal value. In the table below, we’ll use our 16-bit shared DLL component as an illustrated example:

component_attributes

The MSI component attribute only uses the first 13 bits. The last 3 bits will always be zero. By converting the decimal value of 264 in to binary, we can see which bit flags are set and therefore decipher the attribute which has been applied to the component.

Now let’s see it in code….

'Function to convert decimal to 16-bit binary
Function DecToBin(intDec)
  dim strResult
  dim intValue
  dim intExp
  strResult = ""

  intValue = intDEC
  intExp = 32768
  while intExp >= 1
    if intValue >= intExp then
      intValue = intValue - intExp
      strResult = strResult & "1"
    Else
      strResult = strResult & "0"
    end if
    intExp = intExp / 2
  Wend

  DecToBin = strResult
End Function

Dim componentBinary : componentBinary = 0
Dim componentName : componentName = ""

Dim isComponent64Bit : isComponent64Bit = 0
Dim isComponentSharedDLL : isComponentSharedDLL = 0

'Select the name and attribute for each component 
Set View = Database.OpenView("SELECT Component, Attributes FROM `Component`")
View.Execute
Set rec = View.Fetch

Do While Not rec Is Nothing	

	'get component name
	componentName = rec.StringData(1)

	'get the attribute (decimal value) and convert it to 16-bit binary
	componentBinary = DecToBin(Cint(rec.StringData(2)))

	'get bit flag for shared dll (4th bit) - remember vbscript arrays start at 0!
	isComponentSharedDLL = Mid(componentBinary,3,1)

	'get bit flag for 64-bit (9th bit) - remember vbscript arrays start at 0!
	isComponent64Bit = Mid(componentBinary,8,1)

	If isComponentSharedDLL = "1" Then
		MsgBox "Component '" & componentName & "' is a shared DLL resource!"
	End If

	If isComponent64Bit = "1" Then
		MsgBox "Component '" & componentName & "' is a 64-bit component!"
	End If

  	Set rec = View.Fetch
Loop
View.Close
Set View = Nothing
Set rec = Nothing

Thanks for reading how about finding 64-bit components in an MSI database using VBScript.