wixsharp icon indicating copy to clipboard operation
wixsharp copied to clipboard

wix4 broke me; so I came here to ask for help.

Open senpro-ingwersenk opened this issue 1 year ago • 9 comments

Hello there!

First, this project looks amazing and is ought to address my current issues that I have. But, my brain is completely mud after fighting seven hours with Wix, trying to get just a small thing to work...

So, my company distributes a custom package of Telegraf and configurations to our lient as part of our monitoring solution. We even use it to monitor our own fleet of laptops and servers as well. However, demands to this have grown - and now, we have to add a few conditional paths to it. A previous employee left but no documentation whatsoever; so this led me to losing my mind today.

I don't do a lot with C# but I do know JS, C and C++ quite well, so this should not be a big issue at all. So, do you think I could realize the following (simplified) installation flow with Wix#?

Installer inst = new Installer("ProgrammFiles\RMMSolution")
  .withFilesFromDir("<buildroot>\dist"); // has Telegraf + configs

Service svc = new WindowsService(
  "RMMSolution", /// name
  "<installdir>\telegraf.exe", /// argv[0]
  "-config <installdir>\main.conf --config-dir <installdir>\etc"
);
svc.start("Automatic (Delayed)");
svc.onFailure1(restart); svc.onFailure2(restart); svc.onFailure3(restart);

inst.addService(svc)

Feature veeam = inst.addFeature("Veeam monitoring");
if(veeam.isScheduledForInstall()) {
  showDialog( /* We ask for an API token and bucket name here. */ )
}

Feature winSvc = inst.addFeature("Windows Service Monitoring");
if(winSvc.isScheduledForInstall()) {
  showDialog( /* We ask for a list of services to be monitored. */ )
}

inst.onPostInstall(() => {
  if(winSvc.isInstalled()) {
    string[] serviceList = getOutputFromDialog();
    writeFile(
      generateTelegrafConfig(serviceList),
      "<installdir>\etc\winsvc.conf"
  );
});

Again, I am just doing this as pseudo code; I will be re-learning C# just for this one. But basically, do you think this is possible with Wix#? And if so, got any pointers I should look for while I relarn C# to use in this context?

Thanks a lot and I am so sorry for dumping my problem here... but I really have no other idea where to go. I used AlternativeTo.net to come across this project; and since it is closely related to Wix, I figured I might find some expertese here.

The thing that broke me was not getting <Publish .../> to properly resolve the feature installation state property to show an optional dialog when it was selected - so I hope that I could find a solution here. :)

Thank you and kind regards!

senpro-ingwersenk avatar Apr 03 '24 13:04 senpro-ingwersenk

morning, what you want to acvieve is, installer with 2 windows services and this as feature. If you want to know, how and learn Wix#, best pacakge for create Installers with Wix toolset, you will need some practice and Tests. Oleg do super job to create samples for each scneario and supports good way. Download latest wixsharp samples and there is project sample for win services. Link is https://github.com/oleg-shilo/wixsharp/tree/master/Source/src/WixSharp.Samples/Wix%23%20Samples.

You will need some time to learn Wix# and after this you can build, ~95% all installers for your needs.

Torchok19081986 avatar Apr 04 '24 06:04 Torchok19081986

Thank you @Torchok19081986, it's quite complete guidance that you gave.

Only a few extra points...

  • When you want to analyse samples it's easier to do by cloning this whole repo. This way you have a warranty that the samples will compile.

  • I highly recommend that instead of coding the skeleton project by hands you use Visual Studio with WixSharp extension installed. It will let you create the project and simply adding the service(s) as it is demonstrated in the sample.

Since you are going to have your user interacting with the setup (e.g. showDialog) then you need to create WixProject from the template that has "Custom UI" in it.

image

This will pull into your project the source code for the dialogs and it will be easy for you to modify any dialog as required.

oleg-shilo avatar Apr 04 '24 07:04 oleg-shilo

Thanks @oleg-shilo and @Torchok19081986 ! I just read my way through the samples and I am honestly a little stunned by how readable WixSharp's API is. I do come from a programming background ( my non-work profile is @IngwiePhoenix ) so reading those samples was exactly what I needed. Had somehow overlooked them before... apologies for that.

I found what I needed for the Services, and the UI looks quite straight forward - either using ManagedUI or using them in the custom actions.

But two things I couldn't figure out off the samples alone - though I bet high on this being more of a limitation in my C# knowledge more than anything.

  1. When I declare a new Feature(...), how can I check for it's enablement in a custom action? As far as I understand, class Script is executed entirely separately for making the MSI, and thus values from there can not be used in custom actions when the installer itself is running. So how can I test for feature enablement?
  2. Sometimes we deploy this package by hand, but we also use Action1 for automated deployments, especially to new hosts that get enrolled - so, it would be handy to support silent installs by checking if certain properties are already set. As far as I see, msiexec.exe passes properties via PROP=VAL pairs, i.e. msiexec /I build.msi FEATUREA=1 SOMETHINGELSE="a string". So, within the UI, can I skip showing UI by testing if those props are already set? This way, we could pass props when we deploy via Action1, but show the dialog when it is being deployed manually.

That said, I found everything else; services, actions et cetera. Really like the clean and descriptive API!

senpro-ingwersenk avatar Apr 04 '24 07:04 senpro-ingwersenk

Congrats. Your post made me smile. You probably do not even realize how many WixSharp fundamental concepts you got right. Even without being fluent in C#. Just spot on. Good on you.

Now, your questions.

  1. When I declare a new Feature(...), how can I check for it's enablement in a custom action?

    If you look at the code of the Next button click handler in the Features dialog, you will see that all selected features names are concatenated with a coma character and assigned to the ADDLOCAL MSI property. All the features that are not to be installed or removed (if you are modifying an already installed product) are also concatenated and assigned to REMOVE property. Thus you can always check these session properties from a custom action and understand if the feature is to be installed (enabled by user) or not.

  2. class Script is executed entirely separately for making the MSI, and thus values from there can not be used in custom actions when the installer itself is running.

    Bingo!!! That is exactly right. This class is a build script for building an MSI. Tough some methods (e.g. project event handlers or custom actions) of this class are used at run time but not the stratic Main. BTW, I highly recommend switching to event handlers instead of custom actions. An event handler is nothing else but a wrapped "low-noise" custom action but it is so much easier to deal with it and the code is way cleaner than with raw CAs.

  3. So, within the UI, can I skip showing UI by testing if those props are already set?

    You do not have to. There is and msiexec switch that does it for you: image However, if you still want to do it with the MSI property then you can achieve this by analyzing this property in the first dialog and then either continuing as normal or jumping straight to the install action as in the Progress dialog. Though it would be a little trikier.

oleg-shilo avatar Apr 04 '24 12:04 oleg-shilo

Good morning!

Glad to hear I got things right :) I spent some time yesterday fooling around with a project set up with the CustomUI and creating a few extra pages to gather information from the user. I noticed that I could probably even do validation during GoNext() if I wanted to; treating it as the on-submit of typical forms. Quite neat.

The last thing that I am left with is the page flow. So, I am going to be a bit more verbose here; sorry if this reply gets kinda long... ^^'

  • The deployment installs Telegraf and a few base configurations; let's just call this Feature("default"). Those will always be installed.
  • Telegraf is registered as a service.
  • During the installation, we gather information for the InfluxDB connection; namely host and organization (INFLUXDB_HOST and INFLUXDB_ORG respectively as properties). Those are mandatory.
  • Perhaps we want to also monitor additional software using PowerShell scripts - in this case, additional configuration files are to be installed. It's actually a group: Veeam [ VeeamO356, VeeamBR ]. That would probably be a FeatureGroup with those two sub-features part of it. There is a new page for that too (VeeamDialog) that records a bucket name and API token.
  • A new addition is monitoring Windows Service states; we dubbed this feature Feature("WinSCM"). Here we only need to record an API token; the bucket is pre-defined.

So, this produces:

- Default (Always installed) [+ InfluxConnectDialog -> INFLUXDB_HOST, INFLUXDB_TOKEN]
- Veeam [+ VeeamDialog -> INFLUXDB_VEEAM_TOKEN, INFLUXDB_VEEAM_BUCKET]
  * VeeamO365
  * VeeamBR
- WinSCM [+ WinSCMDialog -> INFLUXDB_WINSCM_SVC]

So far, so good. I made the dialogs; though I have yet to add them. With this explained, here's my (hopefuly) last bit of questions:

  1. When I add my dialogs, should they be added via project.ManagedUI.ModifyDialogs.Add<...>() or project.ManagedUI.InstallDialogs.Add<...>()? I couldn't find the difference between the two, though I think I just overlooked it while reading the source files for the doc comments.
  2. Once the dialogs are added, how do I bind them to the feature selection? If any of the Veeam features are enabled, I want to show the VeeamDialog - otherwise, not. Same applies to WinSCM.
  3. How exactly can I make files optional? Since the File(...) objects are added at build time, is there a way to make installing them optional?
  4. I found a few objects that match WixSharp.Registry*; which one do I use to set a registry key? We use this to set the Environment key on our installed service to supply environment variables that are reused in the configuration. Basically, most of the set properties end up as entries in this key.

One guess I had was that I could do the skipping within VeeamDialogModel's constructor; but I wanted to ask first, as this is something that I do believe ManagedUI is ment to do already (plus, it does sound a little hacky).

I am still not fully done reading the whole WixSharp sourcecode, but I am getting there. Certainly much better and more understandable than the XML shenanigans...

Kind regards, and have a nice day!

senpro-ingwersenk avatar Apr 05 '24 05:04 senpro-ingwersenk

I kept reading through the wiki, eventually coming across this page: https://github.com/oleg-shilo/wixsharp/wiki/Deployment-scenarios#injecting-custom-winform-dialog

That explains where I have to anchor my custom dialogs - and I even learned that, technically, I could use something like AvaloniaUI, if I wanted to. That only leaves the part about only showing a dialog, when it's associated feature is selected - or skipping it, should the feature not be selected.

Though I noticed that it takes clicking through a few links to find these entries, they don't just show up on the right-hand sidebar.

senpro-ingwersenk avatar Apr 05 '24 11:04 senpro-ingwersenk

@senpro-ingwersenk, skipping the dialog is simpler than it may look. When, during setup UI interaction, a dialog is displayed, the following sequence of events/steps is executed:

  1. dialog instance is initialized in the constructor
  2. then its method Show is called
  3. this, in turn, triggers OnLoad event handler execution
  4. now dialog "pauses" and lets user interact with the UI elements
  5. Next button is clicked and its event handler is executed.
  6. UI sequence proceeds to the next dialog.

This sequence repeats itself again and again until Progress dialog is activated. This dialog is special, it does not pause in step 4 but triggers the actual MSI execution with Shell.StartExecute() call. Then it waits for the OnExecuteComplete() and simply automatically invokes step 6 (go to next dialog).

If because of some specific runtime conditions you want to skip (fast forward) all dialogs and jump to the installation (Progress dialog), you can do it either by immediately navigating from the OnLoad (or Init in WPF) event handler to the next dialog. Note you need to use the dispatcher to marshal your call to te UI thread:

image

Tough, there is even a more straight forward way. Imgine you want to show WelcomeDialog and when user clicked Next button jump directly to the features dialog. Then you can use a dedicated API for that. Shell.GoTo<T>() instead of Shell.GoNext():

image

oleg-shilo avatar Apr 07 '24 10:04 oleg-shilo

Good morning @oleg-shilo !

I see! So if I wanted to skip the Veeam dialog, all I'd effectively need to do is to dispatch a GoNext() call within Init() to achieve that. Thanks!

I have spent some time over the weekend to read more into the Feature Object, and ended up adding this in my Program.Main:

            var fVeeamO356 = new Feature("Veeam O356", "Backup Office 356", false);
            var fVeeamBR = new Feature("Veeam Backup&Restore", "Klassisches Veeam B&R", false);
            var fWinSCM = new Feature("Windows Service Monitor", "Monitoring für Windows Services (SCM)", false);

            var fDefault = new Feature("SenproRMM", "Monitoring Software-Lösung der Firma SenproIT GmbH");
            fDefault.IsEnabled = true;
            fDefault.AllowChange = false;
            fDefault.Add(fVeeamO356, fVeeamBR, fWinSCM);

            project.Feature = fDefault;

Now how can I check for those features within the dialogs to properly schedule to skip? That is, if I even set them correctly. In the original wix project that a former collegue put together (which broke, and thus drove me here, and was written in plain XML), he used a feature-in-feature constroct akin to a feature group, causing Wix to display a tree structure:

Veeam
|- Veeam O356
|- Veeam Backup&Recover
Windows Service Monitoring

That is why I opted to attempt to replicate this. My idea is to check for fVeeamO356.isEnabled || fVeeamBR.isEnabled to only then show the related dialog. Otherwise, it can stay unused and skipped. Same with the WinSCM part.

So far I haven't found an instance method or property that would return me the features; I could use session["ADDLOCAL"].split(",") to find the plain string, but that seems unoptimal. Is there a proper way to do this?

Also, thank you so much for your support thus far, it's been really helpful! This is my first real touching point with C# too; and I am quite enjoying this.

Kind regards!

senpro-ingwersenk avatar Apr 08 '24 06:04 senpro-ingwersenk

I am sincerely glad you are enjoying C#. Agree, C# is much better than manipulating XML for what we are doing here. Of course, it is subjective :)

Anyway, there is an API for discovering features. If you open the source code for the Features dialog in your project you will see that there is a method BuildFeaturesHierarchy which is used at dialog startup to analyze the session object and initialize treeview UI element that rep[resents features and their "user selection" state. You can use it as a starting point for what you are trying to achieve:

image

all I'd effectively need to do is to dispatch a GoNext() call within Init()

Yes, though I do prefer the second option I described. Basically you have two solutions for your problem:

This is your UI sequence: ... -> PreVeeamDialog -> VeeamDialog -> PostVeeamDialog -> ...

If you do not want to show VeeamDialog then your current plan is to dispatch GoNext() within VeeamDialog.Init(). This is your option. It will work. It will flicker but probably it will be OK.

I do prefer a different approach - call GoTo<PostVeeamDialog>() within PreVeeamDialog.GoNext(). No flickering, no need for dispatching.

Basically, it is "skip" vs "goto".

oleg-shilo avatar Apr 08 '24 12:04 oleg-shilo