Got bit by a bug... Part 2

Add Comment | Mar 07, 2008
I haven't tested this macro, but it should work:
    Dim oFSO
    Dim tblFile, tblWiseSourcePath
    Dim rowFile, rowWiseSourcePath
    Dim fileVersion
    Set oFSO = CreateObject("Scripting.FileSystemObject")
    Set tblFile = WTables("File")
    Set tblWiseSourcePath = WTables("WiseSourcePath")
    On Error Resume Next
    For Each rowFile In tblFile.WRows
        rowWiseSourcePath = tblWiseSourcePath.WRows(rowFile("File"))
        fileVersion = oFSO.GetFileVersion(rowWiseSourcePath("SourcePath"))
        If fileVersion <> "" Then
            rowFile("Version") = CStr(fileVersion)
        End If
    Next

The macro is triggered by the Save event.  If your SourcePath uses a path variable, you will need to resolve the path variable in the WisePathVariable table and use a string replacement on the value obtained from the SourcePath column in the WiseSourcePath table.  If you don't do this, the GetFileVersion method will fail because it is not a valid path.

Got bit by a bug in Wise Installation Studio.

One Comment | Mar 07, 2008
This one hurt a little bit.  Several of our application installs have been rewritten to accommodate Windows Vista support.  We use wildcards to add some of the files because they ship sample files and shared resources that change often.  One of these shared resources was giving me a problem.

We are installing a DLL into our Common Files folder that all of the applications use.  The GUID is lined up appropriately between all of the applications to ensure proper reference counting in case a user installs our entire suite.  When a user would have version 7.0 installed of one of the applications and then install 7.5 of another, this shared resource in Common Files was not being upgraded.  It was causing a problem with our branding since this file handled our splash screens.  As you can imagine, marketing was not happy.

I generated a verbose log file during installation so I could see what was happening.  I immediately noticed this line:
MSI (c) (BC:24) [11:47:11:711]: Disallowing installation of component: {MY-GUID} since the same component with higher versioned keyfile exists

This was simply not the case and I was a little confused.  I was able to delete the file and initiate a repair, and the proper file would be installed.  This would be a little messy to instruct our users to do.  I also considered adding a RemoveFile entry, but the component costing is done before any files are removed so a repair would occur the first time our users ran the application.  This wasn't really an acceptable situation either.

I started to dig a little deeper to find what was causing it.  I went to the File table and the file was missing a version in the File table.  This puzzled me quite a bit.  I started digging through the Altiris knowledge base and found the problem:
KNOWN ISSUE: File versions not populated when using Wildcards and Path Variables

This really stung.  The workaround worked, but it was annoying and time consuming.  I am in the process of writing a macro that will pull the file version of all the files in the install and populate the File table where appropriate.  I will post this macro when complete and I hope it helps others in this situation.  I would hope Altiris would address this situation expeditiously as well.

The .NET Framework Size Debate

3 Comments | Dec 27, 2007
In his latest entry Christopher Painter discusses the .NET Framework distribution size and the complications of distributing the framework.

The "accepted" practice has usually involved embedding the .NET Framework with your installation via a wrapper.  This becomes complicated with the ever increasing size of the redistributable.  There are other problems that can arise and not to mention, it violates the EULA.

If you take the time to read through the EULA, you will see that you are accepting some pretty hefty stipulations (and making some assumptions) on behalf of the user by silently installing the .NET Framework for them.  If you are in any kind of software development or enterprise IT role, you should really take the time to read through the Microsoft Patterns and Practices.  Some of the best practices may seem over the top and excessive, but it will provide a good baseline to begin from.

I don't disagree with Chris on any particular point in his blog, but if it were up to me, I would suggest detecting if the .NET Framework is present and either directing the user to the download site or providing it on your distribution media if you are redistributing from hard media.

So...how do you do that?  I'll show you how to detect the presence of the .NET Framework and cancel your installation if it is not detected.  For the purposes of this article, we'll assume that you need to search for version 2.0.

Microsoft has provided a support article that describes the best method for detecting the required version of the .NET Framework.

Many Windows Installer development products have built in methods for detecting the existence of the .NET Framework, but in case your's does not, here it is.  The first thing you will need to do is populate the RegLocator table:
RegLocator Table
Signature Root Key Name Type
AppReg 2 SOFTWARE\Microsoft\.NETFramework\policy\v2.0 50727 2

We are telling the installation to search for the designated registry key, and populate a property with its value if it is found.  The property to be populated is defined in the AppSearch table:
AppSearch Table
Property Signature
DETECTDOTNOT20 AppReg

If you would like to use the results of this search in a Launch Condition, you will need to ensure that the AppSearch action is sequenced before the LaunchConditions action in both the InstallUISequence and InstallExecuteSequence tables.  Keep in mind that a LaunchCondition is a no frills way to boot the user out of an install.  It will not give the option to ignore the error and continue; it's a pass/fail system.  Any other method would either require a customer installer dialog or a custom action.  With that in mind, if you would like to use the LaunchCondition table, here is how to populate it with the given example:
LaunchCondition Table
Condition Description
DETECTDOTNET20="50727-50727" [ProductName] requires Microsoft .NET Framework 2.0 in order to function properly.  Setup will now terminate.

I find this method superior to cramming the redistributable in your installer.  It saves space, reduces failure points, and adheres to the EULA.

I'm back

Add Comment | Dec 19, 2007
So after a long time, I have decided to start writing again.  There are many great resources in to the world of Windows Installer, but I figured I would continue to add my insight.  I plan to cover all the Windows Installer products from Altiris to WiX and everything in between.

It won't necessarily always be about Windows Installer, but I hope to provide some insight as much as I can.  Welcome back me.  :-)

Registry Keys: Enemies of the clean uninstall

5 Comments | Nov 13, 2005
The most often item left behind upon the uninstall of an application is registry keys. So how do we ensure that our uninstalls are clean?

This can range anywhere from a minor nuisance to a major problem. I had just uninstalled a particular application I was using because the upgrade instructions had told me to do so. Upon attempting to install the new version, I received an error message telling me to uninstall the previous version prior to installing the new version. Being prone to fits of senility, I decided to double-check that I had indeed removed the old version. My brain had not failed, I did and there was no trace in Add/Remove Programs. I looked in the Program Files directory and nothing was to be found there either.

Rather than go on a Snipe hunt in the registry, I employed the usage of a couple of handy tools from Mark Russinovich (http://www.sysinternals.com/index.html). The installer was checking a registry key to see if it existed on the machine. This is how the installer was determining if a previous version of the application was installed. They were checking a registry that they don't remove with their uninstall. Brilliant…

Now this particular application didn't use Windows Installer for the install and they hadn't read the tips I was about to give you, so I won't fault them too much. Prior to the introduction of Windows Installer, it was actually very common to find applications leaving registry keys behind on uninstall. The application usually generates them leaving the installer unaware they exist. Some extra logic would have to have been placed in the uninstall routine to handle such a situation.

Windows Installer by default, will remove any registry keys that it installs. However, you have the same problem you had with legacy installations, they don't get removed if they were created by the application. You need to tell Windows Installer which registry keys to remove. One would automatically assume that they would need to start populating the RemoveRegistry table (http://msdn.microsoft.com/library/en-us/msi/setup/removeregistry_table.asp) in order to ensure that the registry keys are removed when an application is uninstalled. Make sure you look very closely at the remarks for the RemoveRegistry table in the SDK.

"The registry information is deleted from the system registry when the corresponding component has been selected to be installed locally or run from source."

Hmmm... That is not going to be very helpful. The RemoveRegistry table will only remove registry keys when a component is installed locally or run from source, not when a component is being removed. In order to remove registry keys when a component is removed, we'll need to take a look at the Registry table (http://msdn.microsoft.com/library/en-us/msi/setup/registry_table.asp).

Look at the information listed for the Name column. In there it indicates that is a - sign is in the name column that the "key is to be deleted, if present, with all of its values and subkeys, when the component is uninstalled." Now it looks like we are getting somewhere. I need you to be aware though, ALL of the values and subkeys will be removed. This is not a selective removal. If you want to do a selective removal, you will need to use a custom action.

In order to populate the registry table, it will look something like this:
| RemoveReg001 | 2 | Software\My Application\Test | - | {NULL} | RemoveRegistry |

RemoveReg001 is the key for this entry in the registry table or the Registry column. 2 defines the Root column which is HKEY_LOCAL_MACHINE. Software\My Application\Test is the registry key which occupies the Key column. The - symbol in the Name column is what tells Windows Installer to remove all the values and subkeys during an uninstall. The Value column is left blank and the last column is the component to which this registry key belongs. In this case, it is a component called RemoveRegistry.

I hope this will help make for cleaner uninstalls. If anyone has suggestions or requests on topics you would like to hear about, please send me an email. I am more than happy to come up with this stuff on my own, but please let me know. I was thinking about writing on patches and upgrades next. Let me know...

Removing Files and Directories Windows Installer Does Not Install

9 Comments | Jul 27, 2005

The uninstallation behavior of Windows Installer is actually quite efficient.  At uninstall time, Windows Installer will only remove the items that have been installed by the package unless the component containing the resources has been marked as permanent.

At times, this can pose a problem for the setup developer.  Applications will often create files as part of normal everyday usage.  Since these resources did not belong to the original installation, they will remain during an uninstall.  The same applies for registry keys as well.
The functionality is there to avoid such a situation.  Windows Installer has a RemoveFile table which allows you to remove files and folders that were not part of the original installation.  Some people believe that the RemoveRegistry table has the same function as the RemoveFile table.  This is not the case.  Yes, the RemoveRegistry table does remove registry keys, BUT only during the installation of the component that the RemoveRegistry resource belongs too.  In order to remove registry keys that aren't part of the original installation at uninstall time, you will need to use the Registry table.

Populating these tables is quite simple.  The Windows Installer SDK contains detailed information on how to add entries to these tables, but for example I will show you how.

The RemoveFile table has 5 columns, FileKey, Component_, FileName, DirProperty and InstallMode.  Notice the Component column has an underscore after the name.  This is to indicate that the entry in this column is a foreign key in to the Component table.  Here is are some examples of RemoveFile table entries, following will be an explanation of each:

RemoveTempFiles001  |  RemoveFile  |  *  |  FOO  |  2
RemoveAppDir  |  RemoveDir  |  {NULL}  |  INSTALLDIR  |  2

The first example shows how to remove files from directory Foo at uninstall time.  The FileKey entry is arbitrary, you can call it anything you want, but it must be unique.  The Component column must reference an already existing component in the installation.  The FileName column can either be the localizable filename of the file you want to remove, a wildcard, or null.  If it is null, the directory specified in the DirProperty column will be removed if it is empty.  The DirProperty column specifies a directory from the Directory table that contains the file(s) to be removed or the directory to be removed.  The InstallMode column is when the file(s) will be removed.  1 means during install, 2 is during uninstall, 3 means either case.  The second example shows how to remove a directory during uninstall; again, this is assuming that the directory is empty.

I will speak more on removing orphaned registry keys in the next post.

How do I install files to D:\ instead of C:\?

3 Comments | Jul 20, 2005

This post provides a tutorial for specifying installation to a partition such as D:\, as opposed to the default installation drive of C:\. 

 

 

Note

 

 

This will also work on mapped network drives. 

 

 

Caution

 

 

You will receive an error if your user does not have the network drive mapped or the partition on their computer.  You may want to use a custom action to determine the location of the partition or to ensure that the network drive is mapped.  For the purposes of demonstration, I am going to hard code the properties.

 

 

Often times, people refer to ROOTDRIVE when attempting to accomplish this particular task.  At most times, this is not an option.  Look at the following example.

 

 

INSTALLDIR = C:\Program Files\Test\

 

 

In the Directory table, it would contain an entry as follows:

 

 

INSTALLDIR  |  ProgramFilesFolder  |  Test

 

 

ProgramFilesFolder appears as follows:

 

 

ProgramFilesFolder  |  TARGETDIR  |  .:Program Files

 

 

If you set ROOTDRIVE equal to D:\ in this case, INSTALLDIR will still resolve to C:\Program Files\Test\.  The reason is the System Folders property group (msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/system_folder_properties.asp).  The ProgramFilesFolder entry will be resolved by Windows Installer to the defined Shell Folder on the system, regardless of what you have defined for the ROOTDRIVE.

 

 

In order to define the drive I would like to install to, I am going to create a property called D_DRIVE with the definition of D:\.  You can create this property in the Property table or on the Product Tab in the Setup Editor of the Windows Installer Editor.

 

 

My Property table entry would appear as follows:

 

 

D_DRIVE  |  D:\

 

 

Now we need to instruct Windows Installer to use this property.  In order to do this, we are going to create an entry in the Directory table as follows:

 

 

D_DRIVE  |  {NULL}  |  D_DRIVE

 

 

Note 

 

 

Do not actually type “{NULL}” in to the Directory table, just leave that particular column of the entry blank.

 

 

Now at this point, in order to tell the installation to install to the D:\ drive, you would change the parent of INSTALLDIR to D_DRIVE as follows:

 

 

INSTALLDIR  |  D_DRIVE  |  Test

 

 

 

 

Caution 

 

 

I would not change the parent of ProgramFilesFolder to D_DRIVE.  It is a best practice to leave the Shell Folder entries as they are.  If you would like to install to D:\Program Files\AppName, then it is a best practice to create an entry into the Directory for your D:\Program Files as well, such as follows:

 

 

D_PFILES_FOLDER  |  D_DRIVE  |  PROGRA~2|Program Files

 

 

INSTALLDIR  |  D_PFILES_FOLDER  |  Test

 

 

Now, all of the components with the directory assignment of INSTALLDIR will be installed to either of the following, depending on the logic used from the above examples:

 

 

D:\Test\

 

 

D:\Program Files\Test\

 

 

Introduction

4 Comments | Jul 18, 2005

I'd like to use this first post to introduce myself and tell you what qualifies me to talk about setups in the Windows Environment.  Over the last 6 years one of the primary functions of my job, no matter what company I have been at, has been to develop installations.  I have been working with Windows Installer pretty much since it was introduced, and before that spent time developing script-based installations using both Wise Solutions and InstallShield products.

Currently, I work for Wise Solutions which is now owned by Altiris.  My tool selection may be a little bias, but Windows Installer as a ground level technology is the same no matter what tool you are using.  In addition to the tools mentioned above, I have experience with Orca, WiX, OnCommand, ZeroG and a host of other installation tools from “smaller” less recognized companies.

I am a Senior Consultant for Wise, that is just a fancy way of saying I do whatever the customer needs.  I perform training, develop installations, work on repackaging projects, develop courseware, perform best practices assessments and a host of other services.  For a complete list, check out the Wise Professional Services website: http://www.wise.com/consulting_services.asp

So this first post isn't just about me, I will add some Windows Installer know-how.  Windows Installer provides a great deal of functionality you can leverage as part of the service.  A question I seem to get a lot of is how to fix a broken installation.  This can be caused by many things.  For example, your installation may have incorrectly defined KeyPaths in the Components.  Another common “gotcha” is that an installation may not have been validated properly.

If the installation has already been deployed, fixing it can be difficult to say the least.  Sometimes a broken installation will even prevent you from removing the application properly.  Luckily, there is a tool that ships with the SDK that will aid in removing damaged installations from the computer.  The name of this tool is MsiZap.exe.  If you run MsiZap without any command line parameters, you will see the following:

Copyright (C) Microsoft Corporation.  All rights reserved.
MSIZAP - Zaps (almost) all traces of Windows Installer data from your machine.

Usage: msizap T[WA!] {product code}
       msizap T[WA!] {msi package}
       msizap *[WA!] ALLPRODUCTS
       msizap PWSA?!

       * = remove all Windows Installer folders and regkeys;
           adjust shared DLL counts; stop Windows Installer service
       T = remove all info for given product code
       P = remove In-Progress key
       S = remove Rollback Information
       A = for any specified removal, just change ACLs to Admin Full Control
       W = for all users (by default, only for the current user)
       M = remove a managed patch registration info
       G = remove orphaned cached Windows Installer data files (for all users)
       ? = verbose help
       ! = force 'yes' response to any prompt

CAUTION: Products installed by the Windows Installer may fail to function after using msizap

NOTE: MsiZap requires admin privileges to run correctly. The W option requires that the profiles for all of the users be loaded.

The following command line will remove a broken product for all users on the machine:

MsiZap.exe TW! {00000000-0000-0000-0000-000000000000}

This assumes that the broken product has a ProductCode of all zeros.  You will of course want to pass the ProductCode of the broken application.  What MsiZap does is remove all the Windows Installer registration information from the machine.  In effect, Windows Installer will no longer be aware of the application on the computer.  What it does not do is remove application resources, such as, files and registry keys.  This will need to be done manually.  However, you can now safely remove these items and not have to worry about Windows Installer performing self-healing operations on these resources.