WiX RemoveFolderEx 'Missing Folder Property' - wix

I'm trying to use RemoveFolderEx to remove the Roaming/MyApp folder after uninstall, howver, after using the 'msiexec /i /l*v' command and checking the installer logs, I come across this:
MSI (s) (64:40) [10:30:45:254]: Invoking remote custom action. DLL:
C:\Windows\Installer\MSI860E.tmp, Entrypoint: WixRemoveFoldersEx MSI
(s) (64:74) [10:30:45:254]: Generating random cookie. MSI (s) (64:74)
[10:30:45:256]: Created Custom Action Server with PID 52380 (0xCC9C).
MSI (s) (64:8C) [10:30:45:657]: Running as a service. MSI (s) (64:8C)
[10:30:45:704]: Hello, I'm your 32bit Impersonated custom action
server. WixRemoveFoldersEx: Error 0x80070057: Missing folder
property: APPLICATIONFOLDER for row:
wrf4C77709F2CC40D572056B8DB1B2D0A3E CustomAction WixRemoveFoldersEx
returned actual error code 1603 but will be translated to success due
to continue marking Action ended 10:30:45: WixRemoveFoldersEx. Return
value 1.
I just cant seem to get it it to work. I followed this guide to implement it: http://www.hass.de/content/wix-how-use-removefolderex-your-xml-scripts
Here is my Wix code:
<Property Id="APPLICATIONFOLDER">
<RegistrySearch Key="Software\Wah\MyApp" Root="HKCU" Type="raw"
Id="REGSEARCH" Name="Path" />
</Property>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="AppDataFolder">
<Directory Id="PrivateData" Name="MyApp">
<Component Id="RemovePrivateData" Guid="*">
<RegistryValue Root="HKCU" Key="Software\Wah\MyApp"
Name="Path" Type="string"
Value="[AppDataFolder]" KeyPath="yes"/>
<util:RemoveFolderEx On="uninstall"
Property="APPLICATIONFOLDER"/>
<RemoveFolder Id="AppDataFolder" On="uninstall"/>
</Component>
</Directory>
</Directory>
</Directory>
After looking at Regedit, the value does get set to the correct path.
Any help would be greatly appreciated as I've looked at practically every issue with RemoveFolderEx and it hasn't particularly helped.
EDIT: I've fixed this issue by adding a 'Secure="yes"' attribute to my APPLICATIONFOLDER property, like so:
<Property Id="APPLICATIONFOLDER" Secure="yes">
<RegistrySearch Key="Software\Wah\MyApp" Root="HKCU" Type="raw"
Id="REGSEARCH" Name="Path" />
</Property>

Related

How do I tell wix to install a file in a directory set in a property?

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>

Windows Installer not deleting all files on uninstall

I've seen some similar questions asked on here, but none of the solutions given were very clear or worked for me.
I have an installer (created with WiX) which installs certain files and folders. However, when running the installed application, this creates some folders and copies some files into it. These files and folders are not removed on uninstall.
Edited to Show Code so Far:
This INSTALLDIR property:
<Property Id="INSTALLDIR">
<RegistrySearch Id='Registry' Type='raw' Root='HKLM' Key='Software\$(var.Manufacturer)\$(var.ProductName)' Name='Location' />
</Property>
This Component which should set the install location in the registry:
<Component Id="Registry" Guid="*">
<RegistryKey Root="HKMU" Key="Software\$(var.Manufacturer)\$(var.ProductName)">
<RegistryValue Name="Location"
Type="string"
Value="[INSTALLDIR]"
Action="write"
KeyPath="yes" />
</RegistryKey>
<util:RemoveFolderEx On="uninstall" Property="INSTALLDIR" />
</Component>
This does create a record in the registry with the install location, but I'm not sure how to adapt this code to making note of the 'public' directory and removing it - I don't know where the util:RemoveFolderEx should go either (inside which component)
The clearest tutorial I've seen is this one (except that it does have an apparent error).
Replace this block:
<!--
RemoveFolderEx requires that we "remember" the path for uninstall.
Read the path value and set the APPLICATIONFOLDER property with the value.
-->
<Property Id="APPLICATIONFOLDER">
<RegistrySearch Key="SOFTWARE\$(var.Manufacturer)\$(var.SkuName)" Root="HKLM" Type="raw" Id="APPLICATIONFOLDER_REGSEARCH" Name="Path" />
</Property>
with this one:
<!--
RemoveFolderEx requires that we "remember" the path for uninstall.
Read the path value and set the FOLDERTOREMOVE property with the value.
-->
<Property Id="FOLDERTOREMOVE">
<RegistrySearch Key="SOFTWARE\$(var.Manufacturer)\$(var.SkuName)" Root="HKLM" Type="raw" Id="APPLICATIONFOLDER_REGSEARCH" Name="Path" />
</Property>
and this block:
<util:RemoveFolderEx On="uninstall" Property="APPLICATIONFOLDER" />
with this one:
<util:RemoveFolderEx On="uninstall" Property="FOLDERTOREMOVE" />
and you should have a working test.
The reason for using two different properties is given here and here (among with other places).
If you can derive the path from other values you may have set during installation that Windows Installer will preserve for you, such as ARPINSTALLLOCATION, then you can adjust the above implementation to get what you need without having to create your own registry keys.
Builds on B. Murri's answer:
Example: your application installs new files or folders in 'installdir/public'. These files aren't being deleted as they weren't added by the installer.
First, you need to create a registry value which will store where your public directory is installed. This is in case the user changes the install directory.
<!-- Note that the RegistryValue Value is being set to the 'public' directory ID -->
<DirectoryRef Id='INSTALLDIR'>
<Component Id="RemovePublicDir" Guid="your-guid-here">
<RegistryKey Root="HKCU" Key="Software\$(var.Manufacturer)\$(var.ProductName)">
<RegistryValue Name="Location"
Type="string"
Value="[PUBLIC]"
Action="write"
KeyPath="yes" />
</RegistryKey>
<CreateFolder Directory="PUBLIC"/>
<util:RemoveFolderEx Property="FINDPUBLICDIR" On="uninstall"/>
<RemoveFolder Id="PUBLIC" On="uninstall"/>
</Component>
</DirectoryRef>
You now need to add a property which will search for this registry value later on. Make sure your Root value above matches the one below:
<Property Id="FINDPUBLICDIR">
<RegistrySearch Id='Registry' Type='raw' Root='HKCU' Key='Software\$(var.Manufacturer)\$(var.ProductName)' Name='Location' />
</Property>
Add your manufacturer name and product name to variables, like this:
<?define Manufacturer = "My Company"?>
<?define ProductName = "Test Application"?>
Now make sure you call this Component in your Feature tag:
<Feature Id="FeatureId">
<ComponentRef Id="RemovePublicDir" />
</Feature>

Condition property failure in WIX failing entire MSI job

I am using WIX to create MSI installers for C# services. The MSI does 3 jobs :
a) Copy solution file from bin to a particular location.
b) Create a folder where the service writes it's logs.
c) install the service on the machine if it previously does not exist.
The want these to execute in the similar order. But, when the condition to check if the service is installed fails the previous step does not seem to be executing, i.e, copying and creating steps fail too.
Here is the snippet of the code.
<Directory Id="TARGETDIR" Name="SourceDir">
<!--Creating folder hierarchy for storing project solution files; reference defined in fragments-->
<Directory Id="ProgramFilesFolder" Name="PFiles"/>
<!--Creating folder hierarchy for storing logs; reference defined in fragments-->
<Directory Id="LOGS" Name="Logs"/>
</Directory>
<InstallExecuteSequence>
<LaunchConditions After='AppSearch' />
<Custom Action='CMDInstallService' Before='InstallFinalize'></Custom>
</InstallExecuteSequence>
<Property Id="MYSERVICE">
<RegistrySearch Id="SERVICE_CHECK" Root="HKLM" Name="Install" Type="raw"
Key="SYSTEM\CurrentControlSet\services\Service"/>
</Property>
<Condition Message="Service is already installed on your system">
<![CDATA[Installed OR MYSERVICE]]>
</Condition>
<CustomAction
Id='CMDInstallService' Directory='PROJECT_INSTALL' Execute='deferred' Impersonate='no'
ExeCommand='[SystemFolder]cmd.exe /K "C:\Windows\Microsoft.NET\Framework\v4.0.30319\installutil.exe Service.exe"'
Return ='check'/>
This will not work because the files and folder are not committed before InstallFinalize. To install a service, you should use the following command:
<Component Id="Component_WinService" Directory="Directory_WindowsService" Guid="*">
<File Id="File_WindowsService" KeyPath="yes"
Source="WindowsService.exe" />
<ServiceInstall Id="ServiceInstall_WindowsService"
Type="ownProcess"
Vital="yes"
Name="My Windows service"
Description="Windows service."
Start="auto"
Account="LocalSystem"
ErrorControl="ignore"
Interactive="no"
/>
<ServiceControl Id="ServiceControl_WindowsService"
Start="install"
Stop="both"
Remove="uninstall"
Name="My Windows Service"
Wait="no"
/>
</Component>

WIX - howto use RemoveFolderEx with On="install" / "both"?

I am trying to remove a folder on "install" (and "uninstall"), but the folder is only removed on "uninstall".
Any hints how this can be done?
<Property Id="PACKAGEFOLDER">
<RegistrySearch Root="HKLM" Key="$(var.RegKey)" Type="raw" Id="PKGFOLDER_REGSEARCH" Name="PkgDir" />
</Property>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="PACKAGE" Name="$(var.PkgFolder)">
<Component Id="PackagesFiles" Guid="$(var.FilesGUID)">
<RegistryValue Root="HKLM" Key="$(var.RegKey)" Name="PkgDir" Type="string" Value="[PACKAGE]" KeyPath="yes" />
<util:RemoveFolderEx On="both" Property="PACKAGEFOLDER" />
</Component>
</Directory>
</Directory>
</Directory>
just noticed:
if the RegKey is available in registry before installation starts, it will work:
WixRemoveFoldersEx: Recursing path: C:\Program Files (x86)... for
row: wrf945C37509CA5EEDC2367957D5F072DFF. MSI (s) (94!A8)
[19:17:55:185]: PROPERTY CHANGE: Adding _UNOPACKAGEFOLDER_0 property.
Its value is 'C:\Program Files (x86)... MSI (s) (94:D4)
[19:17:55:185]: Doing action: CostInitialize
but if the RegKey is not in registry, log says:
WixRemoveFoldersEx: Error 0x80070057: Missing folder property:
APPLICATIONFOLDER for row: wrfA308D08284221970F6338358BFB75917
CustomAction WixRemoveFoldersEx returned actual error code 1603 but
will be translated to success due to continue marking MSI (s) (84:50)
[19:29:08:529]: Doing action: CostInitialize
Is it possible to write the RegKey before the Property "PACKAGEFOLDER" is set?
I assume that you have also files in this folder that should be deleted. If there are no (arbitrary) subdirectories containing files it should be straight forward by using the RemoveFile-table of the Windows Installer. As it will only delete the folder if it is empty, add an additional entry that will delete the files in it, e.g.:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="PACKAGE" Name="$(var.PkgFolder)">
<Component Id="PackagesFiles" Guid="$(var.FilesGUID)">
<RegistryValue Root="HKLM" Key="$(var.RegKey)" Name="PkgDir" Type="string" Value="[PACKAGE]" KeyPath="yes" />
<RemoveFile Id="RemovePACKAGEFolderFiles" Directory="PACKAGE" Name="*.*" On="both" />
<RemoveFolder Id="RemovePACKAGEFolder" Directory="PACKAGE" On="both" />
</Component>
</Directory>
</Directory>
</Directory>
This way you don't have to deal with any property setting. If you have other subdirectories with files you would have to repeat this also for these.
Another way would be to create a deferred custom action in the system context that will delete the folder completely, e.g. in VBScript.
If you add
<SetProperty Id="PACKAGEFOLDER" Value="[PACKAGE]" After="CostFinalize" />, you can get the value of package during install. From this article regarding property-setting.

Changing folder on major upgrade

I have a default directory for installation:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="INSTALLFOLDER" Name="$(var.Manufacturer) $(var.ProductName)"></Directory>
</Directory>
</Directory>
During installation, I allow users to change directory. If a user does change directory during major upgrade, do I have to retrieve the directory manually and set INSTALLFOLDER with actual path or is there a way to detect it automatically somehow?
This is not supported by Windows Installer directly; I think you're looking for the "remember property" pattern. The strategy is:
On initial installation, save the value of INSTALLFOLDER to the registry in a well-known location.
When starting an upgrade, retrieve the value from the registry and use it.
The authoring looks like this:
<!-- Retrieve the property from the registry during AppSearch -->
<Property Id='REMEMBERME'>
<RegistrySearch Id='RememberProperty'
Root='HKCU'
Key='SOFTWARE\My Company\My App'
Name='Remembered'
Type='raw' />
</Property>
<!-- Save the value in the registry for future upgrades -->
<Component Directory='INSTALLFOLDER'>
<RegistryValue Root='HKCU'
Key='SOFTWARE\My Company\My App'
Name='Remembered'
Value='[REMEMBERME]'
Type='string' />
</Component>
Rob Mensching's blog post describes this in much more detail.