Wix - how to prevent overwrite entire directory? - wix

I have wix installer and it copies some files to some directories. Each file is declared as single component, with some path - if directory does not exist, installer will create it and place file there.
What I want to do: if directory already exists, installer should not copy any files there (even if file does not exists, it should not be copied to already existing directory).
But it's impossible to set "Never overwrite" to directory, so how can I prevent copy new files to already existing directory? Is there any condition (something like "is directory exist") that I can use here?

Is there any condition (something like "is directory exist") that I can use here?
Yes, you can use a Condition element like this:
<Directory Id="FooFolder" Name="Foo">
<Component Id="SomeId">
<File Source="..." />
<Condition>Not FOO_FOLDER_ALREADY_EXISTS</Condition>
</Component>
</Directory>
The kind of things that you can use as a condition are explained in the Conditional Statement Syntax documentation of windows installer.
In this case, I believe you can set the FOO_FOLDER_ALREADY_EXISTS property with a DirectorySearch like this:
<Property Id="FOO_FOLDER_ALREADY_EXISTS">
<DirectorySearch Id="FooFolderSearch" Path="[FooFolder]" />
</Property>
edit: apparently the directory search above doesn't work because the [FooFolder] property is only resolved during the CostFinalize action (see documentation). But the directory search already happens before that during the AppSearch action.
I am unsure how to work around that. It would probably involve setting the FOO_FOLDER_ALREADY_EXISTS property after CostFinalize with a custom action instead of a windows installer directory search.

In regards to how to avoid the timing issue with the property being set before cost finalize, another reliable way to deal with it is to write the properties to registry.
I believe this is the most common work around to the timing issues with properties getting set, and it allows the installer to keep track of those properties for uninstalls, etc... I've used it to great effect.
Here is a good article by Rob Mensching on how to do that to get you started.

Related

Wix: How to remove GAC files marked as Permanent

A previous version of an MSI marks some GAC files as permanent, now I want to remove those files upon upgrading to a newer version of the same program. Is it possible to do this using Wix code or do I have to always manually delete the files ?
I don't know that you can ever fully delete the component since it's permeant but you can remove the file.
I tested this by creating a component called Client.exe and a component called Server.dll which was marked Permanent="yes" and Assembly=".net".
<DirectorySearch
Id="SERVERFOLDER"
Path="[WindowsFolder]Microsoft.NET\assembly\GAC_MSIL\Server\v4.0_1.0.0.0__bb771c317b736a07" AssignToProperty="yes">
</DirectorySearch>
<RemoveFile Id="oldfile" Property="SERVERFOLDER" Name="Server.dll" On="both"/>
<RemoveFolder Id ="old" Property="SERVERFOLDER" On="both"/>
The remove File/Folder elements are added to the Client.exe component.
This results in Server.dll being removed from the GAC. But I do have to ask, why bother? A file in the GAC has a strong name and shouldn't break anything that doesn't reference it. Also it's not something that takes up much disk space or anything the user really sees.

How can I conditionally overwrite file during WIX install?

I have two work modes in my installer:
use config files left from previous installation
delete all existing configs and put default configs instead
The mode is determined by the checkbox in the WPF UI of the installer. If second mode is selected, then CustomAction is run, which manually deletes the configs folder from disk:
<InstallExecuteSequence>
<Custom Action="RemoveConfigsFolder" After="RemoveFolders" Overridable="yes">NOT Installed AND DELETESETTINGS=1</Custom>
</InstallExecuteSequence>
I'm using NeverOverwrite attribute:
<ComponentGroup Id="Configs" Directory="INSTALLDIR" >
<Component Id="Configs" Permanent="yes" NeverOverwrite="yes">
<File Id="main.config" Name="main.config" Source=".\Configs\main.config" KeyPath="yes" />
</Component>
</ComponentGroup>
The first mode works fine in this case, but when I try to use second mode it fails and all configs are just deleted and never created again during the installation.
During my research of the issue, I think I've found the reason why this happens: https://community.flexerasoftware.com/showthread.php?96157-The-truth-the-whole-truth-about-quot-Never-overwrite-quot-and-quot-Permanent-quot-files&p=156826#post156826
Actually this is a Windows Installer issue. If you log the uninstall
you will notice that very early in the installation the Installer
decides that the component containing this file will not be installed
because it is marked "Never Overwrite" and a copy of this file already
exists on the target machine. The uninstall happens after that which
removes the existing file. This is because the Installer decides this
when the "CostFinalize" action is launched. This action HAS to be run
before the "RemoveFiles" action.
But how do I fix it?
The problem with settings such as Never Overwrite or Permanent is they look like build settings, but they are not really - they stick to the system attached to the component id. So resetting in the project won't help because it's associated with that id. It's also not clear why setting Never Overwrite might have been a solution to some problem, because by definition patches and overwrite upgrades won't overwrite it, but overwriting it is a requirement of your setup.
Even if you had not set Never Overwrite the Windows Installer rules would not overwrite the file if it was modified after install. So if you had installed it, then it was altered, and then you did an upgrade, the file would not be overwritten (which is another reason why Never Overwrite does not seem needed).
Another issue is that your custom action RemoveConfigsFolder is not marked with an Execute enumeration value, therefore it is immediate, therefore it does not run elevated, therefore it might simply be failing, so without seeing the code it's impossible to say if reports an issue if it can't do the remove. It's also not possible to determine if it explicitly specifies the full path to the folder correctly. So the most likely quick fix to this issue is to mark the custom action as execute deferred, and the DELETESETTINGS value will need to be passed in via CustomActionData.
My initial thought is to remove the 'Never Overwrite' property. Then create a component condition that checks if the file exists. My thought is that your custom action has the condition to correctly remove the config files. If the files do not exist then the components will be selected for install.

Copy external file or fallback to internal file with WiX

As the title says, I want to install an external file:
<Component>
<File Source="Application.exe.config" Compressed="no" />
</Component>
and–if the external file is not available–install the default, internal file:
<Component>
<File Source="Application.exe.default.config" Name="Application.exe.config" />
</Component>
So that is guaranteed, that there is always a file installed.
How can I achieve that?
To clarify: By external I do NOT mean a CopyFile-Element. Instead I am talking about a normal WiX-File (as seen in the first Snippet) that is simply not compressed into a cab.
Your code snippet shows the file in the MSI, not external, that's the confusion. If it's external it's not in the WiX or the MSI, you would copy it using a WiX CopyFile element.
I'd be tempted to use a file search to see if the file is present and store that resulting property with the WiX remember property pattern. Make your file component transitive and conditioned on "NOT FILEFOUND", for example, so it doesn't get installed if the external file is found. Then you just need the external file copied if FILEFOUND is set. For this, I would add another transitive component containing only a registry entry (it must contain something) with condition FILEFOUND. Put the WiX CopyFile in this component so it runs when FILEFOUND is set. I think that would do it without writing any code.

MSIEXEC using command line REINSTALL not using original INSTALLDIR

I am trying to configure Wix to build my msi to only perform build versions (1.0.x) of my product in conjunction with the REINSTALL property, my problem is that when I run the command line: MSIEXEC.exe /i my.msi /l*vx build-inst.log REINSTALL=ALL REINSTALLMODE=vamus it fails to do anything.
I have checked the msi log and found that it is looking for the existing product in the default folder (.\program files (x86)...\myproduct) yet when I installed it the first time I actually used a custom path (c:\myproduct). It was my impression that using REINSTALL the installer would use the installed path of the original product.
Is this actually the case? Should I be specifying the INSTALLDIR on my command line? I would rather not as this is meant for use by clients and I cannot guarantee I will know where the product was installed.
This method of performing "build" upgrades has been suggested in a couple of places but I can not find anything explaining any need to specify the INSTALLDIR
Is there any way to configure this in Wix?
Thanks
Kieran
The easiest solution would be to store the installation directory in the registry and look it up upon reinstalling.
To look up your registry value, you'd use something of the sort:
<Property Id="INSTALLDIR">
<RegistrySearch Id="InstallLocation" Root="HKCU"
Key="SOFTWARE\Company\Product" Name="Location" Type="raw" />
</Property>
If the registry value isn't found, the INSTALLDIR property will be set to your directory structure.
Rob has a complete solution on his blog for when you specify such a property from the command line.
Normally the original entries in the directory table are stored for reinstall without that you store them yourself.
So there is something "special" in your MSI, if this doesn't work. If you have a custom action which sets directory properties like INSTALLDIR, you should not use it. E.g. give them a condition "Not Installed".
I found out that the problem was due to using a wildcard for the product id, so every time a new msi was built it created a new product id.
By fixing this it seemed to resolve the problem, though I have also implemented the registry key option as it will help for upgrades where I do want to change the product id.
Thanks

Manage configuration files with WiX

I have an application with several files that contain configuration parameters and other data that changes as the user uses the application. These files can change with newer versions of my software, but the user can also modify them (or they may be changed by the application itself). Basically, I'm looking for a solution to prevent the users' changes to these files from being overwritten but also a way to install the potentially updated files when the user upgrades my software.
With RPM on *NIX you could use the %config function to define a file as a configuration file and RPM would then rename the existing file (if it existed) and install the new one on an upgrade (maybe not ideal, but I could live with something like this for WiX).
I'd like to install my config files to a subdirectory or even a different name (e.g. default.cfg) and then use the <CopyFile> element in WiX to copy the files to their correct locations. This way, the default files would get removed on install and overwritten on an upgrade, but the actual user files would stay the same. Unfortunately with <CopyFile>, Windows Installer still wants to manage (and remove) the destination file.
I've also considered using the QtExec action in WixUtilExtension to basically do "copy default.cfg reallocation.cfg" but this wouldn't quite work and it is a bit of a hack.
What is the correct way to handle this?
My recommendation is usually to have the user editable content in a separate file and manage that via the application instead of the install. That also means the separate file is "user content" and should be left out of the install.
I've found trying to do migration of user data declaratively to be deceptively difficult. Trying to do it at setup time when you need to think through install, uninstall, repair, patching and rollback for all of those cases only makes it worse.
For example, what does the RPM behavior do on "repair". Copy the user data out of the way and replace it with a good file? That's probably correct 60% - 80% of the time. And uninstall, should the file be removed? That's tricky if the user is going to just upgrade to the next version.
Again, better to let them decide what to do with their tweaks to the configuration. IMHO.
I think there is no "clean" way to do this, because a msi project must be able to uninstall itself completely by design. I think the best way to solve this, is by using a custom action which executes a batch file and put your configfile update logic in that batch file. The custom action looks like this (only relevant parts):
<Directory Id="MYDIR" Name="MyDir">
<Component Id="update.cmd" Guid="YOUR-GUID">
<File Id="update.cmd" Name="update.cmd" KeyPath="yes"
Source="source\update.cmd" />
</Component>
</Directory>
<CustomAction Id='RunUpdate' Directory='MYDIR'
ExeCommand='[SystemFolder]cmd.exe /c update.cmd' Return='ignore'/>
<InstallExecuteSequence>
<Custom Action='RunUpdate' After='InstallFinalize'>NOT Installed</Custom>
</InstallExecuteSequence>