Search code examples
wixwindows-installercustom-action

Copying files to different directory during WIX Upgrade


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>

Solution

  • 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=&quot;xcopy.exe [PRODUCTNAMEFOLDER]*.xml&quot; %TEMP% /I /Y" />
    <CustomAction Id="CustomActionNameHere" Property="CopyConfigFilesFromTemp" Value="QuietExec3=&quot;xcopy.exe %TEMP%\*.xml [CommonAppDataProduct]&quot; /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.