Is it possible to have advertised shortcuts and ProgId for a per user MSI? - wix

I have a permachine MSI installer that I'm converting to a per user installer that is installing files to the LocalAppDataFolder.
To get rid of ICE38 warnings I added a registry as the keypath. The problem is in one of my components I have a advertised shortcut and ProgId.
I'm now getting:
ICE19 - 'settings' advertises component: 'CMP_Rapid'. This component cannot be advertised because the KeyPath type disallows it
ICE19 - Shortcut: 'SHC_RunConfigExe' advertises component: 'CMP_Rapid'. This component cannot be advertised because the KeyPath type disallows it.
ICE50 - Component 'CMP_Rapid' has an advertised shortcut, but the KeyPath cannot be found.
Is there anyway to advertise in per user installs when installing to the LocalAppDataFolder? I'm not that familiar with advertising. On the shortcut I use it because I like the repair functionality it offers. On the progId I use it because it automatically updates the appropriate icons after an install.
I need to install this on XP and Vista so I can use the Windows 7 solution: ProgramFilesFolder redirection.
Below is the component that generates the errors:
<DirectoryRef Id="INSTALL_FOLDER">
<Component Id="CMP_Rapid"
Guid="{9373A11C-5A3C-49E3-963D-C19B765A4285}">
<File Id="FILE_Rapid"
Source="$(var.FilePath)\Dynagen Configurator.exe">
</File>
<Shortcut Id="SHC_RunConfigExe"
Name="DYNAGEN Configurator"
Description="Opens DYNAGEN Configurator application."
Directory="ConfigShortCutDir"
WorkingDirectory="INSTALL_FOLDER"
Icon="ICO_RunConfigExe.exe"
Advertise="yes"/>
<ProgId Id="Rapid.drcS" Icon="ICO_drcS.ico" Advertise="yes">
<Extension Id="settings">
<Verb Id="Open" Command="Open" Argument="/so "%1"" />
<Verb Id="Edit" Command="Edit" Argument="/edit "%1""/>
<Verb Id="Program" Command="Program" Argument="/program "%1""/>
</Extension>
</ProgId>
<RegistryValue Root="HKCU"
Key="Software\Dynagen\DynagenConfigurator"
Name="CMP_Rapid"
Type="integer"
Value="1"
KeyPath="yes"
/>
</Component>
</DirectoryRef>

Turns out satisfying ICE38 by making a registry key the keypath is wrong. The proper thing to do is to ignore the ICE38 error but going going into the wix properties page and add ICE38 and ICE91 beside "Suppress specific ICE validation.
Microsoft or Wix doesn't have ideal support for pure per-user installs. I couldn't even find any good examples online.
Bryan at WiX-users#lists.sourceforge.net helped me out on this:
Since that is the case, ICE38 is (in my opinion) incorrect and you will want to ignore it. ICE38 implies you are installing per-user resources in the context of a per-machine installation but never verifies that this is so.
Actually authoring a per-user install requires that you ignore ICE38 because it won't ever be accurate for that world.
Once I ignored that the above worked.
[Edit]
See my answer here as well. Turns out there is some edge cases you need to be aware of.

Related

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 does not remove shortcuts in the INSTALLDIR if not default

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.

(WiX) Program files shortcut for per-machine install

Following the example here, I added a shortcut to the ProgramMenuFolder that launches my application. (My code is actually simpler because I don't need the extra folder.)
<DirectoryRef Id='ProgramMenuFolder'>
<Component Id='cmpStartMenuShortcut'
Guid='MY GUID HERE'>
<Shortcut Id='StartMenuShortcut'
Name='$(var.ProductName)'
Icon='MainIcon.ico'
Description='$(var.ProductName)'
Target='[ClientDir]myapp.exe'
WorkingDirectory='ClientDir'/>
<RegistryValue Action='write' Type='integer' Root='HKCU'
Key='Software\Company\Product Name'
Name='installed' Value='1' KeyPath='yes'/>
</Component>
</DirectoryRef>
Since my installation is per machine (ALLUSERS=1, Package/#InstallPrivileges='elevated', and #InstallScope='perMachine') the ProgramMenuFolder is the folder for all users on the machine.
My question has to do with the registry value. My understanding is that it's needed simply to provide a KeyPath for the component that contains the shortcut. The sample uses HKCU, which is a per-user location.
Isn't it a mistake to use a per-user value as a KeyPath for a per-machine component?
If a machine has two admins, and admin #1 installs the product, and admin #2 attempts a repair, then Windows Installer won't see the registry value and think that the shortcut is missing and it will install a duplicate, right?
So I tried changing the RegistryValue/#Root to HKLM, but then WiX complains:
error LGHT0204 : ICE38: Component cmpStartMenuShortcut installs to user profile. It's[sic] KeyPath registry key must fall under HKCU.
error LGHT0204 : ICE43: Component cmpStartMenuShortcut has non-advertised shortcuts. It's[sic] KeyPath registry key should fall under HKCU.
error LGHT0204 : ICE57: Component 'cmpStartMenuShortcut' has both per-user and per-machine data with a per-machine KeyPath.
I don't understand why the key must be under HKCU.
That style of shortcut is for a target that might not be installed now nor at the time it is invoked. It creates the classic .lnk shortcut file. It is useful for shortcuts to targets that your installer is not responsible for but might be useful for users of your product to use (e.g. cmd.exe).
Alternatively, a shortcut for a target you are installing or advertising will be uninstalled when the target is unadvertised (product is uninstalled). For example, WiX installs a shortcut to wix.chm called WiX Documentation. The Shortcut element for an advertised shortcut can be made a child of the File element.
Here is a hand-written example:
<Component Id="ProductComponent">
<File Source="$(var.ConsoleApplication1.TargetPath)" KeyPath="yes">
<Shortcut Id="$(var.ConsoleApplication1.TargetName)Shortcut"
Name="$(var.ConsoleApplication1.TargetName)"
Advertise="yes"
Description="Starts $(var.ConsoleApplication1.TargetName)"
Directory="ProgramMenuFolder" />
</File>
</Component>
To insert the Shortcut element into heat's output, pass it the path to an XSL transform. Snippet:
<xsl:template match="wix:File[contains(#Source,'\myapp.exe')]">
<xsl:copy-of select="." />
<Shortcut Id='StartMenuShortcut'
Advertise="yes"
Name='$(var.ProductName)'
Icon='MainIcon.ico'
Description='$(var.ProductName)'
WorkingDirectory='ClientDir'/>
</xsl:template>
What I would recommend doing is simply making the folder as you said, but not placing the shortcut directly under it. Instead make a shortcut element under the component holding the file. You can use the Directory attribute to specify where you want the shortcut to appear.

Remove registry keys under HKCU on a per machine installation

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.

How do I install to LocalAppData folder?

Following directory setting works perfectly for me.
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id="ProgramFilesFolder">
<Directory Id='INSTALLDIR' Name='MyApp'/>
</Directory>
</Directory>
However, when I tried changing "ProgramFilesFolder" to "LocalAppDataFolder", I got lots of error when using light to link and generate my msi:
D:\runGroup.wxs(53) : error LGHT0204: ICE38: Component cmpA5561BE36D80EB58252E69DDA0C2FF8C installs to user profile. It must use a registry key under HKCU as its KeyPath, not a file.
D:\main.wxs(38) : error LGHT0204 : ICE64: The directory INSTALLDIR is in the user profile but is not listed in the Remove File table.
Looks like "LocalAppDataFolder" is not acceptable for WiX, while I believe it is one of the system folder properties which defined in here.
What am I supposed to use for LocalAppData folder?
I converted an application from being a perMachine install to be a perUser install. In order to properly convert the install I had to add a registry key for each of the components I have.
Originally I had the following:
<Component Id="C.MyExe">
<File Id="Fi.MyExe" Name="$(var.MyExe.TargetFileName)" Source="$(var.MyExe.TargetPath)" DiskId="1">
<Shortcut Id="SC.StartMenu"
Directory="D.ApplicationMenuDir"
Name="$(var.AppName)"
WorkingDirectory="INSTALLDIR"
Icon="MY_ICON.ico"
IconIndex="0"
Advertise="yes"
/>
...
When I moved the exe component to the user install I had to do something like this:
<Directory Id="LocalAppDataFolder" Name="AppData">
<Directory Id="MyAppDirectory" Name="$(var.AppName)">
<Component Id="C.MyExe" Guid="{MY_GUID}">
<CreateFolder />
<RemoveFolder Id="RemoveMyAppDirectory" On="uninstall" />
<RegistryKey Root="HKCU" Key="Software\MyCompany\MyApp">
<RegistryValue Name="MainExe" Value="1" KeyPath="yes" Type="integer" />
</RegistryKey>
<File Id="Fi.MyExe" Name="$(var.MyExe.TargetFileName)"
Source="$(var.MyExe.TargetPath)" DiskId="1" Checksum="yes">
</File>
</Component>
...
The most important part is that you will have to add a registry key which points to HKEY_CURRENT_USER. I added a registry value for each component which indicates that the component is installed.
I also had to remove the following: Advertise="yes".
I had this problem recently. I wanted to convert my installer from per-machine to a per-user but was getting ICE38. I asked on wix-users and one opinion was that you can ignore ICE38 because that was meant as a check for per-machine installs.
See the discussion at wix-users.
Since that is the case, ICE38 is (in my opinion) incorrect and you will want to ignore it. ICE38 implies you are installing per-user resources in the context of a per-machine installation but never verifies that this is so.
Actually authoring a per-user install requires that you ignore ICE38
because it won't ever be accurate for that world.
[Edit]
Looks like you got help here.
From Peter Shirtcliffe:
This is my own, admittedly inexpert, understanding of per-user installations:
Installing to subdirectory of LocalAppDataFolder is perfectly OK in a
per-user MSI. Because of certain scenarios relating to roaming users, you
need to add components containing elements for any
directories you create under LocalAppDataFolder. That's why ICE64 is
appearing.
The ICE38 error is slightly misleading: since you have a per-user
installation, it's safe to ignore as long as the user cannot pick an
alternative installation location that is common to all users. ICE38 is
checking for the situation where multiple users all install the same
component to the same path.
Just posting to help other people (like me).
Ok, just found that we can do it by overwriting "ProgramFilesFolder":
<SetProperty Id="ProgramFilesFolder" Value="[LocalAppDataFolder]" Before="CostFinalize"><![CDATA[NOT Privileged]]></SetProperty>
Another thing to do is, in <Package> we need to set InstallPrivileges to limited.
Well, I can see no reason why "ProgramFilesFolder" can be used directly while "LocalAppDataFolder" can't.
Are you installing per-user or per-machine? Also, what OS versions are you targetting? You might want to read:
Authoring a single package for Per-User or Per-Machine Installation context in Windows 7