2
votes

I'm writing a dapp for ethereum client for windows. In order to make dapp available for user I have to place specific files in the folder in appdata. So I just should place some files in %appdata%\Parity\Ethereum\dapps\mydappname. But I always get weird errors with WIX, the last one is

Error 93 ICE64: The directory dapp is in the user profile but is not listed in the RemoveFile table.

I have following myapp.wixproj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" InitialTargets="EnsureWixToolsetInstalled" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
    <ProductVersion>3.10</ProductVersion>
    <ProjectGuid>8beed2e4-8784-4cb5-8648-cdf55c5defe6</ProjectGuid>
    <SchemaVersion>2.0</SchemaVersion>
    <OutputName>FairsDapp</OutputName>
    <OutputType>Package</OutputType>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
    <OutputPath>bin\$(Configuration)\</OutputPath>
    <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
    <DefineConstants>Debug</DefineConstants>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
    <OutputPath>bin\$(Configuration)\</OutputPath>
    <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
  </PropertyGroup>
  <PropertyGroup>
    <DefineConstants>HarvestPath=dapp</DefineConstants>
  </PropertyGroup>      
  <ItemGroup>
    <Compile Include="Product.wxs" />
    <Compile Include="Dapp.wxs" />
  </ItemGroup>
  <Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' " />
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets" Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets') " />
  <Target Name="EnsureWixToolsetInstalled" Condition=" '$(WixTargetsImported)' != 'true' ">
    <Error Text="The WiX Toolset v3.11 (or newer) build tools must be installed to build this project. To download the WiX Toolset, see http://wixtoolset.org/releases/" />
  </Target>
  <Target Name="BeforeBuild">
  <HeatDirectory
    DirectoryRefId="INSTALLFOLDER"
    OutputFile="Dapp.wxs"
    Directory="dapp"
    ComponentGroupName="SourceComponentGroup"
    ToolPath="$(WixToolPath)"
    PreprocessorVariable="var.HarvestPath"
    AutogenerateGuids="true" />
  </Target>
</Project>

And following wxs:

<?xml version="1.0" encoding="UTF-8"?>
<Wix
    xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <Product Id="*" Name="Dapp" Language="1049" Version="1.0.0.3" Manufacturer="Me" UpgradeCode="fb09dccc-6606-4b5d-8dcb-28146c28663a" Codepage="1251">
        <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
        <MediaTemplate EmbedCab="yes"/>
        <Feature Id="ProductFeature" Title="FDapp" Level="1">
            <ComponentGroupRef Id="ProductComponents" />
        </Feature>
    </Product>
    <Fragment>
        <Directory Id="TARGETDIR" Name="SourceDir">
            <Directory Id="AppDataFolder">
                <Directory Id="Parity">
                    <Directory Id="dapps">
                        <Directory Id="INSTALLFOLDER" Name="F2"></Directory>
                    </Directory>
                </Directory>
            </Directory>
        </Directory>
    </Fragment>
    <Fragment>
        <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"></ComponentGroup>
    </Fragment>
</Wix>

I found that I have to use heat task to create a correct files tree, but now I'm stuck with simple task "copy these files on user machine".

2
This will install the files into the user profile of the user who installed the app. What do you expect to happen when someone else logs in and wants to use the app?Christopher Painter
Usually I recommend per-machine scope and to install the files to CommonAppDataFolder. Then have the application replicate files to the user profile on first run of the application per the needs of the application.Christopher Painter
ClickOnce and newer, emerging technologies such as MSIX (successor to AppX) install per-user, but I can't really advice you much about them as of now. ClickOnce is old now. Just in case if you want to have a quick read on these technologies. Also have a quick look at the long answer I link to below for how to populate per-user data with MSI. Please note that ActiveSetup is now unwise to use due to potential changes in Windows 10. I need to update that answer.Stein Åsmul
@SteinÅsmul great links. Tnank you. I didn't mark your answer because I didn't try it yet, though I upvoted it. If you have any further advices feel free to share them, I need as much information as possible. I never thought placing files in appdata may be that hard :)Alex Zhukovskiy
It might confuse more than help, but have a look, yes. Per-user files and settings has always been hard, and it might be the focus of newer technologies to deploy only per-user - which to me is very bad for many technical reasons - most significantly for serviceability and upgrades. Maybe there are better plans than we think? With Internet usage, online repositories could make things more "self-updating"? We will have to wait and see. Meanwhile we have to find a way to roll with what we have. Are these "addin" sort of files, or is there a real application to launch?Stein Åsmul

2 Answers

2
votes

I am unfamiliar with this type of application (dapp for ethereum client for windows) - so the advice has to be generic I am afraid.

Per-User Files & Registry Settings: In general deploying files to the user profile and HKCU settings is difficult with MSI. As Chris points out it basically just works for the user installing the MSI, unless you actively add constructs to get files copied to all user profiles, and even then it is sort of clunky.

Approaches: I wrote a long answer a long time ago on the subject: Create folder and file on Current user profile, from Admin Profile (long and elaborate, but without any automagic solutions).

Preferred Approach: Before getting involved in too much complexity, the easiest approach is generally to use your application to copy the userprofile files in place for every user on first launch - instead of using the setup to install user-specific files.

This requires that there is a separate application executable to launch, generally via its own shortcut - which it might not be? It generally does not work for addins for example.

  • Approach 1: Install template files per-machine and then copy them to each user's userprofile on application launch.

  • Approach 2: Alternatively I like to download files directly from a server or database and put in the userprofile - also on first launch.

Apply Updates?: There are ways to ensure that you can re-copy files if there are changes to your templates as described here: http://forum.installsite.net/index.php?showtopic=21552 (Feb 2019 converted to WayBack Machine link).


Errors: The specific problem you report has to do with the need for a per-user registry key path and a RemoveFolder entry for all folders targeting userprofile locations:

  <Directory Id="AppDataFolder">
    <Directory Id="Parity">
      <Directory Id="dapps">
        <Directory Id="INSTALLFOLDER" Name="F2">
          <Component Guid="{77777777-7777-7777-7777-7777777777DD}" Feature="MainApplication">

            <RegistryKey Root="HKCU" Key="Software\TestManufacturer\TestApp">
              <RegistryValue Name="Flag" Value="1" Type="string" KeyPath="yes" />
            </RegistryKey>

            <RemoveFolder Id="RemoveINSTALLFOLDER" Directory="INSTALLFOLDER" On="uninstall" />
            <RemoveFolder Id="RemoveParity" Directory="Parity" On="uninstall" />
            <RemoveFolder Id="Removedapps" Directory="dapps" On="uninstall" />

            <File Source="Test.exe" />
          </Component>
        </Directory>
      </Directory>

This is just one of MSI's conventions and quirks. As already stated, install all files per-machine and copy them to the userprofile with the application instead. It will dis-entangle them from any setup interference in the future. Then you do not need to deal with these RemoveFolder issues.


0
votes

I've found and verified that the solution is working when adding a CustomAction which uses PowerShell to iterate over all user profiles and copies a "template appdata folder" to them. The template folder is shipped and installed by the WIX MSI installer to [ProductDir]appdata, e.g "C:\Program Files (x86)\myApplication\appdata".

Here's what I've put to as a CustomAction to the WXS WIX script to achieve this:

<CustomAction 
    Id="ExecPsXcopyCmd" 
    Directory='ProductDir' 
    Execute='deferred' 
    Impersonate='no' 
    ExeCommand='[SystemFolder]WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy "ByPass" -Command "$productDir = &apos;[ProductDir]&apos;; $userRoot = (Split-Path $Env:PUBLIC); Foreach ($profile in Get-ChildItem -Path $userRoot -Directory -Exclude &apos;Public&apos; -Force -ErrorAction SilentlyContinue) { $srcPath = Join-Path -Path $productDir -ChildPath &apos;appdata&apos;; $dstPath = Join-Path -Path $profile.Fullname -ChildPath &apos;AppData\Roaming\myApplication&apos;; xcopy /S /E /I /R /H /Y $srcPath $dstPath}"'
    Return='check'/>

<InstallExecuteSequence>
    <RemoveExistingProducts Before="InstallInitialize" />
    <Custom Action="ExecPsXcopyCmd" After="WriteRegistryValues">NOT REMOVE</Custom>
    <LaunchConditions After="AppSearch"/>
</InstallExecuteSequence>

This assumes your product installation directory is called "ProductDir".