How can best I allow edits on a config file for Windows Service App before the installation?

May 3, 2016 at 9:12 PM
Hello all,

Checking for the best process for allowing the config file to be edited after the files are on the client system and before the app is installed as a Windows Service.

Thanks in advance,

Bob
Coordinator
May 4, 2016 at 12:32 AM
Typically you would need to find the step name which corresponds to the stage you are interested in and schedule a custom action for this step. In your custom actions you modify the the config file.

For the step name you will need to dig the WiX documantation.
For the config file modifications see the "DeferredActions" sample.
For the service install see the "WinService_WiX" sample.

Though you may find that your custom action will not be elevated and most likely this will prevent you from editing the config file. In this case you may reconsider your strategy. You may install your app fully but do not configure to start start the services at the end of install. Then use deferred action (which is elevated) to modify the config and start the service. The easiest way to define deferred action is to use AfterInstall event of ManagedProject:
project.AfterInstall += msi_AfterInstall;
...
static void msi_AfterInstall(SetupEventArgs e)
{
    MessageBox.Show(e.ToString(), "AfterExecute");
}
May 4, 2016 at 2:10 AM
Thanks for your reply Oleg.

Can you please suggest which example for adding a winform dialog is up to date as I see them using the older syntax ands just wanted to build it the best way.

Thanks again,

Bob
Coordinator
May 4, 2016 at 2:19 AM
Edited May 11, 2016 at 12:07 AM
The best way is to create a project from the VS template (you'll need to add VS "WixSharp project templates" extension). The freshly created project (after NuGet packages are restored) will always have the latest working sample code. I suggest CustomUI dialog template. As a side note can you please confirm what exactly did you mean with "using the older syntax". If it is about "project.buildMsi() vs Compiler.BuildMsi(project)" then it's fine but if it is something else then I'd rather update the samples. Thanks
May 5, 2016 at 2:25 PM
Edited May 5, 2016 at 2:27 PM
Hello Oleg,

As I am working to add a custom UI dialog, I did per your suggest start a new project with the Custom UI template where I do see the CustomDialog class and also where it is added to the install dialogs:

project.ManagedUI.InstallDialogs.Add(Dialogs.Welcome)
                                        .Add(Dialogs.Licence)
                                        .Add<CustomDialog>()
                                        .Add(Dialogs.Progress)
                                        .Add(Dialogs.Exit);
Sorry but I don't see an example to properly invoke the dialog so I am asking if you can please give me some guidance.

Thanks again for your great help :)

Bob
Coordinator
May 5, 2016 at 11:36 PM
Edited May 11, 2016 at 12:07 AM
Your dialog (CustomDialog) will be invoked at runtime after you navigate forward from the WelcomeDialig. You don't invoke it directly. Wix# runime will do it. The sample dialog that is generated as part of the project (created from the VS "Managed Setup - Custom Dialog" template) does exactly that. If you run it you will see that the custom dialog is displayed at the step it is scheduled. Cheers, Oleg
May 10, 2016 at 3:41 PM
Hello Oleg,

Thanks to your great help, I have been making good strides in completing the setup work.

I have provided the following link to a pdf file where I am trying to install all of the needed files under program files and then install the windows service but that is causing an error so if I can again ask for your help, that would be awesome :)

https://onedrive.live.com/redir?resid=CE131646C34280CB!8959&authkey=!AITZiG0_KoWVSIw&ithint=file%2cpdf

Thanks again,

Bob
Coordinator
May 11, 2016 at 12:18 AM
Hi Bob,

I think we have a bit of miscommunication here.

You asked me to assist you with "invoking custom dialog". Thus (in my previous email) I asked you to create a Wix# project from the specific VS project template and test it. Did you have an opportunity to do that? Is this problem solved now?

Unfortunately the pdf file you provided has very little practical value as it's nothing else but an image of your VS. I don't have any information about the error you mentioned except your "but that is causing an error".

I think the best approach for you would be to grab some of the samples (there are ~90 of them) and verify that you can build them. Then you can take he one that is most consistent with your scenario and start modifying it to meet your specific requirements. And then I will be able to guide you from there.

Cheers,
Oleg
May 11, 2016 at 1:02 AM
Hello Oleg,

Sorry for my vagueness, yes the custom dialog is resolved (thanks to your help).

Here is where I am having the remaining trouble:

var project = new ManagedProject("ConvertCommandCenter",
                         new Dir(@"%ProgramFiles%\InFlow Technology LLC\ConvertCommandCenter",
                         new DirFiles(@"..\..\..\CommandCenter\bin\Release\*.*",
               service = new File(@"%ProgramFiles%\InFlow Technology LLC\ConvertCommandCenter\CommandCenter.exe"))));
If I change it to:

var project = new ManagedProject("ConvertCommandCenter",
                         new Dir(@"%ProgramFiles%\InFlow Technology LLC\ConvertCommandCenter",
                         new DirFiles(@"..\..\..\CommandCenter\bin\Release\*.*")));
It will successfully place my project files into the destination folder but it needs to set the service variable so I can invoke the service installer method:

File service = null;
        var project = new ManagedProject("ConvertCommandCenter",
                         new Dir(@"%ProgramFiles%\InFlow Technology LLC\ConvertCommandCenter",
                         new DirFiles(@"..\..\..\CommandCenter\bin\Release\*.*",
               service = new File(@"%ProgramFiles%\InFlow Technology LLC\ConvertCommandCenter\CommandCenter.exe"))));

        service.ServiceInstaller = new ServiceInstaller
        {
            Name = "ConvertCommandCenter",
            Description = "InFlow Convert Command Center",
            StartOn = SvcEvent.Install,
            StopOn = SvcEvent.InstallUninstall_Wait,
            RemoveOn = SvcEvent.Uninstall_Wait,
            DelayedAutoStart = true,
            ServiceSid = ServiceSid.none,
            FirstFailureActionType = FailureActionType.restart,
            SecondFailureActionType = FailureActionType.restart,
            ThirdFailureActionType = FailureActionType.restart,
            RestartServiceDelayInSeconds = 30,
            ResetPeriodInDays = 1,
            PreShutdownDelay = 1000 * 60 * 3,
            RebootMessage = "Failure actions do not specify reboot",
        };
I can remove the DirFiles parameter and get the service to install but I then do not get all of the project files placed into the destination:

var project = new ManagedProject("ConvertCommandCenter",
                         new Dir(@"%ProgramFiles%\InFlow Technology LLC\ConvertCommandCenter",
               service = new File(@"%ProgramFiles%\InFlow Technology LLC\ConvertCommandCenter\CommandCenter.exe")));
I am hoping this makes better sense and thanks again for your great help,

Bob
Coordinator
May 11, 2016 at 1:32 AM
Edited May 11, 2016 at 1:35 AM
Great thank you. Now this is something we can work with.

Before I do any further testing we need check out a few things. The line new DirFiles(@"..\Release\*.*", service = new File( puzzles me as DirFiles doesn't have any constructor that takes File object. Anyway, let's assume it's just a typo.

I also assume that your CommandCenter.exe is in the Release directory. If indeed it is the case then passing it to the constructor like on your case may be a bit ambiguous.

The following is another option that should work:
var project = 
     new ManagedProject("ConvertCommandCenter",
                         new Dir(@"%ProgramFiles%\InFlow Technology LLC\ConvertCommandCenter",
                             new DirFiles(@"..\..\..\CommandCenter\bin\Release\*.*")));

File service =  project.ResolveWildCards(ignoreEmptyDirectories: true)
                       .FindFile(f => f.Name.EndsWith("CommandCenter.exe"))
                       .First();
. . .
//or
project.ResolveWildCards(ignoreEmptyDirectories: true);
File service = project.Single(f => f.Name.EndsWith("CommandCenter.exe"))
. . . 

service.ServiceInstaller = new ServiceInstaller
        {
            Name = "ConvertCommandCenter",
            Description = "InFlow Convert Command Center",
            StartOn = SvcEvent.Install,
            ...

If the file is not in the Release folder then indeed you need pass it explicitly but to the correct Dir (not DirFile):
var project = 
     new ManagedProject("ConvertCommandCenter",
                         new Dir(@"%ProgramFiles%\InFlow Technology LLC\ConvertCommandCenter",
                             service = new File(@"<proper path>\CommandCenter.exe"),
                             new DirFiles(@"..\..\..\CommandCenter\bin\Release\*.*")));
I hope this helps.
May 11, 2016 at 1:55 AM
Hello Oleg,

Yes, that was a huge help :)

The final piece of this install's puzzle is that I am, in the Project_AfterInstall event, trying to edit the config file in the destination folder before installing the Windows Service:

project.AfterInstall += Project_AfterInstall;

private static void Project_AfterInstall(SetupEventArgs e)
    {
        if (e.IsInstalling)
        {
            string installDir = e.InstallDir;

            EditConfigFileSettings(installDir);
        }
    }
I hit an error in that method and wanted to check if that should be an issue.

Thanks again for your great help,

Bob
Coordinator
May 11, 2016 at 2:10 AM
Not a problem.

Now about your last bit.

You wrote "Project_AfterInstall event, trying to edit the config file in the destination folder before installing the Windows Service". You probably already see the problem with this statement :)

You are trying to do some "before-install" activity but at the "after-install" stage.

The Project_AfterInstall is in fact a correct place to to do the config file modifications. However at that time your service will be already installed and attempted to start with the original unmodified config file. This is because you set StartOn = SvcEvent.Install.

Of course there can be a service that starts with any config file and it can simply monitor this file for any changes. And then updates itself when the change is detected. Though most likely your service is an ordinary service that doesn't do that. Thus you will need to delay the start of your service by not setting StartOn. And after you changed your config you can start your service from the AfterInstall event handler with something like this:
Process.Start("net", "start ConvertCommandCenter"); 
May 11, 2016 at 4:16 PM
Edited May 11, 2016 at 4:25 PM
Thanks Oleg,

That resolved the issue for allowing me to edit the app.config file.

One questions for persisting data entries from a custom dialog:

I added a public class and a static variable of that class type in Program that I populate from the CustomDialog class in the next_click event which I verified.

When I check for values in both the before and after install event methods, I don't have any values in that static variable so I am just checking if there is something unique that I need to account for.

I appreciate your help,

Bob
May 14, 2016 at 2:22 AM
Could the DeferredActions sample be updated to show the best way to get properties from CustomUI dialogs to Tasks.SetConfigAttribute?

It seems that using the AfterInstall event is cleaner (and preferred?) rather than adding an ElevatedManagedAction.

In either context, ManagedProject Properties need to have IsDeferred true to get copied to the SetupEventArgs Session.Property collection.

I haven't found a way yet to access IsFeatureEnabled or ManagedProject Session values. Do I have to copy Session["ADDLOCAL"] to a Property if I want to modify a config file only if a particular feature is being installed?

Is there a better way to do it?
Coordinator
May 15, 2016 at 2:15 AM
Edited May 15, 2016 at 11:35 AM
Due to the extremely convoluted nature of MSI custom actions hosting model no data exchange is possible between custom actions as they all run in they individual multiple processes. MSI way of overcoming this problem is to save any data for another CA in the session properties. It works, well kind of...
If your custom action is a deferred one then even session properties are "killed" before they are passed to the custom action.

So MSI offers another "elegant" work around by mapping the properties you want to access from the deferred action to the special property called "CustomActionData". Wix# makes this task just simple (but not nicer) as you can nominate the properties you want to preserve with DefaultDeferredProperties project member (by default "INSTALLDIR,UILevel")
project.DefaultDeferredProperties += ",ADDLOCAL";
...
//acces it in your action with
e.Session.Property("ADDLOCAL")
Alternatively you can save any data to the Wix# specific fully persisted data properties "bag" SetupEventArgs.Data.
SetupEventArgs.Data values can be set and accesses at any time from any custom action including deferred ones:
static void project_Load(SetupEventArgs e)
{
    e.Data["persisted_data"] = "test data"; 
...
static void project_AfterInstall(SetupEventArgs e)
{
    var data = e.Data["persisted_data"];
Coordinator
May 15, 2016 at 6:32 AM
As for your previous post...
>Could the DeferredActions sample be updated to show the best way to get properties from CustomUI dialogs to Tasks.SetConfigAttribute?
Yes. I agree. Done: "https://wixsharp.codeplex.com/SourceControl/latest#src/WixSharp.Samples/Wix# Samples/DeferredActions/setup.cs"

> In either context, ManagedProject Properties need to have IsDeferred true to get copied to the SetupEventArgs Session.Property collection.
I think it's a typo and you mean ManagedAction. There is a dedicated derived class ElevatedManagedAction, which already has IsDeferred=true;

>I haven't found a way yet to access IsFeatureEnabled or ManagedProject Session values. Do I have to copy Session["ADDLOCAL"] to a Property if I want to modify a config file only if a particular feature is being installed? Is there a better way to do it?
"IsFeatureEnabled or ManagedProject Session values" cannot be accessed as MSI destroys them before invoking any deferred action. I provided a detailed description in my previous post in this thread.

The best way to discover the features being installed is to preserve ADDLOCAL and then anayse it in the AfterInstall action. BTW the next release will have ADDLOCAL automatically preserved and you will not need update DefaultDeferredProperties in your project definition.
project.DefaultDeferredProperties += ",ADDLOCAL";
...
static void project_AfterInstall(SetupEventArgs e)
{
   bool isFeatureInstalled = e.Session.Property("ADDLOCAL").Split(',').Contains("<name of the feature>");
Note, ADDLOCAL must be accessed via 'Property' method as otherwise (e.Session[<prop_name>]) the value is not available
May 15, 2016 at 11:31 AM
Thanks for the very prompt replies.
Very helpful.
May 16, 2016 at 1:34 PM
Thanks for your great help and guidance in this Oleg :)
Coordinator
May 16, 2016 at 1:41 PM
Not a problem :)