Question

I have an installer which installs a database. The database is created alongside some logins. To create the logins I am using the master database in SqlString elements. Access to the master database is only granted to users who have very high privileges on the SQL server. Oftentimes the installation is aborted because a SQL string designated for the master database cannot be executed due the lack of rights.

I want to edit my installer, so that when a SqlString element cannot be executed, the SQL part of the installation shall be skipped. After the installation has taken place I want the user to be able to execute the SQL statements herself. Every SQL action taken by my installer is stored in SqlString elements. The SqlString elements contain a lot of properties which get replaced during the installation. I want to extract the content of all edited SqlString elements into one sql file stored in the user directory.

I guess I'll have to write a customaction which takes place after the sqlextension has substituted the properties. And then I'll have to access these altered strings. Is there any way I can do this?

Example SqlString element:

    <sql:SqlDatabase Id="MasterDB" Server="[SQLSERVER_SERVER]" Instance="[SQLSERVER_INSTANCENAME]" Database="master" />

<sql:SqlString
      SqlDb="MasterDB"
      Id="CreateNetworkServiceAccount"
      ExecuteOnInstall="yes"
      ContinueOnError="no"
      SQL="IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = N'{[WIX_ACCOUNT_NETWORKSERVICE]}')
        CREATE LOGIN [\[]{[WIX_ACCOUNT_NETWORKSERVICE]}[\]] FROM WINDOWS WITH DEFAULT_DATABASE=[\[]master[\]]"
      Sequence="101"/>

Example of the sql file I'd like to have after the SqlStrings have failed:

USE master;
IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = N'NT AUTHORITY\Network Service')
CREATE LOGIN [NT AUTHORITY\Network Service] FROM WINDOWS WITH DEFAULT_DATABASE=[master]
Was it helpful?

Solution

I have solved this problem with a rather odd solution. I have written a CustomAction which extracts the String elements from the SqlString table and then replaces the formatted fields with the appropriate Properties stored in the session. To have access to the session variable, the CustomAction has to be executed as immediate. I've scheduled it before InstallFinalize to be given access to the PersonalFolder property. With this property I am able to store a Sql script generated by the entries in the SqlScript table in the users Documents directory. To account for different databases in the Installation, I have included a lookup in the SqlDatabase table.

Here is the code to the CustomAction:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Deployment.WindowsInstaller;
using System.IO;
using System.Text.RegularExpressions;

namespace SaveSqlStrings
{
    public class CustomActions
    {
        [CustomAction]
        public static ActionResult SaveSqlStrings(Session session)
        {
            StringBuilder sqlStrings = new StringBuilder();
            Database db = session.Database;
            View view = db.OpenView("SELECT * FROM `SqlString`");
            IList<string> SqlStringElements = db.ExecuteStringQuery("SELECT `String` FROM `SqlString`");
            Regex bracketedProperties = new Regex(@"\[(\b[A-Z_]*\b)\]");
            Regex formattedProperties = new Regex(@"{\[(\b[A-Z_]*\b)\]}");
            Regex openeningSquareBrackets = new Regex(@"\[\\\[\]");
            Regex closingSquareBrackets = new Regex(@"\[\\\]\]");
            string sqlDb_ = "";
            string sqlString = "";
            string Database = "";
            foreach (string dbString in SqlStringElements)
            {
                sqlDb_ = (string)db.ExecuteScalar("SELECT `SqlDb_` FROM `SqlString` WHERE `String` ='{0}'",dbString);
                sqlString = (string)db.ExecuteScalar("SELECT `SQL` FROM `SqlString` WHERE `String` ='{0}'",dbString);
                view.Close();
                view = db.OpenView("SELECT * FROM `SqlDatabase`");
                Database = (string)db.ExecuteScalar("SELECT `Database` from `SqlDatabase` WHERE `SqlDb`='{0}'", sqlDb_);
                if(bracketedProperties.IsMatch(Database))
                {
                    Database = bracketedProperties.Match(Database).Groups[1].Value;
                    Database = session[Database];
                }
                if (openeningSquareBrackets.IsMatch(sqlString))
                    sqlString = openeningSquareBrackets.Replace(sqlString, "[");
                if (closingSquareBrackets.IsMatch(sqlString))
                    sqlString = closingSquareBrackets.Replace(sqlString, "]");
                if(formattedProperties.IsMatch(sqlString))
                {
                    string propertyName = formattedProperties.Match(sqlString).Groups[1].Value;
                    string propertyValue = session[propertyName];
                    sqlString = formattedProperties.Replace(sqlString, propertyValue);
                }
                sqlStrings.AppendLine(String.Format("use {0}",Database));
                sqlStrings.AppendLine(sqlString);
            }
            string home = session["PersonalFolder"];
            string sqlPath = string.Concat(home, @"Script.sql");
            try
            {
                File.WriteAllText(sqlPath, sqlStrings.ToString());
            }
            catch (Exception ex)
            {
                session["FailedTowrite"] = sqlPath;
            }
            view.Close();
            db.Close();
            return ActionResult.Success;
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top