Copying files to different directory during WIX Upgrade - wix

I have been working on a WIX installer for an application and am stuck on a small part of the upgrade: there are two XML configuration files in the install directory that I would like to copy to the new ProgramData directory (since they will not be in ...\Program Files... going forward).
I have tried several solutions, including different brackets/apostrophes/", to no avail. When I compile the WIX installer, I receive several warnings from CANDLE about the Property containing [CommonAppDataProduct] and [PRODUCTNAMEFOLDER], but I am unsure if there needs to be some reference / PropertyRef from those directories defined in the Product.wxs to each custom action.
Snippets of Product.wxs:
<Product Id="*" Name="$(var.ProductName)" Language="0" Version="$(var.Version)" Manufacturer="$(var.Manufacturer)" UpgradeCode="$(var.UpgradeCode)">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<InstallExecuteSequence>
<SelfUnregModules/>
<SelfRegModules/>
<Custom Action="CopyConfigFilesToTemp" After="InstallValidate" />
<Custom Action="LaunchDPInstActionx86" Before="InstallFinalize">NOT Installed OR MaintenanceMode="Modify"</Custom>
<Custom Action="CopyConfigFilesFromTemp" After="LaunchDPInstActionx86" />
</InstallExecuteSequence>
</Product>
...
<Fragment>
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="PRODUCTNAMEFOLDER" Name="$(var.ProductName)"/>
</Directory>
</Fragment>
Custom action CopyConfigFilesToTemp
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<Property Id="QuietExec2" Value='"xcopy.exe [PRODUCTNAMEFOLDER]*.xml" %TEMP% /I /Y'/>
<CustomAction Id="CopyConfigFilesToTemp" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="immediate" Return="ignore"/>
</Fragment>
</Wix>
Custom action CopyConfigFilesFromTemp
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<Property Id="QuietExec3" Value='"xcopy.exe %TEMP%\*.xml [CommonAppDataProduct]" /I /Y /R'/>
<CustomAction Id="CopyConfigFilesFromTemp" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="immediate" Return="ignore"/>
</Fragment>
</Wix>

These custom actions must be deferred custom actions because they are trying to modify something in the program files path which requires elevated privileges. The only portion of the install that has elevated privileges is the server server portion of the install when it is copying over files to the install directory.
Custom actions that are deferred have a special requirement on them if you intend to use one of the msi properties within the action.
As per microsoft's website on Deferred Actions
Because the installation script can be executed outside of the installation session in which it was written, the session may no longer exist during execution of the installation script. This means that the original session handle and property data set during the installation sequence is not available to a deferred execution custom action.
This essentially means that you need to put the values of your properties in a special location that is guaranteed to exist while the elevated portion of the install is happening and must be formatted in such a way that it knows exactly where to look to get that value.
So to run these actions they must be scheduled between InstallInitialize and InstallFinalize and must also be able to grab the property values from a special location.
To use a deferred custom action you just need to change the execution to deferred however, we must add this special property with a formatted value so that you can get the values of QuietExec and QuietExec2 from within your custom actions.
You need to declare a custom action as follows for each of the deferred actions:
<CustomAction Id="CustomActionNameHere" Property="CopyConfigFilesToTemp" Value="QuietExec2="xcopy.exe [PRODUCTNAMEFOLDER]*.xml" %TEMP% /I /Y" />
<CustomAction Id="CustomActionNameHere" Property="CopyConfigFilesFromTemp" Value="QuietExec3="xcopy.exe %TEMP%\*.xml [CommonAppDataProduct]" /I /Y /R" />
Generally I call these the same name as the custom action they're setting a property for with "Set" prefixed to the name. IE: SetCopyConfigFilesFromTemp and SetCopyConfigFilesToTemp so that they are easy to locate.
You also must schedule these custom actions and you can't go wrong scheduling them before the action they set properties for and match the conditions.
<Custom Action="SetCopyConfigFilesToTemp" Before="CopyConfigFilesToTemp">
<Custom Action="SetCopyConfigFilesFromTemp" Before="CopyConfigFilesFromTemp">
In the custom action code, you need to use session.CustomActionData["PropertyName"] instead of just session["PropertyName"]
I would also consider the situations when you want to run these copy commands since I don't think you want to do them when uninstalling the product or if its a fresh install and not an upgrade.

Related

Registering SharpShell extension using SRM via WIX installer

Firstly I should clarify that I am a novice and have been struggling to understand the WIX formatting, but by cobbling together examples found on-line, I now have the files installing fine so I next need to register my DLL.
I used the example here as a starting point: How to deploy a SharpShell-based shell extension via WiX? but it seems that the SharpShell tool srm.exe may not be getting called at installation.
If I manually call srm.exe as follows, it works as hoped i.e. the DLL is registered and my shell extension works.
srm install MyExtension.dll -codebase
I can also see that the registration has been successful via the Server Manager application that comes with SharpShell.
I can also manually uninstall with the following - not that this is particularly relevant to my problem but it at least confirms that the manual methods work:
srm uninstall MyExtension.dll
Here is a fragment of my WXS file. When I run the resultant MSI, the files are installed but the DLL is not being registered; confirmed via SharpShell's Server Manager. Where am I going wrong?
</Component>
<Component Id="SRMexe" Guid="C17BB61F-6471-46F9-AA87-2D14D2456632">
<File Id='srm' Name='srm.exe' DiskId='1' Source='..\MyExtension\packages\SharpShellTools.2.2.0.0\lib\srm.exe' KeyPath='yes'>
</File>
</Component>
<!-- TODO: Insert files, registry keys, and other resources here. -->
<!-- </Component> -->
</ComponentGroup>
</Fragment>
<Fragment>
<CustomAction Id="InstallShell" FileKey="srm"
ExeCommand='install "[INSTALLFOLDER]\MyExtension.dll" -codebase'
Execute="deferred" Return="check" Impersonate="no" />
<CustomAction Id="UninstallShell" FileKey="srm"
ExeCommand='uninstall "[INSTALLFOLDER]\MyExtension.dll"'
Execute="deferred" Return="check" Impersonate="no" />
<InstallExecuteSequence>
<Custom Action="InstallShell"
After="InstallFiles">
NOT Installed
</Custom>
<Custom Action="UninstallShell"
Before="RemoveFiles">
(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")
</Custom>
</InstallExecuteSequence>
</Fragment>
It doesn't look like you have any references to the Fragment with the CustomAction definitions so they are not linked into your final output MSI.
Add a CustomActionRef from your Product element to create the reference.

WIX setup perUser or perMachine depends from current user

I'm new in WIX. I neet to set Installscope to perUser if current user is in local admin groups (not runAsAdmin) or perMachine otherwise
I know about ALLUSERS="2" and MSIINSTALLPERUSER="1|{}", but I can't to set them dynamically BEFORE wix decide how it will be runned - as perUser or perMachine
I used CustomAction to detect if current user is in Administrators group and to set ALLUSERS and MSIINSTALLPERUSER, put in in InstallExecuteSequence, but this action always runs AFTER wix decision
How can I implement dynamically set this properties of InstallScope depend by current user and admin group?
My sample WIX
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="SetupProject1" Language="1033" Version="1.0.0.0" Manufacturer="rrr" UpgradeCode="3664d946-e6b5-468f-8154-0506308d85ab">
<Package InstallerVersion="200" Compressed="yes" >
</Package>
<Binary Id="CustomActionBinary" SourceFile="$(var.CustomAction1.TargetDir)$(var.CustomAction1.TargetName).CA.dll"/>
<CustomAction Id="SampleAction" BinaryKey="CustomActionBinary" DllEntry="CustomAction1" Execute="immediate" Return="check"/>
<InstallExecuteSequence>
<Custom Action='SampleAction' Before='SetProps'/>
</InstallExecuteSequence>
</Product>
</Wix>
Inside SetProps ALLUSERS and MSIINSTALLPERUSER are set to 2 and 1
But installer is run with UAC call (due to perMachine)
The question is closed.
I use Custom Action in InstallUISequence.

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.

WIX Toolset - uninstalling .exe file

I wrote Wix Setup program, that wraps PyTangoArchiving-7.3.2.win-amd64.exe file into into PyTangoArchivingInstaller.msi package.
The installation procces is correct I think, in control pannel -> Programs I can see two additional programs installed:
PyTangoArchiving-7.3.2.win-amd64.exe - the program I wanted to install and
my wrapper - PyTangoArchivingInstaller.
But when I try to uninstall the application, only wrapper is being uninstalled and whole program (PyTangoArchiving-7.3.2.win-amd64.exe ) is still there, I have to uninstall it manually from Control Panel.
Can sb help me with this?
Here is my code:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="PyTangoArchivingInstaller" Language="1033" Version="1.0.0.0" Manufacturer="test" UpgradeCode="PUT-GUID-HERE">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<WixVariable Id="WixUILicenseRtf" Value="$(var.ProjectDir)\License.rtf"/>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLLOCATION"/>
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate EmbedCab="yes"/>
<UIRef Id="WixUI_InstallDir"/>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id='TempFolder'>
<Directory Id="INSTALLLOCATION" Name="MyApp" >
<Component Id='MyComponent' Guid='*'>
<File Id="mysetup_exe" Source="PyTangoArchiving-7.3.2.win-amd64.exe" />
</Component>
</Directory>
</Directory>
</Directory>
<Feature Id="MainApplication" Title="Main Application" Level="1">
<ComponentRef Id="MyComponent" />
</Feature>
<CustomAction Id="run_setup" FileKey="mysetup_exe" ExeCommand="/SP- /SILENT /SUPPRESSMSGBOXES /LANG=English
/NOCANCEL /DIR="[INSTALLLOCATION]""
Execute="deferred" Impersonate="no"
Return="check" />
<InstallExecuteSequence>
<Custom Action="run_setup" Sequence='5401'>NOT Installed</Custom>
</InstallExecuteSequence>
</Product>
</Wix>
As a general comment, you shouldn't usually be running another exe from inside your MSI, especially if it is an install that shows up in add/remove programs. You should instead use a bootstrapper to chain together multiple installs and this is the preferred way to do what you are trying to do.
Since you run your setup_exe from a custom action, you also need a corresponding custom action to uninstall it.
It would basically be the same format as the one you use to install except with the uninstall command line arguments, whatever they may be.
You will need to schedule your uninstall custom action before the "RemoveFiles" standard action so that the setup exe still exists when you try to run the custom action. You should also condition this custom action with REMOVE~="ALL" AND NOT UPGRADINGPRODUCTCODE.
This approach will run into problems when you try to support upgrades with/without upgrades to the packaged exe install. It is highly suggested you use either the wix burn bootstrapper (bit of a learning curve) or one of the other available bootstrappers for multiple install installations. These would more robustly and correctly support two installs along with upgrades and uninstalls.

Wix IniFile error on Uninstall

I'm using wix IniFile element to edit ini file on install. When I try to uninstall i get error 2343:
Начало действия 12:37:47: RemoveIniValues.
MSI (s) (7C:BC) [12:37:47:264]: Note: 1: 2343
DEBUG: Error 2343: Specified path is empty.
My wxs with ini editing is as follows:
<?xml version="1.0"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<Property Id="miktex_config_path" Hidden="yes"/>
<SetProperty Id="miktex_config_path" Value="[INSTALLLOCATION]miktex\miktex\config" After="InstallFiles" Sequence="execute">Not Installed</SetProperty>
<DirectoryRef Id="dirC060208F28327102C690BFF33C18B6C4">
<Component Id="miktex_config_file" Guid="4B9400C2-7EEF-4233-881D-5DFE6F80BB5B">
<CreateFolder />
<IniFile Directory="miktex_config_path" Id="common_install_path" Name="miktexstartup.ini" Action="addLine" Key="CommonInstall" Value="[INSTALLLOCATION]miktex" Section="Paths"/>
<IniFile Directory="miktex_config_path" Id="common_data_path" Name="miktexstartup.ini" Action="addLine" Key="CommonData" Value="[CommonAppDataFolder]miktex_data" Section="Paths"/>
<Condition><![CDATA[Not Installed]]></Condition>
</Component>
</DirectoryRef>
</Fragment>
</Wix>
Why doesn't uninstaller take into account my condition element?
How can I force installer to ignore ini file editing during uninstall?
The condition is against the component, however the action that is running is RemoveIniValue. You can surpress this action by overriding InstallExecuteSequence as follows:
<InstallExecuteSequence>
<RemoveIniValues Suppress="yes" />
</InstallExecuteSequence>
It depends on the task you pursue. If you need to prevent INI value removal at all then suppressing the RemoveIniValues in the InstallExecuteSequence is the way to go as suggested by David Martin. However, that suppression (NO MATTER what you put as a condition for that), prevents the INI entry removals and for install and for uninstall (again, condition doesn't work, don't even try to put condition to suppress on uninstall only). But if you need to allow INI entry/tag removals during the install (IniFile declaration with remove action) but at the same time you need to prevent the rest of your INI settings from removal during the uninstall, then simply mark the Component where you keep your IniFile declarations as Permanent="yes". In that case your INI settings will not be removed on uninstall and your declarations to remove particular INI settings on install will work, And forget about the RemoveIniValues suppression at all.