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.
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.
Figure 3: Adding a reference to ADO.
Figure 4: Adding a reference to the OLE DB Service Components, which provides access to the Data Link Properties dialog (shown in Figure 5).
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:
- Select the setup project.
- Click the View|Editor|Custom Actions menu.
- Right-click the install item and select add custom action.
- Pick the output from MyInstaller as the custom action.
- 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.)
Figure 6: The CustomActionData contains our context parameters, configFile and targetDir.
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:
- http://weblogs.asp.net/scottgu/archive/2007/06/15/tip-trick-creating-packaged-asp-net-setup-programs-with-vs-2005.asp
- http://aspalliance.com/826_CodeSnip_Modifying_Webconfig_file_during_installation
- http://18.220.208.18/db/print.php/2241031
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.