I am trying to create a start menu shortcut for an application using WiX, the problem is that I am receiving an Ice64 error stating that the parent directory of the shortcut directory is not in the RemoveFile table.
I do not want to remove this parent folder (organisation folder) on uninstall as other applications may have shortcuts in other children of it.
My code looks like
<Feature Id="ProductFeature" Title="MyApplication" Level="1">
<ComponentGroupRef Id="Components" />
<ComponentRef Id="ProfilesShortcut"/>
</Feature>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="OrgDir" Name="OrganisationName">
<Directory Id="AppDir" Name="MyApplication" />
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ProgFilesOrgDir" Name="OrganisationName">
<Directory Id="ProgFilesAppDir" Name="MyApplication" />
</Directory>
</Directory>
</Directory>
</Fragment>
<Fragment>
<DirectoryRef Id="ProgFilesAppDir">
<Component Id="ProfilesShortcut" Guid="*">
<Shortcut Id="ApplicationStartMenuShortcut"
Name="MyApplication"
Description="My Application"
Target="[#MyApplication.exe]"
WorkingDirectory="AppDir"/>
<RemoveFolder Id="ProgFilesAppDir" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\Organisation\MyApplication" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
</Component>
</DirectoryRef>
</Fragment>
Feel free to add the remove file element to your shortcut component.
Remove an empty folder if the parent component is selected for installation or removal.
The RemoveFolder element will only remove empty folders so if your product is not the only part of that suite installed it will leave the parent folder alone since other products will have put files/folders there. Consider the scenario where your product is either the only one installed (should remove the folder) or is the last one to be uninstalled from the suite (should remove the folder). In these two cases the folder should get removed. The order of RemoveFolder elements get defined might matter so I would test putting the ProgramFilesOrgFolder remove element before and after ProgFilesAppDir remove element just to see if one of them fails.
Alternatively you can just suppress ICE64 but then you will leave a folder behind once all the products are uninstalled. Not a huge deal really but might be annoying to some users.
Related
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>
There are two answers on Create shortcut to desktop using WiX
Both these answers lack any real explanation of what is going on. What is the difference between these two methods of creating shortcuts? The first method falls in line with WiX - Create shortcut documentation.
The second method has a MergeRedirectFolder which I can't seem to find any documentation on, and I don't understand why the second example doesn't require the registry setting since according to WiX Documentation, a registry setting:
is required as a Shortcut cannot serve as the KeyPath for a component when installing non-advertised shortcuts for the current users.
Does this mean that the second method is an advertised shortcut? Or is it an answer that assumes the user is installing per machine? Or am I lost in the sauce? (Quite possible - second day trying to use WiX, since Microsoft forced me down this path.)
The first one:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="DesktopFolder" Name="Desktop">
<Component Id="ApplicationShortcutDesktop" Guid="*">
<Shortcut Id="ApplicationDesktopShortcut"
Name="Text under your icon"
Description="Comment field in your shortcut"
Target="[MYAPPDIRPROPERTY]MyApp.exe"
WorkingDirectory="MYAPPDIRPROPERTY"/>
<RemoveFolder Id="DesktopFolder" On="uninstall"/>
<RegistryValue
Root="HKCU"
Key="Software/MyAppName"
Name="installed"
Type="integer"
Value="1"
KeyPath="yes"/>
</Component>
</Directory>
<Directory Id="ProgramFilesFolder" Name="PFiles">
<Directory Id="MyCompany" Name="MyCompany">
<Directory Id="MYAPPDIRPROPERTY" Name="MyAppName">
</Directory>
</Directory>
</Directory>
The second one:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="DesktopFolder" SourceName="Desktop" />
<Directory Id="MergeRedirectFolder">
<Component Id="MyExeComponent" Guid="*">
<File Id="MyExeFile" Source="$(var.ExeSourcePath)" KeyPath="yes">
<Shortcut
Id="DesktopShortcut"
Directory="DesktopFolder"
Name="$(var.ShortcutName)"
WorkingDirectory="MergeRedirectFolder" />
</File>
</Component>
</Directory>
</Directory>
Caveat: Per Doc's comment, since neither example specified the Advertise attribute, neither should create an advertised shortcut. I don't remember what led me to write the answer below; it seems likely to be incorrect. I'll leave the answer in tact in case there is some subtle truth behind it.
The first example creates an advertised shortcut; the second creates a non-advertised shortcut. The rules for the two types of shortcuts are described with the Shortcut Table Target column.
A non-advertised shortcut is a standard Windows shortcut like you would create with Windows Explorer. An advertised shortcut enhances resiliency by verifying that all the components in the feature are installed when the shortcut is activated.
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.
I have an issue with some existing installers which I have repeated in a simple test case as follows:
Installer1 installs App1 and LibraryA(v1).
Installer2 installs App2, LibraryA(v2) and LibraryB. LibraryA(v2) requires LibraryB, which is why LibraryB is now installed. LibraryA(v1) had no such dependency. LibraryA(v2) should overwrite LibraryA(v1).
If I run Installer1 then Installer2, then uninstall Installer2, LibraryB gets removed, but LibraryA remains at v2 (sensible - I wouldn't expect it to revert to a prior version).
Forgetting my existing problem and imagine I was starting from scratch, how would you suggest I construct my WiX project to cope with such a situation? In my case, all of the libraries are defined as merge modules - something I'm not in a position to change (wixlibs are out of the question).
I have tried, to no effect, to use a Dependency element to create a dependency between the LibraryA(v2) merge module and the LibraryB merge module - it just seems to issue a linker warning if I forget to reference LibraryB in the installer, rather than creating an actual dependency.
The .wxs scripts in my test case look something like this (they all install to the same folder for ease of testing):
LibraryA(v1).wxs:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="MergeRedirectFolder">
<Component Id="LibraryAComponent" Guid="d98dd742-c3d3-4aee-8d84-87f2b3c837dc">
<File Source="v1\LibraryA.dll" />
</Component>
</Directory>
</Directory>
LibraryA(v2).wxs:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="MergeRedirectFolder">
<Component Id="LibraryAComponent" Guid="d98dd742-c3d3-4aee-8d84-87f2b3c837dc">
<File Source="v2\LibraryA.dll" />
</Component>
</Directory>
</Directory>
<Dependency RequiredId="LibraryBMergeModule.DD524F28_EAE0_47B8_A895_3AF2F7A7361A" RequiredLanguage="1033"/>
LibraryB.wxs:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="MergeRedirectFolder">
<Component Id="LibraryBComponent" Guid="46e6e0da-2a99-4f0d-bed2-e764e16b9eed">
<File Source="LibraryB.dll" />
</Component>
</Directory>
</Directory>
App1.wxs:
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
<Directory Id="TARGETDIR" DiskId="1" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="WiXTest" Name="WiXTest">
<Merge Id="LibraryAv1" Language="1033" SourceFile="LibraryAv1.msm" />
<Component Id="App1Component" Guid="93D11AFF-5307-4355-B261-0096775B6A89">
<File Source="App1.exe" />
</Component>
</Directory>
</Directory>
</Directory>
<Feature Id="Libraries" Title="Shared Files" Level="1">
<MergeRef Id="LibraryAv1" />
</Feature>
<Feature Id="App" Title="Application" Level="1">
<ComponentRef Id="App1Component" />
</Feature>
App2.wxs:
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
<Directory Id="TARGETDIR" DiskId="1" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="WiXTest" Name="WiXTest">
<Merge Id="LibraryB" Language="1033" SourceFile="LibraryB.msm" />
<Merge Id="LibraryAv2" Language="1033" SourceFile="LibraryAv2.msm" />
<Component Id="App2Component" Guid="173C71B6-E403-4AC1-894D-06799C6782A4">
<File Source="App2.exe" />
</Component>
</Directory>
</Directory>
</Directory>
<Feature Id="Libraries" Title="Shared Files" Level="1">
<MergeRef Id="LibraryB" />
<MergeRef Id="LibraryAv2" />
</Feature>
<Feature Id="App" Title="Application" Level="1">
<ComponentRef Id="App2Component" />
</Feature>
I'm guessing that this is entirely the wrong way to go about things and am looking for some pointers to bring me back on track. It feels like you need to have a PhD in Windows Installer in order to use WiX correctly.
I am no WIX expert but yes for MSI you need at least a PhD. At first you should know the rules about MSI components:
What Every Developer Should Know About MSI Components
From painful experience I do know that if several MSIs bring in the same components it is no good. If possible I would strive for the single source principle so that your libs are installed always by the same MSI (let it be a infrastructure MSI). Your application MSIĀ“s then simply check if the infrastructure has been installed and that was it.
Coming back to your question what you should do with your merge modules. I would create for each merge module an extra msi (yes customers do not like crowed installed software catalogs) to be sure that if you are in need to service the libraries you have full freedom.
I do not know your software structure but it could be that you will need LibraryA v1 and v2 at the same time so you should think about deploying your libraries to the WinSxS cache or if they are managed the GAC. Or you come up with something similar in your folder structure or file name convention.
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.