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

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


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