Link to home
Start Free TrialLog in
Avatar of CipherIS
CipherISFlag for United States of America

asked on

WiX - Custom Action Error - DLL is required for this install to complete

I have the below WiX code.  I created a Custom Action.  I have two issues.

1. I have to put the entire path for the SourceFile -> SourceFile="C:\MyDir\GetConnections.dll".  Anyway to shorten?
2.  I'm receiving an error "A DLL is required for this install to complete could not run." etc..  Can't figure out what is causing it.

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
	<Fragment>
    <CustomAction Id="FillingListBox" BinaryKey="FillListBox" DllEntry="FillListBox" />
    
    <UI Id="DatabaseScreen">
      <Dialog Id="DatabaseDialog"
              Width="370"
              Height="270"
              Title="Database Connections"
              NoMinimize="no">

        <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no"
                 Text="!(loc.LicenseAgreementDlgBannerBitmap)" />

        <Control Id="Dbases" Type="ListBox" Sorted="no" 
                 Indirect="no" Property="DBaseValues" 
                 X="10" Y="50" Width="150" Height="180">
          <ListBox Property="DBaseValues">
            <ListItem Text="ARGHH!" Value="1"/>
          </ListBox>
        </Control>
        
        <Control Id="Title"
                 Type="Text"
                 X="15"
                 Y="23"
                 Width="280"
                 Height="15"
                 Transparent="yes"
                 NoPrefix="yes"
                 Text="Database Information" />

        <Control Id="InstallButton"
                 Type="PushButton"
                 Text="Install"
                 Height="17" Width="56"
                 X="300" Y="243"
                 TabSkip="yes">
          <Publish Event="EndDialog" Value="Return" />
        </Control>

        <Control Id="BackButton"
         Type="PushButton"
         X="235" Y="243"
         Height="17" Width="56"
         Text="Back">
          <Publish Event="NewDialog" Value="LicenseDialog" />
        </Control>

        <Control Id="CancelButton"
                 Type="PushButton"
                 X="10" Y="243"
                 Height="17" Width="56"
                 Text="Cancel">
          <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
        </Control>
      </Dialog>

      <InstallUISequence>
        <Custom Action="FillingListBox" After="CostFinalize" />
        <Show Dialog="DatabaseDialog" After="FillingListBox" />
      </InstallUISequence>

    </UI>

    <Binary Id="FillListBox" SourceFile="C:\MyDir\GetConnections.dll" />
  </Fragment>
</Wix>

Open in new window

    public class DBase
    {
        public string Name { get; set; } = string.Empty;
    }

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

            session["USER_DATABASES"] = "DefaultValue";

            int i = 0;
            int j = 0;
            string s = string.Empty;
            var dt = SmoApplication.EnumAvailableSqlServers(false);

            if (dt != null)
            {
                List<DBase> db = new List<DBase>();
                foreach (DataRow row in dt.Rows)
                {
                    foreach (var item in row.ItemArray)
                    {
                        if (i == j)
                        {
                            db.Add(new DBase()
                            {
                                Name = item.ToString()
                            });
                        }
                        else
                        {
                            break;
                        }
                        j++;
                    }
                    i++;
                }

                var z = (from y in db
                              select y).ToList();

                Microsoft.Deployment.WindowsInstaller.View listBoxView = session.Database.OpenView("SELECT * FROM ListBox");

                i = 1;
                foreach (var db1 in z)
                {
                    s += db1.Name + "; ";
                    Record newListBoxRecord = new Record(4);
                    newListBoxRecord[1] = "DBaseValues";
                    newListBoxRecord[2] = i;
                    newListBoxRecord[3] = db1.Name;
                    newListBoxRecord[4] = db1.Name;
                    listBoxView.Modify(ViewModifyMode.InsertTemporary, newListBoxRecord);
                    i++;
                }
            }
            //MessageBox.Show(s, "", MessageBoxButtons.OK, MessageBoxIcon.Information);

            return ActionResult.Success;
        }
    }

Open in new window

SOLUTION
Avatar of Vadim Rapp
Vadim Rapp
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of CipherIS

ASKER

I downloaded Process Monitor.  I do not see my install in the Process Monitor nor the DLL.  How do I debug it?

Oh, and for the Sourcefile I typed c:\.... but it's actuall G:\....  I don't know if that makes a difference.  I'm developing this on an external drive.
The DLL was created by the CustomAction.  Below is the example that I used.

https://www.firegiant.com/wix/tutorial/standard-libraries/custom-actions-and-user-interface/
> I do not see my install in the Process Monitor nor the DLL.

Process monitor monitors processes, so during the installation it will be msiexec. At some point it probably will try to load the dll, but how exactly it will do it depends on how WiX packs it in the installation. The log should tell. You can also open the MSI in Orca and inspect the custom action.

WiX is not the most user-friendly authoring tool, to say the least, and involves quite a learning curve. And on the top of that, you are trying to use in your installation quite advanced stuff, such as this custom action in C++ that connects to the installation session from within, or embedded installation of sql server - where each would be a nontrivial project even with more user-friendly tool, and for the developer already intimately familiar with it. Maybe it would make sense to make your learning curve less steep - such as, when you read those tutorials, you first try each of them verbatim as it's published, on special simple sample projects, and do it in your real project when you already have mastered it. Then move to the next challenge.
It is unfortunate that it requires a steep learning curve.  I was able to get an install to work.  I am now modifying the process and creating UI's.  I was successfully able to create UI's.  

Do you have a recommendation of another tool or any advice with a lesser learning curve that will give me the results I'm looking for?

It would be nice if the tutorials were more explicit, meaning the code samples worked.
Myself, I have been using Wise Installer for many years. Installshield as well. Both are far from being free, and Wise is actually discontinued, so you can't acquire it legally anymore, although imho it's still the best.

Re. learning curve, I mostly meant the absence of visual UI in WiX, so you have to write everything by hand, remembering the keywords, and not seeing the big picture, so to speak.

My main suggestion was basically to separate the complexity of learning new nontrivial tool from the complexity of creating a nontrivial project - with self-written custom actions, embedded installation and configuration of the database, and more. Combining both of these complexities makes it difficult. I'm sure you'll succeed, but most likely you will spend many times more time; plus, if you are going to release the installation to the public or customer, most likely you will make some mistakes because it's new tool, which you wouldn't make after several years of using it, so the end result may be not as excellent as it might be. Make sure to run all the validations.
I used to use Wise in the 90's.  Can't use Installshield so I'm trying to learn this tool.  I miss Wise.  I just wish things were clear cut.  Even their examples and documentation isn't.  I've had to figure out how to get stuff to work.  

I downloaded Orca.  This is what I have.
Orca.jpg
What's in Binary table?
See attached
Binary.jpg
Let's try to figure out whether the installation fails to load the dll, or it launches it, but the code itself fails.

1. Check detailed installation log, or post the log here. Is "Begin GetSQLConnections" in the log?

2. I would also uncomment MessageBox.Show in the dll, and put another one in the very beginning. If you see the beginning message but not the ending one, then either debug by attaching to the process msiexec, once the beginning message is on the screen, or insert even more messagebox's after each line, so you figure where it bombs out.
also - just noticed... in your c++ code, there's smoapplcaition.EnumAvailableSqlServers . But is smoapplication available to the installation, if there's no sql server yet?
There is a SQL Server on my local machine.  I'm testing to see what show's up.  I wrote the C# code in a .NET app and it works.  I then copied it to the CustomActions.

Ok, I fixed some things and figure out some stuff but still have same issue.

1.  My DLLEntry was wrong.  Fixed that.
2.  Renamed BinaryID so it matches BinaryKey.
3.  Renamed CustomActionID so it matches CustomAction in InstallUISequuence.

Also, figured out the sourcefile in <Binary>  I've seen examples where both source.dll and source.ca.dll were used.  If i use my sqlconnection.dll (which is .dll) then I get the error.  If I change the extension to .ca.dll the installer flashes for a sec and disappears.

    <Binary Id="FillListBoxBinary"
            SourceFile="$(var.SQLConnections.TargetDir)$(var.SQLConnections.TargetName).dll" />

    <CustomAction Id="FillListBoxAction" BinaryKey="FillListBoxBinary" DllEntry="GetSQLConnections"
        Execute="immediate" Return="check" />
    
    <InstallUISequence>
      <Custom Action="FillListBoxAction" After="CostFinalize" />
      <Show Dialog="DatabaseDialog" After="FillListBoxAction" />
    </InstallUISequence>

Open in new window


Where is the installation log?
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>Where is the installation log?

https://support.microsoft.com/en-us/kb/223300

the log itself will be in %temp% folder.
Error 1723. There is a problem with this Windows Installer package. A DLL required for this install to complete could not be run. Contact your support personnel or package vendor.  Action FillListBoxAction, entry: GetSQLConnections, library: C:\Users\*****\AppData\Local\Temp\MSI325D.tmp
=== Logging stopped: 9/7/2016  15:36:12 ===
Commented out the <Binary> and the code that wires up the Custom Action on the UI.  I added the below to the ListItem and it is displaying the name of the DLL with the correct path.
        <Control Id="DbaseList" Type="ListBox" Sorted="no" 
                 Indirect="no" Property="DBaseValues" 
                 X="10" Y="50" Width="150" Height="180">
          <ListBox Property="DBaseValues">
            <ListItem Text="ARGHH!" Value="1"/>
            <ListItem Text="$(var.SQLConnections.TargetDir)$(var.SQLConnections.TargetName).dll" Value="2"/>
          </ListBox>
        </Control>

Open in new window

When I set Return to "ignore" the install will run but that dialog will start first when it is supposed to be third.  Anyway to debug the Custom Action?  I don't know if there is an issue in the Custom Action.   Wish I could set a breakpoint and walk through it.
    <CustomAction Id="FillListBoxAction" BinaryKey="FillListBoxBinary" 
                  DllEntry="GetSQLConnections" Execute="immediate" Return="ignore" />

Open in new window

I made it simpler for troubleshooting.  Same exact error.  There is no code in the CustomAction.  

Code in the UI
    <Binary Id="FillListBoxBinary"
        SourceFile="$(var.DBConnect.TargetDir)$(var.DBConnect.TargetName).dll" />

    <CustomAction Id="FillListBoxAction"
                  BinaryKey="FillListBoxBinary"
                  DllEntry="GetListofDB"
                  Execute="immediate" Return="check" />

    <InstallUISequence>
      <Custom Action="FillListBoxAction" After="CostFinalize" />
      <Show Dialog="DatabaseDialog" After="FillListBoxAction" />
    </InstallUISequence>

Open in new window

My Custom Action
namespace DBConnect
{
    public class CustomActions
    {
        [CustomAction]
        public static ActionResult GetListofDB(Session session)
        {
            session.Log("Begin CustomAction1");

            return ActionResult.Success;
        }
    }
}

Open in new window

ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Ok, the .CA.dll seems to work.  There are two issues though.  

1.  The database screen shows up first and not the license screen.  When I click on install then it goes to the License Screen and starts navigating correctly.
2.  If I use a for loop or a foreach loop to get the values from my collection it seems to be causing an issue.  
                    i = 2;
//                    foreach (var db1 in z)
//                    {
                        View listBoxView = session.Database.OpenView("SELECT * FROM ListBox");
                        Record newListBoxRecord = new Record(4);
                        newListBoxRecord[1] = "DBaseValues";
                        newListBoxRecord[2] = i;
                        newListBoxRecord[3] = "Test"; //db1.Name;
                        newListBoxRecord[4] = "Test"; //db1.Name;
                        listBoxView.Modify(ViewModifyMode.InsertTemporary, newListBoxRecord);
                    //}

Open in new window



Error
The installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code is 2878. The arguments are: DatabaseDialog, DbaseList, DESKTOP-******
=== Logging stopped: 9/8/2016  9:31:18 ===
Dbase.png
I figured out that this needs to be outside of the for loop or foreach loop.
View listBoxView = session.Database.OpenView("SELECT * FROM ListBox");
Record newListBoxRecord = new Record(4);

Open in new window

When I do below it works but having it in a loop is better as I don't know what is going to return from the list.  Any ideas?
View listBoxView = session.Database.OpenView("SELECT * FROM ListBox");
Record newListBoxRecord = new Record(4);
newListBoxRecord[1] = "DBaseValues";
newListBoxRecord[2] = i;
newListBoxRecord[3] = z[0].Name;  //sName;  //"Test " + k; //db1.Name;
newListBoxRecord[4] = z[0].Name;  //sName;  //"Test " + k; //db1.Name;
listBoxView.Modify(ViewModifyMode.InsertTemporary, newListBoxRecord);

newListBoxRecord[1] = "DBaseValues";
newListBoxRecord[2] = 3;
newListBoxRecord[3] = z[1].Name;  //sName;  //"Test " + k; //db1.Name;
newListBoxRecord[4] = z[1].Name;  //sName;  //"Test " + k; //db1.Name;
listBoxView.Modify(ViewModifyMode.InsertTemporary, newListBoxRecord);

Open in new window

I think at this point we are already on purely programming issue in your code, so you're on your own :-) - especially since I don't program in C# (I do in VB) and I don't use WiX (I still use Wise).

But that said, the tried-and-true method of debugging, absent any better, is to put "messagebox" after each line in in the code, and see which one bombs out.


                  foreach (var db1 in z)
//                    {
messagebox "1"
                        View listBoxView = session.Database.OpenView("SELECT * FROM ListBox");
messagebox "2"
                        Record newListBoxRecord = new Record(4);
messagebox "3"
                        newListBoxRecord[1] = "DBaseValues";
messagebox "4"

and so on. Then when it runs, you will see which number was the last.

More elaborate is to try to debug by attaching debugger to the process in Visual Studio - see https://msdn.microsoft.com/en-us/library/3s68z0b3.aspx. However, in this case it's probably very problematic because of .ca.dll which probably won't have the symbols.
Just doesn't make sense why adding a for loop would cause a problem.
...by the way, if you only want to list available servers on local machine, it would be much easier with vbcript custom action that would enumerate HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL. Or if not only local but you have SQLDMO installed, you might use that in vbscript as well. Vbscript would avoid all the trouble described in the articles about using managed code in custom actions.
It's not the code in the custom action.  It returns a "Success".  I put message boxes as you suggested.
looking at your code, it looks like you are reopening the view in every loop iteration. It's probably not what you want. Openview probably has to be outside of foreach.
Yes, I moved it outside the loop.  I thought I commented about that.  I did in ID: 41789682.  

Ok, so it is working.  Got all the bugs ironed out.  Now the only issue I have is that the license screen is not opening first.  The database screen is.  If I click on the install on the database then it will go to the license and continue to navigation and back to database screen.  Any idea how to resolve that?
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Ok.  Thanks for that info regarding SQL.  I did resolve my issue with the screens not displaying in the right order.

The issue was that the custom action is set to run immediate so the sequence was putting it before the license on my install.  I changed the database dialog to go after the license dialog in Orca.  The custom action still has to run first.

This gets updated everytime I build.  It's a pain but at least I know what to do.