4

I'm trying to create an .msi installer file with electron-builder (version 20.39.0), that can be parameterized during install time. The parameters (e.g. server endpoint) should be written in a file.

Example:
when MsiExec /i "MyProject.msi" SERVER_ENDPOINT=myapp.example.com
then myapp.example.com should appear in a file in the installation dir.

I tried to edit electron-builder's wix template file adding the following to write ${SERVER_ENDPOINT} to server.txt

File C:\...\MyProject\node_modules\electron-builder-lib\templates\msi\template.xml

...
<CustomAction Id="writeConfigFile" Directory="APPLICATIONFOLDER" Execute="commit" Impersonate="yes" ExeCommand="cmd.exe /c &quot;echo ${SERVER_ENDPOINT} > server.txt&quot;" Return="check" />
...
<InstallExecuteSequence>
  ...
  <Custom Action="writeConfigFile" After="InstallFinalize"/>
</InstallExecuteSequence>

Running with

MsiExec /i "MyProject.msi" /L*v Install.log SERVER_ENDPOINT=myapp.example.com

I does not work yet. It installs but does not show writeConfigFile in the log file.

Do you think this is the right approach to make the msi file parameterized?
Or would you recommend another solution?

I also found Orca.exe, to create an MST file, but I would prefer a simple solution, without manual steps.

KeKru
  • 444
  • 3
  • 13
  • Started writing an answer, but I don't know Electron. Isn't there a feature to apply settings on application launch after installation? [Squirrel events](https://github.com/electron/windows-installer#handling-squirrel-events). I might add my answer later, it is about MSI in general and how to set properties via MSI tables. Back to basics, and probably not what you want to know. – Stein Åsmul Apr 02 '19 at 14:25
  • Following use case: We build an MSI file and ship it to our customer. The customer integrates it inside his "msi rollout system" and want to configure the backend server (param or MST). So I need a way to read MSI parameters or create MST files. I would be very interested in your general things about properties in MSI tables – KeKru Apr 02 '19 at 16:46
  • I just dumped what I had written below. Have a quick skim. – Stein Åsmul Apr 02 '19 at 16:48

3 Answers3

6

With the help of Stein Åsmul, this is my current solution:

I took the current WiX template of electron-builder and added an option to write variables to an ini file.

<Property Id="MYSERVER" Value="notDefined"/>
<Property Id="MYSECONDPROPERTY" Value="notDefined"/>
...
<Directory Id="APPLICATIONFOLDER" Name="${installationDirectoryWixName}">
  <Component Id="AddLineTo_AppConfig.ini" Guid="{4171FB60-FDC5-46CF-A4D8-4AE9CADB4BE9}" KeyPath="yes" Feature="ProductFeature">
    <IniFile Id="AddLineTo_AppConfig.ini1" Name="AppConfig.ini" Directory="APPLICATIONFOLDER" Section="AppConfig" Key="Server" Value="&quot;[MYSERVER]&quot;" Action="addLine"/>
    <IniFile Id="AddLineTo_AppConfig.ini2" Name="AppConfig.ini" Directory="APPLICATIONFOLDER" Section="AppConfig" Key="SecondProp" Value="&quot;[MYSECONDPROPERTY]&quot;" Action="addLine"/>
  </Component>
</Directory>

The complete template looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
  <!-- extended Template from https://github.com/electron-userland/electron-builder/blob/7f0ede7182ab6db8efb0cf4bf3cb183be712fb4e/packages/app-builder-lib/templates/msi/template.xml -->
  <!-- https://blogs.msdn.microsoft.com/gremlininthemachine/2006/12/05/msi-wix-and-unicode/ -->
  <Product Id="*" Name="${productName}" UpgradeCode="${upgradeCode}" Version="${version}" Language="1033" Codepage="65001" Manufacturer="${manufacturer}">
    <Package Compressed="yes" InstallerVersion="500"/>

    <Condition Message="Windows 7 and above is required"><![CDATA[Installed OR VersionNT >= 601]]></Condition>

    <!--
    AllowSameVersionUpgrades:
      When set to no (the default), installing a product with the same version and upgrade code (but different product code) is allowed and treated by MSI as two products.
      When set to yes, WiX sets the msidbUpgradeAttributesVersionMaxInclusive attribute, which tells MSI to treat a product with the same version as a major upgrade.

      So, AllowSameVersionUpgrades="yes" allows to build and test MSI with the same version, and previously installed app will be removed.
    -->
    <MajorUpgrade AllowSameVersionUpgrades="yes" DowngradeErrorMessage='A newer version of "[ProductName]" is already installed.'/>
    <MediaTemplate CompressionLevel="${compressionLevel}" EmbedCab="yes"/>

    <Property Id="ApplicationFolderName" Value="${installationDirectoryWixName}"/>
    <Property Id="WixAppFolder" Value="WixPerUserFolder"/>
    <Property Id="MYSERVER" Value="notDefined"/>
    <Property Id="MYSECONDPROPERTY" Value="notDefined"/>

    {{ if (iconPath) { }}
    <Icon Id="icon.ico" SourceFile="${iconPath}"/>
    <Property Id="ARPPRODUCTICON" Value="icon.ico"/>
    {{ } -}}

    {{ if (isAssisted || isRunAfterFinish) { }}
    <CustomAction Id="runAfterFinish" FileKey="mainExecutable" ExeCommand="" Execute="immediate" Impersonate="yes" Return="asyncNoWait"/>
    {{ } -}}

    <Property Id="ALLUSERS" Secure="yes" Value="2"/>
    {{ if (isPerMachine) { }}
    <Property Id="MSIINSTALLPERUSER" Secure="yes"/>
    {{ } else { }}
    <Property Id="MSIINSTALLPERUSER" Secure="yes" Value="1"/>
    {{ } -}}

    {{ if (isAssisted) { }}
    <!-- Check "Run after finish" checkbox by default -->
    <Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
    <Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Run ${productName}"/>

    <UIRef Id="WixUI_Assisted"/>
    {{ } else if (isRunAfterFinish) { }}
    <!-- https://stackoverflow.com/questions/1871531/launch-after-install-with-no-ui -->
    <InstallExecuteSequence>
      <Custom Action="runAfterFinish" After="InstallFinalize"/>
    </InstallExecuteSequence>
    {{ } -}}

    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="${programFilesId}">
        <Directory Id="APPLICATIONFOLDER" Name="${installationDirectoryWixName}">
          <Component Id="AddLineTo_AppConfig.ini" Guid="{4171FB60-FDC5-46CF-A4D8-4AE9CADB4BE9}" KeyPath="yes" Feature="ProductFeature">
            <IniFile Id="AddLineTo_AppConfig.ini1" Name="AppConfig.ini" Directory="APPLICATIONFOLDER" Section="AppConfig" Key="Server" Value="&quot;[MYSERVER]&quot;" Action="addLine"/>
            <IniFile Id="AddLineTo_AppConfig.ini2" Name="AppConfig.ini" Directory="APPLICATIONFOLDER" Section="AppConfig" Key="SecondProp" Value="&quot;[MYSECONDPROPERTY]&quot;" Action="addLine"/>
          </Component>
        </Directory>
      </Directory>

      <!-- Desktop link -->
      {{ if (isCreateDesktopShortcut) { }}
      <Directory Id="DesktopFolder" Name="Desktop"/>
      {{ } -}}

      <!-- Start menu link -->
      {{ if (isCreateStartMenuShortcut) { }}
      <Directory Id="ProgramMenuFolder"/>
      {{ } }}
    </Directory>

    <!-- Files -->
    <Feature Id="ProductFeature" Absent="disallow">
      <ComponentGroupRef Id="ProductComponents"/>
    </Feature>

    {{-dirs}}

    <ComponentGroup Id="ProductComponents" Directory="APPLICATIONFOLDER">
      {{-files}}      
    </ComponentGroup>
  </Product>
</Wix>

Using electron-builder@20.39.0, I create the MSI with

set DEBUG=electron-builder:*
cp template.xml .\node_modules\app-builder-lib\templates\msi\template.xml
electron-builder

And then install the MSI with

MsiExec /i "myapp.msi" MYSERVER=myapp.example.com MYSECONDPROPERTY=helloworld /L*v Install.log

After installation finished, I got the AppConfig.ini in my installdir (%USERPROFILE%\AppData\Local\Programs\MyApp\AppConfig.ini)

[AppConfig]
Server="myapp.example.com"
SecondProp="helloworld"
KeKru
  • 444
  • 3
  • 13
  • Great solution. Do you think this is something you would consider PR'ing to `electron-builder`? Seems like it could be very useful to others. – greenimpala Sep 24 '19 at 07:53
  • Also.. Is there a need for the `Guid` in the template, if so - how is it derived? – greenimpala Sep 24 '19 at 08:01
  • 2
    It's a while ago, but I think the Guid was necessary to make it work. Otherwise there was an error, I think. I did not yet consider to invest time to create a PR on Github. I created an issue for this topic, but there was no response and it became closed. See https://github.com/electron-userland/electron-builder/issues/3784 – KeKru Sep 24 '19 at 13:59
4

Setting MSI Properties

I am unfamiliar with Electron builder. However, in MSI terms you need to specify that the content in the file should be replaced by an MSI Property, and then you need to set the property either in a transform, by command line or in the property table (embedded in MSI).

In fact you can set all three at once, and I am not sure which one would apply :-). Command line certainly overrides the property table, but I am not sure what wins in a battle between a transform and a command line parameter:

Transform (applying transform on command line, actual settings inside the transform file - mst):

msiexec.exe /i "MySetup.msi" TRANSFORMS="MyTransform.mst"

Command Line (setting PUBLIC properties on the command line):

msiexec.exe /i "MySetup.msi" MYPROPERTY="My Value here"

Property Table (the built-in Property table in every MSI can also have a value set):

Property Table


Using MSI Properties

Setting properties is obviously not enough, you have to define where the value goes during installation.

  • If the file is an INI file it is quite easy to set a parameter, since this is a built-in feature of MSI.
  • XML file updates and text file updates are worse because then you rely on third-party solutions or you do it yourself via custom actions (I would not do the latter).

Advanced Installer has very nice features to replace parameters in XML and text files. Installshield also has such features. The open source WiX toolkit also has features to support XML file updates, but it is much more involved than the commercial tools.

With regards to Electron I don't know how it works. But, in either case the central task is to get the MSI to contain a construct such as this:

Advanced Installer

This is from an MSI compiled with Advanced Installer. You see that I have a parameterized value [MYVALUE]. It can be set on the command line since it is an ALL UPPERCASE property - also known as a PUBLIC MSI property. During installation the property in braces will be replaced by the value passed in. Obviously.


Some Links:

Stein Åsmul
  • 39,960
  • 25
  • 91
  • 164
  • Thank you, for this detailed explanation :) The INI way sounds simple and interesting. I just tried this: `MsiExec /i "myApp.msi" SAVEINI=Response.ini SERVER_ENDPOINT=myapp.example.com` But no Response.ini was created. Was it the command you think of? – KeKru Apr 03 '19 at 12:18
  • There is an [IniFile table](https://docs.microsoft.com/en-us/windows/desktop/msi/inifile-table) in an MSI that will update / create an INI file. MSI features merging and rollback of entries (aborted installation). You can even try to modify a finished electron MSI to try it. You need to refer to an existing component for the foreign key Component_, but apart from that you can just add a row. Test on virtual only! Downloading [a trial version of a commercial deployment tool](https://stackoverflow.com/a/50229840/129130) will help you create test MSIs to study quickly without any coding. – Stein Åsmul Apr 03 '19 at 12:24
  • ok, I experimented with it. Using ``` ``` and running with `MsiExec /i "myapp.msi" SERVERADDRESS=myapp.example.com` it wrote me serveraddres=myapp.example.com to the already existing ini file. But It worked only once... I need to experiment more ;) – KeKru Apr 03 '19 at 18:07
  • As far as I can see that should work in general. What happened? – Stein Åsmul Apr 03 '19 at 18:12
  • I placed the myfile.ini inside my source files dir before creating the msi (so it is available in the target dir). For any reason, it changes to read-only during installation and a pop up came up, telling me, that the file is not writable. After manually removing the read-only flag, I pressed "retry" on the dialog. Then the value is written correctly to the file. When I try to install again, then it does not update the value. Don't know why. – KeKru Apr 04 '19 at 14:35
  • Ini files are supposed to not be installed as files, but as key-value pairs defined in the IniFile table. Sorry, I forgot to point this out. We take so much for granted after a few years of doing anything. – Stein Åsmul Apr 04 '19 at 14:40
  • I tried again my very first approach, from my question (see above). ` ... ` running with `MsiExec /i "myapp.msi" SERVERENDPOINT={"url":"my-app.example.com","secondParam":"something"} /L*v Install.log` works nice, also with a JSON input.Hope this will not cause escape character problems – KeKru Apr 04 '19 at 14:43
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/191264/discussion-between-stein-asmul-and-kekru). – Stein Åsmul Apr 04 '19 at 14:46
1

A recent config property was added to the 22.11.x and versions that supports modifying the project.wxs before it is compiled into an MSI.

msiProjectCreated

It takes a function or a string of a function and accepts one parameter for the path to the project.wxs

This should let you better utilize extensions or create a custom ini file based on install time properties.

gritter
  • 11
  • 1