How to create setup and deployment using Visual Studio.Net

.NET programming topics
Post Reply
User avatar
Shane
Captain
Captain
Posts: 226
Joined: Sun Jul 19, 2009 9:59 pm
Location: Jönköping, Sweden

How to create setup and deployment using Visual Studio.Net

Post by Shane » Sat Jan 30, 2010 8:35 pm

PART 1 - Getting started with setup projects
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.
fig1.gif
fig1.gif (18.16 KiB) Viewed 11813 times
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.
fig2.gif
fig2.gif (10.05 KiB) Viewed 11813 times
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.
fig3.gif
fig3.gif (15.18 KiB) Viewed 11813 times
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.
fig4.gif
fig4.gif (20.4 KiB) Viewed 11813 times
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.
fig5.gif
fig5.gif (22.75 KiB) Viewed 11813 times
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.
fig6.gif
fig6.gif (8.98 KiB) Viewed 11813 times
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.
fig7.gif
fig7.gif (10.42 KiB) Viewed 11813 times
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.
fig8.gif
fig8.gif (8.07 KiB) Viewed 11813 times
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.
fig9.gif
fig9.gif (7.33 KiB) Viewed 11813 times
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).
fig10.gif
fig10.gif (19.44 KiB) Viewed 11813 times
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.
fig11.gif
fig11.gif (9.99 KiB) Viewed 11813 times
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.
fig12.gif
fig12.gif (7.61 KiB) Viewed 11813 times
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.
fig13.gif
fig13.gif (23.1 KiB) Viewed 11813 times
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:

Code: Select all

Option Explicit
Public installer, fullmsg, comp, a, prod, fso, pname, ploc, pid, psorce

Set fso = CreateObject("Scripting.FileSystemObject")
Set a = fso.CreateTextFile("comps.txt", True)

‘ Connect to Windows Installer object
Set installer = CreateObject("WindowsInstaller.Installer")
a.writeline ("Components")
on error resume next
For Each comp In Installer.Components
      a.writeline (comp & " is used by:")
      for each prod in Installer.ComponentClients (comp)
            pid = installer.componentpath (prod, comp)
            pname = installer.productinfo (prod, "InstalledProductName")
            a.Writeline (" " & pname & " " & prod & " at " & pid)
      Next
Next
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.
fig14.gif
fig14.gif (8.19 KiB) Viewed 11813 times
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.
fig15.gif
fig15.gif (13.1 KiB) Viewed 11813 times
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:

Code: Select all

msgbox Session.Property ("CustomActionData")
fig16.gif
fig16.gif (22.85 KiB) Viewed 11813 times
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:

Code: Select all

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.



PART 2 - Projects and custom actions

This article describes the kinds of custom actions that can be used in your Visual Studio setup project.

Introduction
It’s often necessary to add your own code to a Visual Studio setup project, which is accomplished using custom actions. Custom actions are in contrast to the standard actions that Windows Installer performs during an installation. Examples of standard actions include copying files and registering COM servers.

(You can download the code examples to go with this article using the link to the right of the title.)

Properties and CustomActionData
To understand the use of custom actions, it’s important to know how to use Windows Installer properties, and in particular the CustomActionData property. To pass data into your custom-action code in Visual Studio setup projects, for example, you set the CustomActionData property to the data you want to pass into the custom action, and in your code you retrieve the CustomActionData property that contains the data. This is a common theme in the examples that follow.

Remember that the properties can be standard Windows Installer properties (TARGETDIR, VersionNT and so on), or properties that you created yourself, perhaps by using Search Target Machine as we saw in the previous article.

We’ll illustrate this CustomActionData idea with a simple VBScript custom action. This custom-action script will display the value of the CustomActionData property:

Code: Select all

msgbox Session.Property("CustomActionData")
In the Visual Studio IDE for the properties of that custom action, you specify the value of your CustomActionData. In this example you’ll do this:

Code: Select all

[OriginalDatabase]<>[TARGETDIR]<>[ComputerName] 
You’re passing the value of this string to the CustomActionData property, and then in the custom-action code you get the value from the CustomActionData property. That is, you’re passing this data indirectly via the CustomActionData property.

The data consists of three Windows Installer properties. TARGETDIR is the primary installation folder, OriginalDatabase is the full path to the current MSI file being installed, and ComputerName is the name of the computer. Since angle brackets are not allowed in file names, they can serve as separators for parsing the combined properties and separating them out. So angle brackets are used for passing multiple property values into a custom action. Square brackets indicate that the actual value will be resolved at run time.

One caveat: Anti-virus software sometimes intercepts VBScript code and could prevent your custom action from working properly.

With the basics in place with regard to properties and CustomActionData, we’ll look at other types of custom actions.

C++ custom action DLL
You can write custom actions as C++ function calls in a DLL. These function calls must be exported and have a signature like this:

Code: Select all

UINT __stdcall SomeCA (MSIHANDLE hInstall) 
The MSIHANDLE parameter is similar in concept to the Session value used in the earlier VBScript custom action. The MSIHANDLE enables you to access properties using the Win32 MsiGetProperty API call, so we can code a complete custom action call like this:

Code: Select all

UINT __stdcall SomeCA (MSIHANDLE hInstall)
{
WCHAR vbuff [500] = {0};
DWORD vlen = 500;
UINT gp = MsiGetPropertyW(hInstall, L"CustomActionData", vbuff, &vlen);
WCHAR msg [500] = {0};
wsprintfW (msg, L"gp = %d vlen = %d vbuff = %s", gp, vlen, vbuff );
LogMessage (hInstall, msg);
MessageBoxW (NULL, msg, L"Result", MB_OK);
return 0;
}
You can do much more than just get properties. There is an MsiSetProperty API, for example, that can set a property you can use later in the install or in another custom action. If the property name you specify does not exist, Windows Installer will create it for you. I’ll describe what that LogMessage call does in a moment.

You’ll need to include msiquery.h and link to msi.lib to use C++ custom actions. Use of an explicit .def file that names the exported functions will save you from potential name-mangling issues.

One of the useful features of these direct call custom actions (in which your code is called directly from Windows Installer) is the ability to write your own entries to the Windows Installer log. The LogMessage function below shows how to do this fairly easily in your C++ custom-action DLL, especially if you ignore error checking as I’ve done here:

Code: Select all

UINT LogMessage (MSIHANDLE hInstall, LPCWSTR szMsg)
{
PMSIHANDLE hRecord = MsiCreateRecord(1);
MsiRecordSetStringW(hRecord, 0, szMsg);
MsiProcessMessage(hInstall, INSTALLMESSAGE(INSTALLMESSAGE_INFO),
hRecord);
return ERROR_SUCCESS;
}
You don’t get the logging output by default. You have to enable it at install time with a command line like this:

Code: Select all

Msiexec /I
/l*v myinstall.log 
You would see a few lines like this in the log file:

Code: Select all

MSI (s) (A0:80) [10:48:48:049]: Invoking remote custom action. DLL:
C:\WINDOWS\Installer\MSI15E.tmp, Entrypoint: SomeCA 

gp = 0 vlen = 35 vbuff = C:\Program Files\PhilW\InstWinForm\ 
The first line is a standard logging entry reporting that the custom action is being called; the second line is the output from the call to LogMessage in the custom action. This is a useful debugging technique for tracing the path of your custom-action DLL calls.
fig1b.gif
fig1b.gif (26.95 KiB) Viewed 11811 times
Figure 1

But why does it say that the custom-action DLL is called MSI15E.tmp? Look at Figure 1, which shows the properties of the custom-action DLL in Solution Explorer. It has an Exclude property value of True. This means that the DLL isn’t being installed. Rather, the DLL is stored in the MSI file and streamed out when the custom action is called, so that unusual name is your custom-action DLL.

Calling an executable as a custom action
Visual Studio also lets you use an executable as a custom action. You can’t use CustomActionData to pass values to the program, because you’re not passing properties via a call directly from the Windows Installer Service. Instead you can use the arguments value shown in Figure 2, the properties window of a custom action, to call the forms program executable in the sample Visual Studio solution. The argument string is surrounded by quotes so that it gets passed as a single string into the main of the receiving program.
fig2b.gif
fig2b.gif (10.94 KiB) Viewed 11811 times
Figure 2

Because this call is synchronous, you can’t use it to launch your application after the install: While the custom-action program is running, the install process waits for it to finish.

Installer classes
.NET assemblies can contain installer classes, and Visual Studio .NET lets you call methods in these installer classes as custom actions. If you select a .NET project in the Solution Explorer, a right-click lets you add a component, one of which is an installer class. The boilerplate code generated for you includes an attributed class with [RunInstaller(true)], which is what makes it an installer class. To add code of your own, override the existing class methods and use the install method shown here:

Code: Select all

public override void Install(IDictionary savedState)
{
base.Install(savedState);
// Add your code here
}
This is where you add your install code. You should also add a corresponding uninstall method that reverses whatever your install code does to the system.
fig3b.gif
fig3b.gif (25.12 KiB) Viewed 11811 times
Figure 3

By adding a custom action in the same general way as described earlier, you add these calls as custom actions in your setup project. Figure 3 shows the properties for the custom action. Note that the InstallerClass property is set to True. Figure 3 also shows how to pass CustomActionData into your installer class methods, but it’s not the same format as other custom actions. Here’s a sample CustomActionData string:

Code: Select all

/targ="[TARGETDIR]\"
This is the general format required for passing data into installer classes. You must put quotes around Windows Installer folder properties so that the internal parsing works correctly. The parameter string ends up in Context.Parameters of the installer class, an IDictionary object with keys and values, and there will be a key corresponding to your CustomActionData key (targ in the example). This means that there will be a this.Context.Parameters["targ"] key with a value of TARGETDIR resolved to the run-time value.

You can enumerate through these context parameters with the following code, which is intended to be called as a debug method passing "Install" as the string parameter if it’s being called from an install custom action:

Code: Select all

private void ShowContext(string where)
{
StringBuilder sb = new StringBuilder();
StringDictionary myStringDictionary = this.Context.Parameters;
sb.Append("From " + where + "\n");
if (this.Context.Parameters.Count > 0)
{
foreach (string myString in this.Context.Parameters.Keys)
{
sb.AppendFormat("String={0} Value= {1}\n", myString,
this.Context.Parameters[myString]);
}
}
MessageBox.Show(sb.ToString());
}
This will give you a message display as shown in Figure 4. There is a "targ" key where the value is the install folder TARGETDIR, and there is an assemblypath key with the install path of the assembly. This is a useful alternative to passing TARGETDIR because it tells you the path to the assembly. This is not documented anywhere, so I do not recommend that you rely on it in different versions of Visual Studio, because it’s being passed in as part of the internal implementation of installer classes.
fig4b.gif
fig4b.gif (6.01 KiB) Viewed 11811 times
Figure 4

The installer class mechanism isn’t native to Windows Installer – there’s no support for calling installer classes directly from the Windows Installer Service. Visual Studio packages a shim DLL into your setup and generates a call to the ManagedInstall function in this DLL, passing parameters via CustomActionData in the same way as the custom action call into a C++ DLL that you saw earlier. This shim DLL is then responsible for finding and calling your installer class methods, and ultimately .NET reflection is used to locate your installer class with its [RunInstaller(true)] value. This is why assemblypath is passed in, as we just saw in Context.Parameters.

The installer class mechanism isn’t as versatile as writing your own C++ custom-action method because you are a calling layer away from the install process and you don’t have access to the MSIHANDLE in your installer classes like you do in a C++ function. So you can’t set installer properties, only pass them one way into your class methods.

Installer classes prompt you to install Windows Services using the ServiceInstaller class, although installing services using custom actions is rather unusual in the Windows Installer world. Most third-party install tools provide IDE support for the ServiceInstall and ServiceControl tables that are standard features of Windows Installer and are provided for the very purpose of installing services.

(Important note about the installer: As detailed on the Microsoft support site, Visual Studio .NET setup projects execute the custom action of the previous version when upgrading. See http://support.microsoft.com/default.as ... -us;555184 for more information.)

When is my custom action called?
There are no clues in a Visual Studio setup project that tell you when your custom action is called. Before explaining that, however, I’ll describe the three stages of a Windows Installer setup:
A user interface sequence during which data is gathered (file locations, choices and so on) and the system is not being changed.
An auditing sequence in which the actions that will be done are written to an audit file, and the system typically is not yet changed.
An updating sequence in which the audit script is processed and changes are made to the target system.

The reason for the audited nature of the process is apparent if you’ve seen a Windows Installer setup fail and roll back. What’s happening is that the changes made to the system are backed out with the help of that audit script. It’s a feature of a Windows Installer setup that a failure will restore the system as if the install had never started.

Visual Studio custom actions are called during the third stage of the setup. It should be apparent that you can’t use install custom actions in Visual Studio to make significant changes to your setup, because by the time your custom action is called, the install is almost finished and it’s far too late to change something like TARGETDIR. On the other hand, install custom actions are called toward the end of stage 3, and that means that your files, program executables, and data are installed on the target system and you can use them.

If you want to run one of your programs or make modifications to a configuration file, it’s helpful that these files have been installed by the time your install custom action runs. Also, the user interface can be suppressed in a command line install, so it wouldn’t work well to call the custom actions from the user interface sequence.

Custom actions that are called during stage 3 are known as deferred custom actions in Windows Installer, and it’s the internal handling of these custom actions that requires use of CustomActionData to pass data to them. The installation process is actually a client/server environment in which the user interface runs on a user-mode Msiexec.exe and the server installation process runs on the Windows Installer Service Msiexec.exe.

It’s normal to see two or more copies of Msiexec.exe running at the same time because of the client/server architecture, among other factors. During the service part of the installation process, there is limited access to installer properties, and this is why properties are passed to our deferred custom actions using the CustomActionData property.

During stage 2 the CustomActionData values are prepared so they can be used in stage 3. This client/server architecture is also related to property naming. When Visual Studio prompts you for a property name, the names are uppercase. Because Windows Installer properties can be private or public, public properties are denoted with uppercase names. There are two main attributes of public properties that concern us here: They can be passed from the UI sequence to the execution sequences, and they can be specified on a command-line install.

The first attribute is critical when properties are created during something like a Search Target Machine (see the first article in this series). Because Search Target Machine is implemented as a Windows Installer AppSearch action, it will be processed early in the UI sequence (stage 1) or the execute sequence (stage 2), but not in both. So a property that the search creates is public so it can be passed from the UI sequence to the execute sequence.

When you want to do a command-line install and pass in a value for TARGETDIR, for example, use something like this:

Code: Select all

Msiexec /i
TARGETDIR=c:\MyFolder 
You can’t get a property in VBScript with Session.Property("TARGETDIR") because of the behavior of Visual Studio’s deferred custom actions and CustomActionData. If Visual Studio custom actions were called during stage 2, you would be able to use properties like the one mentioned above, known as immediate custom actions in Windows Installer, but your files wouldn’t yet be installed on the system.

In other words, the Visual Studio design restricts you to custom actions that are called when your files are on the system (deferred custom actions), which means that you must use the CustomActionData property. Other tools that generate MSI files are often more flexible, so if you anticipate complex setups, investigate those tools.

It’s important to understand where your Visual Studio custom actions fit into the installation process and how that affects the kind of thing you can do with them. You should think of Visual Studio install custom actions as occurring after the install.

It’s worth noting that Windows Installer doesn’t know whether a custom action is an install or an uninstall, which is how custom actions appear in the Visual Studio IDE. Visual Studio adds a condition to the custom actions that is true on an install or an uninstall, but you can’t see it in the IDE. If you add your own condition, it’s added to the existing condition with an "and." The importance of this will become apparent when we look at repair in the next section.

Beware of repair
Visual Studio creates a Windows Installer component for each file being installed. When the component is installed, the file is installed. It’s crucial to understand that your custom-action call is internally conditioned on that component being marked for installation, because when a repair occurs the component may be marked for reinstallation. That is, a repair can call your custom action again.

This is easy to see if you install an executable that is the target of a shortcut as well as the subject of an install custom action. If you remove the executable and then use the shortcut, a repair will kick in. The repair will install the component, your custom action will be called, and you’ll see your executable run as a custom action. When you close it down you’ll see it start again as a result of your original shortcut request.

The key point is that your file is reinstalled by the repair. Your custom action, conditioned on the Windows Installer component containing that file, is also being called. You probably expected your custom action to be called only once when your product was installed, but as you see it may be called again. I’ll explain how to fix that in a moment.

Take another look at Figure 1. It shows the CADll custom-action DLL and the VBScript custom action with the Exclude property True in the properties pane in the Solution Explorer pane (note the circle with a line through it in both cases). So the file won’t be installed, as we described in the C++ custom-action DLL. The DLL and the VBScript are packaged separately in the MSI file, streamed out into a temporary location, and run from there.

I just mentioned that an install custom action is conditioned on the component being installed, which is why it also gets called during a repair. But CADll and showprop.vbs aren’t being installed because they’re excluded! So what makes this an install custom action? The internal condition is that the custom action is called if this is not a complete uninstall of the product.

The last key piece of information is that a repair ensures that the entire feature is installed, and Visual Studio setup projects have a single feature. If you look at Figure 1, during a repair the WinForm.exe custom action is called if it is reinstalled, because it’s conditioned on the file/component being marked for installation. Showprop.vbs (and CADll.dll) will also be called because their conditions are based on "this is not an uninstall." So all your custom actions might be called during a repair.

I’ve spent a lot of time on this because it can cause awful problems. You might do a repair from the context menu of an MSI file and your custom actions will run again. If you were creating a database from scratch, that’s a big problem!

The solution is to add an extra condition to the custom actions to prevent them from being called during a repair. The condition is "Not Installed," where "Installed" is a case-sensitive Windows Installer property that’s set if your product is already installed. In other words, during a repair your product is installed and it’s just being repaired, so "Not Installed" ensures that you run your custom actions once during the first install. Figure 5 shows this condition in the custom-action properties.
fig5b.gif
fig5b.gif (9.07 KiB) Viewed 11811 times
Figure 5

Conclusion
Custom actions are a useful way to add functionality to your Visual Studio setup projects, so it’s important to understand how to use them and their capabilities. I hope this article will help you to use them successfully.



PART 3 - Updates to setup projects
This article explains how to install a new version of your setup project using the Windows Installer update mechanism.

Introduction
When you need to build a new version of your setup project or ship fixes to it, it’s tempting to rebuild the project and try to install the MSI file. Doing so, however, typically launches a message box indicating that another version of the product is already installed and directing you to the control panel to uninstall it.

The primary reason for this is that the ProductCode Guid identifies the product to Windows (see the setup project properties in Figure 1). Since you have already installed the product identified by that Guid, you must use a Windows Installer update mechanism to install a new version of your product.
fig1c.gif
fig1c.gif (13.67 KiB) Viewed 11811 times
Figure 1

How to update your product
Visual Studio supports the RemovePreviousVersions project property mechanism in its IDE. Figure 1 shows that the value is false. When you’re ready to build a new version of your product to replace an older one, follow these steps:
Increment the version property (see Figure 1). Visual Studio displays a message box that prompts you to change the ProductCode and PackageCode. Select yes.
Set the RemovePreviousVersions property to true.

Setting the RemovePreviousVersions property to true removes previous versions of the product from the system as you install the new version. Since products are identified by the ProductCode Guid, changing the ProductCode creates a new product. That is, the old product is uninstalled as you install a new one. Note that the version property, which becomes the Windows Installer ProductVersion property, is a key piece of data that relates to updating your product. It is not simply a descriptive string.

Figure 1 also shows a DetectNewerInstalledVersion property that checks, at install time, to see that you’re not trying to upgrade an existing installed version with an older version.

Before describing how RemovePreviousVersions works, I’ll explain how PackageCode and ProductCode interact.

PackageCode, ProductCode and repair
The PackageCode is a Guid that uniquely identifies the MSI file from which a product was installed. When you install an MSI file, the PackageCode and ProductCode are recorded on the system. When you attempt to install another MSI file, the PackageCode and ProductCode interact in two ways:
If the new MSI file has the same ProductCode and PackageCode as a product that’s already installed, Windows indicates that you must repair or remove the product (see Figure 2) Remove uninstalls the product, but repair can be more confusing.

Repair does not use your new MSI file to repair the product, nor does it update what you previously installed. Instead, it repairs the existing installed product. That is, it behaves as if you went to the original MSI file used to install the existing product, selected the context menu, and chose repair. (Note: Repair can also be initiated from Add/Remove programs.)
If the new MSI file has the same ProductCode as an installed product but a different PackageCode, you’ll receive a message indicating that another version of the product is already installed.

You cannot set the PackageCode property (Figure 1). Instead, Visual Studio generates a new one when required.
fig2c.gif
fig2c.gif (9.41 KiB) Viewed 11811 times
Figure 2

When a product is installed, Windows caches a copy of the MSI file in the Windows\installer folder and records the location from which the MSI was originally installed. The cached MSI file contains the data from the original MSI file but does not contain the actual files, so it can be used to check that the product is installed properly. If there are any missing files, Windows will go to the recorded install location to retrieve the installation MSI file. If a repair is triggered, Windows may ask for the original CD or network location.

The product upgrade
Another Guid, the UpgradeCode, recognizes that a previous version is installed (see Figure 1). When RemovePreviousVersions is set to true, Visual Studio builds an MSI file that checks for other installed products with the same UpgradeCode and uninstalls them. This is known as a Windows Installer major upgrade.

The upgrade uninstalls the older product and then installs the new one, so everything that was installed in the older version will be uninstalled. It does not update files with newer versions. If data files were installed that the user may have updated, they will be uninstalled.

You may need to take this behavior into account when you build the first version of your installation package. It may be better to have your application produce data files at run time, for example, rather than adding them to the setup. This ensures that RemovePreviousVersions will not uninstall them, and they will carry over into the next version of the product.

Everyone or just me?
At install time, the dialog box in which you choose the installation folder is also where you set the "Everyone" or "Just me" radio-button choices (see Figure 3). In Windows Installer terms, it’s a choice between a per-machine install (for all users of the system) or a per-user install (for the installing user). An Everyone install does not upgrade a Just me install, and vice versa.
fig3c.gif
fig3c.gif (12.82 KiB) Viewed 11811 times
Figure 3

There is no way to preset the Everyone/Just me setting in Visual Studio 2003’s IDE. Figure 4 shows the properties of a setup project in the Beta 1 version of Visual Studio 2005. Note that there is an InstallAllUsers property to set the default behavior.
fig4c.gif
fig4c.gif (16.99 KiB) Viewed 11811 times
Figure 4

Be mindful of the issues
The internal logic that uses UpgradeCode to search for other versions of the product allows a range of version values to be used. A tool called Orca in the Windows Installer section of the Platform SDK can be used to view and edit MSI files. More on this later.

Figure 5shows the values in the Upgrade table in the MSI file. The VersionMin and VersionMax values show the range of values for which to search, but Visual Studio does not allow you to specify this version range, which starts at 1.0.0. If you are partial to versions before 1.0—0.5, for example—your RemovePreviousVersions will fail.
fig5c.gif
fig5c.gif (13.36 KiB) Viewed 11811 times
Figure 5

Recall that a Just me install will not upgrade an Everyone install. In Visual Studio .NET, the internal search for a matching UpgradeCode occurs before the per-machine/per-user value is resolved. In Visual Studio setups the default install is per user, so a RemovePreviousVersions upgrade will not upgrade an Everyone per-machine install because it is still in the default per-user mode when it searches for the previously installed versions. Visual Studio 2003 does not have this issue.

Custom actions in RemovePreviousVersions upgrades may also give unexpected results. In a Windows Installer major upgrade, custom actions are called in the same process space.

Let’s walk through the scenario with a DLL, including .NET assemblies. The first custom action called will be the uninstall custom action from the older, already installed version. Windows loads the associated DLL and calls the custom action. Later, the install custom action from the new product is called. If this DLL has the same name as the DLL in the older product, Windows assumes that the DLL is loaded and it will not load the version of the DLL from your new setup.

This has some interesting effects. The install method from your old custom-action DLL is called instead of the one from the DLL in the new setup. Similar problems have been reported with COM DLLs. So although it might be awkward, I recommend that you change the name of your DLL in these cases. See http://support.microsoft.com/default.as ... -us;555184 for additional information.

Other types of updates
Products can be updated using Windows Installer patches, or MSP files. The main advantage of such patches is that they are small. The patches function as a delta between one version of an MSI file and another—the difference between the MSI tables—and new versions of the updated files. In the optimizations that produce the smallest patches, the patches contain binary patches to individual files. In cases that are less optimized for size, patches contain the entire file that has changed.

Patches can be built using PCP files, described in the Windows Installer SDK documentation. I don’t recommend that you build patches based on setups that were generated by Visual Studio setups, however, because you don’t have enough control over the way Visual Studio generates MSI files. In particular, the generation of patches is sensitive to the name of the internal CAB file and associated streams embedded in the MSI file. A mismatch between old and new versions will give you error 2356 when you apply the patch.

Expert mode
The great thing about Visual Studio setup projects is that they isolate you from the complexities of Windows Installer: You don’t need to worry about what’s going on internally. There are other tools that enable you to control more of the internals of MSI setups and give you the proverbial rope with which to hang yourself (see http://installsite.org/pages/en/msi/authoring.htm for a good summary).

The reason that setup programs require extra care in design and implementation is best illustrated by comparing them with other programs. A typical application program processes input data and produces some result for consumption by humans or other programs. If it breaks, you replace it on the system by putting a new copy out there.

An installation program isn’t like that. It makes changes to the system, many of which are in sensitive areas such as the Windows registry. Once those changes have occurred, it’s too late to replace the setup program with a new one because the changes, and the damage they may have done, are already on the system.

Perhaps the closest analogy to a setup program is an application program that updates a critical database. If the program breaks, the database is corrupted and you need to restore a previous version of the database or find a way to undo the damage.

If you want to learn more about what goes on inside MSI files, take a look at a tool called Orca from the Windows Installer section of the Platform SDK. If you install from Orca.msi in the \bin folder, you will see the tables and their contents in the MSI file.

I use the word tables because the MSI file is a database, and the tables and their uses are documented in the Platform SDK. MSI files are transparent—you can see everything except for the content of binary files used in the custom actions that Visual Studio generates. To see what happens, take a look at the InstallExecuteSequence table of an MSI file in conjunction with a log from installing that MSI file with this type of command line:
Msiexec /I <path to your MSI file> /l*v nameoflogfile.log

Conclusion
Visual Studio setup projects offer a limited but capable introduction to Windows Installer-based setups. They give you the basic functionality of a Windows Installer setup with MSI files, but to do so they hide some of the more complex functionality of Windows Installer. Hopefully, this series of articles will help you use setup projects successfully.
User avatar
oghenez
Posts: 1
Joined: Thu Apr 28, 2011 4:58 pm

Re: How to create setup and deployment using Visual Studio.Net

Post by oghenez » Thu Apr 28, 2011 5:07 pm

i need to build and installation package that give user to install server or client version of my application.

if the user choose server, then install sql server express r2, if they choose client, then skip the sql server installation

thanks
User avatar
Shane
Captain
Captain
Posts: 226
Joined: Sun Jul 19, 2009 9:59 pm
Location: Jönköping, Sweden

Re: How to create setup and deployment using Visual Studio.Net

Post by Shane » Fri Apr 29, 2011 2:29 am

i need to build and installation package that give user to install server or client version of my application.
You need to code this up. However, it is easier to do such customised installation projects with Install Shield.
if the user choose server, then install sql server express r2, if they choose client, then skip the sql server installation
Installing server and client versions is possible. But I don't think you can install SQL server without a license. According to my memory, there was a free version called Desktop Edition but I don't know this is yet available.
Post Reply

Return to “.NET Programming”