WiX minor upgrade doesn't remove old Shortcut - wix

The following steps had been performed:
Setup1.msi had been built in VS2005 + WiX 3.0 (.NET Framework 2.0).
Version 1.0.0.0 had been installed by Setup1.msi.
For the purpose of minor upgrade a Setup2.msi had been built
(Setup2.msi differs from Setup1.msi ONLY in ProductVersion="1.0.1.0")
The following Patch.wxs had been prepared:
<Patch
AllowRemoval="no"
Classification="Update"
Comments="..."
Description="..."
DisplayName="..."
Manufacturer="..."
TargetProductName="...">
<Media Id="1000" Cabinet="MyPatch.cab">
<PatchBaseline Id="MyPatch" />
</Media>
<PatchFamily
Id="MyPatchFamily"
Version="1.0.1.0"
ProductCode="...THE SAME AS IN Setup1.msi..."
Supersede="yes">
<ComponentRef Id="CMP_Program_EXE" />
<ComponentRef Id="CMP_Desktop_Shortcut" />
<ComponentRef Id="CMP_ProgramMenu_Shortcut" />
</PatchFamily>
</Patch>
Patch.msp had been created with help of candle, light, torch and pyro.exe.
The following command had been invoked:
msiexec /p Patch.msp REINSTALL=ALL REINSTALLMODE=vomus
As a result, Program.exe was updated and new shortcuts "v. 1.0.1"
were created.
However, old Shortcut "v. 1.0.0" remained both on "DesktopFolder" and on
"ProgramMenuFolder".
How can I make the Patch remove old Shortcut?
Thanks in advance.

The simplest way is not to add version to shortcut name. See Windows UX Guidelines:
Avoid putting a version number in a program name unless that is how users normally refer to your program.
Otherwise your minor upgrade has to remove the shortcut to the old version and create a new shortcut that points to the new version.
During minor upgrade, the old version does not get uninstalled, that's why the shortcut is not updated.

You can use the RemoveFile element within the Component one owning the Shortcut:
<DirectoryRef Id="DesktopFolder">
<Component Id="..." Guid="...">
<Shortcut Id="..." Name="foobar_1.0.1" ... />
<RemoveFile Id="..." Name="foobar_1.0.0.lnk" On="install" />
...
</Component>
</DirectoryRef>
But. There's one problem remained -- when you uninstall you MSP patch, new shortcut (foobar_1.0.1.lnk) is not removed (because the Shortcut table is transformed back, I believe). Therefore, user ends up with two shortcuts. I do not know how to fix that, therefore I asked here.

Related

How to avoid having two versions of a product installed with Windows Installer / MSI?

I have written and am maintaining a number of Wix installation source files which I use to build MSI files for distribution of my application.
I have not explicitly programmed for any kind of upgrading, updating, reinstallation or anything of the kind -- there is a single feature that consists of a number of components with stable GUIDs and I have observed that at least a clean installation does what I expect it to.
However, I (and anyone in possession of the MSI files I distribute) may seemingly install distinct versions of my application side-by-side using their respective (distinct) MSI files. Which isn't a problem in itself, except that I obviously use the same folder as installation target -- "%ProgramFiles(x86)%\Foobar" -- to install the application (version regardless) in. Meaning that in effect there is always ever one version that ends up being installed.
I would argue that Windows Installer behaves correctly in so far that it updates files from whichever MSI package it installed last. One interesting side-effect of this is that if the last MSI was of earlier version, the files in the application folders would be overwritten with the copies from that earlier version.
But none of that seems to be the actual problem to me. I want to fix the disparity between what is actually installed (a single application version) and what Windows tracks as installed -- in my case two records of two distinct application versions.
Since I install the application in a folder that doesn't depend on the version being installed, tracking multiple application versions by Windows is a mistake.
So I guess my question is, how do I fix this so that only one version is shown (reflecting reality) or what's the idiomatic approach in these kind of cases? I deliberately did not overspecify my Wix source code, hoping in return that Windows Installer would use some built-in intelligence to figure everything out on its own. But I may need to add some explicit upgrade or uninstall-previous-version-first instructions, I suppose.
My minified Wix source code (file "foobar.wxs") would look like this:
<?xml version="1.0" encoding="utf-8" ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Product Name="Foobar" Manufacturer="ACME Inc." Id="*" UpgradeCode="ae9a7d6d-6c2d-446a-97d9-9dbe829d2ea8" Language="1033" Codepage="1252" Version="!(wix.PRODUCT_VERSION)">
<Package Id="*" Languages="1033" SummaryCodepage="1252" Compressed="yes" InstallerVersion="200" />
<Icon Id="foobar" SourceFile="!(wix.APPPATH)/foobar.ico" />
<Property Id="ARPPRODUCTICON" Value="foobar" />
<Property Id="ARPCOMMENTS" Value="Gives you full foobar powers" />
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="DesktopFolder" />
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLDIR" Name="Foobar" FileSource="!(wix.APPPATH)">
<Component>
<File Id="foobar.exe" Name="foobar.exe" />
</Component>
<!-- There are other components like above (assets) -->
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="foobar_menu" Name="Foobar">
<Component Id="foobar_shortcut" Guid="e80a6b95-a145-453a-b327-65a977e741fe">
<Shortcut Icon="foobar" Id="foobar_shortcut" Name="Foobar" Target="[foobar]foobar.exe" />
<Shortcut Directory="DesktopFolder" Icon="foobar" Id="foobar_desktop_shortcut" Name="Foobar" Target="[foobar]foobar.exe" />
<RegistryValue KeyPath="yes" Root="HKMU" Key="Software\[Manufacturer]\[ProductName]" Type="string" Value="" />
<RemoveFolder Id="remove_foobar_menu" On="uninstall" />
</Component>
</Directory>
</Directory>
<Directory Id="CommonAppDataFolder">
<Directory Id="app_data_foobar" Name="foobar">
<Component Guid="" Id="app_data_config_folder">
<CreateFolder />
</Component>
<Component Guid="" Id="app_data_config_folder_log_file">
<File Name="foobar.log" Source="foobar.log.template">
<!-- Add write access permission to the log file to members of "Users" group. -->
<!-- PermissionEx Sddl="D:AR(A;;GWGR;;;BU)" / -->
<!-- Bug with Windows Installer, can't use PermissionEx/MsiLockPermissionsEx table. See https://stackoverflow.com/questions/55145282/how-to-include-inherited-permissions-when-specifying-permissions-for-a-file-inst -->
<util:PermissionEx Append="yes" GenericWrite="yes" User="Users" />
</File>
</Component>
</Directory>
</Directory>
</Directory>
<Feature Id="foobar">
<ComponentGroupRef Id="foobar" />
<ComponentRef Id="foobar_shortcut" />
<ComponentRef Id="app_data_config_folder" />
<ComponentRef Id="app_data_config_folder_log_file" />
</Feature>
</Product>
</Wix>
I am compiling the object file with the following Windows Command Prompt line:
candle.exe -ext WixUtilExtension -out %TEMP% foobar.wxs
And then generating the MSI file with:
light.exe -ext WixUtilExtension -spdb "-dAPPPATH=%apppath%" "-dPRODUCT_VERSION=%version%" -out %TEMP%\foobar-%version%.msi %TEMP%\foobar.wixobj
(using Wix 3.11.1.2318)
Upgrade Code: As long as you have set an upgrade code (which identifies a bunch of related products) you can use a major upgrade element to indicate products that are to be uninstalled as part of a new MSI's installation.
MajorUpgrade Element: Just inject a MajorUpgrade element for default treatment of major upgrades into your existing WiX source. It is a sort of "magic element" doing a lot for you making a number of (usually good) assumptions. There are older and more flexible ways to do it - if you need more detailed control (for legacy purposes usually - auto-magic does not cover all bases):
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
The above is the standard use for all WiX files created in Visual Studio.
Note: I will try to tune up this answer shortly with more links, but give that a go first?
First link: Using Visual Studio to make WiX files. The Hello WiX and Visual Studio-type of scenario.
Major Upgrade Suggested Reading: A few things to know about major upgrades. All WiX markup essentially revolves around the compiled MSI's Upgrade table. It is there that major upgrade logic is configured. Custom actions could also affect things, and a few other things such as launch conditions perhaps.
WiX Documentation: How To: Implement a Major Upgrade In Your Installer
Major Upgrade - Common Problems: WIX does not uninstall older version
Major Upgrade - Manual Configuration: Adding entries to MSI UpgradeTable to remove related products (using old-style Upgrade elements)
Further:
Major Upgrades - How-To & Concept: Doing Major Upgrade in Wix creates 2 entries in Add/Remove Programs

Registry key not updating when value was set to 0 when using WiX Toolset

The registry key value's isn't being updated with its intended data by WiX Toolset for a MSI. If the k:v is missing, it adds it. If the k:v's data is set to 0, it ignores it completely, which is the actual problem here (I think)
The basic goal is to verify this registry key value exists with the intended data-value before installation, and the reboot prompt triggers if the key had to be added/updated.
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Product Id="*" Name="SampleInstaller" Language="1033" Version="1.0.0.0" Manufacturer="ACME" UpgradeCode="cf6248e9-d7da-4996-9b8e-90072e8510f6">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" Platform="x64"/>
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<Feature Id="ProductFeature" Title="SampleInstaller" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder64">
<Directory Id="INSTALLFOLDER" Name="SampleInstaller" />
</Directory>
</Directory>
<Property Id="VKB_QUERY_HKCU" Secure="yes">
<RegistrySearch Id="VkbVisibleHkcu"
Win64="yes"
Type="raw"
Root="HKCU"
Key="Software\Microsoft\TabletTip\1.7"
Name="TipbandDesiredVisibility"/>
</Property>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="ShowVKB_Registry_HKCU" Guid="{97AB4B1D-C9C8-4B34-9328-FF8CA3ED8992}" Directory="INSTALLFOLDER">
<RegistryKey Id="VKB_Registry_Key_HKCU" Root="HKCU" Key="Software\Microsoft\TabletTip\1.7" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="no">
<RegistryValue Id="VKB_Registry_Value_HKCU" Action="write" Type="integer" Name="TipbandDesiredVisibility" Value="1"/>
</RegistryKey>
</Component>
</ComponentGroup>
<InstallExecuteSequence>
<ScheduleReboot After="InstallFinalize">NOT (VKB_QUERY_HKCU = "#1")</ScheduleReboot>
</InstallExecuteSequence>
</Fragment>
</Wix>
Also, it seems like the exit code from MSIEXEC is always returning 0 instead of 3010 of 1641 when checking $LastExitCode and %errorlevel%. I haven't messed with the different reboot behaviors, but I thought having the reboot prompt would have caused my installer to exit non-zero, so any guidance there is also appreciated.
Do that install and create a verbose log with:
msiexec /I [path to msi] /l*vx [path to a text log file]
and look at the property values etc. The most likely reason you're not getting the 3010 exit result is that the ScheduleReboot is conditioned false.
Assuming that everything is otherwise working as intended, the problem might be that you need to set Secure to Yes in your property declaration, otherwise the value won't be transferred from the UI sequence registry search into the execute sequence. If the log shows it gettingthe correct value at the start of the install but losing it later this is most likely the issue.
One of your comments refers to %errorlevel% but it's not clear why this is relevant. If you are initiating this from a batch file or similar then this is useful information to add. Also, if you are installing this in some way that is separate from the current interactive user then this is also useful to know.
The log seems to indicate that everything is fine. The properties have values that look correct, and the ScheduleReboot action is performed. The only issue I see is that Windows Installer did not show the dialog asking the user to do the reboot, so it does the non-interactive thing, which is to return 3010 to tell the caller that a reboot is required. There are no obvious reasons why Windows Installer didn't prompt for the reboot (which is what ScheduleReboot does) but if the install is running in a non-interactive user context then Windows will not show a desktop dialog to another user (or to no user nobody is logged on).
So, the sample works, but you can't test it repeatedly by re-running install. You have to uninstall it first, or stumble upon the reason like I did, and re-compile before re-running.
I noticed that the sample was only working right after a re-compile. So, I'm guessing the installer has a GUID or something tied to it at compile-time, which is then included with your installation. And when re-running the installation, it would just quickly run and close, not ask you to uninstall first or remove the existing product, so I was assuming that it just wasn't evaluating that my installer had to write the keys, and just ended when not having to do more.
So basically it was a test bug/lack of intrinsic WiX knowledge.
So, always uninstall your MSI before re-running it unless you are specifically trying to trigger upgrade behavior.
I'm not sure why I wasn't getting an error about the product already existing. I would have thought for sure that would be the default behavior.

Old Property with RegSearch is affecting Product upgrade (new version without this prop)

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.

WiX minor upgrade remove Windows 7 taskbar pinned shortcut

Every update to new version WiX for some reason removed pinned start menu shortcut from taskbar. How can I fix this?
Shortcut was created using this command:
<DirectoryRef Id="ProgramMenuFolder">
<Component Id="GitExtensions.newstartmenu" Guid="*">
<Shortcut
Id="GitExtensions.newstartmenu"
Name="$(var.ProductName)"
Description="$(var.ProductName)"
Icon="gitextensions.ico"
Target="[INSTALLDIR]GitExtensions.exe"
WorkingDirectory="INSTALLDIR"/>
<RegistryValue
Root="HKCU" Key="$(var.InstalledRegKey)"
Name="GitExtensions.newstartmenu" Value="" Type="string"
KeyPath="yes"/>
</Component>
</DirectoryRef>
WiX code: https://github.com/gitextensions/gitextensions/blob/f9490e3e6e34cc2f6770fd9e1d6132cf5cfd0b0b/Setup/Product.wxs#L385-L399
Setup had been built in VS2010 + WiX 3.5.
It's actually doing a major upgrade and by scheduling RemoveExistingProducts early, the upgrade is removing the older version before installing the newer version. The shell removes the pin when the older shortcut is removed. You can try experimenting with a later scheduling of RemoveExistingProducts but note that there are costs associated with that.

WiX Conditional Feature/Component Orphaned on Uninstall

Edit: Quoting myself because I summarized the issue much better in one of the comments below…
I have a condition that is true when the package is installed, but
not true when it is removed. I expected MSI to remember that it had
installed the conditional component and remove it with the uninstall,
but this is not the case. I am trying to find out A) the proper way to
clean up this orphaned component, and B) the best way to protect
against this problem in the future.
I guess my question boils down to, is it safe to just delete an orphaned feature/component after a product is uninstalled? And is there any way to check what, if anything, is still referencing a component that I believe to be an orphan? And how do I fix my installer to keep this from happening in the future?
We have a wix project to install a library, Foo. This installer puts copies of Foo.dll into the GAC, and a folder, Program Files\Reference Assemblies\Foo\<version> by default. The installer also adds two registry keys, one is a custom key which stores the path of the Foo folder for reuse in future installs, the other tells Visual Studio to include the full <version> folder path in its search for installed libraries so that Foo shows up in the “Add References” dialog. Multiple versions of the Foo library can be installed on the machine at a time, each will be located in the appropriate <version> folder under Foo.
Foo 2.0.0 had a bug that slipped through testing, Foo 2.0.1 contained the bug fix, no other changes. It was decided that since the bug fix was the only change, we would add a policy file to the GAC which would redirected references for Foo 2.0.0 to Foo 2.0.1. This policy file was added to the installer as a new component inside of a new feature. An upgrade tag was added to detect and remove Foo 2.0.0 when Foo 2.0.1 was installed. The installation of the policy feature was made conditional on Foo 2.0.0 being detected. Everything seemed to be working and Foo 2.0.1 was pushed out.
Now, a year later, we discover that we again missed noticing a bug, this time in the installer setup rather than the library code. It turns out that when Foo 2.0.1 replaces 2.0.0, and is then uninstalled, the policy file is orphaned and remains in the GAC while all other files and keys are removed. I have tested this on a clean install of windows (virtual machines can be so useful) and confirmed that the problem can be replicated, i.e. no additional references to the component have snuck in to cause it to stay behind.
All of this was originally done in WiX 3.0 but we have recently moved up to using WiX 3.5. Our WiX code looks like this:
<Product Id="Guid 1" Name="Foo v2.0.1" Language="1033" Version="2.0.1" Manufacturer="My Team" UpgradeCode="Guid 2">
<Package InstallerVersion="300" Compressed="yes" />
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
<Upgrade Id="Guid 2">
<UpgradeVersion Minimum="2.0.0" Maximum="2.0.0" IncludeMaximum="yes" IncludeMinimum="yes" OnlyDetect="no" Property="UPGRADE2X0X0"></UpgradeVersion>
</Upgrade>
<Property Id="FOODIR">
<RegistrySearch Id="FooPath" Type="directory" Root="HKLM" Key="Software\Foo" Name="InstallPath"></RegistrySearch>
</Property>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="RefAssemb" Name="Reference Assemblies">
<Directory Id="FOODIR" Name="Foo">
<Component Id="FooLibPath" Guid="Guid 3">
<RegistryKey Root="HKLM" Key="Software\Foo" Action="createAndRemoveOnUninstall">
<RegistryValue Name="InstallPath" Type="string" Value="[FOODIR]" KeyPath="yes"></RegistryValue>
</RegistryKey>
</Component>
<Directory Id="FOOVERSION" Name="v2.0.1">
<Component Id="Foo_VSFile" Guid="Guid 4">
<File Id="Foo_DLL" Source="$(sys.CURRENTDIR)2.0.1\Foo.dll" KeyPath="yes"></File>
</Component>
<Component Id="Foo_VSRegKey" Guid="Guid 5">
<RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\.NETFramework\v3.5\AssemblyFoldersEx\Foo v2.0.1" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="[FOOVERSION]" KeyPath="yes"></RegistryValue>
</RegistryKey>
</Component>
<Directory Id="FOOGAC" Name="GAC">
<Component Id="Foo_GAC" Guid="Guid 6">
<File Id="Foo" Source="$(sys.CURRENTDIR)2.0.1\Foo.dll" KeyPath="yes" Assembly=".net"></File>
</Component>
<Component Id="Foo_Policy_2x0x1" Guid="Guid 7">
<File Id="Foo_PolicyDLL" Source="$(sys.CURRENTDIR)2.0.1\policy.2.0.Foo.dll" KeyPath="yes" Assembly=".net"></File>
<File Id="Foo_PolicyConfig" Source="$(sys.CURRENTDIR)2.0.1\policy.2.0.Foo.config" CompanionFile="Foo_PolicyDLL"></File>
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
<Feature Id="ProductFoo" Level="1">
<ComponentRef Id="Foo_GAC"/>
<Feature Id="Foo_VSSupport" Level="1">
<ComponentRef Id="FooLibPath"/>
<ComponentRef Id="Foo_VSFile"/>
<ComponentRef Id="Foo_VSRegKey"/>
</Feature>
<Feature Id="Foo_Policy_v2x0x1" Level="0">
<ComponentRef Id="Foo_Policy_2x0x1"/>
<Condition Level="1">UPGRADE2X0X0</Condition>
</Feature>
</Feature>
</Product>
is it safe to just delete an orphaned feature/component after a
product is uninstalled?
No, it's not. If you just delete it, its component registration information is still left on the machine.
And is there any way to check what, if anything, is still referencing
a component that I believe to be an orphan?
Not really. But if there is something referencing one of your components, it's most likely another product developed by you or an older version of your current product which wasn't uninstalled correctly.
It's very unlikely that a random product would reference your component or assembly.
And how do I fix my installer to keep this from happening in the
future?
Use major upgrades which uninstall the old component and install the new one. No special policy files, no conditional installations or removals.
Multiple versions of the Foo library can be installed on the machine
at a time, each will be located in the appropriate folder
under Foo.
Why? If you have a single product, you can use major upgrades. This way the user will have only one version installed with only one version of your assembly.
Versioned assemblies installed side by side make sense only for different products.
It was decided that since the bug fix was the only change, we would
add a policy file to the GAC which would redirected references for Foo
2.0.0 to Foo 2.0.1. This policy file was added to the installer as a
new component inside of a new feature.
This is a hack and most likely this is what is causing the problem. Your new installed should have uninstalled the old version along with Foo 2.0.0.
Major upgrades should always be standalone.