Getting Started With Setup Projects

This article describes the basics of Visual Studio setup and deployment projects. Subsequent articles will cover more on custom actions, .NET installer classes, and an upgrade for the product.

First in a series on Visual Studio setup and deployment

This article describes the basics of Visual Studio setup and deployment projects. Subsequent articles will cover advanced topics, including more on custom actions, .NET installer classes, and how to build an upgrade for the product.

Introduction

Setup and deployment projects have always been a part of the Visual Studio.NET environment. Setup projects can be used to build various deployment packages, most notably Windows Installer setups packaged as MSI files.

I’ll start with a basic setup that installs a C# Windows forms program and adds a shortcut to it.

First, select Setup Project, grouped under Setup and Deployment in the Other Project Types section. Choosing this will give you an empty project for adding your deployment units. With the project selected in Solution Explorer, the View->Editor choice gives you a selection that includes File System – this is where you add your files and shortcuts.

Application Folder is the recommended place to install your programs and other binaries – I’ll explain why in a moment. You can right-click in the Name area and choose Add to browse to your files, or simply drag a file from Windows Explorer. See Figure 1, showing our C# program in the application folder.

177-fig1.gif

Figure1

You can see the application folder’s properties by choosing right-click->Properties (or using F4); these properties are shown in Figure 2. Note that the DefaultLocation property is a sequence of names in square brackets. These are not specific properties of Visual Studio setup projects; they are standard properties that apply to any Windows Installer setup.

177-fig2.gif

Figure 2

ProgramFilesFolder is the property name for the path to the standard program files folder on the target system-you can’t hard-code something like C:\Program Files because that’s not always the location. The other properties, Manufacturer and ProductName, are also standard Windows Installer properties that you can set as appropriate. (Incidentally, if you’re wondering why there is a backslash after Manufacturer but not after ProgramFilesFolder, it’s because ProgramFilesFolder is a folder property, and these properties already include a trailing backslash.) You set Manufacturer and ProductName in the properties of the project (select the project in Solution Explorer and use F4) as shown in Figure 3. I’ll talk about the other properties there shortly.

177-fig3.gif

Figure 3

Note that Figure 2 shows that the property associated with the application folder is named TARGETDIR. This is another standard Windows Installer property that is most often used for the primary installation folder, as it is used here. The application folder is the usual place to install your binaries because it’s the standard folder location for program files.

You add a shortcut by selecting your executable in the IDE, right-clicking and choosing Create Shortcut. Visual Studio puts the shortcut in the same location in the IDE (the application folder), but you can then select it and cut and paste it to its required location, such as the User’s Desktop folder (see in Figure 1, center pane).

It’s common practice to add a shortcut into the user’s program menu, and that’s shown in Figure 4. You add your folder names to the program menu by using right-click and choosing Add Folder. Interestingly enough, you can’t use [Manufacturer] and [ProductName] here – they don’t resolve to the actual values at install time as they do when used in the application folder properties.

177-fig4.gif

Figure 4

You may have noticed that there is a Project Output choice when you right-click on a folder in the IDE and choose Add to add your files. This lets you select a project, the output of which will be added to the folder when you build the projects. This is convenient when you are switching between debug and release builds because it transparently adds the correct files. I don’t recommend using project output, however, for the simple reason that you don’t actually see what files you’re getting. In addition, it might be obvious that project output means something like an executable or DLL, but if you need to add a data file you have no choice except to add it manually.

In Figure 2 we saw that TARGETDIR is a Windows Installer property constructed from other installer properties that can be used in square brackets to cause their resolution to actual values during the install. You can use Windows Installer properties like this in other parts of the installation. It’s common, for example, to want to know where the application was installed. Figure 5 shows you how to do this using the registry view in the IDE. In the name of the registry field, you can create a string-valued registry item (MyLocation in this example) that has the value [TARGETDIR]. Note also in Figure 5 the use of installer properties in the registry key folders, used in the same general way as with TARGETDIR and DefaultLocation in Figure 2.

177-fig5.gif

Figure 5

Resilience

One of the features of a product installed with a Windows Installer setup is its resilience, meaning that it will self-repair if it is damaged by actions such as removing files.

Repair descriptors are created during the install and are associated with certain installed items, including shortcuts. This means that using a shortcut can initiate a health-check of the associated files and restore them if they are missing (and the same repair mechanism can occur to restore registry entries). You can show this quite easily by installing the sample setup and removing the program that is the target of the shortcut (WinForm.exe in this example). If you then use the shortcut referring to the missing file, you’ll see a Windows Installer progress dialog as it repairs the installation by restoring the missing file.

This behavior sometimes takes developers by surprise when they want to install a product and then remove unwanted files or registry entries, so take resilience into account when designing a setup.

Installing .NET assemblies

You can install .NET assemblies (class libraries) into the application folder in the same way as installing executables such as our Windows Forms program. It’s not so clear how you install assemblies into the global assembly cache (GAC), but if you right-click on File System on Target Machine in the Visual Studio IDE view of the file system, you’ll see an Add Special Folder choice. After you’ve clicked this you’ll be able to select the GAC as a destination for your assemblies.

Installing COM servers

You’re probably familiar with the idea that you install COM servers by running Regsvr32.exe on them, which internally calls the DllRegisterServer function that creates the required registry entries. In Windows Installer, you are encouraged to create the registration entries at build time instead of at install time. You do this by selecting the file in the IDE, going to the properties (F4), and setting the appropriate register value. Your choice will depend on whether the file is a traditional COM DLL or a .NET assembly that exports COM classes and interfaces.

Traditional COM servers

A traditional COM DLL will have the following choices in the register property in the IDE:

vsdrfCOMSelfReg

This is the self-registration option where Windows Installer will call the DLL’s DllRegisterServer entry point at install time. This is the setting that’s not encouraged, as described in the next setting, vsdrfCOM.

vsdrfCOM

When you select this and build the MSI file, the registration entries are extracted at build time and stored in the MSI file. When the MSI file gets installed, the DLL is copied to the system and the required registry entries are created from the data in the MSI file, such as the CLSID, ProgId and type library information. The end result is the same as if the DLL self-registered, except that the DLL code was not actually called at install time. This can be particularly important if your DLL is linked to another DLL that hasn’t yet been installed and therefore would result in the DLL failing to load if it was called during the install.

vsdrfCOMRelativePath

This is the same as vsdrfCOM except that the path to the DLL is relative, not absolute; it’s just the actual name of the DLL. This is used in a variety of COM side-by-side called DLL/COM redirection, where an application executable is installed with a redirection file (just the executable file name with .local appended). This particular side-by-side scheme was introduced in the Windows 2000 timeframe and is superseded by the manifest-based registration described in my earlier article on simple COM registration using a side-by-side method.

.NET assemblies as COM servers

If you have a .NET assembly that exports COM interfaces and you mark it to be installed in your application folder, you’ll see register choices for vsdraCOM and vsdraCOMRelativePath. These create path information in the same way as the vsdrfCOM and vsdrfCOMRelativePath choices above (absolute or relative path), but assemblies aren’t registered with DllRegisterServer, so clearly something else is going on here!

If you were to monitor Visual Studio while it was building your setup, you’d see that it runs Regasm.exe, the assembly registration tool. It’s using Regasm.exe to produce a .REG file and then importing the results into the MSI file. In other words, your setup will now produce the same entries for your assembly as running Regasm.exe with the /regfile command line option. It’s important to realize that this not the same as just running Regasm.exe on your assembly. The one crucial difference is the type library information.

When you install a traditional COM DLL, the type library information is usually in the DLL, so the registration options for traditional COM DLLs will also register type library information. .NET assemblies do not contain COM type library data, so if the COM client/server interfaces in your .NET assembly require type library marshaling, you’ll need to export a type library from your assembly (using Tlbexp.exe) and add it to your setup. You’ll see that this .TLB file has a vsdrfCOM choice in the register property, which will cause the type library to be registered on the system in the traditional way.

Searching the system during the install

One of the other view options in the Visual Studio IDE is called Launch Conditions. If you look in this view you’ll see that there are two categories shown: Search Target Machine and Launch Conditions. If you look in Launch Conditions for a setup that installs something requiring the .NET framework, you’ll see that Visual Studio has already added a launch condition for the .NET framework. If you look in the properties for this you’ll see something like Figure 6.

177-fig6.gif

Figure 6

Two interesting items here are the SupportedRuntimes value and the URL. SupportedRuntimes is the .NET Framework runtime version your application uses; this value can be a semi-colon delimited list of the versions your application can use. The InstallUrl is a link to where the framework can be downloaded.

When you run the setup, one of the first things that happens is that Visual Studio runs some code to check for the presence of the framework on the target system. If it’s not there, Visual Studio will offer a yes/no choice for the client to install it from the named InstallUrl. If you look at the documentation for InstallUrl you’ll see that it doesn’t need to be a URL-there are choices for a UNC path or a relative path location that’s useful when installing from a CD.

The other category, Search Target Machine, offers you three choices with a right-click. These choices have one thing in common: They create a property if the search is successful, and the resulting property value tells you something about what the search found. Since these searches can be extremely useful in your install, I’m covering them in some detail. We’ll review the searches first, then look at the properties and what you can do with them.

Search for a file

This search is what I call the brute force approach. Figure 7 shows a search for notepad.exe in the SystemFolder. Note those square brackets, meaning that it’s a standard installer property referring to the system folder on the target machine. This search creates a property called FILEEXISTS1 if it’s successful. It’s brute force because it scans the designated folder to the depth required to find that file. Note that the search stops when it finds the first occurrence of the file, so use the date and version specifications to help find the right one in case there are multiple copies of the file.

177-fig7.gif

Figure 7

Search for a registry entry

In Figure 8 I’ve created a registry search that returns the version of MDAC (Microsoft Data Access Components) on the target system by reading the registry entry where this value is stored. This time I’ve created a property called MDACVER. There’s a root property where you specify the hive for the search; RegKey names the key and Value names the item you want to retrieve. Leave this value empty if you want the default item from that key.

177-fig8.gif

Figure 8

Search for a Windows Installer component

A Windows Installer component is the basic unit of installation in any MSI install. You can’t tell this as you use Visual Studio because it’s generating these components and giving them a GUID when it builds the MSI file, but not exposing them in the IDE. This GUID should not be confused with the ProductCode or UpgradeCode GUIDs. In Figure 9 I’ve created a search for a component GUID that happens to be the value for the WinWord executable from Microsoft Office 2003. I’ll explain how to find values for these GUIDs in a moment.

177-fig9.gif

Figure 9

It can be difficult to see the property values that result from these searches when you’re debugging. One way to do this is by showing them in the welcome screen of the setup. This works because these searches take place before this dialog is shown. Looking at Figure 10, you can see I’ve deleted the standard text in the CopyrightWarning and WelcomeText properties of the welcome dialog and partially replaced them with those search properties in square brackets. Note that the standard welcome text includes [ProductName] to show the product name (see Figure 3 to see where you set it).

177-fig10.gif

Figure 10

When you run the install, the welcome dialog will show you the property values, as in figure 11. As you can see, the WORDPATH property is the directory where Winword.exe is installed, FILEEXISTS1 is the actual location of Notepad.exe, and MDACVER is the version of the data access components.

177-fig11.gif

Figure 11

One of the ways you can use the result of a search is in the launch conditions. The property will be created if the search succeeds, but not if it fails. This means that you can use these properties as Boolean values in a launch condition. If you go to the launch conditions node in the IDE and add a condition, you’ll see that there’s a condition drop-down that is pre-filled with the properties named in your searches. Figure 12 shows a condition that depends on the FILEEXISTS1 property. You can use these searches to add your own error message and stop the install if certain conditions are not satisfied.

177-fig12.gif

Figure 12

The property value returned by the Windows Installer component search is a path to that component and its installed location on the system. One of the places you can use a path is when you create a custom folder in the file system view. Figure 13 shows a custom folder that has a property value of WORDPATH so that the file (Form1.cs in this case) installs to the same folder where Winword.exe is installed. If that WORDPATH value is not set, the install defaults to the application folder specified by TARGETDIR in that DefaultLocation property in the IDE.

177-fig13.gif

Figure 13

So how do you identify the installer component for a GUID? One of the ways is to run a VBScript like the one below:

This script uses the automation features of Windows Installer to enumerate all the installer components on the system (Installer.Components), the products that are using them (Installer.ComponentClients), and the path to the component, which most often is the path to a file. The output is a text file with this kind of content:

“{1EBDE4BC-9A51-4630-B541-2561FA45CCC5} is used by:
Microsoft Office Professional Edition 2003 {91E30409-6000-11D3-8CFE-0150048383C9} at C:\Program Files\Microsoft Office\OFFICE11\WINWORD.EXE

This shows that the named component GUID is used by Microsoft Office 2003. It also shows the ProductCode of Microsoft Office 2003 followed by the path to the component, in this case the path to Winword.exe. These component GUIDs are constant for any particular setup, but you can’t rely on them being constant across all versions of a product (such as several versions of Microsoft Office in this example).

In the registry search, the version of MDAC was recovered as a string value. In this type of search you’re more likely to care about the version value rather than whether it’s in the registry or not. So you might be looking for a way to prevent the installation if (in this example) the version of MDAC is lower than your application requires. You can do this with a launch condition that executes a string comparison on the value of MDACVER.

Figure 14 shows a launch condition requiring an MDAC version greater than 2.81.119.0. That’s higher than the version on my system (2.81.1117.0), so the install will fail, showing the version that is actually present. It works this way because the value retrieved is a string, a REG_SZ. If you get the value of a REG_DWORD from the registry, the value returned into your property will be preceded by a # character, a crosshatch in the USA character set. This lets you know it’s a REG_DWORD, but, more important, if you need to compare it with a value you’ll need to include the # in your comparison string.

177-fig14.gif

Figure 14

A property value retrieved from a search can be used as a condition when you’re installing a file. Figure 15 shows a condition where the file will be installed only if the WORDPATH property has been set.

177-fig15.gif

Figure 15

You can also use standard installer property values in all these places where we’ve used the properties created by the searches. So if you want to install a file only on the Windows NT/2000/XP series, you can condition the file install on the VersionNT property. Similarly, you can use the VersionNT property as a launch condition. For a full list of properties, look in the Windows Installer SDK section of the platform SDK.

Custom Actions

It’s extremely useful to be able to add your own code to a setup. Windows Installer calls these custom actions, and they are basically functions that are called during the install. I’ll look at a simple one for now, and cover them in detail in a subsequent article.

Perhaps “real programmers” look down on VBScript, but there is excellent support in Windows Installer for calling VBScript code. Figure 16 shows an install custom action configured in the IDE, calling a custom action that is a VBScript consisting of this single line of code:

177-fig16.gif

Figure 16

As you can see from Figure 16, CustomActionData is being set to our WORDPATH property value (in square brackets so that it’s resolved as a property value). Session.Property in that script is part of the object model framework supplied to the script at run time.

Windows Installer supplies the same object framework to interact with the hosting environment as Internet Explorer and Windows Script Host. In this example, the Windows Installer session object is being used with Property to get the value of the CustomActionData property. Sure enough, the setup will display the value of the WORDPATH property that’s been passed in via the CustomActionData property.

CustomActionData is a standard Windows Installer property that’s used in certain circumstances to pass property values to custom actions. The mechanism works by setting CustomActionData to the value you wish to pass into the custom action, which then retrieves it by looking at the CustomActionData property. This is unique per custom action, not per setup, so you can use CustomActionData in every custom action you use and it gets the value you passed in. This might seem fairly convoluted, especially if you’re sitting there wondering why you can’t simply display the property with code such as:

msgbox Session.Property (“WORDPATH”)

The reason you can’t do that in a Visual Studio setup project is related to the architecture of Windows Installer and how Visual Studio setup projects use it, something I’ll cover in a later article.

Conclusion

Visual Studio setup projects offer a limited but capable introduction to Windows Installer-based setups. Their advantage is that they give you the basic functionality of a Windows Installer setup with MSI files. Their principal drawback is that they hide some of the more complex functionality of Windows Installer. This article should help you use Visual Studio setup projects successfully.