Implementing a Custom ConnectionString Installer for Setup

Introduction

MSBuild is supposed to be powerful. However, its integration into Visual Studio does not seem intuitive. Sometimes, changing the simplest things—like the text that shows up on the opening screen of the install—seems to be challenging.

MSBuild has a lot of potential, and the integration in .NET is confusing, so I think. However, adding custom actions lets you do just about anything you want to do. One of the things that you might want to do is let the user define the database connection, and if you are security conscious—and we all should be—you might want to encrypt that connection string. In this article, you will define a setup project with a custom action that lets the user define the connection string using the Data Links dialog and encrypts that connection string using RSA encryption.

Creating a Simple Database Project

The database project is used in this case to test the correct functioning of the setup project. Listing 1 contains a very simple console application that consumes the encrypted connection string of the setup project. The database console application reads the connection string, opens the database, and checks the state of the connection. Assuming the setup performed correctly, the database state should be open. Here is the sample database application.

Listing 1: A sample application that tests the correct deployment of the encrypted connection string.

Imports System.Data
Imports System.Data.SqlClient
Imports System.Configuration

Module Module1
   Sub Main()
      Using connection As _
         New SqlConnection(My.MySettings.Default.MyConnectionString)
            connection.Open()
         Console.WriteLine(connection.ConnectionString)
         Console.WriteLine(connection.State = ConnectionState.Open)
         Console.ReadLine()
      End Using
   End Sub
End Module

Adding an Application Connection String

You can use the Visual Studio properties editor to define the connection string referred to (in Listing 1 as My.MySettings.Default.MyConnectionString). The connection string property referred to in the sample application is the same value you want to write in the setup project. To add a connection string in the settings editor (shown in Figure 1), follow the steps listed next:

  • Click the Project|appname Properties menu item.
  • Click the Settings tab (see Figure 1).
  • In the Name field type "MyConnectionString".
  • In the Type column select "(Connection string)".
  • In the Scope column, select Application.
  • In the Value column click the button with the ellipses and use the Connection Properties dialog (see Figure 2) to define the connection string

Figure 1: Use the Settings tab to define a connection string.

Figure 2: The connections property dialog supports defining a connection string.

Reading the Connection String from MySettings

When you set a connection string in the Properties|Settings tab, you can read this value with My.MySettings.Default.settingsname. In the example, the settingsname part is the value provided, MyConnectionString. (Refer to Listing 1 for an example that demonstrates using the My feature and accessing connection strings.)

Connecting to a Database

The using statement is syntactic sugar for the try finally block. (Sugar in moderation is good.) The using clause is designed to invoke Dispose on classes that implement IDisposable. In the case of the SqlConnection class, the Dispose method closes an open connection. Refer to Listing 1 for an example of the using statement.

Implementing a Custom Action

A custom action is a class that inherits from the System.Configuration.Install.Installer class. Because an installer is just a class, you can define any behaviour your solution requires. In the example, you will run the Data Links dialog to help configure the connection string. You'll remove the provider clause from the connection string, and you'll use .NET's built-in support for encrypting a config section.

Creating an Installer Project

An installer is a template in Visual Studio. To create the installer, add a Class Library project to the solution. From the Project|Add New Item, select the Installer Class (see Figure 2). The installer is a component that includes a partial class. The generated partial class—in filename.designer.vb—contains the RunInstaller(True) attribute and the Inherits from System.Configuration.Install.Installer statement. All you have to do is override the Install method and add the desired behavior.

Figure 2: The Add New Item dialog includes an Installer template.

Implementing a Custom ConnectionString Installer for Setup

Before you begin adding the installer code, add a reference to Microsoft ActiveX Data Objects and Microsoft OLEDB Service Components. (Use Figures 3 and 4 to guide you here.) These references support displaying and using the Data Link Properties dialog in Figure 5. The code for the installer is shown in Listing 2.

[Custom4.jpg]

Figure 3: Adding a reference to ADO.

[Custom5.jpg]

Figure 4: Adding a reference to the OLE DB Service Components, which provides access to the Data Link Properties dialog (shown in Figure 5).

[Custom6.jpg]

Figure 5: The Data Link Properties editor that you will use to configure the connection string at install time.

Listing 2: The installer's code that you need to write.

Option Strict On
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Configuration.Install
Imports System.Configuration
Imports System.IO
Imports MSDASC
Imports ADODB
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Diagnostics

Public Class Installer1

      Public Sub New()
         MyBase.New()

         'This call is required by the Component Designer.
         InitializeComponent()

         'Add initialization code after the call to
         'InitializeComponent

      End Sub


   Public Overrides Sub Install( _
      ByVal stateSaver As System.Collections.IDictionary)
      MyBase.Install(stateSaver)

      System.Diagnostics.Debugger.Break()

      Dim instance As DataLinksClass = New DataLinksClass()
      Dim connection As ConnectionClass = New ConnectionClass
      Dim o As Object = connection
      If (instance.PromptEdit(o) = True) Then

         Dim targetDirectory As String = Context.Parameters("targetDir")
         If (targetDirectory Is Nothing) Then
            Throw New InstallException("Target directory not specified")
         End If

         Dim parmConfigFile As String = Context.Parameters("configFile")

         ' what is target config file name
         Dim configFile As String = targetDirectory + parmConfigFile

         Debug.WriteLine(configFile)

         If (File.Exists(configFile) = False) Then
            Throw New InstallException( _
               String.Format("Configuration file {0} not found", _
                             configFile))
         End If

         Dim config As Configuration = _
            ConfigurationManager.OpenExeConfiguration(configFile)
         Dim settings As ConnectionStringSettings = _
            New ConnectionStringSettings
         settings.Name = _
            "UsesConnection.My.MySettings.MyConnectionString"
         Dim connectionToUse As String = _
            GetAdjustedConnectionString(connection.ConnectionString)
         settings.ConnectionString = connectionToUse
         config.ConnectionStrings.ConnectionStrings.Clear()
         config.ConnectionStrings.ConnectionStrings.Add(settings)

         ' Encrypt the connection strings section
         Dim section As ConfigurationSection = _
            config.GetSection("connectionStrings")
         If ((section Is Nothing = False) And _
            (section.IsReadOnly() = False)) Then
            section.SectionInformation.ProtectSection _
               ("RsaProtectedConfigurationProvider")
            section.SectionInformation.ForceSave = True
         End If
         config.Save()
      End If
   End Sub

   Function GetAdjustedConnectionString(ByVal connectionString As String) _
      As String
      Dim parts() As String = _
         connectionString.Split(Convert.ToChar(";"))
      Dim builder As StringBuilder = New StringBuilder()
      For Each part As String In parts

         If (Regex.IsMatch(part, "provider", RegexOptions.Compiled _
            Or RegexOptions.IgnoreCase) = False) Then
            builder.AppendFormat("{0};", part)
         End If
      Next
      Return builder.ToString
   End Function
End Class

The salient aspects of the code in Listing 2 are explained in the sub-sections that follow.

Inserting a Breakpoint for Debugging

First, if you write a custom action/installer, like all code, you will want to debug it. The key to debugging installers is to add this statement:

System.Diagnostics.Debugger.Break().

If you try attaching to a running install process—one way to debug a running process—Visual Studio seems incapable of breaking into the installer. However, with the Debugger.Break() call you will be prompted to attach an instance of Visual Studio.

Creating the DataLinksClass

The first few lines after the Debugger.Break statement create the DataLinksClass and ConnectionClass. The DataLinksClass represents the Data Link Properties dialog and the ConnectionClass will contain the connection string. The call to instance.PromptEdit displays the dialog.

Reading Context Parameters

The Context.Parameters dictionary lets you get values from the Setup process. You want the installation target directory and the name of the configuration file to modify. (You will set these when you define the setup project and custom action.)

If, for some reason, the config file can't be found, the code throws an exception that will roll back the install. You could, in practice, let the install continue because you could always set the connection string manually, too.

Using the ConfigurationManager to Overwrite the ConnectionString

The code wraps by using the ConfigurationManager.OpenExeConfiguration, reading the connection string section, and setting the connection string dynamically. The call to GetAdjustedConnectionString strips out the Provider, which you don't need.

Finally, you use the ConfigurationSection class and the ProtectSection method to encrypt the connection string before saving. .NET includes a provider statement to the config section and automatically "knows" how to unencrypt and encrypted config section. When finished, you save the changes.

Defining the Setup Project

Visual Studio defines a setup project template and includes a wizard. You can add the setup project by using the wizard. The wizard will include the application and the custom installer.

To add the custom action, follow these steps:

  1. Select the setup project.
  2. Click the View|Editor|Custom Actions menu.
  3. Right-click the install item and select add custom action.
  4. Pick the output from MyInstaller as the custom action.
  5. Press F4 to open the Properties window and, in the CustomActionData field, add the parameters your installer needs: /configFile="UsesConnection.exe"/targetDir="[TARGETDIR]\"

Figure 6 shows approximately what Visual Studio will look like after you complete the steps listed above. (Hint: Pay close attention to the use of quotes and slashes in the CustomActionData.)

[Custom7.jpg]

Figure 6: The CustomActionData contains our context parameters, configFile and targetDir.

Implementing a Custom ConnectionString Installer for Setup

Run and Test

Finish up by running the install project by selecting the setup project and clicking the Project|Install menu item. After running the install, open the UsesConnection.exe.config file and you will see the encrypted connection string and the Rsa provider information. Run the deployed UsesConnection project, and you will see that the connection string works, implying clearly that .NET has unencrypted the string for you.

If you need some more help, check these supporting links:

Summary

MSBuild is powerful. The setup integration in .NET is sometimes a little confusing. However, when you figure out the Custom Actions and installer behaviors you can add just about any behavior you want. In ther example, you used an existing COM feature through Interop to define a connection string. You also used the .NET framework to encrypt the connection string. (This is a good idea in most cases, falling under the auspices of protecting secrets.)

I also including information on debug installers using Debugger.Break. Javascript uses debugger, most Visual Studio code can be debugged in the IDE, but installers need Debugger.Break. (It'd be nice if Microsoft homogenized debugging here too.) I hope with these elements combined, you will be able to create some cool install features for your applications. Enjoy!

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Look for his upcoming book LINQ Unleashed for C# from Sams. You may contact him for technology questions at pkimmel@softconcepts.com.

If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org. Glugnet has two groups, Glugnet in East Lansing and Glugnet-Flint (in Flint, of course) and some of the best sponsors (and some of the best companies) in the free world. If you are interested in attending, check out the www.glugnet.org web site for updates or contact me.

Copyright © 2007 by Paul T. Kimmel. All Rights Reserved.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Instead of only managing projects organizations do need to manage value! "Doing the right things" and "doing things right" are the essential ingredients for successful software and systems delivery. Unfortunately, with distributed delivery spanning multiple disciplines, geographies and time zones, many organizations struggle with teams working in silos, broken lines of communication, lack of collaboration, inadequate traceability, and poor project visibility. This often results in organizations "doing the …

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds