How do I tell wix to install a file in a directory set in a property? - wix

I want to do the following where XLSTART is defined as:
<CustomAction Id="AssignXLSTART" Return="check" Execute="firstSequence" Directory ='XLSTART' Value='[AppDataFolder]\Microsoft\Excel\XLSTART'>
</CustomAction>
And then I have a subsequent CustomAction that calls some C# code that may change this value.
And then in the list of files to install I have:
<Directory Id="XlStartFolderId" Name="[XLSTART]">
<Component Id="ExcelMacro_xla" Guid="26D21093-B617-4fb8-A5E7-016493D46055" DiskId="1">
<File Id="ExcelXLA" Name="AutoTagExcelMacro.xlam" ShortName="XLMacro.xla" Source="$(var.srcFolder)\AutoTagExcelMacro.xlam"/>
</Component>
</Directory>
But the above puts it in the INSTALLDIR[XLSTART]. How do I get it to read this as a property?

You should be able to install to the userprofile directory you refer to like this:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="LocalAppDataFolder">
<Directory Id="Microsoft" Name="Microsoft">
<Directory Id="Excel" Name="Excel">
<Directory Id="XLSTART" Name="XLSTART">
<Component Id="ExcelAddIn" Feature="MyFeature" Guid="{11111-1111-GUID-HERE-YOURGUIDHERE}">
<File Source="C:\SourceFiles\MyAddin.xla" />
<RemoveFolder Id="Microsoft" On="uninstall" Directory="Microsoft" />
<RemoveFolder Id="Excel" On="uninstall" Directory="Excel" />
<RemoveFolder Id="XLSTART" On="uninstall" Directory="XLSTART" />
<RegistryValue Root="HKCU" Key="Software\MySoftware" Name="installed" Type="integer" Value="1" KeyPath="yes" />
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
I would suggest you use the per-machine xlstart folder instead - if it still exists. I am not sure it does. The the addin is loaded for every user on the box on every launch. Generally I prefer this. It has been ages since I looked at this, so this could have changed in newer Office versions - in fact I am sure it has, but the details are unclear to me.
System Folder Properties: There are a number of System Folder Properties that can be used in MSI files to specify installation location - LocalAppDataFolder is just one of them: https://learn.microsoft.com/en-us/windows/win32/msi/property-reference#system-folder-properties

Figured it out. You need to install to the INSTALLDIR and then use CopyFile
<!-- place it in C:\Program Files (x86)\Microsoft Office\Root\Office16\XLSTART\ -->
<Component Id="ExcelMacro_xla" Guid="26D21093-B617-4fb8-A5E7-016493D46055" DiskId="1">
<File Id="ExcelXLA" Name="AutoTagExcelMacro.xlam" ShortName="XLMacro.xla" Source="$(var.srcFolder)\AutoTagExcelMacro.xlam">
<CopyFile Id='CopyXlMacro' DestinationProperty='XLPATH' DestinationName='AutoTagExcelMacro.xlam'/>
</File>
</Component>

Related

Add resource files in wix installer

I have created a multi lingual app that uses 2 diferent resource files to manage the UI language, so when I build and execute my program, in my bin directory I have my app files and two folders, en-GB and pt-PT.
I am now trying to create a installer with Wix, for that I am defining the following directories:
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="App" >
<Directory Id="LOCALEEN" Name="en-GB"/>
<Directory Id="LOCALEPT" Name="pt-PT"/>
</Directory>
</Directory>
</Directory>
</Fragment>
And then, I define the following components:
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="App.resources.en.GB.dll" Guid="...">
<CreateFolder />
<File Id="App.resources.en.GB.dll" Name="App.resources.dll" Source="$(var.App.App_TargetDir)en-GB\App.resources.dll" />
</Component>
<Component Id="App.resources.pt.PT.dll" Guid="...">
<CreateFolder />
<File Id="App.resources.pt.PT.dll" Name="App.resources.dll" Source="$(var.App.App_TargetDir)pt-PT\App.resources.dll" />
</Component>
... Other components...
</ComponentGroup>
</Fragment>
When I rebuild my solution I get the following error:
'App.resources.dll' is installed in '[ProgramFilesFolder]\App\' by two
different components on an LFN system: 'App.resources.en.GB.dll' and
'App.resources.pt.PT.dll'. This breaks component reference counting.
I understand the problem, both resources dll are being copied to the installation folder, and not to the specific resources file... But I don't know how to solve it. Anyone can give any hints on how to solve this?
Just reference the directory where you want your components eg. Directory="LOCALEEN". There is no need to specify <CreateFolder />
I also recomend to maintain some kind of naming convention. Your Components and Fils have the same id. See https://stackoverflow.com/a/1801464/4634044. So this should do what you expect:
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="C_EnglishLocale" Guid="..." Directory="LOCALEEN">
<File Id="Fi_EnglishLocale" Name="App.resources.dll" Source="$(var.App.App_TargetDir)en-GB\App.resources.dll" />
</Component>
<Component Id="C_PolnishLocale" Guid="..." Directory="LOCALEPT">
<File Id="Fi_PolnishLocale" Name="App.resources.dll" Source="$(var.App.App_TargetDir)pt-PT\App.resources.dll" />
</Component>
</ComponentGroup>
</Fragment>

Why do we get an ICE57 error for non advertised shortcuts in per machine installations?

This question is asking whether one of the ICE57 validators creates a false positive error report.
I am using WIX 3.9 to generate an installer. I want a per machine installation with non advertised shortcuts.
This WXS example installs a text file and a shortcut to open the text file:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="ShortcutTest" Language="1033"
Version="1.0.0.0" Manufacturer="Widget Co"
UpgradeCode="--YOUR GUID1--">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate EmbedCab="yes"/>
<Feature Id="ProductFeature" Title="ShortcutTest" Level="1">
<ComponentRef Id="TextFile" />
<ComponentRef Id="ShortCut" />
</Feature>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="ShortcutTest">
<Component Id="TextFile" Guid="--YOUR GUID2--">
<File Id="File" Name="TextFile.txt" Source="TextFile.txt" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder" Name="Shortcut Test">
<Component Id="ShortCut" Guid="--YOUR GUID3--">
<RegistryValue Root="HKMU" Key="Software\WidgetCo\ReadMeTextFile\TextFile" Name="Installed" Type="string" Value="yes" KeyPath="yes"/>
<Shortcut Id="Shortcut"
Name="Open Text File"
Description="Opens a text file"
Target="[INSTALLFOLDER]TextFile.txt"
WorkingDirectory="INSTALLFOLDER"/>
<RemoveFolder Id="ApplicationProgramsFolder" Directory="ApplicationProgramsFolder" On="uninstall"/>
</Component>
</Directory>
</Directory>
</Directory>
</Product>
</Wix>
If you build the above example into an MSI package, you get this Internal Consistency Evaluator (ICE) error:
D:\Robert\Documents\Visual Studio 2013\Projects\ShortcutTest\Product.wxs(27,0): error LGHT0204: ICE57: Component 'ShortCut' has both per-user data and a keypath that can be either per-user or per-machine.
ICE57 is implying an inconsistency between per-user and per-machine data. But, the key path of the component is HKMU, which in a per machine installation resolves to HKLM (HKEY_LOCAL_MACHINE). The location of the shortcut derives from 'ProgramMenuFolder', which in a per-machine installation resolves to C:\ProgramData\Microsoft\Windows\Start Menu\ (on Windows 8.1). None of the component's resources appear to have any per-user association.
You can build the installer package into an MSI by suppressing ICE57. The resulting MSI package installs without any obvious errors. Multiple users can log on and access the shortcut. Any user can un-install the package and all of the resources in the package are removed.
The answer to Wix create non advertised shortcut for all users / per machine has an interesting workaround, which is to author advertised shortcuts and then turn off advertising. Seems a round about way of creating un-advertised shortcuts.
A common fix for the ICE57 error is to change the <RegistryValue...> root to HKCU (HKEY_CURRENT_USER). However this creates an installer that can leave a user registry key behind when un-installed. For example if user A installs the package, a registry entry is added to user A's registry hive. If user B removes the package, the registry entry is not removed from user A's registry hive.
In this scenario is the ICE57 error a bug in the Internal Consistency Evaluators? Or is there something I have miss-understood?
While researching another problem I found this comment on http://sourceforge.net/p/wix/mailman/message/26687047/ from Rob Mensching:
IIRC, this is a bug in ICE57. The Windows Installer team didn't look at
ALLUSERS property when evaluating these values... that was a long time ago
though so my memory may have decayed a bit.
It looks like a bug in ICE57.
Move your Shortcut to the be a child of File and add the Adversite="yes" attribute. The RegistryValue should do the trick for converting the shortcut from perUser to perMachine.
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="MyApp" />
</Directory>
<Directory Id="ProgramMenuFolder" Name="Programs">
<Directory Id="ApplicationProgramsFolder" Name="My App Name" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ComponentGroup_Core">
<Component Id="Component_App" Guid="INSERT_GUID_HERE" Directory="INSTALLFOLDER">
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\[AppName]"
Name="AppInstalled" Type="string" Value="yes" KeyPath="yes"/>
<File Id="MyApp" Name="My Test App.txt">
<Shortcut Id="Shortcut"
Name="Open Text File"
Description="Opens a text file"
Directory="ApplicationProgramsFolder"
WorkingDirectory="INSTALLFOLDER" />
</File>
</Component>
<Component Id="Component_MenuFolder" Guid="INSERT_GUID_HERE"
Directory="ApplicationProgramsFolder">
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\[AppName]"
Name="MenuFolderInstalled" Type="string" Value="yes"
KeyPath="yes"/>
<RemoveFolder Id="RemoveFolder_App" On="uninstall" />
</Component>
</ComponentGroup>
</Fragment>

WIX - howto use RemoveFolderEx with On="install" / "both"?

I am trying to remove a folder on "install" (and "uninstall"), but the folder is only removed on "uninstall".
Any hints how this can be done?
<Property Id="PACKAGEFOLDER">
<RegistrySearch Root="HKLM" Key="$(var.RegKey)" Type="raw" Id="PKGFOLDER_REGSEARCH" Name="PkgDir" />
</Property>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="PACKAGE" Name="$(var.PkgFolder)">
<Component Id="PackagesFiles" Guid="$(var.FilesGUID)">
<RegistryValue Root="HKLM" Key="$(var.RegKey)" Name="PkgDir" Type="string" Value="[PACKAGE]" KeyPath="yes" />
<util:RemoveFolderEx On="both" Property="PACKAGEFOLDER" />
</Component>
</Directory>
</Directory>
</Directory>
just noticed:
if the RegKey is available in registry before installation starts, it will work:
WixRemoveFoldersEx: Recursing path: C:\Program Files (x86)... for
row: wrf945C37509CA5EEDC2367957D5F072DFF. MSI (s) (94!A8)
[19:17:55:185]: PROPERTY CHANGE: Adding _UNOPACKAGEFOLDER_0 property.
Its value is 'C:\Program Files (x86)... MSI (s) (94:D4)
[19:17:55:185]: Doing action: CostInitialize
but if the RegKey is not in registry, log says:
WixRemoveFoldersEx: Error 0x80070057: Missing folder property:
APPLICATIONFOLDER for row: wrfA308D08284221970F6338358BFB75917
CustomAction WixRemoveFoldersEx returned actual error code 1603 but
will be translated to success due to continue marking MSI (s) (84:50)
[19:29:08:529]: Doing action: CostInitialize
Is it possible to write the RegKey before the Property "PACKAGEFOLDER" is set?
I assume that you have also files in this folder that should be deleted. If there are no (arbitrary) subdirectories containing files it should be straight forward by using the RemoveFile-table of the Windows Installer. As it will only delete the folder if it is empty, add an additional entry that will delete the files in it, e.g.:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="PACKAGE" Name="$(var.PkgFolder)">
<Component Id="PackagesFiles" Guid="$(var.FilesGUID)">
<RegistryValue Root="HKLM" Key="$(var.RegKey)" Name="PkgDir" Type="string" Value="[PACKAGE]" KeyPath="yes" />
<RemoveFile Id="RemovePACKAGEFolderFiles" Directory="PACKAGE" Name="*.*" On="both" />
<RemoveFolder Id="RemovePACKAGEFolder" Directory="PACKAGE" On="both" />
</Component>
</Directory>
</Directory>
</Directory>
This way you don't have to deal with any property setting. If you have other subdirectories with files you would have to repeat this also for these.
Another way would be to create a deferred custom action in the system context that will delete the folder completely, e.g. in VBScript.
If you add
<SetProperty Id="PACKAGEFOLDER" Value="[PACKAGE]" After="CostFinalize" />, you can get the value of package during install. From this article regarding property-setting.

WIX 3.8 installer: Add files to a pre-existing folder

How would I install files directly into a pre-existing folder on the user's computer? All documentation I read only explains creating a custom INSTALLDIR.
Eg. c:\ProgramFiles(x86)\ExampleFolderA\ExampleFolderB\InstalledFile.exe
You should first define the directory structure:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="ExampleFolderAId" Name="ExampleFolderA">
<Directory Id="ExampleFolderBId" Name="ExampleFolderB" />
</Directory>
</Directory>
</Directory>
Note that the definition above does NOT create directories when the installation runs. In order for the directories to be actually "created" you have to either place files there (using Component elements), or explicitly state that the directory is empty.
Something like this:
<DirectoryRef Id="ExampleFolderAId">
<Component Id="SampleComponent" Guid="GUID-GOES-HERE">
<File Id="SampleFile" Source="C:\readme.txt" KeyPath="yes" />
</Component>
</DirectoryRef>
or
<DirectoryRef Id="ExampleFolderBId">
<Component Id="EmptyFolderComponent" Guid="GUID-GOES-HERE">
<CreateFolder />
</Component>
</DirectoryRef>
Hope you get the idea.

Copy file from setup location to another location in wix on install

I have created an msi setup file which includes some files in a "Sample" folder which should be copied to a temp folder. Anybody suggest how to do this?
Something like this:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="MyVendor" Name="MyVendor">
<Directory Id="INSTALLDIR" Name="MyDir">
<Component Id="MyFileId" Guid="...G1...">
<File Id="MyFileId" Name="MyFile" Source="...blabla...\MyFile" KeyPath="yes" >
</File>
</Component>
<DirectoryRef Id="TARGETDIR">
<Component Id="MyFileCopyId" Guid="...G2...">
<RemoveFile Id="MyFileRemoveId" Name="MyFile" On="install" Directory="MyCopyDir" />
<CopyFile Id="MyFileCopyId" FileId="MyFileId" DestinationDirectory="MyCopyDir" />
</Component>
<Feature Id="MyFeature" ... >
<ComponentRef Id="MyFileId" />
<ComponentRef Id="MyFileCopyId" />
The important Xml element is CopyFile. You need to create a new component that is a copy of the first one (with different ids, guids, ... of course). Both components needs to be declared in a feature.
CopyFile element is your friend. You can nest it under the original File element a number of times, depending on how many times you need to copy it. Put the correct destination folder(s) and let Windows Installer do the rest.