When VBScript and JScript custom actions are even more evil than usual

As everyone knows, script custom actions are inherently evil. A security addition to Windows Installer 4.0 in Windows Vista means that script CAs are even more likely to fail; see Heath’s blog entry on the issue and Aaron’s follow-up.

But did you know that script CAs, evil that they are, nonetheless ship in Orca, MsiVal2, and even the WiX toolset? Shocking but true. The Internal Consistency Evaluators (ICEs) are implemented as custom actions in .cub files that are MSI databases with a vastly different schema you’re used to seeing. A couple of ICEs are written in VBScript so a misregistered VBScript engine will cause those ICEs to fail. As WiX v3 runs validation by default during linking (and on-demand using the Smoke tool), failed ICEs fail your setup build.

Of 98 ICEs in the version of Darice.cub in the Windows Vista SDK, only four are written in VBScript — yet that’s all it takes to fail your build.

If you run into the problem, check out the workaround Aaron offers before you take the drastic step of disabling validation.

Feature conditions and UI

A question that’s come up on the wix-users mailing list several times lately has been about how to use feature conditions with properties set in the UI. It doesn’t work as most people expect so I thought I’d dig a little deeper than I would in a bunch of replies on the mailing list.

The basic idea is that you have some kind of optional functionality — like a Web site or Visual Studio integration — that you want the user to be able to enable or disable from the installer UI. (I’m assuming here that the functionality is in reality just a set of discrete components organized into a feature.)

The easiest way to get this working is to use MSI’s built-in SelectionTree control — the normal, boring, not-quite-easy-to-use feature tree that you see in most installers. That’s the approach we used with the WiX installer; in addition to features for plain sets of files, there are features for Visual Studio, Votive, and MSBuild integration.

The thrust of this question, though, is generally about using some other UI, like checkboxes or groups of radio buttons, to offer a better user experience during setup. But if you replace the SelectionTree control, you need to also replace how it manages feature states for you. Feature conditions seem like a fairly cheap way of accomplishing that, assuming you use public (i.e., all UPPERCASE) and secure (Property/@Secure=”yes”) properties to ensure they’re always passed to the MSI server for the installation execution sequence.

But it doesn’t work and as is usual with MSI, reading the SDK doc and examining a verbose log tell us why — almost.

Let’s start with the doc on the Condition table, which is the table where feature conditions are stored. A promising blurb:

The Level may be set based on any conditional statement, such as a test for platform, operating system, or a particular property setting.

A nice feature of the MSI SDK doc is that each table lists the actions that refer to the table. For the Condition table, it says:

This table is referred to when the CostFinalize action is executed.

Follow that link and you get another couple of interesting blurbs:

The CostFinalize action must be executed before starting any user interface sequence which allows the user to view or modify Feature table selections or directories.

and

The CostFinalize action queries the Condition table to determine which features are scheduled to be installed.

So far, everything sounds like it will work. It doesn’t, though, so clearly the docs aren’t telling the whole story. Verbose logs are our next step. Starting with MSI 3.1, they include entries when properties are added, changed, or deleted, so you can verify that the properties are being set correctly during the UI sequence. You can also verify that MSI is passing those properties to the execution sequence. Search the log for “Switching to server” to see the list of properties being passed. For example, from a complete install of the Windows Vista SDK version of Orca:

MSI (c) (6C:24) [14:54:54:015]: Switching to server: CUBDIR=”C:\Program Files (x86)\Orca\” ORCADIRECTORY=”C:\Program Files (x86)\Orca\” TARGETDIR=”V:\” MS.51D569E0_8A28_11D2_B962_006097C4DE24=”C:\WINNT\SysWOW64\” MS.51D569E2_8A28_11D2_B962_006097C4DE24=”C:\WINNT\SysWOW64\” MS.7EBEDD6A_AA66_11D2_B980_006097C4DE24=”C:\WINNT\SysWOW64\” MS.7EBEDD3E_AA66_11D2_B980_006097C4DE24=”C:\WINNT\SysWOW64\” INSTALLLEVEL=”1000″ COMPANYNAME=”MS” USERNAME=”Bob Arnson” CURRENTDIRECTORY=”X:\” CLIENTUILEVEL=”0″ CLIENTPROCESSID=”3180″ SOURCEDIR=”X:\” ACTION=”INSTALL” EXECUTEACTION=”INSTALL” ROOTDRIVE=”V:\” SECONDSEQUENCE=”1″ ADDLOCAL=OrcaHelp,Orca,EvalComServer,MergeModServer,CUBFiles,FullCUBFile,LogoCUBFile,XPLogoCUBFile,MMCUBFile

Notice the ADDLOCAL property setting. It’s a comma-delimited list of feature names that are to be installed. I didn’t pick them individually; instead, I used the “complete” button to say I wanted everything. The orca.msi package publishes a SetInstallLevel control event with a high install level to let MSI decide to install all features rather than using an AddLocal control event to manually list them.

Let’s assume that MSI uses the ADDLOCAL property and its friends the REMOVE and ADDSOURCE properties (among other friends) to communicate to the MSI server the feature choices the user made during the UI sequence. If you think about the many different ways the UI can implicitly and explicitly set which features get installed — from “typical” and “complete” buttons publishing SetInstallLevel control events and SelectionTree controls letting users pick and choose individual features — it makes sense that MSI would need a rich way of controlling feature installation and it makes sense for MSI to use its own properties mechanism rather than invent a new one. (And note that the same thing applies to uninstallation and reinstallation — those are all handled on a per-feature basis via properties.)

OK, so what does that have to do with feature conditions? (I’m getting there, really.) All the feature-selection property topics have this blurb:

The installer sets the Preselected Property to a value of “1” during the resumption of a suspended installation, or when any of the above properties are specified on the command line.

Follow the Preselected link and you get this blurb:

The Preselected property indicates that features have already been selected and that the selection dialog need not be shown.

Now it’s time to enter the murky world of speculation. There’s no doc to indicate that the feature-selection properties or the Preselected property have any affect on feature-condition evaluation in CostFinalize. But I think it’s reasonable to infer that an explicit feature selection (via ADDLOCAL, for example) would override the more implicit feature-selection options available, including the Condition table and INSTALLLEVEL property.

And, to make a long story short — too late! — Carolyn, MSI Team Dev Lead, confirmed that’s the case. Feature conditions are evaluated only if no feature-selection properties are set. And, as the UI sequence converts SetInstallLevel control events, feature-selection control events, and SelectionTree control settings into the corresponding feature-selection properties, feature conditions that include properties set during the UI sequence won’t work as expected.

Note that an installation that doesn’t run the UI sequence (e.g., using the /qb command-line switch to run a basic-UI installation) can use properties set during the execute sequence in feature conditions. But the same caveat applies: If any feature-selection properties are set, feature conditions aren’t evaluated.

OK, fine, what now?

So how do you get this working? As I said near the beginning of this much-longer-than-expected tome, “if you replace the SelectionTree control, you need to also replace how it manages feature states for you.” As SelectionTree controls end up setting the feature-selection properties, you need to do the same thing, directly or indirectly. The easiest way is to publish feature-selection control events from your feature-selection dialog.

For example, the WixUI dialog set WixUI_Mondo uses SetInstallLevel published from SetupTypeDlg:

<Control Id=”CompleteButton” Type=”PushButton” X=”40″ Y=”171″ Width=”80″ Height=”17″ ToolTip=”!(loc.SetupTypeDlgCompleteButtonTooltip)” Text=”!(loc.SetupTypeDlgCompleteButton)”>
<Publish Property=”WixUI_InstallMode” Value=”InstallComplete”>1</Publish>
<Publish Event=”SetInstallLevel” Value=”1000″>1</Publish>
</Control>

If you wanted to explicitly list features, the easiest way to do so is to use an AddLocal control event with an argument of ALL to install all features locally, then use individual Remove control events to remove the features that don’t apply. Doing so covers lets the user both install and uninstall features. For example:

<Control Id=”Next” Type=”PushButton” X=”235″ Y=”243″ Width=”57″ Height=”18″ Default=”yes” Text=”&Next >”>
<Publish Event=”AddLocal” Value=”ALL”>1</Publish>
<Publish Event=”Remove” Value=”FeatureX”>NOT FEATUREX_CHECKBOX</Publish>
<Publish Event=”Remove” Value=”FeatureY”>NOT FEATUREY_CHECKBOX</Publish>
<Publish Event=”Remove” Value=”FeatureZ”>NOT FEATUREZ_CHECKBOX</Publish>
</Control>

Hope this helps understand why things aren’t always as simple as a first glance might indicate.

Flight Simulator X Service Pack 1 ships

The project that has kept me busy since moving to the ACES Studio at the beginning of the year is finally out the door! Flight Simulator X Service Pack 1 is now available from fsinsider.com. Also available is Flight Simulator X Software Development Kit SP1A.

I’ll be discussing both projects in more detail in future posts, but for now, here’s a quick summary:

  • FSX SP1 is delivered in multiple per-language self-extractors, each of which contains two patches, one for each of the two editions Flight Simulator X is available in (Standard and Deluxe). Of the approximately 42,000 files(!) in RTM, only a couple of hundred were patched or added in SP1.Because of how the FSX RTM setup was built, it wasn’t feasible to ship one patch for all the editions and languages. I built the patch-creation packages (.pcp files) in WiX v3 and built the patches using MsiMsp and PatchWiz 3.1. However, it wasn’t otherwise a “typical” patch build; the RTM setup auto-generated most of its 42,000 files so file IDs and component GUIDs and IDs weren’t stable. That’s why I needed to build 13 distinct patches (two each for English, French, German, Italian, Polish, and Spanish and one for Japanese). It also meant that I couldn’t follow the typical approach of rebuilding the upgrade MSI packages — the GUIDs and IDs kept changing. The story of building custom upgrade packages deserves its own series of posts.
  • FSX SDK SP1A is delivered in one self-extractor. It’s available only in English and only for users of the Deluxe editions, so we needed to deliver only one package. Unlike the game itself, the SDK update is delivered as a new product, not a patch. I investigated delivering a patch for the SDK too, but ran into the same issues with changing IDs as with the game. Another complication was the release of SDK SP1 (without the “A” suffix); it also shipped as a product but with custom code that manually updated and deleted files in the RTM SDK. Both issues combined, a patch wasn’t something I could pull off in a reasonable timeframe. So I created a new SDK product using WiX v3. The self-extractor is the WiX v3 setup.exe chainer. By dropping the auto-generation, I had more work to keep the setup current but we’ll be in a great position for future patches.

The entire ACES Studio can be justifiably proud of the tremendous effort that went into FSX SP1 and SDK SP1A. Naturally, I just wrote above about the work that went into patching and setup, because that’s what I worked on. But a huge amount of work went into performance enhancements and content improvements. I hope every FSX user enjoys it!

Now on to the expansion pack and DirectX 10 update

New system information custom actions coming soon to WiX v3

The Windows Home Server folks have an SDK for extending the server with what they call “add-ins.” Their deployment sample is written with WiX. I love to see that, so I’m looking into what setup functionality would make a useful WHS extension for WiX. Right now, it doesn’t look like there’s much required — the requirements of a WHS add-in installer are pretty minimal over and above a good installer in general.

One thing they mention is detecting WHS if your add-in requires it. It’s not one of the standard Operating System Properties that MSI provides (yet, at least). The WHS SDK recommends calling the GetVersionEx API function. Clearly, it’s time for a custom action: Enter WixQueryOsInfo.

I wanted to follow the same model as MSI; that is, set a property if the OS running the package is of a particular edition or supports a particular feature. That lets you use them in conditions for features or components that only make sense when the user is running a supported OS or configuration.

As I’d be writing a CA to call GetVersionEx, I looked at what else it could detect that MSI didn’t already support. There were several, though sometimes the MSI naming convention didn’t match how GetVersionEx named them. So I decided to set properties for everything GetVersionEx offered using its naming convention. That way, it’s easy to mentally translate from the GetVersionEx doc to the properties WixQueryOsInfo sets.

Then, while I was in there, I added another CA to do something similar: WixQueryOsDirs sets properties for the “special folders” Windows defines for system and user directories. MSI already supports a long list of System Folder Properties but there are extra ones that Windows supports. For WixQueryOsDirs, I didn’t duplicate the ones MSI already supports.

Using WixQueryOsInfo and WixQueryOsDirs

The CAs are part of WixUtilExtension so you need to link with the “-ext WixUtilExtension” switch. Your WiX source must reference the CAs via their properties, to cause the linker to pull in the DLL and sequencing:

<Product …>
<PropertyRef Id=”WIX_SUITE_SINGLEUSERTS” />
<PropertyRef Id=”WIX_DIR_COMMON_DOCUMENTS” />
</Product>

Note that WixQueryOsInfo and WixQueryOsDirs both always set all the properties that apply, regardless of which you specify via PropertyRef. The PropertyRef just pulls in the CA, the DLL in the Binary table, and InstallExecuteSequence and InstallUISequence scheduling.

Look for OSInfo in the next weekly release of WiX v3.

Here’s the documentation page that’s part of WiX.chm:

OSInfo custom actions

The WixQueryOsInfo and WixQueryOsDirs custom actions in wixca (part of WixUtilExtension) set properties over and above the MSI set for OS product/suite detection and standard directories. Here’s a complete list:

WixQueryOsInfo properties

WIX_SUITE_BACKOFFICE

Equivalent to the OSVERSIONINFOEX VER_SUITE_BACKOFFICE flag.

WIX_SUITE_BLADE

Equivalent to the OSVERSIONINFOEX VER_SUITE_BLADE flag.

WIX_SUITE_COMMUNICATIONS

Equivalent to the OSVERSIONINFOEX VER_SUITE_COMMUNICATIONS flag.

WIX_SUITE_COMPUTE_SERVER

Equivalent to the OSVERSIONINFOEX VER_SUITE_COMPUTE_SERVER flag.

WIX_SUITE_DATACENTER

Equivalent to the OSVERSIONINFOEX VER_SUITE_DATACENTER flag.

WIX_SUITE_EMBEDDEDNT

Equivalent to the OSVERSIONINFOEX VER_SUITE_EMBEDDEDNT flag.

WIX_SUITE_EMBEDDED_RESTRICTED

Equivalent to the OSVERSIONINFOEX VER_SUITE_EMBEDDED_RESTRICTED flag.

WIX_SUITE_ENTERPRISE

Equivalent to the OSVERSIONINFOEX VER_SUITE_ENTERPRISE flag.

WIX_SUITE_MEDIACENTER

Equivalent to the GetSystemMetrics SM_SERVERR2 flag.

WIX_SUITE_PERSONAL

Equivalent to the OSVERSIONINFOEX VER_SUITE_PERSONAL flag.

WIX_SUITE_SECURITY_APPLIANCE

Equivalent to the OSVERSIONINFOEX VER_SUITE_SECURITY_APPLIANCE flag.

WIX_SUITE_SERVERR2

Equivalent to the GetSystemMetrics SM_SERVERR2 flag.

WIX_SUITE_SINGLEUSERTS

Equivalent to the OSVERSIONINFOEX VER_SUITE_SINGLEUSERTS flag.

WIX_SUITE_SMALLBUSINESS

Equivalent to the OSVERSIONINFOEX VER_SUITE_SMALLBUSINESS flag.

WIX_SUITE_SMALLBUSINESS_RESTRICTED

Equivalent to the OSVERSIONINFOEX VER_SUITE_SMALLBUSINESS_RESTRICTED flag.

WIX_SUITE_STARTER

Equivalent to the GetSystemMetrics SM_STARTER flag.

WIX_SUITE_STORAGE_SERVER

Equivalent to the OSVERSIONINFOEX VER_SUITE_STORAGE_SERVER flag.

WIX_SUITE_TABLETPC

Equivalent to the GetSystemMetrics SM_TABLETPC flag.

WIX_SUITE_TERMINAL

Equivalent to the OSVERSIONINFOEX VER_SUITE_TERMINAL flag.

WIX_SUITE_WH_SERVER

Windows Home Server. Equivalent to the OSVERSIONINFOEX VER_SUITE_WH_SERVER flag.

WixQueryOsDirs properties

WIX_DIR_ADMINTOOLS

Per-user administrative tools directory. Equivalent to the SHGetFolderPath CSIDL_ADMINTOOLS flag.

WIX_DIR_COMMON_ADMINTOOLS

All-users administrative tools directory. Equivalent to the SHGetFolderPath CSIDL_COMMON_ADMINTOOLS flag.

WIX_DIR_COMMON_DOCUMENTS

All-users documents directory. Equivalent to the SHGetFolderPath CSIDL_COMMON_DOCUMENTS flag.

WIX_DIR_COOKIES

Per-user Internet Explorer cookies directory. Equivalent to the SHGetFolderPath CSIDL_COOKIES flag.

WIX_DIR_HISTORY

Per-user Internet Explorer history directory. Equivalent to the SHGetFolderPath CSIDL_HISTORY flag.

WIX_DIR_INTERNET_CACHE

Per-user Internet Explorer cache directory. Equivalent to the SHGetFolderPath CSIDL_INTERNET_CACHE flag.

WIX_DIR_PERSONAL

Per-user documents directory. Equivalent to the SHGetFolderPath CSIDL_PERSONAL flag.

To use the WixQueryOsInfo and WixQueryOsDirs custom actions, add PropertyRef elements for the properties above you want to be set and add WixUtilExtension to your link options (a.k.a. the Light command line). For example:

<PropertyRef Id="WIX_SUITE_SINGLEUSERTS" />
<PropertyRef Id="WIX_DIR_COMMON_DOCUMENTS" />

WixUtilExtension automatically schedules the custom actions as needed after the AppSearch standard action.