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

Russell Haley russ.haley at gmail.com
Sun Feb 16 22:22:32 PST 2020


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



More information about the wix-users mailing list