How to register file assocation in a separate component in WiX - windows

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.

Related

Service not being installed properly using WIX installer

As the title says, I am have the following snippet of a code that installs a service using wix
<Directory Id="Test" Name="Test">
<Component Id="ConnectorMainService" Guid="{SOME-ID}">
<CreateFolder/>
<ServiceInstall Id="ServiceInstaller" Type="ownProcess" Name="$(var.PRODUCT_NAME)" DisplayName="$(var.PRODUCT_NAME)" Description="$(var.DESCRIPTION)" Start="auto" Account="NT AUTHORITY\LocalService" ErrorControl="normal" Interactive="no" Vital="yes"/>
<ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="$(var.PRODUCT_NAME)" Wait="yes"/>
</Component>
</Directory>
I had to put a <CreateFolder> in there, otherwise I have the same issues as described in this question (Why does my WiX installer need an empty CreateFolder to conditionally update an Xml file?).
The difference I have is even having the <CreateFolder> there, the installer exits without creating the service, it just creates the Folder.
Replace the CreateFolder element with a File element for the service executable itself. The Windows Installer requires the service's executable File and ServiceInstall elements to be in the same Component (specifically, the service executable must be the KeyPath of the Component but WiX will take care of that for you if you put the executable File element first in the Component).

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%

How to conditionally set a file extension using Wix?

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>

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?

Can't get Wix custom action to work in Votive/VS2010

Help! I need to execute a managed custom action in my Wix 3.5 setup project and no matter what I've tried I can't get it to work.
I'm using the Votive integration in Visual Studio 2010. My Wix Product.wxs file is basically unchanged from the visual studio template except a few text changes:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="666ffc07-90b2-4608-a9f0-a0cc879f2ad0" Name="Product Name" Language="1033" Version="5.5.0002" Manufacturer="TiGra Astronomy" UpgradeCode="d17a5991-b404-4095-9e93-08d2db984cfd">
<Package InstallerVersion="200" Compressed="yes" />
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLLOCATION" Name="Directory Name">
<!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
<!-- <Component Id="ProductComponent" Guid="3ea5ade7-9b7b-40da-9e83-13e066a000ef"> -->
<!-- TODO: Insert files, registry keys, and other resources here. -->
<!-- </Component> -->
</Directory>
</Directory>
</Directory>
<Feature Id="ProductFeature" Title="ASCOM Driver" Level="1">
<!-- TODO: Remove the comments around this ComponentRef element and the Component above in order to add resources to this installer. -->
<!-- <ComponentRef Id="ProductComponent" /> -->
<!-- Note: The following ComponentGroupRef is required to pull in generated authoring from project references. -->
<ComponentGroupRef Id="Product.Generated" />
</Feature>
</Product>
I have set a reference to my managed custom action project, set the HARVEST property to true. The project is called WIX.CustomActions and produces WIX.CustomActions.dll and WIX.CustomActions.CA.dll
I see that Wix is processing the reference during build and the WIX.CustomActions.dll assembly shows up in the Binary table in the final setup project, but the WIX.CustomActions.CA.dll does not.
I have a CustomActions.wxs that should package and invoke the custom action:
<?xml version="1.0" encoding="UTF-8" ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<Binary Id="DriverRegistrationCA" SourceFile="$(var.WIX.CustomActions.TargetDir)\$(var.WIX.CustomActions.TargetName).CA.dll" />
<CustomAction Id="RegisterDriver" BinaryKey="DriverRegistrationCA" DllEntry="RegisterAscomDriver" Execute="deferred" Return="check" />
<CustomAction Id="UnregisterDriver" BinaryKey="DriverRegistrationCA" DllEntry="UnregisterAscomDriver" Execute="immediate" Return="check" />
<InstallExecuteSequence>
<Custom Action="RegisterDriver" After="InstallFinalize" />
<Custom Action="UnregisterDriver" Before="RemoveFiles" />
</InstallExecuteSequence>
</Fragment>
</Wix>
I've looked at various 'howto' sources on the interweb and they are at best confusing, with contradictory advice. As I understand it, the WIX.CustomActions.CA.dll file is an unmanaged dll that loads the .NET framework and passes control to the 'real' managed custom action. However, the WIX.CustomActions.CA.dll does not get packaged in my MSI file. I've followed examples as best I can but I can't see what's wrong.
Please, has anyone got this working in Votive? Can you give me an actual working example?
You need a reference (e.g., CustomActionRef) from your product to the fragment; otherwise, it's discarded by the smart linker.
Following on from Bob Arnson's suggestion, I added the following two lines near the top of my Product.wxs file:
<CustomActionRef Id="RegisterDriver"/>
<CustomActionRef Id="UnregisterDriver"/>
That seems to have done the trick. Orca now shows that I have a Binary table, containing my CA dll, and a CustomAction entry in InstallExecuteSequence.
None of the examples I found on the web mentioned this requirement. I guess people were just recycling received wisdom with little or no understanding. So here is the answer, thanks to Bob!

Resources