I have a WiX package that should always deliver files, but should only uninstall the files when a condition is met. The condition is that common files should not be uninstalled if another version of the product is installed (we are not supporting true upgrades, our upgrade is installing the files to a new folder that has the version of the package and installing new versions of the common files).
I realize this can be achieved by using the same GUIDs across components or using merge modules, but I also have to account for the fact that I have legacy packages from InstallShield that could be installed. Unfortunately, the designers of those packages had no idea of what was going on with installers, and they installed files to a temp directory, then copied them to a new location, so Windows Installer has no knowledge that the files are installed.
I have tried a couple different things.
Approach #1:
<Component Id="myfile.dll" Guid="{YOUR-GUID-HERE}" Transitive="yes" >
<Condition>OTHER_VERSIONS_PRESENT ~= "FALSE"</Condition>
<File Id="myfile.dll" KeyPath="yes" Source="$(var.PATH_TO_BIN_FILES)myfile.dll" />
</Component>
Approach #2:
<Component Id="myfile.dll" Guid="{YOUR_GUID_HERE}" Permanent="yes" >
<File Id="myfile.dll" KeyPath="yes" Source="$(var.PATH_TO_BIN_FILES)myfile.dll" />
</Component>
<Component Id="myfile.dll_remove" Guid="{YOUR_GUID_HERE}" Transitive="yes" >
<RemoveFile Id="myfile.dll_remove" Name="myfile.dll" On="uninstall" />
<Condition>OTHER_VERSIONS_PRESENT ~= "FALSE"></Condition>
</Component>
Additional Info
Here is my property that I am using with the custom actions and the condition:
<Property Id="OTHER_VERSIONS_PRESENT" Value="FALSE" />
Here is my scheduling of the custom action that sets the OTHER_VERSIONS_PRESENT property. I have verified that it is correctly set to true or false, based on whether another version of the product is present.
<Custom Action="FindOtherVersionsOfProduct" After="CostFinalize" />
I have also tried the above approaches with CDATA wrapped around the condition, but this also failed. Additionally, I tried changing the install sequence of when I set the property. I've tried different conditions. But nothing has worked.
Thank you in advance for any support you can give me.
Edit: Approach 1 is working, but once I change it to be
<Condition>NOT Installed OR ((REMOVE ~= "ALL") AND (OTHER_VERSIONS_PRESENT ~= "FALSE"))</Condition>
it no longer works.
My suggestion is that both of those approaches are probably incorrect. The general solution used in all cases I can think of that both packages need to install the same files to same common location. A common merge module containing those components ensures that the Windows Installer sharing works, so uninstalling one product leaves the files behind for the remaining product (because ref counting is based on component ids and just decrements the count when a product is uninstalled). In other words it all just works without transitive components or conditions.
Related
I've inherited project with MSI created in WiX and now I'm trying to solve some of the issues that unfortunately exist.
There's a remember property pattern which is used to found specific directory saved in registry entry:
<Property Id="AUTO_FOUND_DIR" Secure="yes" Admin="yes">
<RegistrySearch Id="regsrch_AUTO_FOUND_DIR"
Root="HKCU"
Key="$(var.RegPath)"
Name="$(var.SpecificKey)"
Type="raw"
/>
</Property>
The SpecificKey value is then saved in AUTO_FOUND_DIR property.
Then the black magic appears. A separate component is holding (among other stuff) a shortcut located in ProgramMenuFolder (non-advertised) to the main executable.
I've been told that usage of util:RemoveFolderEx is a workaround for an old issue where this shortcut was orphaned and hasn't been removed during uninstall:
<Feature>
<DirectoryRef Id="ProgramMenuDir">
<Component Id="cmp_ProgramMenuDir" Guid="{0E8BD13A-GUID-IS-HERE-6E5092ECA9EF}">
<CreateFolder />
<RemoveFolder Id='ProgramMenuDir' On='uninstall' />
<RegistryKey Id='reg_SpecificKeyID' Root='HKCU' Key='$(var.RegPath)' ForceCreateOnInstall="yes">
<RegistryValue Type='string' Name='$(var.SpecificKey)' Value='[ProgramMenuDir]'/>
</RegistryKey>
<!-- other content: shortcut to ProgramMenuFolder and other stuff -->
<util:RemoveFolderEx Id="rm_dirID" On="install" Property="AUTO_FOUND_DIR"/>
</Component>
</DirectoryRef>
</Feature>
The problem is: I don't need this workaround (and usage of AUTO_FOUND_DIR property as well. I've removed that code but during upgrade (major, Product and Package GUIDs set to "*", UpgradeCode has the same value as previous version) I can see in verbose log from MSI that this AUTO_FOUND_DIR exists, the RegistrySearch reads the key value with specific directory and as a result the util:RemoveFolderEx removes that directory and all components that are there located.
My question is: how can I detect why this old property is being used during upgrade and how to get rid of it?
Additional information: the install scope is PerMachine, ALLUSERS is set to 1. The MSI with upgraded version has this property removed.
Without a close look at your complete verbose log to see what's going on, remember that an upgrade does an uninstall of the older installed product. This means that a lot of the logic in the older installed product will happen during your upgrade. So you will definitely see the RegistrySearch running as the older product uninstalls, setting AUTO_FOUND_DIR, and you will see the RemoveFolder that runs during the uninstall.
So it's not clear if you actually have an issue if all you're seeing is the uninstall activity of that older product being uninstalled. That activity is embedded in the installed product.
Why WIX does not remove a shortcut in the INSTALLDIR if it is not the default install directory is used? My WIX code look like?
<DirectoryRef Id="INSTALLDIR">
<Component Guid="..." Id="shortcuts_INSTALLDIR">
<RegistryKey ForceDeleteOnUninstall="yes" Id="shortcuts_reg_INSTALLDIR" Key="Software\MyCompany\MyProduct" Root="HKCU">
<RegistryValue KeyPath="yes" Name="shortcut_INSTALLDIR" Type="string" Value=""/>
</RegistryKey>
<Shortcut Arguments="my args " Description="my description" Id="InstallDir_my_name" Name="my name" Target="[INSTALLDIR]mydir\my.exe" WorkingDirectory="INSTALLDIR"/>
</Component>
</DirectoryRef>
It look like that the uninstaller does not know the new value of INSTALLDIR. Any idea?
Windows Installer is a bit of an odd beast here. It doesn't record the operations it performs; instead it tries to record the information necessary to reverse them. In this case it appears you're falling into a gap in that implementation.
Windows Installer notes that it has installed component shortcuts_INSTALLDIR. When a file is installed to a specific directory, it records the directory's location. Then during maintenance it restores all the directories it recorded. But it does not record (and thus does not restore) the directory for just a shortcut. Typically shortcuts are installed to predefined paths under the ProgramMenuFolder. Since such locations are not affected by changes to INSTALLDIR, this is usually not a problem.
To solve this you have to ensure the alternate INSTALLDIR is restored during maintenance. You can convince Windows Installer to do so automatically by installing any file directly to INSTALLDIR (if the extra file is not a problem, this is my preferred option). Alternately you can do so manually through the remember property pattern, possibly leveraging ARPINSTALLLOCATION and its saved value in the Uninstall key.
I want to have 2 installers with common part. So I used merge module, and created 2 wix installers.
Here is what I want to achive with more details (problem described there was solved): wix installers with common component
I am using WixUI_InstallDir so user is able to choose directory where application will be installed.
When second installer is launched I want to load previously choosen installation directory.
When user does not change default path all works fine. But if INSTALLDIR was changed in first installation, then second installer adds plugin to right path but also extracts core to default path - which is wrong.
However right path (from previous installation) is shown on "Destination Folder" dialog.
Here is significant code:
<Property Id="CORE_INSTALLATION_PATH">
<RegistrySearch Id="InstallFolderRegistrySearch" Type="raw" Root="HKLM" Key="SOFTWARE\PluginCompany\Plugins" Name="InstallFolder"/>
</Property>
<SetDirectory Id="INSTALLDIR" Value="[CORE_INSTALLATION_PATH]">CORE_INSTALLATION_PATH</SetDirectory>
<DirectoryRef Id="TARGETDIR">
<Component Id="CoreRegistryEntries" Guid="{C1701385-12CA-47EF-9FB2-884139B56390}">
<RegistryKey Root="HKLM" Key="SOFTWARE\PluginCompany\Plugins" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Name="InstallFolder" Value="[INSTALLDIR]" KeyPath="yes"/>
</RegistryKey>
</Component>
</DirectoryRef>
You can download and run full sample solution from https://github.com/bwojdyla/wixplugins/tree/04f61b89b0465311818bec1cc06371b3dced5671
In logs I found this:
MSI (c) (38:CC) [08:54:03:324]: Dir (target): Key: INSTALLDIR , Object: C:\Program Files (x86)\PluginCompanyFolder\PluginInstaller2\
MSI (c) (38:CC) [08:54:03:324]: Dir (target): Key: INSTALLDIR.751E70EB_CF76_413B_B8C8_231A31F9C946 , Object: C:\Program Files (x86)\PluginCompanyFolder\PluginInstaller\
So there are two properties INSTALLDIR and INSTALLDIR with guid.
This second property is added by merge module and updating INSTALLDIR does not change second property. That is why core components were extracted to custom location by second installer.
To disable mudularization I used SuppressModularization attribute:
<Property Id="INSTALLDIR" SuppressModularization="yes"/>
Notice SuppressModularization description at:
http://wixtoolset.org/documentation/manual/v3/xsd/wix/property.html
Use to suppress modularization of this property identifier in merge
modules. Using this functionality is strongly discouraged; it should
only be necessary as a workaround of last resort in rare scenarios.
A couple or three things:
Make sure you have the correct registry, maybe you need a Win64 search, maybe not, depends if the data is in the native registry or the x86 WOW one.
If it's not too late, it's typically better to install shared components to a common location (such as the common files folder for your Company Name and Product Name) to avoid this kind of thing. It's perfectly ok if not all the files are in the main application folder, as long as the app doesn't care.
Do the install with a verbose log and see what the AppSearch is doing - that's where it will set (or not) that property value. That's where you'll see if the property is being set or not, and therefore whether something else is going wrong later on.
I build a perMachine installer using WiX 3.6 to install a software I had not developed. Unfortunately the software creates some registry keys under HKCU during execution.
On uninstall, the self created keys should also be removed. It seems not so easy to remove these keys. I am "fighting" with ICE57 and/or ICE38. Both complaining the mix between perUser and perMachine data.
Hopefully you can point me in the right direction on fixing this issue.
To overcome ICEs you should move Per-User registry to separate components and use some registry entry as keyPath for that component, i.e.:
<Component Id='PerUserRegistry' Guid='*'>
<RegistryValue Id="PerUserRegistry_KeyPAth" KeyPath="yes" Root="HKCU" Key="Software\[Manufacturer]\[ProductName]\[ProductCode]\PerUserRegistry" Name="[PackageCode]" Value="[ProductVersion]" Type="string" />
<!--Other Per-user registry goes here-->
</Component>
I completely agree with Christopher: It is common practice to leave per-user data on uninstall, but if removal is necessary, then Active Setup is the only real option.
First I propose you to remove them on Install or Re-Install instead of uninstall, you just need add RemoveRegirty entry and Active Setup, i.e. with this WiX code:
<Component Id='ActiveSetup' Guid='*'>
<RegistryValue Id="ActiveSetup00" Root="HKLM" KeyPath="yes" Key="SOFTWARE\SOFTWARE\Microsoft\Active Setup\Installed Components\[PackageCode]\" Name="StubPath" Value="msiexec /fup [ProductCode] /qb-!" Type="string" />
<RegistryValue Id="ActiveSetup01" Root="HKLM" Key="SOFTWARE\SOFTWARE\Microsoft\Active Setup\Installed Components\[PackageCode]\" Value="[ProductName] [ProductVerion] Configuration" Type="string" />
</Component>
<Component Id='PerUserRegistryCleanup' Guid='*'>
<RegistryValue Id="PerUserRegistry_KeyPath" Root="HKCU" KeyPath="yes" Key="SOFTWARE\SOFTWARE\Microsoft\Active Setup\Installed Components\[PackageCode]\" Name="StubPath" Value="msiexec /fup [ProductCode] /qb-!" Type="string" />
<RemoveRegistryKey Id='PerUserRegCleanup' Root='HKCU' Action='removeOnInstall' Key='Key\To\Be\Removed'/>
</Component>
Note: [PackageCode] use in ActiveSetup is very recommended, so with each new version (build) of MSI package you add separate entry (also see my final note). I used per-user active setup registry as key-path on purpose, so you don't run it for current user twice.
As for removing them after uninstall,
Now, hopefully you need to remove entire key, and not just some values. In either case, I would create custom action to add Registry entry for Active Setup during uninstall (or if there are many such keys/values, create and deploy .CMD file with those and launch it on uninstall, before RemoveFiles action, to add all of them to registry).
Note: that I would strongly recommend adding deleting this registry during install, or you might end up removing per-user values when software is yet installed.
So here's WiX code for all of this:
<CustomAction Id="CA_UninstallRegistryCleanUp" Directory="SystemFolder" ExeCommand="REG.exe ADD "HKLM\SOFTWARE\Microsoft\Active Setup\Installed Components\MySoftName_CleanUp" /v StubPath /d "reg add ^"HKCU\Key\To\Be\Removed^" /va /f" /f" Return="ignore" />
<InstallExecuteSequence>
<Custom Action='CA_UninstallRegistryCleanUp' After='RemoveRegistryValues'>REMOVE~="ALL"</Custom>
</InstallExecuteSequence>
<Component Id='RegCleanup_Remover' Guid='*'>
<RegistryValue Id="PerUserRegistry_KeyPAth" Root="HKLM" KeyPath="yes" Key="SOFTWARE\[Manufacturer]\[ProductName]\[ProductCode]\" Name="DummyKey" Value="[ProductVersion]" Type="string" />
<RemoveRegistryKey Id='RegCleanup_Remover' Root='HKLM' Action='removeOnInstall' Key='SOFTWARE\Microsoft\Active Setup\Installed Components\MySoftName_CleanUp'/>
</Component>
Final notes:
There just two small issues with all this Active Setup stuff: be careful on Windows Terminal Servers; and once active setup was run for one user for current .MSI, it will not run again if you decide to reinstall same package, unless you change its PackageConde or raise version under ActiveSetup registry key. These are topics for another day, let me know if need them clarified.
And don't forget to add all of above Components to some Feature.
The Windows Installer considers this user data and best practice is to not remove it. Either way, it's very difficult to try to remove it anyways since other user profiles are out of scope / context. It's theoretically possible to write a custom action to enumerate profiles and load registry hives but on some versions of Windows ( Vista ) that won't work due to restricted permissions granted to the windows installer service.
If you really, really must be able to remove custom action data on uninstall then take a look at:
Active Setup Explained
You are going to need to leave behind an program (exe for example ) by marking a component as permanent. Then you'll need a custom action to write a registry value during the uninstall (because Windows Installer doesn't support this).
The concept is during the install you lay down an EXE and during the uninstall you leave you. You then write to the ActiveSetup registry key telling it to run your EXE once for each subsequent user to logon to the machine. The EXE then deletes your registry values. Reboot (politely) if needed to unload the extensions from explorer.
But honestly, a better designed application wouldn't need all of this.
I'm using Wix 3.6 beta from the command line, not as VS projects. I have a web application that is harvested with heat as a directory. This works. I'm using web.config transforms to manage each of the target environment web.config files. These are output with msbuild, this works and keeps things visible in Visual Studio and source control.
I've hit a problem deploying one of the several web.config files which I am manually including in product.wxs as components with conditions. I was expecting to include all components as deployable features and let the conditions select just one as active. For example:
<DirectoryRef Id="wwwroot">
<Component Id="setup_a" Guid="some_guid" >
<File Source="$(var.ConfigSourceDir)\setup_a\web.config" />
<Condition>ENVIRON = setup_a</Condition>
</Component>
<Component Id="setup_b" Guid="some_guid" >
<File Source="$(var.ConfigSourceDir)\setup_b\web.config" />
<Condition>ENVIRON = setup_b</Condition>
</Component>
This didn't create any file renaming, moving or deleting issues, but has the very fundamental problem that multiple web.config files are mapped to the same destination and this gives me a light error of "Product.wxs(xxx) : error LGHT0091 : Duplicate symbol 'File:web.config' found. This typically means that an Id is duplicated. Check to make sure all your identifiers of a given type (File, Component, Feature) are unique."
An alternative approach was to use different named .config files and rename/move one to be the web.config, so something like:
<DirectoryRef Id="wwwroot">
<Component Id="setup_a" Guid="some_guid" >
<File Id="setup_a.config" Source="$(var.ConfigSourceDir)\setup_a.config" />
<CopyFile Id="moveit" SourceDirectory="wwwroot" SourceName="setup_a.config" DestinationDirectory="wwwroot" DestinationName="web.config" />
</Component>
This doesn't throw an error, bot the CopyFile command does nothing at all. I just get setup_a.config in the wwwroot folder.
If I nest the CopyFile inside the File, the copy action then works:
<DirectoryRef Id="wwwroot">
<Component Id="setup_a" Guid="some_guid" >
<File Id="setup_a.config" Source="$(var.ConfigSourceDir)\setup_a.config" >
<CopyFile Id="moveit" DestinationName="web.config"/>
</File>
</Component>
...but nested CopyFile means I can't add (it's disallowed) the Delete="yes" attribute to create a 'move' action. Instead I'm left with both setup_a.config and web.config in the wwwroot folder. Alternatively, if I add a seperate removefile within the same component element it also does nothing:
<RemoveFile Id="removefile" On="install" Directory="wwwroot" Name="setup_a.config"/>
</Component>
So, I'm hoping for a working example of how handle multiple web.config files in a conditional deployment, that doesn't leave files behind. the destination filename of web.config is fixed by the framework and can't be changed. The different configs are also pre-generated outside of wix using config transforms, this also can't be changed but the generated filenames could be anything.
cheers!
You complicate it too much. This should work:
<Component Id="setup_a" Guid="some_guid" >
<File Name="web.config" Id="config_a" Source="$(var.ConfigSourceDir)\setup_a\web.config" />
<Condition>ENVIRON = setup_a</Condition>
</Component>
<Component Id="setup_b" Guid="some_guid" >
<File Name="web.config" Id="config_b" Source="$(var.ConfigSourceDir)\setup_b\web.config" />
<Condition>ENVIRON = setup_b</Condition>
</Component>
Pay attention to a couple of things here:
the File/#Name is the same - that's the target file name you'd like to have (web.config)
the File/#Id is different for each File in order to avoid the light error you mentioned first
the File/#Source can be anything - it just describes what file to take as a source
In this sample light will still complain with warning LGHT1076, but that's just a warning - it pays your attention that conditions MUST be mutually exclusive to avoid problems.
I usually take a different approach. Rather then placing multiple mutually exclusive files into an installer that are tightly coupled to specific instances I put a generic file in the installer and use XML changes to transform the XML with the variation point data such as connection string and what not.
This allows me to make installers that can be deployed anywhere silently just by passing a few properties and the command line.