Decoupling Wix Component from ComponentGroup - wix

I'm trying to generate Wix source from a custom Visual Studio extension. As such, I'd like to be able to (somehow) just add one file (plus project reference) to the Wix Project, and have the new DLLs added in to the Product.
As an example:
<Product Id="*" Name="blah" Version="..." Manufacturer="foo" UpgradeCode="...">
<Package InstallerVersion="200" ... />
<MajorUpgrade DowngradeErrorMessage="..." />
<MediaTemplate EmbedCab="yes" />
<Feature Id="ProductFeature" Title="blah" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
<!-- Custom actions, Directories, etc .... -->
</Product>
Then in separate files (which I want to be generated), I have some Fragments:
<Fragment>
<DirectoryRef Id="MYINSTALLDIR">
<Component Id="CMP_FILE1" Guid="...">
<File Id="FILE1" Source="file1.dll" Assembly=".net" KeyPath="yes" />
</Component>
</DirectoryRef>
</Fragment>
and
<Fragment>
<DirectoryRef Id="MYINSTALLDIR">
<Component Id="CMP_FILE2" Guid="...">
<File Id="FILE2" Source="file2.dll" Assembly=".net" KeyPath="yes" />
</Component>
</DirectoryRef>
</Fragment>
So far so good. The problem is that I need to tie those together with something like:
<Fragment>
<ComponentGroup Id="ProductComponents">
<ComponentRef Id="CMP_FILE1" />
<ComponentRef Id="CMP_FILE2" />
</ComponentGroup>
</Fragment>
That works, but I don't want to do that, because it requires editing of the ComponentGroup when I want to add the next file.
So I want to try to localise the information into my added file. I can live with it always being part of the same Feature.
I tried adding the Feature attribute to Component element:
<Fragment>
<DirectoryRef Id="MYINSTALLDIR">
<Component Id="CMP_FILE1" Guid="..." Feature="ProductFeature">
<File Id="FILE1" Source="file1.dll" Assembly=".net" KeyPath="yes" />
</Component>
</DirectoryRef>
</Fragment>
but that didn't seem to add the Component to the parent feature (empty Media table warning from Wix on build, and Orca confirmed it).
I also tried adding the ComponentGroup to each generated file, but of course I can't duplicate Id attributes, and unique Id just pushes the coupling problem up into Feature...
Is there a way to add a Component without editing the ComponentGroup?

No. But you could generate (rather than edit) your ComponentGroup[#Id="ProductComponents"]. The file where it is defined can be "hidden" by generating it into the obj folder and dynamically adding it to the compile. This is effectively what HarvestDirectory and the other targets that call heat do.
While your extension is adding project references and files into the project, it can also add an MSBuild Include that defines a Target with BeforeTargets="Compile". That Target can do the generation and add the generated file to the Compile ItemGroup.
You just have to have a contract that the extension will use a particular ComponentGroup Id for this purpose. (Heat uses unique names for component and file ids to prevent conflicts. I suggest you do that too, especially for "hidden" source files.)

Related

Wix create silent uninstall file

my idea is make an uninstall file with .msi install file. I read some information about creating uninstaller shortcut here: http://wixtoolset.org/documentation/manual/v3/howtos/files_and_registry/create_uninstall_shortcut.html , But i cant found information about make uninstall file after msi build , maybe whom know it's possible ? and if possible how i can do it ? or maybe it possible to do with cmd script? Just write script for automatically uninstall my program from mashine. My code is :
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"><?define WpfApp1_TargetDir=$(var.WpfApp1.TargetDir)?>
<Product Id="*" Name="SetupProject2" Language="1033" Version="1.0.0.0" Manufacturer="Andrejka" UpgradeCode="PUT-GUID-HERE">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<Property Id="WIXUI_INSTALLDIR" Value="TESTFILEPRODUCTDIR" />
<Property Id="WixShellExecTarget" Value="[#WpfApp1.exe]" />
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
<Property Id="LAUNCH_APP_ON_EXIT" Value="1" />
<InstallExecuteSequence>
<Custom Action='LaunchApplication' After='InstallFiles'/>
</InstallExecuteSequence>
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate EmbedCab="yes"/>
<Feature Id="ProductFeature" Title="SetupProject2" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="TESTFILEPRODUCTDIR" Name="SetupProject2">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="SetupProject2" />
</Directory>
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
<!-- <Component Id="ProductComponent"> -->
<!-- TODO: Insert files, registry keys, and other resources here. -->
<!-- </Component> -->
<Component Id="WpfApp1.exe" Guid="*">
<File Id="WpfApp1.exe" Name="WpfApp1.exe" Source="$(var.WpfApp1_TargetDir)WpfApp1.exe" />
</Component>
<Component Id="WpfApp1.exe.config" Guid="*">
<File Id="WpfApp1.exe.config" Name="WpfApp1.exe.config" Source="$(var.WpfApp1_TargetDir)WpfApp1.exe.config" />
</Component>
<Component Id="aws_sdk_net_core_support.dll" Guid="*">
<File Id="aws_sdk_net_core_support.dll" Name="aws-sdk-net-core-support.dll" Source="$(var.WpfApp1_TargetDir)aws-sdk-net-core-support.dll" />
</Component>
<Component Id="AWSSDK.Core.dll" Guid="*">
<File Id="AWSSDK.Core.dll" Name="AWSSDK.Core.dll" Source="$(var.WpfApp1_TargetDir)AWSSDK.Core.dll" />
</Component>
<Component Id="AWSSDK.SimpleNotificationService.dll" Guid="*">
<File Id="AWSSDK.SimpleNotificationService.dll" Name="AWSSDK.SimpleNotificationService.dll" Source="$(var.WpfApp1_TargetDir)AWSSDK.SimpleNotificationService.dll" />
</Component>
<Component Id="MimeSharp.dll" Guid="*">
<File Id="MimeSharp.dll" Name="MimeSharp.dll" Source="$(var.WpfApp1_TargetDir)MimeSharp.dll" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>
In general you are not supposed to put uninstall shortcuts in the start menu, it is in fact a violation of Microsoft's logo requirements for Windows applications I believe. Rather you should let people uninstall your product the normal way via the add/remove programs applet.
UPDATE: I found this answer with some more information on this topic: Shortcuts with name "Uninstall <Program Name>" are not displayed in Windows 8/8.1/10
Also, just so it is clear, uninstall, is a built-in feature of MSI files - it is always automatically available unless actively blocked (such as some applications hiding themselves from display in add/remove programs). There is nothing extra you have to do in your WiX sources to support uninstall properly. Just follow Windows Installer guidelines and it comes "for free".
If what you are asking is for a way to create an uninstall batch file, then you can find a plethora of ways to uninstall your MSI file in this "uninstall reference": Uninstalling an MSI file from the command line without using msiexec.
In short, just run the command line below to uninstall your MSI if you have the MSI's product code (you can find your product code by querying your system as described here: How can I find the product GUID of an installed MSI setup? - you might need to look it up since you auto-generate your product code):
msiexec.exe /x {your-product-guid}
or just uninstall by referring to your original MSI installation file like this:
msiexec.exe /x "c:\filename.msi
See the linked answer above (the uninstall reference) for a lot more information about this.

WiX shortcut only project

I have my bootstrapper project and I need to add a shortcut only when another third part msi is selected on bootstrapper's UI. So I end up with another little msi like this (removing not relevant data):
<Wix >
<Product >
<Package />
<MajorUpgrade />
<MediaTemplate />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder" Name="FolderName">
</Directory>
</Directory>
</Directory>
<Component Id="ApplicationShortcut" Guid="PUT-GUID-HERE" Directory="ApplicationProgramsFolder">
<Shortcut Id="ApplicationStartMenuShortcut"
Name="App"
Description="desc"
Target='"[ProgramFiles64Folder]Folder1\Folder2\app.exe"'
Arguments=' -n name'
/>
<RemoveFolder Id="RemoveProgramMenuDir" Directory="ApplicationProgramsFolder" On="uninstall" />
<RegistryValue Root="HKCU" Key="Software\ACME\App" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
</Component>
<Feature Id="ProductFeature" Title="Shortcuts" Level="1">
<ComponentRef Id="ApplicationShortcut" />
</Feature>
</Product>
But I get
ICE71: The Media table has no entries.
That source doesn't compile anyway because of issues with missing languages, productcodes and so on. It would have helped if you'd posted a complete working example. After fixing those issues and seeing ICE71, you basically need to delete mediatemplate and add a proper media entry such as (in angle brackets)
Media Id="1" Cabinet="product.cab" EmbedCab="yes"
then all you get is warning LGHT1079 : The cabinet 'product.cab' does not contain any files. If this installation contains no files, this warning can likely be safely ignored. Otherwise, please add files to the cabinet or remove it.
I would just augment the third party MSI with a transform that creates a shortcut when that MSI is installed. This way you don't have to do anything special in the bootstrapper.

WiX - How to change name of a directory after installation

Pretty simple question, suppose my app will be installed as
myApp
+-- bin
+-- lib
I'd like to rename "lib" to "plugins" after installation, how can i do that?
There is a MoveFiles Element that might help, but no idea how to use it.
EDIT:
The problem here is, in my case, source files could be installed into different paths, this scenario described in here.
The only way I can figure out is to create two component groups and install them conditionally. However, using heat to harvest same subdir twice will cause ID conflicting, so I'm thinking to use different paths(e.g. lib and another-lib), and then rename one of the path back after installation, so this question arised...
Don't. Install the files correctly up front. The way that the Windows Installer tracks things will fight you every step of the way. Just install the files in the correct folder from the beginning. Probably not the answer you wanted.
I finally wrote a C++ program to extend heat generated wxs with another directory structure. So we can decide which path to install under different situations. It worked just like changing name during installation.
Here is the wxs file patched by my program. Basically it creates another directory WEBIDR and different subdirs, then adds another component group webGroup for later reference by condition element.
You can do the same thing manually, but if there are thousands of files awaiting, and if they are frequently updating, maybe a program(or script) is a better choice.
<Fragment>
<DirectoryRef Id="INSTALLDIR">
<Directory Id="dirA5528701EE26FFBF346CCE20EE8ACE99" Name="bin">
<Component Id="cmpEBA9C2A32D81BA8646BD1A64DBB39DB1" Guid="{142C531A-C71C-4890-9318-0FC42026C8FC}">
<File Id="filDB56E052EC783676CEF361C0C5AA71F3" KeyPath="yes" Source="$(var.runDir)\bin\boost_date_time-vc100-mt-1_47.dll" />
</Component>
</Directory>
<Directory Id="dir3279BEF4E08D9A00D2F205F325F00A81" Name="modules">
<Component Id="cmpDECCAE13F8937500E4AC367A8EAC95F4" Guid="{85CC0C94-1BFB-4062-BC4E-FBF143921301}">
<File Id="filDD3B40D68D0437B18B1108FBA49ABC1B" KeyPath="yes" Source="$(var.runDir)\modules\HelloAPI.dll" />
</Component>
</Directory>
</DirectoryRef>
<DirectoryRef Id="WEBDIR">
<Component Id="webcmpEBA9C2A32D81BA8646BD1A64DBB39DB1" Guid="{fec110c5-a1a0-4b07-8a35-50f1af84001a}">
<File Id="webfilDB56E052EC783676CEF361C0C5AA71F3" KeyPath="yes" Source="$(var.runDir)\bin\boost_date_time-vc100-mt-1_47.dll" />
</Component>
<Directory Id="webdirpluginF4E08D9A00D2F205F325F00A81" Name="plugins">
<Component Id="webcmpDECCAE13F8937500E4AC367A8EAC95F4" Guid="{3ef79a47-7681-4991-9726-02db38c22f6d}">
<File Id="webfilDD3B40D68D0437B18B1108FBA49ABC1B" KeyPath="yes" Source="$(var.runDir)\modules\HelloAPI.dll" />
</Component>
</Directory>
</DirectoryRef>
</Fragment>
<Fragment>
<ComponentGroup Id="runGroup">
<ComponentRef Id="cmpEBA9C2A32D81BA8646BD1A64DBB39DB1" />
<ComponentRef Id="cmpDECCAE13F8937500E4AC367A8EAC95F4" />
</ComponentGroup>
<ComponentGroup Id="webGroup">
<ComponentRef Id="webcmpEBA9C2A32D81BA8646BD1A64DBB39DB1" />
<ComponentRef Id="webcmpDECCAE13F8937500E4AC367A8EAC95F4" />
</ComponentGroup>
</Fragment>

Features installed to different locations but referencing the same components

I have a product that consists of multiple features that can be installed to different locations e.g. Feature 1 is an executable installed in Program Files and Feature 2 is a website installed in wwwroot. However both Feature 1 and Feature 2 rely on many of the same dll's and hence require the components containing those dll's to be installed in 2 different locations depending on which Features are installed.
Is there a way to achieve this without defining every component twice?
To provide a further complete example of what I am trying to achieve, the following complete wxs file can be compiled using:
> candle.exe Foobar.wxs
> light.exe -ext WixUIExtension Foobar.wixobj
> msiexec /i Foobar.msi
<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<Product Name='Foobar 1.0'
Id='E578DF12-DDE7-4BC2-82CD-FF11862862D5'
UpgradeCode='90F09DD5-E01B-4652-8971-515997730195'
Language='1033'
Codepage='1252'
Version='1.0.0'
Manufacturer='Acme Ltd.'>
<Package Id='*'
Keywords='Installer'
Description="Acme 1.0 Installer"
InstallerVersion='100'
Languages='1033'
Compressed='yes'
SummaryCodepage='1252' />
<Media Id='1' Cabinet='Sample.cab' EmbedCab='yes' DiskPrompt="CD-ROM #1" />
<Property Id='DiskPrompt' Value="Acme 1.0 Installation" />
<Directory Id='TARGETDIR' Name='SourceDir'>
<!-- Directory 1 (Program Files) -->
<Directory Id="ProgramFilesFolder" Name="PFiles">
<Directory Id="PROGRAM_INSTALLDIR" Name="Acme" />
</Directory>
<!-- Directory 2 (wwwroot) -->
<Directory Id="Inetpub" Name="Inetpub">
<Directory Id="wwwroot" Name="wwwroot">
<Directory Id="WEBSITE_INSTALLDIR" Name="AcmeWebSite" />
</Directory>
</Directory>
</Directory>
<DirectoryRef Id='PROGRAM_INSTALLDIR'>
<Component Id="Component1" Guid="79EC9E0B-8325-427B-A865-E1105CB16B62">
<File Id="File1" Name="File1.txt" Source="File1.txt" />
</Component>
</DirectoryRef>
<DirectoryRef Id='WEBSITE_INSTALLDIR'>
<Component Id="Component2" Guid="702E6573-8FBC-4269-A58D-FD1157111F0F">
<File Id="File2" Name="File2.txt" Source="File2.txt" />
</Component>
</DirectoryRef>
<Feature Id="Feature.Program"
Title="My Program"
TypicalDefault="install"
Level="1"
ConfigurableDirectory="PROGRAM_INSTALLDIR" >
<ComponentRef Id="Component1"/>
<ComponentRef Id="Component2"/>
</Feature>
<Feature Id="Feature.Website"
Title="My Website"
TypicalDefault="install"
Level="1"
ConfigurableDirectory="WEBSITE_INSTALLDIR" >
<ComponentRef Id="Component1"/>
<ComponentRef Id="Component2"/>
</Feature>
<UIRef Id="WixUI_Mondo" />
<UIRef Id="WixUI_ErrorProgressText" />
</Product>
</Wix>
This will however result in ONLY File1.txt being installed in
C:\Program Files (x86)\Acme
and ONLY File2.txt being installed in
_C:\Inetpub\wwwroot\AcmeWebsite_
One solution is to define the components twice such as:
<DirectoryRef Id='PROGRAM_INSTALLDIR'>
<Component Id="Component1" Guid="79EC9E0B-8325-427B-A865-E1105CB16B62">
<File Id="File1" Name="File1.txt" Source="File1.txt" />
</Component>
<Component Id="Component2" Guid="702E6573-8FBC-4269-A58D-FD1157111F0F">
<File Id="File2" Name="File2.txt" Source="File2.txt" />
</Component>
</DirectoryRef>
<DirectoryRef Id='WEBSITE_INSTALLDIR'>
<Component Id="Component1.Web" Guid="397E93AA-32FB-425A-A783-386E0CCA2357">
<File Id="File1.Web" Name="File1.txt" Source="File1.txt" />
</Component>
<Component Id="Component2.Web" Guid="5C3AFF06-3623-4524-A90B-72B46DE5572A">
<File Id="File2.Web" Name="File2.txt" Source="File2.txt" />
</Component>
</DirectoryRef>
<Feature Id="Feature.Program"
Title="My Program"
TypicalDefault="install"
Level="1"
ConfigurableDirectory="PROGRAM_INSTALLDIR" >
<ComponentRef Id="Component1"/>
<ComponentRef Id="Component2"/>
</Feature>
<Feature Id="Feature.Website"
Title="My Website"
TypicalDefault="install"
Level="1"
ConfigurableDirectory="WEBSITE_INSTALLDIR" >
<ComponentRef Id="Component1.Web"/>
<ComponentRef Id="Component2.Web"/>
</Feature>
But then what happens if we add a third feature that is to be installed in another location? Do we then have to redefine every component again? With over 100 components, managing duplicate components will become a big job.
Any suggestions?
You are seeing a limitation in the Windows Installer. A Component can only be installed once via an MSI. Each Component can only be installed to a single Directory. To have the contents of a Component installed to two different locations, you either have to create another Component with the same content or try to use the CopyFile element to duplicate the content.
Probably not what you wanted to hear but that is the way the Windows Installer works.
Fortunately, if you chose to go with option 1, then WiX toolset will only compress the duplicated content across the Components once. Smart cabbing rocks!
I recommend creating a separate feature which contains only the common components. It shouldn't be installed by default. You can then create a custom action which marks this feature for installation only when one of your actual features is installed.
To mark the feature for installation you can use MsiSetFeatureState function:
http://msdn.microsoft.com/en-us/library/aa370387(VS.85).aspx
The custom action which does this can be conditioned with the feature action of your features:
http://msdn.microsoft.com/en-us/library/aa368561(VS.85).aspx
Features can reference a directory for Browse capability but that only means something if the components use directories that are that directory or a child of that directory. Otherwise the component will go to the directory specified. In other words, you could have INSTALLDIR for the feature and most components yet have ANOTHERDIR ( say [CommonFilesFolder]Company\Shared for another component and it will go there. That component can then belong to multiple features and you'll be ok.

WiX: Define a File component that may not exist

I need to define a Wix file component that may not exist in certain circumstances. Is there any way to do this? Condition elements in Wix all seem to work at install time, and I need something that'll detect at compile time if a file is present and build the installer accordingly.
It seems you need to check out the wix preprocessor. Check out wix documentation on the topic:
wix preprocessor
for example, suppose you have an environment variable called APPTYPE. If its value is set to 'Full', then MyApp_Full.exe will be included and processed by the wix compiler (candle).
<Component Id='MyComponent1' Guid='fff60f72-5553-4e6b-9bf0-ad62ab9a90b1'>
<?if $(env.APPTYPE) = Full?>
<File Name='MyApp_Full.exe' DiskId='1' Source='..\MyApp_Full.exe' Vital='yes' />
<?endif?>
...
</Component>
There is more! Variables, defines, conditionals. Check out that doc page.
As iwo said, preprocessor variables are your friend! However the example from iwo can (and will) violate component rules, as the component isn't 'stable'. Better to condition an entire component (or component group)...
<?if $(var.releasetype)=full ?>
<ComponentRef Id="Somefile.dll" />
<?elseif $(var.releasetype)=enterprise ?>
<ComponentGroupRef Id="SomethingElse" />
<?endif?>
And then include the Component and ComponentGroups in separate Fragment tags so that they will only be compiled when referenced :)
<Fragment>
<Component Id="Somefile.dll" Guid="*">
<File Id="Somefile.dll" KeyPath="yes" Source="SourceDir\Somefile.dll" />
</Component>
</Fragment>
<Fragment>
<ComponentGroup Id="SomethingElse">
<ComponentRef Id="Somefile.dll" />
<Component Id="AnotherFile.dll>
<File Id="AnotherFile.dll" KeyPath="yes" Source="SourceDir\AnotherFile.dll" />
</Component>
</ComponentGroup>
</Fragment>
Personally I use nant to call candle and light targets, defining different variables for various different builds and products, effective use of fragments and preprocessor variables provides a great opportunity for code re-use between projects, or various releases of the same project.
In your case, to check if a file exists... then you'd just use the internal functions to define, or redefine a variable that is later passed to WiX. e.g.:
<if test="${not file::exists('something.dll')}">
<property name="releasetype" value="blahblahblah" />
</if>