How to implement Rollback?

Oct 18, 2015 at 12:16 PM
Edited Oct 23, 2015 at 7:10 AM
I'm very new in wix, msi and wix#...

How to implement Rollback and Repair and custom elevated uninstall using wix# ?
For example, I have installed my app to folder. And now I need to do something: call some 3d party application or connect to database, launch task scheduler...and something goes wrong, throws execptions. How to implement it.
Coordinator
Oct 21, 2015 at 1:35 AM
You need to implement CustomAction and in this action return success or failure. The easiest way of doing this is to use ManagedSetup events:
project.AfterInstall += project_AfterInstall;
...
static void project_AfterInstall(SetupEventArgs e)
{
    try
    {
        //do your stuff
    }
    catch (Exception ex)
    {
        e.Session.Log(ex.Message);
        e.Result = ActionResult.Failure;
    }
}
Marked as answer by asartem on 10/23/2015 at 12:08 AM
Oct 21, 2015 at 4:16 PM
Edited Oct 21, 2015 at 4:57 PM
Thanks. But the project_afterInstall and other built-in handlers during uninstallation has less permissions (somehow) when during installation. That's why we used elevated managed action.
In our custom uninstall action ( UnInstallCertificatesAndStopTaskAction ) we delete certificates first (while all files are still exisisting in installDir ).
But another problem appears while we select and press Repair button on setup.msi :
the same custom action (UnInstallCertificatesAndStopTaskAction) is called again (like in unstallation process). And how to check that it's a Repearing and Uninstall state now while I'm inside custom action?
session.IsRepeare() throws exception, that it doesn't contain such key. In your project_AfterInstall you can get it from SetupEventArgs e but not in my custom elevated action. Answer from stackoverflow like session.EvalulateCondition("Installed"); throws the same error. Really I have only 2 properties in sesssion: INTSALLDIR = "C:\Program Files..." and UI = 3.

My action looks like this:
new ElevatedManagedAction("UnInstallCertificatesAndStopTaskAction",
                                            Return.check,
                                            When.Before,
                                            Step.RemoveFiles,   // we need files in install dir to remove certificates and stop tasks in scheduler first
                                            Condition.Installed )
How to figure out which state is now running: uninstall or repeair inside custom action ?
Oct 21, 2015 at 5:07 PM
Edited Oct 21, 2015 at 5:30 PM
Or maybe it's possible to disable repairing button or evaluate permissoins for uninstaller?
Coordinator
Oct 22, 2015 at 1:06 AM
> But the project_afterInstall and other built-in handlers during uninstallation has less permissions (somehow) when during installation.
Actually it is vice versa. AfterInstall is deliberately based on the deferred custom action so you have elevated execution context.

Modify the Setup Events sample as below:
static void project_AfterInstall(SetupEventArgs e)
{
    if (WindowsIdentity.GetCurrent().IsAdmin())
        MessageBox.Show(e.ToString(), "AfterExecute (admin)");
    else
        MessageBox.Show(e.ToString(), "AfterExecute");
}
And you will see that it is elevated.
Image

> ... press Repair button on setup.msi...the same custom action (UnInstallCertificatesAndStopTaskAction) is called again (like in unstallation process)....
Correct. All custom actions are executed every time msi is executed unless they are bound to the conditions detecting install/repair/modify/upgrade/uninstall. This is the reason why I suggested to use AfterInstall as it already has the all information you need. Look at the image above and you will see that SetupEventArgs has property IsInstalled (and others) properly initialized.

> session.IsRepeare() throws exception,
I am not sure what you are referring to. Wix# has session.IsRepairing() extension and it doesn't throw as it uses top level try catch. Though if you use AfterInstall then you just need to evaluate the SetupEventArgs (CLR not session) properties.

> Really I have only 2 properties in sesssion: INTSALLDIR = "C:\Program Files..." and UI = 3.
Correct. This is that known MSI limitation: all deferred (elevated) custom actions are executed outside of the session and no session properties are available. Wix# (e.g. AfterInstall) helps the situation by preserving all important properties into the session CustomActionData and then exposing them as CLR properties to the deferred actions. You can also do it by yourself for any deferred action by adjusting the project DefaultUsesProperties, which by default preserves only INSTALLDIR and UILevel. However... I would really encourage you to go with AfterInstall as it does exactly what you need.

Start with the Setup Events sample. Have a look and play with it a little. Most likely it will satisfy all your needs.
Oct 22, 2015 at 8:33 AM
Edited Oct 22, 2015 at 9:25 AM
oleg_s wrote:
Actually it is vice versa. AfterInstall is deliberately based on the deferred custom action so you have elevated execution context.
My fault. Sorry. I think it was BeforeInstall with less permissions. I had problems with permissions while I tried to remove certificates (could it be elevated in code?).
With AfterInstall I had another problem (and why I started to implement custom elevated action "InstallRunTaskAction" ) I didn't found inforamtion how to pass reference to external dll ( Microsoft.Win32.TaskScheduler.dll) which comes togather with application to AfterInstall handler to start tasks from inside of mentioned handler. In all other cases it was very good to resolve my problems.
var runTaskSchedulerAction =
                new ElevatedManagedAction("InstallRunTaskAction",
                                            Return.check,
                                            When.Before,
                                            Step.InstallFinalize,
                                            Condition.NOT_Installed)
                {
                    RefAssemblies =
                    new[] { Path.Combine(pathToSetupProjectBinDirectory, AssebmlyFileName) }
                };
oleg_s wrote:
I am not sure what you are referring to. Wix# has session.IsRepairing() extension and it doesn't throw as it uses top level try catch.
But it throws ( in deferred custom action ) and even causes rollback on uninstall and program like "could be never deleted app" by uninstaller because of exception inside custom action of uninstaller.

So
Is it possible to pass my assembly to AfterInstall and BeforeInstall ?
Is it possible to elevate permssions of BeforeInstall (not with RightClick->"Run as Admin" but in code) ?

My goal is:
during installation add certs and run task using referenced assambly after files were copied (admin permissions required)
during remove remove certs first and stop task using referenced assambly (admin permissions required)
during repair remove certs first and stop task and then add certs and run task using referenced assambly ( (admin permissions required))
in case of rolback remove certs and stop task (reference to assambly and permissions required. With permissions for afterinstall you've just helped)
Coordinator
Oct 23, 2015 at 6:15 AM
> With AfterInstall I had another problem I didn't found inforamtion how to pass reference to external dll which comes togather with application to AfterInstall
To be entirely accurate you will have this problem regardless of what custom action you are in (ElevatedManagedAction or ManagedSetup event).

The problem is caused by the fact that all MSI CustomActions are sandboxed (isolated) and they are not invoked from the location of your installed files. Thus you have a few options here.
  • You can either load the assembly dynamically from the INSTALLDIR location
  • Or you can ignore the fact that the dependency dll is to be installed and pack it with the Custom Action as well. The simplest way is as follows:
project.DefaultRefAssemblies.Add(@"libraries\Microsoft.Win32.TaskScheduler.dll");

> But it throws ( in deferred custom action )
Sorry. Indeed my reference to "top level try catch" was incorrect. Let's start again but with more details :)
The IsRepairing extension method under the hood has an error prevention mechanism that redirects it either to session.Properties or to sesson.CustomActionData so you will never hit the 'null' session from the deferred action. However if the property was not preserved into CustomActionData then of course there is not much can be done about it.

Thus as I mentioned you have to preserve the properties you are interested in (in this case REMOVE and INSTALLED) with project.DefaultUsesProperties.
Or you can use ManagedProject.AfterInstall event which already does all 'preserving' for you and all SetupEventArgs members are already initialized and set.
Oct 23, 2015 at 7:03 AM
Edited Oct 23, 2015 at 7:10 AM
My solution was something like that. So I could do all with required permissions and refs and even track the status (this topic was helpfull):


Actions:
 var runTaskSchedulerAction =
                new ElevatedManagedAction("InstallRunTaskAction",
                    Return.check,
                    When.Before,
                    Step.InstallFinalize,
                    Condition.NOT_Installed)
                {
                    UsesProperties = "Installed, REMOVE, REINSTALL",  // send this values to session to get it like session.CustomActionData["Installed"]
                    RefAssemblies =
                        new[] { Path.Combine(pathToSetupProjectBinDirectory, AssebmlyFileName) }
                };

var stopTaskAndUninstallCertsCustomAction =
                new ElevatedManagedAction("RepairRunTaskAction",
                    Return.check,
                    When.Before,
                    Step.InstallFinalize,
                    Condition.NOT_BeingRemoved)
                {
                    //Condition = new Condition("REINSTALL=ALL"), //somehow it with this option it doesnt appear
                    UsesProperties = "Installed, REMOVE, REINSTALL", ,
                    RefAssemblies = new[] { Path.Combine(PathToSetupProjectBinDirectory, .AssebmlyFileName) }
                };
Custom actions looks something like this:
[CustomAction]
        public static ActionResult InstallRunTaskAction(Session session)
        {
            var installDir = session.Property("INSTALLDIR");    
            ActionResult resultOfOpeartion = MyInstallLogic(installDir);

            if (resultOfOpeartion != ActionResult.Success)
            {
                // NOTE: rollback doesn't call unsintall custom action. Need to remove all manually                
// ... my remove logic
                return  ActionResult.Failure;
            }
            return ActionResult.Success;
        }


    [CustomAction]
        public static ActionResult RepairRunTaskAction(Session session)
        {
             var statusService = new InstallationStatusService(session.CustomActionData);
           // service read data from passed propeties added to CustomActionData
           //  where we do check like    CustomActionData["REINSTALL"] == "ALL"

            if (!statusService.IsRepairing) // and here it checks the status of installation
                return ActionResult.Success;

            //... other logic

            return ActionResult.Success;
        }
does project.DefaultUsesProperties can send assembly even to default events like BeforeInstall ?
Marked as answer by asartem on 10/23/2015 at 12:08 AM
Coordinator
Oct 23, 2015 at 1:48 PM
> does project.DefaultUsesProperties can send assembly even to default events like BeforeInstall ?
No it doesn't. I assume it is typo. For assemblies you need to use DefaultRefAssemblies.
And yes DefaultRefAssemblies works for events. All ManagedSetup events are nothing else but predefined custom actions. Thus MSI/WiX runtime copies all CA assemblies and DefaultRefAssemblies into temp dir for the execution and deletes then after.

You may find the following two wiki sections useful:
about DefaultUsesProperties
about DefaultRefAssemblies
Oct 23, 2015 at 6:56 PM
Thank you for quick answers and help!