3

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".

Alex Zhukovskiy
  • 9,565
  • 11
  • 75
  • 151
  • 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 Oct 01 '18 at 18:29
  • @ChristopherPainter well, it's UB to me so anything could happen, including user unable to do that. Maybe i'm wrong with `perMachine` scope. I'm new in wix so that may cause the problem. – Alex Zhukovskiy Oct 02 '18 at 08:58
  • 1
    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 Oct 02 '18 at 11:00
  • Unfortunately, I have no access to the app. I just have to place these files before it runs. I just added its installer in the WIX chain so it gets installed after I copy these files so it works this way. It's a bit hacky, but I didn't find any fancy solution. It's sad there is no regular tools to perform such a simple task. There are a lot of different workarounds, but nothing coming out of the box itself. Thank you for details. – Alex Zhukovskiy Oct 02 '18 at 13:05
  • 1
    [ClickOnce](https://en.wikipedia.org/wiki/ClickOnce) and newer, emerging technologies such as [MSIX](https://www.advancedinstaller.com/msix-windows-package.html) (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 Oct 02 '18 at 14:44
  • 1
    @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 Oct 02 '18 at 14:49
  • 1
    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 Oct 02 '18 at 14:54
  • It's kinda plugins that adds functionallity to the application. It search for specific manifests in this directory and plugs these apps. This is why I'm kinda limited in choosing install location, installation precedence and so on. – Alex Zhukovskiy Oct 02 '18 at 14:56
  • If you can't modify the app design the best I have is to use the ActiveSetup key to trigger a repair at logon for each user. Also put all the per-user stuff in it's own subfeature generally. – Christopher Painter Oct 02 '18 at 15:23
  • @ChristopherPainter Not sure if you have seen [this update on ActiveSetup](https://twitter.com/glytzhkof/status/1015273711086206978). I think it still works, but I don't dare to recommend it anymore. I think I need to update several old answers of mine. What do you think? – Stein Åsmul Oct 02 '18 at 16:46
  • We use it in production on current win 10 builds without issues. If isses started I would crank out my own custom.exe and put it in the Run Key and have it do the same kind of logic to conditionally run repair on cached MSI if conditions warrant it. – Christopher Painter Oct 02 '18 at 17:07
  • FYI I just replied to that tweet pointing out that this is why transitive components exist. https://learn.microsoft.com/en-us/windows/desktop/msi/using-transitive-components – Christopher Painter Oct 02 '18 at 17:12
  • Great, good point on the transitive components - thanks. I guess we still need to watch this ActiveSetup stuff since it is not officially supported and they seem a little ominous about their intent. I wonder what will happen with per-user installs going forward? We will see. – Stein Åsmul Oct 02 '18 at 19:19

2 Answers2

2

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.


Stein Åsmul
  • 39,960
  • 25
  • 91
  • 164
2

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".

pit
  • 21
  • 1