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

Edwin Castro egcastr at gmail.com
Tue Feb 18 09:50:50 PST 2020


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

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?

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