0

I have a custom action using CAQuietExec that is failing in certain scenarios - the only error messages appear in the log file which is great for me as a developer, and useless for the end user. My goal is to catch the failed action and preset a standard error message prior to rolling back installation.

To do that, based on my research I decided I'd need to write my own custom action dll so I started following this tutorial. At this stage my dll is only logging a test message and trying to pass an error message back to the dialog screens. However when I compile and run the msi, nothing happens at all - nothing logged and no error messages anywhere. Looking at the msi in Orca, it looks OK to me - but as you can see below the test dll should be causing installation to abort immediately if it actually runs and it doesn't.

So my question: Am I going about providing feedback to the user in the best way? And if so, any ideas why my custom action isn't getting run?

Thanks

CustomAction.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Deployment.WindowsInstaller;
using System.Collections.ObjectModel;
using System.Management.Automation;

namespace MyCustomActions
{
public class CustomActions
{
    [CustomAction]
    public static ActionResult CustomAction1(Session session)
    {
        session.Log("Begin CustomAction1");

        DisplayMSIError(session, "A test message");

        return ActionResult.Failure;
    }

    static void DisplayMSIError(Session session, String msg)
    {
        var r = new Record();
        r.SetString(0, msg);
        session.Message(InstallMessage.Error, r);
    }
}
}

Product.wxs

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="MyPackage" Language="1033" Version="0.0.0.1" Manufacturer="Acme" UpgradeCode="GUID">
<Package InstallerVersion="300" Compressed="yes" InstallScope="perMachine" />

<Binary Id="MyCustomActions.CA.dll" src="MyCustomActions\MyCustomActions.CA.dll" />

<CustomAction Id="MyCustomActionsTest"
  Return="check"
  Execute="immediate"
  BinaryKey="MyCustomActions.CA.dll"
  DllEntry="CustomAction1" />

<InstallExecuteSequence>
  <Custom Action="MyCustomActionsTest" After="LaunchConditions"  />
  ...
</InstallExecuteSequence>
</Product>
</Wix>
Anna Forrest
  • 1,711
  • 14
  • 21

2 Answers2

0

Ugh, I have wasted hours on this: but for anyone who is having the same problem I'll post the answer to my problem with the custom action - hopefully it will save you time and frustration.

I put my test action very early in the sequence thinking that I would see the error message dialog then. Therefore I didn't actually attempt to walk through the installation - when the welcome dialog appeared I assumed my action didn't run. However, it must have run because when you actually select a feature and allow installation to progress, the error message appears then.

Still, if anyone can provide some insight as to if this is the best way to give some user feedback from an action that is otherwise running fine with CAQuietExec it would be appreciated.

Anna Forrest
  • 1,711
  • 14
  • 21
  • 2
    I think maybe you just need a bit more understanding of the way an install works. One of the big ideas is the one you ran into: the InstallUISequence runs first, then the execute sequence, so your CA is after the standard UI dialogs. And there's nothing wrong with session.message to tell the user the error. – PhilDW Oct 06 '14 at 17:10
0

what do you think about this solution:

      Record record = new Record();
      record.FormatString = string.Format("Something has gone wrong!");

      session.Message(
         InstallMessage.Error | (InstallMessage) ( MessageBoxIcon.Error ) |
         (InstallMessage) MessageBoxButtons.OK,
         record );

from this answer

Another way - is create your own UI and show any message as you want. Small example:

Custom action:

    [CustomAction]
    public static ActionResult CheckSqlSessionState(Session session)
    {
        try
        {
            session.SendMessage(InstallMessage.Info, "Check Sql Session State");

            return ActionResult.Success;
        }
        catch (Exception exception)
        {
            session.SendMessage(InstallMessage.Error,
                string.Format("Error during the cheking sesssion state. {0}", exception.Message));
            return ActionResult.Failure;
        }
    }

Registration:

    <CustomAction Id="WiXSetup.CustomAction.CheckSqlSessionState"
          BinaryKey="WiXSetup.CustomActions.dll" 
          DllEntry="CheckSqlSessionState" Execute="deferred" />

    <CustomAction Id="SetCheckSqlSessionStateDataValue"
          Return="check"
          Property="WiXSetup.CustomAction.CheckSqlSessionState" />

    <InstallExecuteSequence>
         <Custom Action="SetCheckSqlSessionStateDataValue" Before="WiXSetup.CustomAction.CheckSqlSessionState">NOT Installed</Custom>
         <Custom Action="WiXSetup.CustomAction.CheckSqlSessionState" After="WiXSetup.CustomAction.UpdateHomePageUrls">NOT Installed</Custom>
   </InstallExecuteSequence>

CSharp UI:

   using Microsoft.Deployment.WindowsInstaller;

   private ExternalUIRecordHandler recordHandler;
   // ...

   IntPtr parent = IntPtr.Zero;

   recordHandler = RecordHandler;

   private MessageResult RecordHandler(InstallMessage messageType, Record messageRecord, MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton)
    {
        switch (messageType)
        {
            case InstallMessage.Info:
                log.Info(message);
                return MessageResult.OK;
            case InstallMessage.Initialize:
                log.Debug(message);
                return MessageResult.OK;
            case InstallMessage.ActionData:
                log.Debug(message);
                return MessageResult.OK;
            case InstallMessage.ActionStart:
                log.Debug(message);
                return MessageResult.OK;
            case InstallMessage.CommonData:
                return MessageResult.OK;
            case InstallMessage.Error:
                log.Error(message);
                return MessageResult.OK;
            case InstallMessage.FatalExit:
                log.Fatal(message);
                return MessageResult.OK;
            case InstallMessage.FilesInUse:
                return MessageResult.No;
            case InstallMessage.OutOfDiskSpace:
                break;
            case InstallMessage.Progress:
                return MessageResult.OK;
            case InstallMessage.ResolveSource:
                return MessageResult.No;
            case InstallMessage.ShowDialog:
                return MessageResult.OK;
            case InstallMessage.Terminate:
                return MessageResult.OK;
            case InstallMessage.User:
                return MessageResult.OK;
            case InstallMessage.Warning:
                log.Warn(message);
                return MessageResult.OK;
        }

        return MessageResult.No;
    }
Community
  • 1
  • 1
Pavel Nasovich
  • 223
  • 1
  • 7
  • I guess my concern is more that it seems total overkill to have to recreate an action as a dll just because I can't figure out how to give a nice error message if a CAQuietExec action fails. As my install currently stands - if the action fails the user just sees the generic 'something went wrong' message provided by the installer. I'm totally OK with a generic message, but I want to specify my own string because I can give the user a better idea of where the problem is. – Anna Forrest Oct 06 '14 at 20:03