[wix-users] Custom Action to Create and Install a File?

Russell Haley russ.haley at gmail.com
Sat Mar 21 22:21:15 PDT 2020


On Wed, Feb 19, 2020 at 10:33 AM Edwin Castro <egcastr at gmail.com> wrote:

> I think you should start with designing your deferred custom action. It
> will need to read all data it needs from CustomActionData. Begin in the
> deferred custom action so you can define all the bits of data you need and
> how they will be marshalled through CustomActionData. I think the managed
> API provides a mechanism where the CustomActionData is effectively
> marshalled as key-value pairs in a dictionary. It's been nearly a decade
> since I've worked on managed custom actions so bare with me.
>
> In your WiX source, you will want to declare your deferred custom action
> using the CustomAction element but you may not want to schedule it in
> InstallExecuteSequence. You may want to let your immediate custom action do
> the scheduling. Your immediate custom action would be responsible for
> making all decisions and writing all the required data into
> CustomActionData which is "passed" to the deferred custom action when the
> immediate custom action schedules the deferred custom action to run. If you
> find out you need a rollback custom action, then the immediate custom
> action is the right place to make decisions about rollback data that may
> need to be passed to the rollback custom action through CustomActionData.
> Depending on your needs you make need to assemble separate CustomActionData
> for your deferred custom action and your rollback custom action. Remember
> rollback custom actions are scheduled before (first) deferred custom
> actions.
>
> If you do not have a lot of decision making the a "full blown" immediate
> custom action might be overkill. In some cases, you can use simple
> SetProperty custom actions to directly set the CustomActionData for your
> rollback and deferred custom actions. In that case, you use something like
> SetProperty to set a property with the same name as the target
> deferred/rollback custom action. That property becomes the CustomActionData
> for the deferred/rollback custom action. If you format the contents just
> right, then the managed API can properly parse the CustomActionData and
> provide it to you through the managed API. In a case like this you would
> explicitly schedule your deferred/rollback custom actions in
> InstallExecuteSequence since there is no immediate custom action
> responsible for doing that work.
>
> Let me try a concrete example for the SetProperty approach. NOTE: I have
> not checked this code and make no guarantees. This is meant as a starting
> point so you can do some research and fix it for your own purposes.
>
> <Fragment>
>     <Property Id="LuaVersion2" Value="5.3" />
>     <Property Id="LuaRocksConfigFileName" Value="config-[LuaVersion2].lua"
> />
>
>     <!-- Construct required data for deferred custom action. These are not
> strictly necessary as they can be used inline in the SetProperty for
> Configure.LuaRocks but I think this easier to understand. -->
>     <SetProperty Id="LuaRocksConfigPath"
> Value="[LUAROCKS_INSTALLLOCATION]\[LuaRocksConfigFileName]"
> Before="SetConfigure.LuaRocks" />
>     <SetProperty Id="LuaRocksRootPath" Value="[LUAROCKS_INSTALLLOCATION]"
> Before="SetConfigure.LuaRocks" />
>     <SetProperty Id="LuaRootPath" Value="[LUA_INSTALLLOCATION]"
> Before="SetConfigure.LuaRocks" />
>     <SetProperty Id="ToolsPath" Value="[LLVMMINGW_INSTALLLOCATION]"
> Before="SetConfigure.LuaRocks" />
>
>     <!-- Set "CustomActionData" for Configure.LuaRocks deferred custom
> action. I think for managed custom actions the format is
> key=value;key=value -->
>     <SetProperty Id="Configure.LuaRocks"
> Value="LuaRocksConfigPath=[LuaRocksConfigPath];LuaRocksRootPath=[LuaRocksRootPath];LuaRootPath=[LuaRootPath];ToolsPath=[ToolsPath]"
> />
>
>     <!-- The deferred custom action -->
>     <CustomAction Id="Configure.LuaRocks"
> BinaryKey="WinLua.Installer.CustomAction" DllEntry="ConfigureLuaRocks"
> Execute="deferred" Return="Check" />
>
>     <!-- Schedule the deferred custom action between InstallInitialize and
> InstallFinalize -->
>     <InstallExecuteSequence>
>         <!-- You may need a condition to make sure this does not run
> during uninstall -->
>         <Custom Action="Configure.LuaRocks" After="InstallFiles" />
>     </InstallExecuteSequence>
> </Fragment>
>
> If you have a need for a rollback custom action then it must be scheduled
> before Configure.LuaRocks and any data it needs will need to be set in a
> similar fashion. If you create a back up file in the deferred custom action
> that is meant to be used in the rollback custom action for rollback
> purposes, then you will likely want to pass the back up file path to the
> deferred custom action and rollback custom action through CustomActionData.
> You would also want a commit custom action to clean up the back up file
> (during rollback the rollback custom action would be responsible for
> cleaning up the back up file.
>
> The managed code for the deferred custom action just reads
> LuaRocksConfigPath, LuaRocksRootPath, LuaRootPath and ToolsPath from
> session.CustomActionData (?) or something similar and uses the values to
> write the config file. Any other custom actions you need would work
> similarly but do whatever work is appropriate. I'd start out with the
> deferred custom action first but please do consider (and test) rollback
> scenarios for install, repair, uninstall and upgrade!
>
> Adding a row to the RemoveFile table is a little more involved. You'd want
> an immediate custom action that runs during uninstall and is scheduled
> before RemoveFiles. This immediate custom action needs to insert a row into
> the RemoveFile table so it will use a SQL query to do so but since you need
> to provide dynamic data you will use a Record to provide that data when the
> query is executed. The details here are a little more involved and you must
> understand the RemoveFile table format. All that said, you may not need to
> do all that. Perhaps it is as simple as adding a RemoveFile element to a
> component associated with the config file. Something like
>
> <Directory Id="luarocksbin" Name="bin">
>     <Component Id="..." Guid="...">
>         <File Id="..." Source="...\luarocks.exe" KeyPath="yes" />
>         <RemoveFile Id="RemoveConfigFile"
> Property="LUAROCKS_INSTALLLOCATION" Name="[LuaRocksConfigFileName]"
> On="uninstall" />
>     </Component>
> </Directory>
>
> I'm less sure about this and it might have some odd interactions with your
> use of merge modules. BUT if you can do something like that then you don't
> need an immediate custom action to dynamically add a row to the RemoveFile
> table which would be a win.
>
Hello again Edwin, et all,

I've done a fair bit of testing and the install custom action is working to
expectation. I appreciate all your help getting through that. My next, and
hopefully final step(s) are custom actions to remove two things: the config
file created by the install custom action, and a directory of components
(called a Rocktree) that may or may not have been created by the user.
These components live in the installation directory but are not under the
control of the installer; they've been added after the fact.

My preference is to have a checkbox asking if the config file and rocktree
should be removed. I *think* that can be done with the
WIXUI_EXITDIALOGOPTIONALCHECKBOX,
but I don't know if that checkbox can be used just for an uninstall
sequence.

I have Two problems:
1) I noted that the SetProperty calls for the paths are always before
"SetConfigure.LuaRocks". How would I get the properties to set before both
the SetConfigure.LuaRocks and the new SetRemove.ConfigAndRocktree.LuaRocks?

2) My Configure.LuaRocks custom action is running on the remove step
*after* the Remove.ConfigAndRocktree.LuaRocks.

 Anyway, this is what I've roughed in so far (I've disabled the exit dialog
checkbox for the moment):

...
    <!--Properties for the LuaRocks configuraiton file that we generate
with Configure.LuaRocks custom Action-->
    <SetProperty Id="LuaVersion" Value="$(var.LuaVersion)"
Before="SetConfigure.LuaRocks" Sequence="execute" />
    <SetProperty Id="LuaRootPath" Value="[LUA_INSTALLLOCATION]"
Before="SetConfigure.LuaRocks" Sequence="execute" />

    <SetProperty Id="LuaRocksRootPath" Value="[LUAROCKS_INSTALLLOCATION]"
Before="SetConfigure.LuaRocks" Sequence="execute">
      <![CDATA[Installed OR ((&LuaRocks=3) AND NOT(!LuaRocks=3)) OR
((&LuaRocks=2) AND (!LuaRocks=3))]]>
    </SetProperty>

    <!--Don't set this property if the LLVM compiler is not being
installed-->
    <SetProperty Id="ToolsPath" Value="[LLVMMINGW_INSTALLLOCATION]"
Before="SetConfigure.LuaRocks" Sequence="execute">
      <![CDATA[Installed OR ((&LLVM_MinGW=3) AND NOT(!LLVM_MinGW=3)) OR
((&LLVM_MinGW=2) AND (!LLVM_MinGW=3))]]>
    </SetProperty>
    <SetProperty Id="Configure.LuaRocks"
Value="LuaVersion=[LuaVersion];LuaRocksRootPath=[LuaRocksRootPath];LuaRootPath=[LuaRootPath];ToolsPath=[ToolsPath]"
Before="Configure.LuaRocks" Sequence="execute" />
    <SetProperty Id="Remove.ConfigAndRocktree.LuaRocks"
Value="LuaVersion=[LuaVersion];LuaRocksRootPath=[LuaRocksRootPath];LuaRootPath=[LuaRootPath];ToolsPath=[ToolsPath]"
Before="Remove.ConfigAndRocktree.LuaRocks" Sequence="execute" />

    <!--<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Remove
LuaRocks Options"  />-->

    <!-- The deferred custom action -->
    <Binary Id='WinLua.Installer.CustomAction'
SourceFile='..\Winlua.Installer.CustomAction\bin\Release\Winlua.Installer.CustomAction.CA.dll'/>
    <CustomAction Id="Configure.LuaRocks"
BinaryKey="WinLua.Installer.CustomAction" DllEntry="ConfigureLuaRocks"
Impersonate="no" Execute="deferred" Return="check" />
    <CustomAction Id="Remove.ConfigAndRocktree.LuaRocks"
 BinaryKey="WinLua.Installer.CustomAction" DllEntry="RemoveLuaRocksConfig"
Impersonate="no" Execute="deferred" Return="check" />
      <!-- Schedule the deferred custom action between InstallInitialize
and InstallFinalize -->
      <InstallExecuteSequence>
        <!-- Only run this when the LuaRocks feature is installed-->
        <Custom Action="Configure.LuaRocks" After="InstallFiles">
          <![CDATA[Installed OR (&LuaRocks=3) AND NOT(!LuaRocks=3)]]>
        </Custom>
        <!--Option to Luarocks configuration file and Rocktree-->
        <Custom Action="Remove.ConfigAndRocktree.LuaRocks"
Before="RemoveFiles">
          <!--<![CDATA[REMOVE and (WIXUI_EXITDIALOGOPTIONALCHECKBOX =
1)]]>-->
          <![CDATA[REMOVE]]>
        </Custom>
    </InstallExecuteSequence>
...

In the custom deferred action, I'm hoping to forgo the SQL at this point
and just delete the files in C#, including the left over WinLua
directories. Thoughts?

As usual, all the code is on github. The installer files are here:
https://github.com/WinLua/WinLua-Source-Code/tree/master/WinLua-Release3/WinLua-Installer.
The above WIX code is in
https://github.com/WinLua/WinLua-Source-Code/blob/master/WinLua-Release3/WinLua-Installer/WinLua-Installer/Code/Product.wxs

Cheers!
Russ

>
> Lots to think about and lots to try. Let me know how it goes and I'll try
> to help more.
>
> --
> Edwin G. Castro
>
>
> On Tue, Feb 18, 2020 at 11:17 PM Russell Haley <russ.haley at gmail.com>
> wrote:
>
>>
>> On Tue, Feb 18, 2020 at 9:51 AM Edwin Castro <egcastr at gmail.com> wrote:
>>
>>> Am I reading this correctly? It appears the WriteConfigFile method
>>> creates a file with static content. I am less sure about what the
>>> RunConfigScript method actually does.
>>>
>>>
>>> https://github.com/WinLua/WinLua-Source-Code/blob/master/WinLua-Release3/WinLua-Installer/Winlua.Installer.CustomAction/CustomAction.cs
>>>
>>
>> Hi Edwin, thanks so much for responding! The current code is static
>> because I'm testing, but the real file contains paths that need to come
>> from the installer. WriteConfigFile was my attempt to do that.
>> RunConfigScript is an existing hack to run an external script that creates
>> the file I need (by elevating privilege). I don't like that; I want to
>> internalize the process in the installer, with hopes to have a "modify"
>> option to re-generate the file (not yet though, future feature).
>>
>>
>>>
>>>
>>> If the file content is static then would it be possible to author the
>>> config file with a static filename, something like config-version.lua, and
>>> use an immediate custom action to update just the FileName column in the
>>> appropriate row in the File table? Something like that would get you all
>>> the content in the right places and then you just fix up the one bit of
>>> data that varies by version at runtime.
>>>
>>> If the content for the config file really is dynamic, then could you
>>> just generate the file at install time as you already do and use an
>>> immediate custom action at uninstall time to add a row to the RemoveFile
>>> table to remove the config file?
>>>
>> Yes, I'd be satisfied with the second suggestion (dynamic) if I can make
>> it work. Thank you for the background information on Immediate/Deferred
>> actions. If I understand, it seems that I need to write some properties
>> during the immediate action and then in a deferred action, create the file
>> based on the properties?
>> I found something here:
>> https://vadmyst.blogspot.com/2006/05/deferred-custom-actions-with-wix.html
>> but I want to set those properties in code as well?
>>
>> I don't know anything about adding a row to the RemoveFiles table yet.
>> Would that be done via SQL (or via a Record)?
>>
>> This is an innocuous question: Is this easier than writing a new file to
>> the database and letting the installer write the file (assuming that's even
>> going to work)? I suppose I would still need to manually remove the file?
>>
>> Your time is greatly appreciated. This has been hanging over my head for
>> years!
>>
>> Regards,
>> Russ
>>
>>>
>>> --
>>> Edwin G. Castro
>>>
>>>
>>> On Tue, Feb 18, 2020 at 9:07 AM Edwin Castro <egcastr at gmail.com> wrote:
>>>
>>>> Hi Russell,
>>>>
>>>> I haven't quite followed exactly what you are trying to accomplish but
>>>> I think I can give you a little context.
>>>>
>>>> The wcautil library is indeed a C++ library. It is a small wrapper
>>>> library over most of the MSI API that you could read about on MSDN. I think
>>>> the C# API you are using already provides all the goodies you need to do
>>>> the transformations you want.
>>>>
>>>> The concept of a semi custom action is that you write an immediate mode
>>>> custom action to change the msi database temporarily to add entries to msi
>>>> tables. Those tables are then processed normally by the Windows Installer
>>>> engine to do the "real" work. An example would be to dynamically add files
>>>> to the RemoveFile table to delete files generated by the installed
>>>> application at runtime. WiX has this custom action already baked in so you
>>>> would not need to write it yourself but it is a good example.
>>>>
>>>> Immediate custom actions run in user context and cannot change
>>>> protected system resources. Deferred custom actions can change protected
>>>> system resources but they cannot change the msi database nor read/write
>>>> most properties. Rollback custom actions are like deferred custom actions
>>>> but they are used to "undo" actions taken by a deferred custom action.
>>>> There are also commit custom actions but I have not used them yet. The main
>>>> point is that custom actions run at different times and have different
>>>> responsibilities. You'll need to understand that context first before you
>>>> go writing custom actions. See the following for more information.
>>>>
>>>>
>>>> http://lists.wixtoolset.org/pipermail/wix-users-wixtoolset.org/2018-May/006932.html
>>>>
>>>> The Saw Tooth diagram article is missing but it can be found at
>>>>
>>>>
>>>> https://web.archive.org/web/20140412115309/http://flaming.com/images/SawTooth.PNG
>>>>
>>>> as mentioned in
>>>>
>>>>
>>>> http://lists.wixtoolset.org/pipermail/wix-users-wixtoolset.org/2019-November/008511.html
>>>>
>>>> In any case, it sounds like you wrote an immediate custom action so you
>>>> could modify the database but could not change the system by design.
>>>>
>>>> Let me re-read the thread more carefully and see if I can provide more
>>>> tailored advice. I have a gut feeling that a semi custom action may not be
>>>> appropriate if you feel you need to insert rows into the Component,
>>>> FeatureComponent and File tables. Perhaps there is an easier way to handle
>>>> all this.
>>>>
>>>> --
>>>> Edwin G. Castro
>>>>
>>>>
>>>> On Sun, Feb 16, 2020, 22:58 Russell Haley via wix-users <
>>>> wix-users at lists.wixtoolset.org> wrote:
>>>>
>>>>> On Sun, Feb 16, 2020 at 8:44 PM Russell Haley <russ.haley at gmail.com>
>>>>> wrote:
>>>>>
>>>>> > Thank you for the response, I didn't see this come through!l That's
>>>>> an
>>>>> > interesting post from 13 years
>>>>> >
>>>>> Thanks again for this but it seems to be a long shot. I have not been
>>>>> able
>>>>> to find the wcautil library mentioned in the article:
>>>>> https://www.joyofsetup.com/2007/07/01/semi-custom-actions/. This
>>>>> seems to
>>>>> be C++, is there a C# frontend? My attempts below seem to be a
>>>>> failure...
>>>>>
>>>>> >
>>>>> > I started drilling into the MSI database information here:
>>>>> >
>>>>> https://docs.microsoft.com/en-us/windows/win32/msi/about-the-installer-database
>>>>> >
>>>>> > From what I can tell I need my custom action to run after the feature
>>>>> > selection and then create my file. If my desired feature is
>>>>> selected, then
>>>>> > I need to insert into Component, FeatureComponent and File. I
>>>>> started going
>>>>> > down the SQL route. Here is a test custom action that is supposed to
>>>>> create
>>>>> > a new config file and add it to "Feature2" (NOTE: This code doesn't
>>>>> work
>>>>> > yet...or at all?).
>>>>> >
>>>>>
>>>>> > public class CustomActions
>>>>> >     {
>>>>> >         [CustomAction]
>>>>> >         public static ActionResult CustomAction1(Session session)
>>>>> >         {
>>>>> >             session.Log("Begin CustomAction1");
>>>>> >             if(session.Features.Contains("Feature2"))
>>>>> >             {
>>>>> >                 session.Log("TESTING: Found Feature2");
>>>>> >                 File.WriteAllText(@"C:\temp\mytemp1.txt", "This is a
>>>>> test
>>>>> > of the public broadcast system.");
>>>>> >                 var db = session.Database;
>>>>> >
>>>>> >                 Guid id = Guid.NewGuid();
>>>>> >                 session.Log("TESTING: id = " + id);
>>>>> > //Used a string builder just because I thought the queries would get
>>>>> long
>>>>> >                 StringBuilder sb = new StringBuilder();
>>>>> >                 sb.Append("INSERT INTO `Component` (`ComponentId`,
>>>>> > `Directory_`) VALUES(?, ?)");
>>>>> >                 session.Log("TESTING: " + sb.ToString());
>>>>> >                 db.Execute(sb.ToString(), id, "feature2folder2");
>>>>> >
>>>>> >                 sb.Clear();
>>>>> >                 sb.Append("INSERT INTO `FeatureComponent`
>>>>> (`Feature_`,
>>>>> > `Component_`) VALUES(?, ?)");
>>>>> >                 session.Log("TESTING: FeatureComponent " +
>>>>> sb.ToString());
>>>>> >                 db.Execute(sb.ToString(), "Feature2", id);
>>>>> >
>>>>> >                 db.Execute("INSERT INTO `File` (`Component_`,
>>>>> `FileName`,
>>>>> > `FileSize`) VALUES (?, ?, ?)",
>>>>> >                     id, "config-5.3.lua", 1024);
>>>>> >                 session.Log("TESTING: File");
>>>>> >                 db.Commit();
>>>>> >             }
>>>>> >             return ActionResult.Success;
>>>>> >         }
>>>>> > }
>>>>> >
>>>>>
>>>>> My code doesn't seem to work. I cleaned up the SQL and made it one hard
>>>>> coded string.   I also queried the directory table and realized the
>>>>> folder
>>>>> name has the package id appended:
>>>>>
>>>>>     public class CustomActions
>>>>>     {
>>>>>         [CustomAction]
>>>>>         public static ActionResult CustomAction1(Session session)
>>>>>         {
>>>>>             session.Log("Begin CustomAction1");
>>>>>             if(session.Features.Contains("Feature2"))
>>>>>             {
>>>>>                 session.Log("TESTING: Found Feature2");
>>>>>                 File.WriteAllText(@"C:\temp\mytemp1.txt", "This is a
>>>>> test
>>>>> of the public broadcast system.");
>>>>>                 var db = session.Database;
>>>>>
>>>>>                 PrintView(session);
>>>>>                 Guid id = Guid.NewGuid();
>>>>>                 session.Log("TESTING: id = " + id);
>>>>>
>>>>>                 //string InsertString = "INSERT INTO `Component`
>>>>> (`ComponentId`, `Directory_`) VALUES(?, ?)";
>>>>>                 //session.Log("TESTING: " + InsertString);
>>>>>                 //var compRecord = session.Database.CreateRecord(2);
>>>>>                 //compRecord.SetString(1, id.ToString());
>>>>>                 //compRecord.SetString(2, "feature2folder2");
>>>>>                 //db.Execute(InsertString, compRecord);
>>>>>
>>>>>                 string InsertString =
>>>>>                     string.Format("INSERT INTO `Component`
>>>>> (`ComponentId`,
>>>>> `Directory_`) VALUES('{0}', '{1}')",
>>>>>                     id.ToString(),
>>>>> "feature2folder2.76597108_E928_4F11_BE09_3DC27AFA3AA3");
>>>>>
>>>>>                 session.Log("TESTING: " + InsertString);
>>>>>
>>>>>                 db.Execute(InsertString);
>>>>>
>>>>>                 InsertString = "INSERT INTO `FeatureComponent`
>>>>> (`Feature_`,
>>>>> `Component_`) VALUES(?, ?)";
>>>>>                 session.Log("TESTING: FeatureComponent " +
>>>>> InsertString);
>>>>>                 var FCRecord = db.CreateRecord(2);
>>>>>                 FCRecord.SetString(1, "Feature2");
>>>>>                 FCRecord.SetString(2, id.ToString());
>>>>>                 db.Execute(InsertString, FCRecord);
>>>>>
>>>>>                 db.Execute("INSERT INTO `File` (`Component_`,
>>>>> `FileName`,
>>>>> `FileSize`) VALUES (?, ?, ?)",
>>>>>                     id, "config-5.3.lua", 1024);
>>>>>                 session.Log("TESTING: File");
>>>>>
>>>>>                 db.Commit();
>>>>>             }
>>>>>             return ActionResult.Success;
>>>>>         }
>>>>>
>>>>>         public static void PrintView(Session session)
>>>>>         {
>>>>>             //Database db2 = ins.OpenDatabase(strFileMsi,
>>>>> WindowsInstaller.MsiOpenDatabaseMode.msiOpenDatabaseModeDirect);
>>>>>             View vw2 = session.Database.OpenView(@"Select * FROM
>>>>> Directory");
>>>>>
>>>>>             vw2.Execute(null);
>>>>>             session.Log("Woot woot");
>>>>>             Record rcrd2 = vw2.Fetch();
>>>>>             while (rcrd2 != null)
>>>>>             {
>>>>>                 session.Log("Directory: " + rcrd2.GetString(1));
>>>>>                 session.Log("Parent_Directory: " + rcrd2.GetString(2));
>>>>>                 session.Log("DEFAULT: " + rcrd2.GetString(3));
>>>>>                 //rcrd2.set_StringData(1, "No data");
>>>>>
>>>>> //vw2.Modify(WindowsInstaller.MsiViewModify.msiViewModifyUpdate,
>>>>> rcrd2);
>>>>>
>>>>>                 rcrd2 = vw2.Fetch();
>>>>>
>>>>>             }
>>>>>         }
>>>>>
>>>>> Execution of the insert into component gives me "Function failed during
>>>>> execution.". Here is my log files when I execute my custom query.
>>>>>
>>>>> TESTING: INSERT INTO `Component` (`ComponentId`, `Directory_`)
>>>>> VALUES('8f26ef55-767b-4b27-aac5-b673694ddfb3',
>>>>> 'feature2folder2.76597108_E928_4F11_BE09_3DC27AFA3AA3')
>>>>> MSI (s) (88!F4) [21:35:14:523]: Note: 1: 2259 2:  3:  4:
>>>>> Exception thrown by custom action:
>>>>> System.Reflection.TargetInvocationException: Exception has been thrown
>>>>> by
>>>>> the target of an invocation. --->
>>>>> Microsoft.Deployment.WindowsInstaller.InstallerException: Function
>>>>> failed
>>>>> during execution.
>>>>>    at Microsoft.Deployment.WindowsInstaller.View.Execute(Record
>>>>> executeParams)
>>>>>    at Microsoft.Deployment.WindowsInstaller.Database.Execute(String
>>>>> sql,
>>>>> Record record)
>>>>>    at Microsoft.Deployment.WindowsInstaller.Database.Execute(String
>>>>> sqlFormat, Object args)
>>>>>    at CustomActionCreateFile.CustomActions.CustomAction1(Session
>>>>> session)
>>>>> in
>>>>>
>>>>> C:\Users\russh\source\repos\TestCreateActionInstaller\CustomActionCreateFile\CustomAction.cs:line
>>>>> 40
>>>>>    --- End of inner exception stack trace ---
>>>>>    at System.RuntimeMethodHandle.InvokeMethod(Object target, Object
>>>>> arguments, Signature sig, Boolean constructor)
>>>>>    at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object
>>>>> obj,
>>>>> Object parameters, Object arguments)
>>>>>    at System.Reflection.RuntimeMethodInfo.Invoke(Object obj,
>>>>> BindingFlags
>>>>> invokeAttr, Binder binder, Object parameters, CultureInfo culture)
>>>>>    at
>>>>>
>>>>> Microsoft.Deployment.WindowsInstaller.CustomActionProxy.InvokeCustomAction(Int32
>>>>> sessionHandle, String entryPoint, IntPtr remotingDelegatePtr)
>>>>> CustomAction CustomActionCreateFile returned actual error code 1603
>>>>> (note
>>>>> this may not be 100% accurate if translation happened inside sandbox)
>>>>> Action ended 21:35:14: CustomActionCreateFile. Return value 3.
>>>>>
>>>>> I fear this problem is a black hole of my time. I have in front of me
>>>>> three
>>>>> possible options:
>>>>>
>>>>> - This option of adding to the database which seems fraught with
>>>>> unknowns
>>>>> and half answers. A article from 13 years ago that mentions an
>>>>> undocumented
>>>>> feature and solves a completely different problem than mine is a little
>>>>> hard to put faith in.
>>>>> - I also have a custom action to simply create a file in the
>>>>> installation
>>>>> folder but it doesn't work. The problem I am having is my custom action
>>>>> doesn't seem to have administrative privileges and I get an accessed
>>>>> denied
>>>>> error.
>>>>> - I have a third and final option which is to execute an external Lua
>>>>> script with elevated privileges in the InstallFinalize step of my
>>>>> installer. This has been tested and works, but it just feels like
>>>>> cheating
>>>>> and my uninstall leaves files and folders around. I can try and run a
>>>>> custom action to remove the left over but... :(
>>>>>
>>>>> I suppose in the end I'll have to settle for a hard coded file name and
>>>>> copy the contents into the file with elevated privileges after the
>>>>> fact.
>>>>> How is it that we have this huge "feature rich" MSI system and my
>>>>> simple
>>>>> use case evades all but the most convoluted solutions? I'd be relieved
>>>>> to
>>>>> know the answer is "You're doing it wrong" if someone could also point
>>>>> me
>>>>> in the correct direction...
>>>>>
>>>>> Regards,
>>>>> Russ
>>>>>
>>>>>
>>>>> > However, what I don't know is how to point to the source file in the
>>>>> > database? Do I need to put it in the database as a blob or a varchar
>>>>> or
>>>>> > something? Or can I just add a reference from the local temp
>>>>> directory?
>>>>> >
>>>>> > Thanks again!
>>>>> > Russ
>>>>> >
>>>>> >
>>>>> > On Thu, Feb 6, 2020 at 5:34 PM Blair Murri via wix-users <
>>>>> > wix-users at lists.wixtoolset.org> wrote:
>>>>> >
>>>>> >> In a word: yes, for both questions. Look for Bob's blog entry about
>>>>> >> "semi-custom" actions.
>>>>> >>
>>>>> >> Blair
>>>>> >>
>>>>> >> Get Outlook for Android<https://aka.ms/ghei36>
>>>>> >>
>>>>> >> ________________________________
>>>>> >> From: wix-users <wix-users-bounces at lists.wixtoolset.org> on behalf
>>>>> of
>>>>> >> Russell Haley via wix-users <wix-users at lists.wixtoolset.org>
>>>>> >> Sent: Sunday, February 2, 2020 11:42:46 AM
>>>>> >> To: WiX Toolset Users Mailing List <wix-users at lists.wixtoolset.org>
>>>>> >> Cc: Russell Haley <russ.haley at gmail.com>
>>>>> >> Subject: [wix-users] Custom Action to Create and Install a File?
>>>>> >>
>>>>> >> Hi,
>>>>> >>
>>>>> >> I'm pretty chuffed. I have a new installer for my WinLua
>>>>> distribution that
>>>>> >> now includes the Lua package manager called LuaRocks and a custom
>>>>> compiler
>>>>> >> toolchain using llvm-mingw. My current installer has a CustomAction
>>>>> that
>>>>> >> creates a config file for the package manager named for the
>>>>> appropriate
>>>>> >> version of Lua. The current version of Lua is 5.3.5, so the config
>>>>> file
>>>>> >> needs to be named Config-5.3.lua. I currently save this file under
>>>>> the
>>>>> >> install directory of <program files>\WinLua\LuaRocks.  This custom
>>>>> action
>>>>> >> runs POST installation so I have the paths I need for creating the
>>>>> >> contents
>>>>> >> of the file. I do not want this file in a user accessible directory.
>>>>> >>
>>>>> >> My questions:
>>>>> >>
>>>>> >> 1) Can I use a custom action to create the file before the
>>>>> installation
>>>>> >> completes and ADD the config file to the list of files to be
>>>>> removed (I
>>>>> >> think that's termed the installer database)?
>>>>> >>
>>>>> >> 2) What about things like environment variables? Can I create them
>>>>> based
>>>>> >> on
>>>>> >> installer information prior to the completion of the install
>>>>> process and
>>>>> >> have the installer remove them? Specifically I want to create an
>>>>> >> environment variable called LUAROCKS_SYSCONFDIR and store the path
>>>>> of the
>>>>> >> config-5.3.lua mentioned above. However, the name and path won't be
>>>>> known
>>>>> >> until AFTER the file is created? I can already add to the PATH
>>>>> variable
>>>>> >> based on the installer paths, but this feels different.
>>>>> >>
>>>>> >> All my source code can be found here:
>>>>> >>
>>>>> >>
>>>>> https://github.com/WinLua/WinLua-Source-Code/tree/master/WinLua-Release3/WinLua-Installer
>>>>> >>
>>>>> >> Regards,
>>>>> >> Russell
>>>>> >>
>>>>> >> ____________________________________________________________________
>>>>> >> WiX Toolset Users Mailing List provided by FireGiant
>>>>> >> http://www.firegiant.com/
>>>>> >>
>>>>> >> ____________________________________________________________________
>>>>> >> WiX Toolset Users Mailing List provided by FireGiant
>>>>> >> http://www.firegiant.com/
>>>>> >>
>>>>> >
>>>>>
>>>>> ____________________________________________________________________
>>>>> WiX Toolset Users Mailing List provided by FireGiant
>>>>> http://www.firegiant.com/
>>>>>
>>>>


More information about the wix-users mailing list