Creating Installer for HttpVPN-Enabled ASP.NET Web Apps Using Visual Studio

Overview

Fellow Visual Studio developers, this document is a step-by-step guide to the process of creating an installation package that will comprise your ASP.NET 3.5 or 2.0 web application and redistributable components of HttpVPN Proxy and UltiDev Cassini Web Server.

The content of this guide assumes that the reader is familiar with the following topics:
- Developer's Guide: The Overview.
- Developer's Guide: Creating Your Web App Store.
- Developer's Guide: Creating Application's Release Definition and managing product lines.
- Developer's Guide: Installer Creation Overview.

If you have experience creating UltiDev Cassini-based redistributable intranet apps, this guide will contain lots of familiar steps, along with some important differences.

Here we will use a simple two-page ASP.NET web application project as an example. The sample app is a standard ASP.NET web application of a "Hello, World!" type. We will use it to show how any existing ASP.NET application can be converted into a secure, self-hosting Internet apps in a matter of just a couple of hours.

The high-level flow of the process of converting an existing ASP.NET application into a secure, redistributable and HttpVPN-hosted web app accessible on the Internet, consists of following steps:

  • Adding of a Setup project to the solution that already has ASP.NET application.
  • Adding HttpVPN Proxy as a prerequisite to the Setup project so that installation bootstrapper (Setup.exe) could download and install HttpVPN Proxy like other prereqs you may have (.NET Framework, UltiDev Cassini Web Server, SQL Server Express, etc.) before installing your app.
  • Implement installer custom actions that will start the process of registering your app with HttpVPN service at the end of the installation.
  • Creating self-extracting, single-file installation EXE.

Prerequisites

In order to be able to complete this walk-through, you will need following tools and components:

  • Microsoft Visual Studio 2008 or 2005. Visual Studio 2010 is not supported yet. Express editions of Visual Studio are not supported.
  • HttpVPN Proxy prerequisite for Visual Studio.
  • If your target audience is not guaranteed to have Microsoft IIS installed, as it's almost always the case, please use UltiDev Cassini Web Server prerequisite for Visual Studio. UltiDev Cassini is a lightweight redistributable web server capable of running ASP.NET applications.
  • An ASP.NET web application to practice installation creation with. You may download sample ASP.NET 2.0 project (VS 2008 version, and VS 2005 version) that will be used throughout this walk-trough.
  • Your Web App Store needs to be set up at the HttpVPN Portal, complete with the product line definition, target platform definition and release definition for your app.

Creating a Setup Project

Please start with opening an existing ASP.NET web application in Visual Studio 2008 or 2005. This screen shot shows our above-mentioned sample ASP.NET project opened in Visual Studio 2008 and running in IE:
Sample ASP.NET application opened in Visual Studio 2008 Sample ASP.NET running in Internet Explorer
This project contains two simple pages linking to each other, as well as a few content files, including an image, a JavaScript file and a CSS file. This sample ASP.NET project is not optimized in any way for HttpVPN.

If you are using Visual Studio 2005 and your ASP.NET project is of the "ASP.NET Web Site", consider converting it into ASP.NET Web Application, because Web Site projects require either code-behind files to be deployed, or employing awkward precompilation process. Visual Studio 2008 developers need to worry about this problem only if your project was a web site app migrated from Visual Studio 2005 into VS'08. If it was, the fix is the same - conversion to Web Application. Sample apps used in the walk-through are both of the Web Application type.

First, let's ensure that assembly file version number is incremented every time the project is built. This is important for the upgrade mode of the installation, especially for installers made with Visual Studio 2008. Unlike installers created by Visual Studio 2005, when new version of an app is installed in the same folder as the old one, MSIs generated by VS'08 will replace executable files only if new ones have higher version number. Installers generated by Visual Studio 2005 act in upgrade mode more or less as if old version is uninstalled before new one is installed.

To take care of this new behavior of VS'08 installer, please open AssemblyInfo.cs file in the Properties folder and modify AssmeblyVersion attribute value.
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:

[assembly: AssemblyVersion("1.0.*")]
Incrementing version number automatically on every build may not be necessary, although it's a good practice. If you wish, you may forgo auto-incrementing and instead change the version number manually before releasing the application.

Now we'll add a Setup project to the solution:

Since Microsoft IIS is not installed by default on most Windows machines, we recommend employing free, redistributable UltiDev Cassini Web Server instead of counting on IIS being present on the target PC.
If you decided to go with UltiDev Cassini Web Server, please add a new regular (non-web) Setup project to the solution:
Adding regular (non-web) Setup project to the solution

If you decided that your end users won't mind getting IIS installed on the target system, you may choose Web Setup project instead:
Adding WebSetup project for apps targeting IIS.
Please note that even though most important differences between Cassini- and IIS-targeting are covered throughout this walk-through, from this point forward this guide is describing how to create an installer using UltiDev Cassini Web Server, unless stated otherwise.

Once setup project was added, please ensure that it is going to be built. For that right-click the solution item and select "Configuration Manager" item. There check the "Build" checkbox for your setup project for the Release build configuration:
Ensuring Setup project is build in Release mode
Building Setup project in Debug configuration is not necessary. If you choose not to build Setup project in Debug configuration, please switch to the Release configuration for the rest of the walk-through.

Configuring Setup Project Prerequisites

Now let's add setup project prerequisites and give meaningful name to the output MSI file. For that please right-click setup project and select Properties menu item:
Setup project properties
Click Prerequisites button to bring up prereqs window:
Setup project prerequisites
Be sure to include
- UltiDev HttpVPN Web Hosting Proxy,
- An appropriate .NET Framework version (sample project uses .NET 2.0),
- Windows Installer 3.1, and
- If your target systems are not guaranteed to have IIS installed (as it is in most cases), include UltiDev Cassini Web Server.

If you don't see HttpVPN Proxy in your Visual Studio prerequisite list as shown on the screenshot above, please download and install it using the first link on the HttpVPN™ Proxy download page. If you don't see UltiDev Cassini among the prerequisites, please download and install it from Cassini download page. Also, please note that selected radio-button in the "install location for prerequisites" will generate setup package that will download missing prerequisites from the Internet during installation, instead of including them into the setup project, making installation package smaller, but requiring Internet connection. Click OK button to close dialogs.

Adding Files to The Setup Project

Now we need to add web application files to the setup project. Please start with opening setup project's File System Editor by, first, selecting setup project in the Solution Explorer pane, and then clicking the "File System Editor" button on the toolbar:
Setup project's file system editor

Before we add any application files to the setup package, it's a good idea to place web application files in its own subfolder, because the installation package will eventually contain some other files, and it makes sense to keep web application files separate from other files. In the File System Editor window, please right-click "Application Folder" and select Add | Folder, and give new folder "WebApp" name. (If you name it something else, you will need to update the folder name in the Installer1.cs file later in the process.)
Now, right-click "WebApp" folder and select Add | Project Output...

In the dialog that will have appeared, select at least "Primary output" and "Content Files". Do not select "Source Files" because ASP.NET Web Application type of project compiles code-behind files into the DLL that will be added to the installer as a "Primary output".
Adding setup project outputs.
Click OK button to close the dialog.

>> Visual Studio 2005 Step
If you are using Visual Studio 2008, please skip to the next step. Developers using VS'05, let's add Bin folder to the target folder. In the File System Editor window, please right-click "Application Folder", select Add | Folder, then type in Bin and hit Enter.

Move "Primary Output" from WebApp to the Bin folder:
VS'05 - Move Primary Output to Bin folder
>> End of Visual Studio 2005 Step

Please be sure to check which version of the .NET Framework the installer will be looking for as a Launch Condition in order to start the installation. (The version of the .NET Framework selected among the prerequisites is not related to the "launch conditions" version of the framework.) To set "launch conditions" framework version please 1) select Setup project in the Solution Explorer pane, then 2) click "Launch Conditions Editor" tooltip on the toolbar, then 3) select .NET Framework item in the Launch Conditions pane, finally 4) select .NET Framework version matching the one selected as a the prerequisite:
Specifying proper launch conditions .NET Framework version

Specifying Setup Property Values

Let's do some housekeeping and update setup project settings that were left unchanged since project was created. The most important setting for the future is to set RemovePreviousVersions to True. Others are less consequential.
Setup project property grid.

If you compile the solution at this stage and run generated Setup.exe (not the MSI or "Install" menu item), you will see that all the components, like HttpVPN Proxy and UltiDev Cassini Web Server, as well as your application itself, are deployed fine, but your application files simply sit there. Your app is not yet hosted by a web server (unless your app targets IIS) and is not accessible on the Internet via HttpVPN service. To do the magic of registering your app with both Cassini Web Server and HttpVPN Proxy, custom installer actions need to be created. (If you have built and installed the package, please uninstall it before going forward.)

Installer Custom Actions Overview

Implementation of custom actions will be different depending whether you are targeting IIS or UltiDev Cassini. The major difference is caused by the fact that for IIS, the URL of the application is likely to be known at the design time, and will be something like http://machinename/appUniqueVirtualFolder/Default.aspx. With Cassini, on the other hand, the URL of the app will be established only at the installation time because current version of Cassini does not support virtual directories and uses unique port numbers for each application, making application's URL look something like http://machinename:1234/defaul.aspx. Any particular port number, however, is not guaranteed to be free when your application is installed on user's PC, making specifying hard-coded static port number at the design time risky, if you plan distributing the application to the masses. Because of all this, when applications register themselves with UltiDev Cassini Web Server, they let Cassini find and assign free TCP port, thus shifting URL determination to the installation time, instead of design time.

This all will make custom actions for IIS and static-port case of Cassini relatively straightforward to implement, because in both cases the URL of the app will be know at the design time. The most common case, however, will be where Cassini Web Server determines app's URL at the installation time, so the newly minted URL will be used when registering the app with HttpVPN. If you don't have UltiDev Cassini components installed, you can download Cassini here.

If you're familiar with redistributing ASP.NET with UltiDev Cassini, where adding custom actions does not require any coding, you will notice that shipping your application with both UltiDev Cassini and HttpVPN Proxy will require a little bit of coding to implement custom action logic.

Custom Actions Implementation Step by Step

Let's start with adding a little new Class Library project to the solution where installer's custom action will reside. Please right-click the solution item in the Solution Explorer and select Add | New Project. Select Class Library as a project type and name the project something like InstallHelper:
Adding installer project
Click OK to get new project added to the solution. Remove the dummy Class1.cs class from the solution.

Next step is to add an .HVPNA file to the project. Files with ".hvpna" extension are associated with HttpVPN Proxy Registration utility and contain web application settings such as local URL and app release ID. The file you add at this stage will be empty, because it will be filled out programmatically during the installation, as shown further in the guide. Please right-click the InstallHelper project in the Solution Explorer, select Add | New Item, and then select Text File from the Templates list, and name the file HttpVpnAppReg.hvpna.
Adding .HVPNA file
If you decide to give the file some other name, you will need to modify the value of hvpnaFile constant in the installer custom action implementation, as described later in this guide. This newly-created .hvpna file will remain blank because it will be modified programmatically at installation time by custom actions code.

Now let's add a reference to UltiDev Cassini registration & configuration API assembly to the InstallHelper project:

...and then browse to "C:\Program Files\UltiDev\Cassini Web Server for ASP.NET 2.0" folder (on x64 systems path will be "C:\Program Files (x86)\UltiDev\Cassini Web Server for ASP.NET 2.0") to add reference to UltiDevCassiniServerConfiguration.dll file:
Adding reference to Cassini configuration API assembly.

Now an installer class has to be added to the InstallHelper project. Right-click InstallHelper in the Solution Explorer, select Add | New Item from the menu, and add an installer class named Installer1:
Adding installer class to the ASP.NET application project
Please be sure to name the class something other than "Installer", or it will create a conflict with another Installer class defined in one of .NET Framework namespaces.

Now please open code-behind file of the Installer1.cs by right-clicking the Installer1.cs and selecting View Code menu item. Please open this Installer.cs sample, and copy & paste its content over the default Installer1.cs source: 
InstallHelper implementation
You most likely will need to change constant values in the highlighted section - UltiDev Cassini Web Server application registration parameters. (Other constants, hvpnaFile and webAppDir may need to be changed only if you didn't use names suggested earlier in this guide.) Application's name and description values will show up on the Cassini Explorer page after the app is registered with UltiDev Cassini Web Server. WebAppDefaultDoc value is the path to the default document relative to the web app root. And application's webAppID GUID could be pretty much any new GUID, but it's recommended to use Setup project's UpgradeCode property value. Once you chose the webAppID value, it should never change throughout the entire lifetime of the product line, even as you create and release new versions of your application - just like UpgradeCode of your installer won't change.

Build the project to ensure there are no build errors.

Now let's ensure InstallHelper project participates in the installation process. Please select Setup project in the Solution Explorer and then click on the File System Editor icon:
Setup project's file system editor
Then right-click Application Folder in the right-side pane and select Add | Project Output:
Adding project for installation in to Application Folder
And select Primary Output of the InstallHelper project:
Adding InstallHelper project output
Click OK.

Now HttpVpnAppReg.hvpna file needs to be added to the Application Folder of the Setup project. To do that, right-click Application Folder, select Add | File, navigate to the location of the file, select it and click Open button:
Adding HttpVpnAppReg.hvpna file to Setup project
Please keep in mind that if you decide to put HttpVpnAppReg.hvpna file in some other folder, please be sure to update the UpdateHvpnaFile() function in the Installer1.cs accordingly. Sample implementation of Installer1.cs assumes that HttpVpnAppReg.hvpna file is located in the Application Folder of the Setup project. Once the file is added, your Application Folder should look like this:
Application Folder after InstallHelper is added
To complete HttpVpnAppReg.hvpna file installation sequence, it's necessary to create a shortcut to the .HVPNA file and place the shortcut into the User's Program Menu folder. To do that, right-click HttpVpnAppReg.hvpna file in the Application Folder of the File System Editor, and select "Create Shortcut to HttpVpnAppReg.hvpna" and give it a name you would like to see in the Programs menu. For example, "Make HelloWorld accessible on Internet via HttpVPN". After that move the shortcut to the User's Program Menu folder:
Shortcut to HttpVpnAppReg.hvpna file

Now it's time to make installer call custom actions code. To do that, open Custom Actions window of the setup project, right-click Install step and select "Add Custom Action..." menu item:

...navigate to the Application Folder and select "Primary Output from InstallHelper...":


Repeat the step above for each type of custom action:
This will ensure that installer will call Install(), Uninstall(), Commit() and Rollback() methods of Installer1.cs in the InstallHelper project.

Select "Primary Output..." item of the Install custom action and set CustomActionData property to /AppLocation="[TARGETDIR]\" /HttpVpnSKU="<Your app release ID>". Actual release ID value for your product can be found by clicking "Proxy Reg Info" link and then finding the value of the /SKU command line parameter on the Registration command line pop-up on the Release Management screen at your Web App Store.
Install custom action command line
This way installer will pass AppLocation and HttpVpnSKU parameters to Install() method of the Installer class.

Please do not forget to change the value of /HttpVpnSKU parameter of the Install step
every time you publish a new release at your Web App Store!

Now you may build the entire solution, create a test order and test-run the installation. Please ensure that at the end of your application's installation, HttpVPN app registration flow was started as described in User's Guide.

Also, it's a good idea to test whether the HttpVPN app registration shortcut was created and whether is works:
App registration shortcut in Windows Programs menu

Once you get the application accessible both locally and on the web via HttpVPN portal, compare the HTML source of the home page when it's served directly by a web server, and then compare it with the HTML rendered by HttpVPN. You will see something like this:
Page Served by Web Server Page Served via HttpVPN
<form name="aspnetForm" method="post"
  action="Default.aspx" id="aspnetForm">
     <div>
     <p>Hello, World!!</p>
     <p><a href="WebForm2.aspx">Link to Page 2</a></p>
     <p>
     <img border="1"
         src="Contents/RoofsOfBuenosAires.jpg"
         style="width: 648px; height: 486px" /></p>
     </div>
</form>

<form name="aspnetForm" method="post"
action="https://MyOwnSecureWeb.com/
08a2e66d-c5df-4383-bf8b-78147a179c7a/
UdVpnRGVmYXVsdC5hc3B4/SecureTunnel.axd
"
id="aspnetForm">
    <div>
    <p>Hello, World!!</p>
    <p>
    <a href="https://MyOwnSecureWeb.com/
08a2e66d-c5df-4383-bf8b-78147a179c7a/
UdVpnV2ViRm9ybTIuYXNweA==/
SecureTunnel.axd">
  Link to Page 2</a></p>
    <p><img border="1"
 src="https://MyOwnSecureWeb.com/
08a2e66d-c5df-4383-bf8b-78147a179c7a/
UdVpnQ29udGVudHMvUm9vZnNPZkJ1ZW5vc0FpcmVzLmpwZw==/
SecureTunnel.axd
"
    style="width: 648px; height: 486px" />
 </p>
    </div>
</form>

To ensure your application works alright in the upgrade mode, please do following:

  • Increment the value of the Version property of the Setup project - in this case set it at 1.0.1.
  • Accept changed ProductCode.
  • Create new Release at the App Store.
  • Create new test order using Release management screen of the App Store.
  • Plug new release ID as a parameter of the Install custom action.
  • Build the solution and install it without uninstalling previous version.

Building Single-File, Self-Extracting Installation EXE

Visual Studio Setup project creates two-file installation package: one file is Setup.exe bootstrapper, and another is the MSI file. In order to ensure that prerequisites are installed, both files must be distributed, and launching Setup.exe must be the starting point of the installation, since it's Setup exe (not MSI) that downloads and installs prerequisites like HttpVPN Proxy.

HttpVPN App Store requires that entire installation package is be placed in a single file. Although it's possible to create a ZIP archive of your app installation package, including both Setup.exe and the MSI file, and publish it to the App Store that way, we recommend creating a self-extracting archive that launches installation after unpacking itself.

7-Zip is a free archiver that works well for the purpose. Here we show how to use 7-ZIP in order to automatically create a single self-extracting EXE archive as a part of the solution building sequence.

Please start with creating following solution folders:
SFX folder structure

Next, using Windows Explorer create folder called "SFX" in the solution folder:
SFX folder

Open SFX folder and extract this archive with 7-ZIP components content into it:
7-ZIP folder

Go back to Visual Studio, right-click "SFX" solution folder and select Add | Existing Item. Using Open File window, Navigate to the SFX folder shown above and add three files: SetupStarter.exe, 7zipSxfConfig.txt and SfxSetup.bat. Next, right-click "7-zip" solution folder, select Add | Existing Item, and add all files from the SFX\7-zip folder:
7-ZIP files added to solution

Finally, select Setup project in the solution explorer and in the Properties pane select PostBuildEvent. Hit "..." button at the end of the property value line and paste these two commands:

  1. "$(ProjectDir)..\SFX\7-Zip\7z.exe" a -t7z -aoa "$(ProjectDir)$(Configuration)\Package.7z" "$(ProjectDir)..\SFX\SfxSetup.bat" "$(ProjectDir)$(Configuration)\Setup.exe" "$(BuiltOuputPath)" "$(ProjectDir)..\SFX\SetupStarter.exe"
  2. copy /b /Y "$(ProjectDir)..\SFX\7-Zip\7zS.sfx" + "$(ProjectDir)..\SFX\7zipSxfConfig.txt" + "$(ProjectDir)$(Configuration)\Package.7z" "$(ProjectDir)$(Configuration)\YourAppNameSetup.exe"

Please replace highlighted portion with more meaningful name for the final output EXE file, but be sure to keep words "Setup" or "Install" as part of the name - they will tell Windows 7 and Windows Vista to pop up Administrator privileges request when program is launched, w/o having to create and embed the manifest into the EXE.

PortBuildEvent window should look like this:
Setup project post-build event commands
These commands assume that Setup project folder and SFX folder have the same parent folder. If not, it will be necessary to modify commands by replacing "$(ProjectDir)..\SFX\" with appropriate path.

Once you build the solution, the Release folder of the Setup project will look like this:
SFX EXE is generated
YourAppNameSetup.exe SFX archive is completely self-contained and does not require the presence of any other files in the folder shown above. Starting YourAppNameSetup.exe causes it to unpack itself and launch Setup.exe, which in turn will ensure that prerequisites are installed before starting MSI package installation.

Next Steps

  • Once everything is verified and working as expected, you may make YourAppNameSetup.exe available for downloading and then publish the new release of your app to make it available for ordering!