Different Packages are Required for Different Processor Architectures

A common question is how to create a Windows Installer package that installs 32-bit binaries on 32-bit platforms, and both 32- and 64-bit binaries on 64-bit platforms. If you’re actually trying to install 64-bit binaries to appropriate directories and write to the 64-bit view of the registry, the short answer is that you can’t build a single package.

Before delving into the details, let me clarify that both 32- and 64-bit binaries can be installed to either ProgramFilesFolder or ProgramFiles64Folder, but may not load correctly into a 64- or 32-bit process, respectively. The Windows Installer SDK, for example, installs all of x86, IA64, and x64 binaries under ProgramFilesFolder for link time support.

But when you need to install files correctly, multiple packages are required because not all 64-bit features are lit up if the package isn’t marked as a 64-bit package. That is, the Template summary property must distinctly list Intel for x86, Intel64 for IA64, and x64 for x64 platforms (AMD64 is supported for backward compatibility). The Template summary property documentation clearly states that only a single platform can be specified.

A Windows Installer package cannot be marked as supporting both Intel64 and x64; for example, the Template Summary property value of Intel64,x64 is invalid.

A Windows Installer package cannot be marked as supporting both 32-bit and 64-bit platforms; for example, Template Summary property values such as Intel,x64 or Intel,Intel64 are invalid.

Attempting to install a 64-bit package on a 32-bit platform results in Windows error 1633. Assuming no other errors, installing a 32-bit package on a 64-bit platform appears to work but components may not actually be installed correctly. Files in components to be installed under ProgramFiles64Folder, for example, wind up in ProgramFilesFolder.

If we examine a log from a 32-bit package with 64-bit components defined that is installed on an x64 platform, we can see that directories like ProgramFiles64Folder are initially defined correctly but are redirected back to the 32-bit equivalent directory.

MSI (s) (B4:20) [16:59:17:976]: WIN64DUALFOLDERS: 'C:Program Files (x86)' will substitute 17 characters in 'C:Program Files' folder path. (mask argument = 0, the folder pair's iSwapAttrib member = 0).
MSI (s) (B4:20) [16:59:17:976]: PROPERTY CHANGE: Modifying ProgramFiles64Folder property. Its current value is 'C:Program Files'. Its new value: 'C:Program Files (x86)'.

But when writing registry values, you can see by the BinaryType parameter that Windows Installer knows the difference and Windows Installer will write to the correct 32- or 64-bit view of the registry. You can confirm this using a tool like regedit.exe.

MSI (s) (B4:20) [16:59:18:445]: Executing op: RegOpenKey(Root=-2147483646,Key=SoftwareHeath StewartExample,,BinaryType=0)
MSI (s) (B4:20) [16:59:18:445]: Executing op: RegAddValue(,Value=32-bit Example,)
MSI (s) (B4:20) [16:59:18:445]: Executing op: RegAddValue(Name=Example,Value=32-bit Registry Example,)
MSI (s) (B4:20) [16:59:18:445]: Executing op: RegOpenKey(Root=-2147483646,Key=SoftwareHeath StewartExample,,BinaryType=1)
MSI (s) (B4:20) [16:59:18:445]: Executing op: RegAddValue(,Value=64-bit Example,)
MSI (s) (B4:20) [16:59:18:445]: Executing op: RegAddValue(Name=Example,Value=64-bit Registry Example,)

You can’t simply condition components using standard properties like VersionNT64. Even when installing a package marked as Intel on a 64-bit machine, properties like VersionNT64, Msix64, and Intel64 are still defined.

Essentially, most features are lit-up depending on the machine type except for where files are installed. Even 64-bit custom actions will run in a 64-bit custom action server despite how the package is attributed as shown in the log snippet below. For binary custom actions, the bitness of the binary will dictate whether a 32- or 64-bit custom action server is created. For script custom actions, the custom action must be attributed with msidbCustomActionType64BitScript (0x1000); of course, you shouldn’t use script custom actions anyway.

MSI (s) (74:14) [11:04:43:203]: Executing op: CustomActionSchedule(Action=CA_OutputString32,ActionType=1025,Source=BinaryData,Target=OutputString,)
MSI (s) (74:14) [11:04:43:203]: Creating MSIHANDLE (1) of type 790536 for thread 3604
MSI (s) (74:44) [11:04:43:203]: Invoking remote custom action. DLL: C:WINDOWSInstallerMSI243.tmp, Entrypoint: OutputString
MSI (s) (74:2C) [11:04:43:203]: Generating random cookie.
MSI (s) (74:2C) [11:04:43:219]: Created Custom Action Server with PID 2260 (0x8D4).
MSI (s) (74:64) [11:04:43:250]: Running as a service.
MSI (s) (74:64) [11:04:43:250]: Hello, I'm your 32bit Impersonated custom action server.

MSI (s) (74:14) [11:04:43:250]: Executing op: CustomActionSchedule(Action=CA_OutputString64,ActionType=1025,Source=BinaryData,Target=OutputString,)
MSI (s) (74:14) [11:04:43:250]: Creating MSIHANDLE (3) of type 790536 for thread 3604
MSI (s) (74:8C) [11:04:43:266]: Invoking remote custom action. DLL: C:WINDOWSInstallerMSI244.tmp, Entrypoint: OutputString
MSI (s) (74:2C) [11:04:43:266]: Generating random cookie.
MSI (s) (74:2C) [11:04:43:266]: Created Custom Action Server with PID 3480 (0xD98).
MSI (s) (74:08) [11:04:43:282]: Running as a service.
MSI (s) (74:64) [11:04:43:282]: Hello, I'm your 64bit Impersonated custom action server.

This is predicated on whether or not you’re invoking embedded binaries or installed binaries. Since your installed binaries in a 32-bit package will install to the 32-bit location even on a 64-bit machine, whichever file was installed last – taking into account default file versioning – would dictate the bitness of the custom action server.

If the goal is to decrease the payload, you can still create separate packages but define your media entries so that 32- and 64-bit payloads (each of IA64 and x64 if you’ll be installing either) are in separate cabinets. Read more about file sequences and media entries. You can then put all the .msi packages in the same directory and put all of the cabinets in that directory as well. This is actually how the .NET Framework 2.0 SP1 and 3.0 SP1 packages are laid out. Cabinets can be shared between packages for common files, rather than having distinct sets for each supported platform.