Tutorial 18: Getting component attributes

Ahhhh. A lovely topic.

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….

Tutorial 17: Editing MSIs in the Windows Installer cache

In this example, let’s say a user has an MSI installed and they want to remove it. Every time they try to remove it, a Custom Action called ‘AlkaneCustomAction’ keeps throwing an error, and as a result the uninstall fails.

This piece of code searches all products installed or advertised for the current user and machine. When we find the product code, we open the database in trasact mode, set the condition of the Custom Action to ‘1=0’ (I.e, NEVER run it) and commit the change. The MSI will now remove without error.

Obviously this is just an example. Simply not running the Custom Action may not be the optimal solution!

Tutorial 16: Writing to the Windows Installer log file from a Custom Action

Logging is extremely useful when debugging MSIs. Most people that write Custom Actions don’t write logs, but if you’re writing quite extensive logic you may wish to consider it. Simply paste the following logic into your CA:

and write to the log like this:

Tutorial 15: Using SQL inside a running Windows Installer Session

As well as creating administrative scripts like the previous examples, we can also use SQL queries during the installation of an MSI. When an MSI is installing, we
can refer to this as a ‘session’. Take the following excerpt – this is from a tutorial I wrote regarding using VBScript Custom Actions to automate the installation and removal of Excel add-ins:

You can see that we don’t explicitly create an Installer object now, because we’re already in the session of one! Instead, we use this syntax:

another handy tip in here is the use of:

to detect if our session is installing/uninstalling or repairing. In this example, I’m detecting if we’re installing and if so I search for all Excel add-ins, finding their install locations by using:

Tutorial 14: Generating Transforms

Now we’re going to attempt to generate a transform from changes which we make to an MSI. An example of this could be adding certain properties for a customer, or possible an audit key in the registry table.

When we’re generating an MST from a script, here’s the idea:

  • We take a copy of the original base MSI, and create a new temporary MSI
  • We apply any changes we make to this temporary MSI
  • We find the difference between the temporary MSI and the base (original) MSI
  • We generates a transform from the differences between the base MSI and the temporary MSI.

So, here goes. We’re going to insert the following entry into the Registry table:

generate_xfm

The comments throughout the example above explain what each section does. What I would say, though, is:

  • Always remember to call the CreateTransformSummaryInfo method! The Windows Installer SDK examples don’t include this. Omitting this may mean your transform wont generate/apply without error.
  • Ensure you close every view object, otherwise the temporary file will not delete at the end.
  • If no changes are made to the base MSI and you attempt to generate a transform, there will be an error because the difference between the base MSI and the new one will be nothing.

Tutorial 13: Applying Transforms

For this example I’ve created a sample MST file. Our test MSI has an entry in the Registry table with a primary key of ‘SampleReg’ and a Value of ‘This is an MSI’. To create the transform I used Orca. I opened our test MSI, selected the ‘Transform’ menu option, and scrolled down and clicked ‘New Transform’. I then changed the Value of our ‘SampleReg’ row to be ‘This is an MST’. To create the new transform file, I then selected the ‘Transform’ menu option, and scrolled down and clicked ‘Generate Transform’. By changing this value via a transform, it should enable us to see if the transform has applied.

To start with, copy and paste the following code into your script file:

To run it, we need to specify both our MSI file and our MST file:

Open up a Command Prompt
Type: cscript.exe alkaneTestScript.vbs “c:\alkaneMSIs\testMSI.msi” “c:\alkaneMSIs\testMST.mst”

You should see the following result:

The only new part to this script is that we use the ApplyTransform method to apply one (or many!) transforms to our MSI:

In the excerpt of code above, we loop through the arguments we’ve passed in to the VBCsript (exclusing the first argument – the MSI), we apply the transform to the database and we commit the changes.

Tutorial 12: Inner joins

Let’s say we want to know what directory a file gets installed too. In order to do that, we’d need to read the FileName column from the File table, and the Directory_
column from the Component table.

tipTip: All foreign key column names end with an underscore (_).

 

We can see that the File table includes a foreign key into the Component table (Component_). For this reason, we’ll join the two tables where:

File.Component_ equals Component.Component

Tutorial 11: Inserting a row using the Modify method

In this example, we change the SQL statement to include the columns of data which we want to insert. Note that we MUST include all of the non-nullable columns as a minimum. When a column is not nullable, it means we MUST specify a value when we insert the record. Otherwise the insertion will fail. To find the non-nullable columns, consult the Windows Installer help file (MSI.chm) or MSDN. For example:

The four non-nullable columns which we MUST include when inserting into the Registry table are:

Registry
Root
Key
Component_

If we were lazy, we could just return all rows using the following:

This time when we execute the query, we’re not going to loop through any records using the While…Wend loop. All we want to do is see if the record already exists:

and if it doesn’t, we’ll start to create a new record and populate the values….

Note how we’ve used StringData for columns which can accept strings as their data types, and IntegerData for columns which accept numeric values only.
Again, to find columns with an ‘Integer’ data type you should consult the Windows Installer help file (MSI.chm) or MSDN.

Once we’ve created our new record, we insert it:

So, here it is:

Tutorial 10: Deleting a row using the Modify method

Use the scripts in Tutorial 8/Tutorial 9, but replace these lines:

with

Tutorial 9: Modify a primary key using the Modify method

Here we would change the SQL query to select our primary key value (in other words, we’re selecting the Registry column now instead of the Value column):

and we would modify it like this:

Note that this essentially won’t update that particular row. Instead it will delete the current row, and insert the new row containing the new primary key value.