Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WIX, Dot Net managed custom Action, dynamically fill combo box with SQL Server instances, MSI

In WIX am in-need of a dot net managed custom code to dynamically populate a combo box with the values of sql server instances in that network.

I tried to google but got nothing worked

Any help is greatly appreciated.

like image 778
Ramji Avatar asked Nov 01 '09 17:11

Ramji


3 Answers

    [CustomAction]   
    public static ActionResult FillServerInstances(Session xiSession)
    {         
        xiSession.Log("Begin CustomAction");

        xiSession.Log("Opening view");
        View lView = xiSession.Database.OpenView("DELETE FROM ComboBox WHERE ComboBox.Property='DBSRVR'");
        lView.Execute();

        lView = xiSession.Database.OpenView("SELECT * FROM ComboBox");
        lView.Execute();

        int Index = 1;
        bool flag = false;
        try
        {
            foreach (DataRow dr in Microsoft.SqlServer.Management.Smo.SmoApplication.EnumAvailableSqlServers(false).Rows)
            {
                String InstanceName = dr["Name"].ToString();

                if (InstanceName.Equals(xiSession["ComputerName"] + @"\" + xiSession["SQLINSTANCENAME"], StringComparison.InvariantCultureIgnoreCase))
                { flag = true; }

                Record lRecord = xiSession.Database.CreateRecord(4);
                xiSession.Log("Setting record details");
                lRecord.SetString(1, "DBSRVR");
                lRecord.SetInteger(2, Index);
                lRecord.SetString(3, InstanceName);
                lRecord.SetString(4, InstanceName);

                xiSession.Log("Adding record");
                lView.Modify(ViewModifyMode.InsertTemporary, lRecord);

                ++Index;
            }
        }
        catch (Exception ex)
        {
            logException(xiSession, ex);              
        }
        if (flag)
        {
            xiSession["DBSRVR"] = xiSession["ComputerName"].ToString() + @"\" + xiSession["SQLINSTANCENAME"].ToString();
        }

        lView.Close();

        xiSession.Log("Closing view");
        lView.Close();
        return ActionResult.Success;       
    }
like image 124
Ramji Avatar answered Oct 07 '22 22:10

Ramji


OP didn't ask, but in case someone is wondering, yes, You can do this in script, as well. This example populates a ListBox, not a ComboBox, but the idea is the same.

function EnumerateWebSites_CA()
{
    try 
    {
        LogMessage("function EnumerateWebSites_CA() ENTER");

        var c = 1;
        var serverBindings, aBindings;

        var listboxesView = Session.Database.OpenView("SELECT * FROM ListBox");
        listboxesView.Execute();

        var record = Session.Installer.CreateRecord(4);
        record.StringData(1) = "WEBSITE";     // Property
        record.IntegerData(2) = c++;          // display order
        record.StringData(3) = "Server";      // returned by the selection
        record.StringData(4) = "Server-wide"; // displayed in the UI
        listboxesView.Modify(MsiViewModify.InsertTemporary, record);

        // Create this table dynamically.  We could also create this
        // custom table in the WiX .wxs file , but that's not necessary.
        var query = "CREATE TABLE AvailableWebSites " + 
            "(Num INT NOT NULL, Name CHAR(64), Desc CHAR(64), Port CHAR(16) NOT NULL, IP CHAR(32), Hostname CHAR(80) PRIMARY KEY Num)";
        var createCmd = Session.Database.OpenView(query);
        createCmd.Execute();
        createCmd.Close();

        LogMessage("Table 'AvailableWebSites' has been created");

        var websitesView = Session.Database.OpenView("SELECT * FROM AvailableWebSites");
        websitesView.Execute();

        LogMessage("Query from Table 'AvailableWebSites' has returned");

        // do a WMI query to get the list of Websites on the server
        var iis = GetObject("winmgmts://localhost/root/MicrosoftIISv2"); 

        // See the metabase hierarchy diagram here:
        //   http://msdn.microsoft.com/en-us/library/ms524661.aspx

        // http://msdn.microsoft.com/en-us/library/ms525545.aspx
        // list "virtual servers", which is the same as websites. 
        query  = "SELECT * FROM IIsWebServerSetting" 

        // get the list of virtual servers
        var results = iis.ExecQuery(query);

        LogMessage("WMI Query completed.");

        LogMessage("WMI Query results : " + typeof results);

        for(var e = new Enumerator(results); !e.atEnd(); e.moveNext()) 
        { 
            var site = e.item();
            // site.Name                   // W3SVC/1, W3SVC/12378398, etc
            // site.Name.substr(6)         // 1, 12378398, etc
            // site.ServerComment)         // "Default Web Site", "Site2", etc
            // site.ServerBindings(0).Port // 80, 8080, etc

            LogMessage("Web site " + site.Name);

            LogMessage("listbox record");
            record = Session.Installer.CreateRecord(4);
            record.StringData(1) = "WEBSITE";
            record.IntegerData(2) = c++;
            record.StringData(3) = site.Name.substr(6); // site.Name;
            record.StringData(4) = site.ServerComment + " (" + site.Name + ")";
            listboxesView.Modify(MsiViewModify.InsertTemporary, record);

            LogMessage("websites record");
            LogMessage("website(" + site.Name + ") name(" + site.ServerComment + ") port(" + site.ServerBindings(0).Port + ")"); 
            record = Session.Installer.CreateRecord(6);
            record.IntegerData(1) = parseInt(site.Name.substr(6));  // WebSiteNo
            record.StringData(2) = site.Name;                       // name, like W3SVC/1
            record.StringData(3) = site.ServerComment;              // WebSiteDescription
            record.StringData(4) = site.ServerBindings(0).Port;     // WebSitePort
            record.StringData(5) = site.ServerBindings(0).Ip;       // WebSiteIP; maybe empty
            record.StringData(6) = site.ServerBindings(0).Hostname; // WebSiteHeader; maybe empty
            websitesView.Modify(MsiViewModify.InsertTemporary, record);
        }
        listboxesView.Close();
        websitesView.Close();

        LogMessage("function EnumerateWebSites_CA() EXIT");
    }

    catch (exc1)
    {
        Session.Property("CA_EXCEPTION") = exc1.message ;
        LogException(exc1);
        return MsiActionStatus.Abort;
    }
    return MsiActionStatus.Ok;
}

There are a few supporting methods:

var MsiViewModify = 
{
    Refresh          : 0,
    Insert           : 1,
    Update           : 2,
    Assign           : 3,
    Replace          : 4,
    Merge            : 5,
    Delete           : 6,
    InsertTemporary  : 7,   // cannot permanently modify the MSI during install
    Validate         : 8,
    ValidateNew      : 9,
    ValidateField    : 10,
    ValidateDelete   : 11
};


// http://msdn.microsoft.com/en-us/library/sfw6660x(VS.85).aspx
var Buttons = 
{
    OkOnly           : 0,
    OkCancel         : 1,
    AbortRetryIgnore : 2,
    YesNoCancel      : 3
};

var Icons= 
{
    Critical         : 16,
    Question         : 32,
    Exclamation      : 48,
    Information      : 64
}

var MsgKind =
{
    Error            : 0x01000000,
    Warning          : 0x02000000,
    User             : 0x03000000,
    Log              : 0x04000000
};

// http://msdn.microsoft.com/en-us/library/aa371254(VS.85).aspx
var MsiActionStatus = 
{
    None             : 0,
    Ok               : 1, // success
    Cancel           : 2,
    Abort            : 3,
    Retry            : 4, // aka suspend?
    Ignore           : 5  // skip remaining actions; this is not an error.
};

// spool an informational message into the MSI log, if it is enabled. 
function LogMessage(msg)
{
    var record = Session.Installer.CreateRecord(0);
    record.StringData(0) = "CustomActions: " + msg;
    Session.Message(MsgKind.Log, record);
}

// Pop a message box.  also spool a message into the MSI log, if it is enabled. 
function LogException(exc)
{
    var record = Session.Installer.CreateRecord(0);
    record.StringData(0) = "CustomActions: Exception: 0x" + decimalToHexString(exc.number) + " : " + exc.message;
    Session.Message(MsgKind.Error + Icons.Critical + Buttons.btnOkOnly, record);
}

function decimalToHexString(number)
{
    if (number < 0)
        number = 0xFFFFFFFF + number + 1;
    return number.toString(16).toUpperCase();
}

And yes, I disagree philosophically with Rob Mensching's viewpoint that Script Custom Actions suck

like image 21
Cheeso Avatar answered Oct 07 '22 23:10

Cheeso


There's a C++ CA to do exactly this in http://msiext.codeplex.com.

like image 5
dB. Avatar answered Oct 08 '22 00:10

dB.