How to conditionally set a file extension using Wix? - installation

I'd like to conditionally set a file extension during install. As I understand it, in order to do anything conditionally in Wix, it should be a self-contained component. So for each file type association I would like to allow the user to set, I've got a component similar to the following:
<Component Id="FileAssocComponent_PS" Guid="DAFE9461-2DF0-934A-F204-6B28CEA23C01">
<Condition>FILE_ASSOC_PS</Condition>
<RegistryValue Root="HKLM" Key="SOFTWARE\PrinterApp\Capabilities\FileAssociations" Name=".prn" Value="PrinterApp.ps" Type="string" />
<RegistryValue Root="HKLM" Key="SOFTWARE\PrinterApp\Capabilities\MIMEAssociations" Name="application/postscript" Value="PrinterApp.ps" Type="string" />
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\PrinterApp.ps" Name="FriendlyTypeName" Value="PostScript File" Type="string" />
<ProgId Id="PrinterApp.ps" Description="PostScript File" Icon="PrinterApp.ico" Advertise="yes">
<Extension Id="ps">
<Verb Id="open" Command="Open" Argument=""%1""/>
</Extension>
</ProgId>
</Component>
But this gives me the following error:
error LGHT0204: ICE19: Extension: 'ps' advertises component: 'FileAssocComponent_PS'. This component cannot be advertised because the KeyPath type disallows it.
I've tried setting KeyPath="yes" on one of the registry entries, but that doesn't work - and from what I've been able to find it's expecting a file KeyPath. But this is a component that doesn't contain any files!
How do I get around this error, or am I going about this the wrong way?

Advertised components require a keyfile so here are some ways around the error.
1)
Give the component a fake file ( printermimeinstalled.txt ) that won't harm the system.
2)
Author PrinterAppMime.ps as the keyfile of this component. Use the CopyFile element to get the file copied to PrinterApp.ps
Author PrinterAppNoMime.ps (same contents) as the keyfile of another component. Also use the CopyFile element to get the file copied to PrinterApp.ps. Give this component a mutually exclusive component condition so that only 1 component ever gets instaleld.
3)
Change the design of your app a little bit. Have PrinterApp.ps always installed and PrinterAppMimeServer.ps conditionally installed.
4)
Eliminate this custom action and use a custom action to author MSI temp table rows at installtime to defind the MIME stuff if the checkbox is selected.
Each of these 4 approaches have pro's and con's and I personally would choose #3.

If you set Advertise="no" you should be able to use the code you wrote. Here's an example I posted a couple of years ago here using a separate component for optional file associations.
<Component ....>
<ProgId Id="AcmeFoobar.Document" hDescription="ACME XYZ Document">
<Extension Id="pdf" ContentType="application/xyz">
<Verb Id="open" Command="Open" TargetFile="[APPLICATIONFOLDER]AcmeFoobar.exe" Argument="%1" />
</Extension>
</ProgId>
<Condition><![CDATA[DEFAULTVIEWER=1]]></Condition>
</Component>

I found a solution that worked for me. The issue I had was that I had a condition on the association of the extension to the exe. If the extension was unchecked to not associate, I needed the exe component to get installed but without the progid. Problem was that if a put a condition on the component, the progid wouldn't be created but the exe didn't get installed as well. The solution I found was two create two components. One with the condition and one with mutually exclusive condition. This is basically option 2 from Christopher Painters post.
See below:
<Component Id="My.exe" Guid="{D9CF6FDD-1234-4E90-85A1-3BF1F912C1E3}">
<Condition>NOT FILES_ASSOCIATIONS_ABC</Condition>
<File Id="My.exe.without_assoc" Name="My.exe" KeyPath="yes" Vital="yes" Compressed="yes" DiskId="1" Source=".\SourceDir\My.exe" />
</Component>
<Component Id="My.exe_assoc" Guid="{07F96643-5D74-1234-9DAE-CDEB5AC2D11E}">
<File Id="My.exe.with_assoc" Name="My.exe" KeyPath="yes" Vital="yes" Compressed="yes" DiskId="1" Source=".\SourceDir\My.exe" />
<Condition>FILES_ASSOCIATIONS_ABC</Condition>
<ProgId Id="My.Document" Description="My exe" Icon="MyIcon" Advertise="yes">
<Extension Id="abc">
<Verb Id="open" Command="My Exe" Argument=""%1"" />
<MIME Advertise="yes" ContentType="application/abc" Default="yes" />
</Extension>
</ProgId>
</Component>

Related

How to register file assocation in a separate component in WiX

I'm trying to make a separate feature for registering a file association in WiX installer:
<Feature Id="AssociationFeature" Title="File Association" Description="Register file association">
<ComponentRef Id="AssociationComponent" />
</Feature>
However, the parts responsible for registering a file association and installing the executable itself are located in different components:
<!-- Component containing application executable -->
<Component Id="ExeComponent" Guid="F183BFA1-A7AB-45E4-1FB7-0A680826C58E">
<File Name="my-application.exe" Id="Executable" />
</Component>
<!-- Component registering the file extension association -->
<Component Id="AssociationComponent" Guid="1575A831-5FE0-4720-9646-535C88CDE46B">
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\my-application.abc" Name="FriendlyTypeName" Value="My Application" Type="string" />
<ProgId Id="my-application.abc">
<Extension Id="abc">
<Verb Id="Open" TargetFile="Executable" /> <!-- This gives the error -->
</Extension>
</ProgId>
</Component>
This leads to the following error:
error LGHT0204 : ICE69: Mismatched component reference. Entry '...' of the Registry table belongs to component 'AssociationComponent'. However, the formatted string in column 'Value' references file 'Executable' which belongs to component 'ExeComponent'. Components belong to different features
Is there any way to register a file association in a separate component different from the executable?
The general way to deal with this is to have two components. Both contain the file, but only one component contains the shortcut. At install time just choose the appropriate component. As long as the conditions on the components (or the features) ensure that both components won't be installed, this just works.

How do you add multiple entries to the `PATH` Envirnoment Variable using WiX?

I'm creating a MSI installer and need to add more than one entry to the PATH environment variable. Per the MSDN documentation:
Each row can contain only one value. For example, the entry Value;Value;[~] is more than one value and should not be used because it causes unpredictable results. The entry Value;[~] is just one value.
My installer source code looks like this currently (note, this is a per-machine installation), which is a violation of the above documentation:
<!-- NOTE: These two features are mutually exclusive -->
<Feature Id="Feature1" Level="1000" Absent="allow" AllowAdvertise="no" InstallDefault="local" TypicalDefault="install">
<Component Directory="INSTALLFOLDER">
<RegistryValue Action="write" Type="int" Root="HKLM" Key="SOFTWARE\MyProduct" Name="MyPathEntry1" Value="1" KeyPath="yes" />
<Environment Id="AddPathEntry1" Name="PATH" Value="[INSTALLFOLDER]SubDir1" Action="set" Permanent="yes" Part="last" System="yes" />
</Component>
</Feature>
<Feature Id="Feature2" Level="1000" Absent="allow" AllowAdvertise="no" InstallDefault="local" TypicalDefault="install">
<Component Directory="INSTALLFOLDER">
<RegistryValue Action="write" Type="int" Root="HKLM" Key="SOFTWARE\MyProduct" Name="MyPathEntry2" Value="1" KeyPath="yes" />
<Environment Id="AddPathEntry2" Name="PATH" Value="[INSTALLFOLDER]SubDir1;[INSTALLFOLDER]SubDir2;[INSTALLFOLDER]SubDir3" Action="set" Permanent="yes" Part="last" System="yes" />
</Component>
</Feature>
Now even though the above is "technically" a violation according to the MSDN documentation, it seems to work. I've tested fresh installations, modifying an installation, and upgrading. All seem to work with no hitch. But one thing I've learned with MSI is whenever possible, it's best to follow the rules to avoid messing up people's machines.
The natural solution of adding independent (i.e. not mutually exclusive) features containing only individual path components won't work because with MSI, you cannot guarantee the order in which features and/or components are installed. However, in this case, the order in which path components are added to the PATH environment variable is important due to how the PATH variable is used when finding unqualified executables.
The other question that may come to mind is, why am I using features? I want to give installers of the product the option to change their installation choice at a later date in time via the standard Add/Remove Programs or Program and Features Control Panel applet.
So, how can I add more than one path entry to the PATH environment variable in a deterministic order while following the recommended guidance from the MSDN? Or is the guidance from MSDN outdated and what I'm currently doing is perfectly fine?
I figured a way to do this which does not violate the guidance at MSDN for my particular situation.
The exact situation is I have a main program executable that installers may or may not want to be able to run from a Windows Command Shell. Additionally, there are other utilities that also installers may or may not wish to run from a Windows Command Shell.
The way this was originally modeled was as a set of 3 mutually exclusive features using radio buttons to decide how to update the system PATH environment variable. The 3 features boiled down to:
Don't modify the system's PATH environment variable (i.e. the program and optional tools can't be run from a Windows Command Shell by simply typing the name of the executable at any directory location).
Update the system's PATH environment variable so that only the main executable can be run from a Windows Command Shell at any given path.
Update the system's PATH environment variable to also add additional utility tool paths.
What I came to realize is that there are really only 2 features here, and they're not even mutually exclusive, but one is dependent on the other. The first radio button listed above is not really a feature--it's a no-op; it doesn't install anything! The second radio button represents the "main" feature in that the primary executable's directory is added to the system's PATH. The 3rd radio button depends upon the second: if the main executable is added to the PATH, then you can additionally add other utilities to the PATH as well.
This results in a much simpler UI. Instead of radio buttons, use checkboxes, and the second checkbox is disabled unless the first checkbox is checked. This also results in a much simpler implementaion for ensuring that the combination of features selected (say, for a command-line installation) is valid.
In a nutshell, here's what the WiX XML will boil down to in my particular case. I'm sure that this can be generalized (or modified) to suit other similar scenarios.
<Feature Id="AddExeToPath" Level="1" Absent="allow" AllowAdvertise="no" InstallDefault="local" TypicalDefault="install">
<Component Id="ExePath" Guid="{YOURGUID-HERE-0000-0000-00000000}" Directory="INSTALLFOLDER">
<CreateFolder />
<Environment Id="ExePath" Name="MY_PATHS" Value="[INSTALLFOLDER]SubDir1" Action="set" Part="first" System="yes" />
</Component>
<Component Directory="INSTALLFOLDER">
<RegistryValue Action="write" Type="integer" Root="HKLM" Key="SOFTWARE\MyProduct" Name="AddExeToPath" Value="1" KeyPath="yes" />
<Environment Id="AddToPath" Name="PATH" Value="%MY_PATHS%" Action="set" Part="last" System="yes" />
</Component>
<Component Id="RemoveExeFromPathOnUninstall" Guid="{YOURGUID-HERE-0000-0000-00000000}" Directory="INSTALLFOLDER">
<Condition>REMOVE><AddExeToPath OR REMOVE="ALL"</Condition>
<Environment Id="RemoveFromPath" Name="PATH" Value="%MY_PATHS%" Action="remove" />
</Component>
</Feature>
<Feature Id="AddToolsToPath" Level="1000" Absent="allow" AllowAdvertise="no" InstallDefaut="local" TypicalDefault="install">
<Component Id="SubDir2" Guid="{YOURGUID-HERE-0000-0000-00000000}" Directory="INSTALLFOLDER">
<CreateFolder />
<Environment Id="SubDir2" Name="MY_TOOLS" Value="[INSTALLFOLDER]SubDir2" Action="set" Part="first" System="yes" />
</Component>
<Component Id="SubDir3" Guid="{YOURGUID-HERE-0000-0000-00000000}" Directory="INSTALLFOLDER">
<CreateFolder />
<Environment Id="SubDir3" Name="MY_TOOLS" Value="[INSTALLFOLDER]SubDir3" Action="set" Part="last" System="yes" />
</Component>
<Component Directory="INSTALLFOLDER">
<RegistryValue Action="write" Type="integer" Root="HKLM" Key="SOFTWARE\MyProduct" Name="AddToolsToPath" Value="1" KeyPath="yes" />
<Environment Id="AddToolsToPath" Name="MY_PATHS" Value="%MY_TOOLS%" Action="set" Part="last" System="yes" />
</Component>
</Feature>
<CustomAction Id="InvalidPathFeatureSelection" Error="25000" Execute="firstSequence" />
<InstallExecuteSequence>
<Custom Action="InvalidPathFeatureSelection" Before="InstallValidate">
<![CDATA[NOT (REMOVE="ALL" OR (&AddToolsToPath >= 3 IMP &AddExeToPath >= 3))]]>
</Custom>
</InstallExecuteSequence>
This set of features and components results in the following:
If the feature AddExeToPath is selected for installation, the following components of the feature result in:
An environment variable named MY_PATHS is created and contains the path to the main executable.
The environment variable PATH is updated, placing %MY_PATHS% at the end its current value.
If the feature AddToolsToPath is selected, 3 components are installed:
The environment variable MY_TOOLS is created/set, and [INSTALLFOLDER]SubDir2 is put at the front of the variable's existing value (if any).
The environment variable MY_TOOLS is created/set and [INSTALLFOLDER]SubDir3 is put at the end of the variable's existing value (if any).
Note
These two components defined the way that they are ensure that the paths added to MY_TOOLS are added in the appropriate order.
The environment variable MY_PATHS is updated, placing %MY_TOOLS% at the end of the exisiting variable; again, this preserves the correct ordering of paths.
And so what you end up with is:
MY_TOOLS = [INSTALLFOLDER]SubDir2;[INSTALLFOLDER]SubDir3
MY_PATHS = [INSTALLFOLDER]SubDir1;%MY_TOOLS%
PATH = <existing_PATH_value>;%MY_PATHS%

Unregister COM component in WIX during rollback, is it necessary?

I am creating a WIX installer project. Part of the work in the installation is to register some COM component. I have (hopefully) succeeded with that by using heat.exe to generate the COM component declaration, although I needed to modify a little after the wxs files were generated. To make it clear, I am pasting my COM component declaration here:
<ComponentGroup Id="COMComponent" Directory="INSTALLDIR">
<Component Id="COMDll" Guid="MY_GUID1">
<Class Id="{MY_CLASSID1}" Context="InprocServer32" Description="DESCRIPTION1" ThreadingModel="both" ForeignServer="mscoree.dll">
<ProgId Id="MYID1" Description="DESCRIPTION1" />
</Class>
...
<File Id="FILE_ID_1" KeyPath="yes" Source="MY_COM.dll" />
<File Id="FILD_ID2" KeyPath="no" Source="MY_COM.tlb">
<TypeLib Id="{xxx}" Description="xxx" HelpDirectory="INSTALLDIR" Language="0" MajorVersion="525" MinorVersion="0">
<Interface Id="{xxx}" Name="xxx" ProxyStubClassId32="{00020424-0000-0000-C000-000000000046}" />
...
</TypeLib>
</File>
<ProgId Id="Record" />
<RegistryValue Root="HKCR" Key="CLSID\{xxx}\Implemented Categories\{xxx}" Value="" Type="string" Action="write" />
<RegistryValue Root="HKCR" Key="CLSID\{xxx}\InprocServer32\525.0.4573.25681" Name="Class" Value=" MYID1" Type="string" Action="write" />
...
</Component>
</ComponentGroup>
I replaced some IDs with xxx, so they are not a problem. If there is anything else wrong, please inform me.
Now my question is: do I need to explicitly specify to uninstall the COM component in the rollback phase? Because I have experienced some problem, that after some failure installation, the further tries of installations wouldn't go through and some "ActiveX object can't be created" error popped up. I suspected it is because COM component was not unregistered successfully in the previous fail installation. But I am not sure. In fact I have limited knowledge in COM interop, so I am not sure what's necessary for install/uninstall them. Any help would be appreciated. Thanks!
When you use Heat to harvest the COM components, that will be sufficient. It should handle unregistering your COM components if the installation fails and is rolled back.

How do I allow a user to select a configuration file when building my WiX installer?

I'm using WiX XML to create an install package.
One of the things I'd like the user/installer to be able to do is select from one (and only one) of several config files (i.e., config1.txt, config2.txt, config3.txt) that would wind up in the same location after the install is done (i.e., the final file is config.txt). How do I do this in WiX?
The manual install has the user copying the desired config file to the final location/file. i.e., if the user wants to use config2.txt, he copies config2.txt to config.txt. He runs the program. Then later, if he needs to use config1.txt, he copies config1.txt to config.txt and runs the program.
Any idea how to do this in WiX?
Thanks in advance!
-Adeena
I solve a similar problem using a CopyFile tag;
Somewhere I have a <Directory> containing the following components:
<Component Id="Config_6.txt" Guid="{}">
<File Id="Config_6.txt" Name="Config_6.txt" KeyPath="yes" Source="..\..\..\bin\Config_6.txt" />
</Component>
<Component Id="Config.txt" Guid="{}">
<File Id="Config.txt" Name="Config_8.txt" KeyPath="yes" Source="..\..\..\bin\Config.txt" />
</Component>
<Component Id="Config_7.txt" Guid="{}">
<File Id="Config_7.txt" Name="Config_7.txt" KeyPath="yes" Source="..\..\..\bin\Config_7.txt" />
</Component>
<Component Id="R8_Config.txt" Guid="{}">
<CreateFolder/>
<CopyFile Id="R8_Config.txt" FileId="Config.txt" DestinationName="Config.txt"/>
</Component>
<Component Id="R7_Config.txt" Guid="{}">
<CreateFolder/>
<CopyFile Id="R7_Config.txt" FileId="Config_7.txt" DestinationName="Config.txt"/>
</Component>
<Component Id="R6_Config.txt" Guid="{}">
<CreateFolder/>
<CopyFile Id="R6_Config.txt" FileId="Config_6.txt" DestinationName="Config.txt"/>
</Component>
And in the Features part something like this:
<Feature Id="Config" Title="Config directory" Display="expand" Level="1" ConfigurableDirectory="Config">
<Feature Id="Config8" Title="Config 8" Level="1" Description="Select only one version!">
<ComponentRef Id="R8_Config.txt"/>
</Feature>
<Feature Id="Config7" Title="Config 7" Level="1002" Description="Select only one version!">
<ComponentRef Id="R7_Config.txt"/>
</Feature>
<Feature Id="Config6" Title="Config 6" Level="1004" Description="...">
<ComponentRef Id="R6_Config.txt"/>
</Feature>
</Feature>
To display the features, add a UIRef that contains CustomizeDlg, for example:
<UIRef Id="WixUI_FeatureTree" />
A disadvantage of this solution is that the user can select multiple config files. That will probably cause the last one to become the active one.
Is this a web app? Are these ini files or xml files or something else? I prefer application configuration to be done by the application itself after installation during first launch (if it is an exe file). An installation is about getting default settings and files in place, any custom configuration is beyond the scope of initial deployment in my opinion.
Wix provides a feature to update an xml file during installation, here is a sample: http://www.tramontana.co.hu/wix/lesson6.php#6.10 . This would allow you to write a specific config setting to the file if it is an XML file.
There is nothing stopping you from installing several flavors of the same config file in the same folder and allowing the user to manually switch between them. What values typically change if you switch the base file? One or many settings?

WIX 3 : Using HEAT for Visual Basic 6 COM Dlls

I am using WIX 3. I have used heat to create a wxs file for a VB6 dll. The msi creates without any errors, and the installation is successful as well.
All seems to be fine, and I can invoke the component successfully from a VB client.
However, if I invoke the component from an ASP page, I get 0x800401f3.
If instead of the installer, I use self registration (regsvr32), both work fine.
I did a registry difference to figure out what was the difference between self registration (regsvr32) and the installer, and I see the following
All entries in HKCR match - all well here
regsvr32 adds entries in HKLM, while the installer does not touch HKLM
I am wondering if this is the issue, or am I completely on a wrong track.
MSDN (http://msdn.microsoft.com/en-us/library/ms694355(VS.85).aspx) mentions that registry entries are required in HKLM, wondering what am I missing here.
Following is the file created by heat.
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="TARGETDIR">
<Directory Id="dirAD70B10292EAB7CAC7171859FBB23AA9" Name="vbdll" />
</DirectoryRef>
</Fragment>
<Fragment>
<DirectoryRef Id="dirAD70B10292EAB7CAC7171859FBB23AA9">
<Component Id="cmp9D818C62A6239E8B51E971A0048D0C05" Guid="PUT-GUID-HERE">
<File Id="filDD6F51EC5018EF4A9A312FFA6AC4257D" KeyPath="yes" Source="SourceDir\vbdll\act.dll">
<TypeLib Id="{80D8DA04-72C9-4D36-B269-57D989187ACF}" Description="act" HelpDirectory="dirAD70B10292EAB7CAC7171859FBB23AA9" Language="0" MajorVersion="1" MinorVersion="0">
<Class Id="{31BD65B6-9479-40EB-83C0-E717CD4793DD}" Context="InprocServer32" Description="act.def" ThreadingModel="apartment" Version="1.0" Programmable="yes">
<ProgId Id="act.def" Description="act.def" />
</Class>
<Interface Id="{C6D46026-CD7E-4AB0-B3B6-810FBF435BEF}" Name="def" ProxyStubClassId="{00020424-0000-0000-C000-000000000046}" ProxyStubClassId32="{00020424-0000-0000-C000-000000000046}" />
</TypeLib>
</File>
<RegistryValue Root="HKCR" Key="CLSID\{31BD65B6-9479-40EB-83C0-E717CD4793DD}\Implemented Categories\{40FC6ED5-2438-11CF-A3DB-080036F12502}" Value="" Type="string" Action="write" />
</Component>
</DirectoryRef>
</Fragment>
</Wix>
Update : Using the "SelfReg" option for the File makes the ASP client work as well. I read from other posts that this is not to be used. Can someone tell me what's to be done?
To get the installer to put entries under HKLM, the installation has to be marked as perMachine, the default seems to be perUser, as done below.
<Package InstallScope="perMachine" InstallerVersion="200" Languages="1033" Compressed="yes" SummaryCodepage="1252" />
Once this is done, the entries come in HKCR and also HKLM.
I hope somebody finds this useful, took me a good 6 hours..

Resources