How To Install a Web Application for a Site with a Specific Port

Apr 10, 2007
by:   Tim Stanley

MSI installation projects created in Visual Studio 2003 allowed the setting of a PORT value. This port value would be used to search the IIS sites and install the web application by default on the first site that corresponded to the specific port.

Users migrating from Visual Studio 2003 to Visual Studio 2005 have found that support for the PORT property has been removed and there are few technical options available to solve this problem.

This article outlines how using Visual Studio 2005 SP1 and a command line and MSI custom actions to install a web application on a site configured for a specific port.

After an update to VS 2008, I see no options that resolves this in VS 2008 either.

Visual Studio 2003 Background

In IIS, on Windows Server 2003, multiple sites with multiple ports are used in most deployments. For example, if two sites are configured, one with port 80, the other with port 8080, they would appear as follows in the IIS administration tool.

DefaultSiteProperties8080  

DefaultSiteProperties2

In IIS admin, the port value and the host header value can also be set under the advanced settings.

If one wanted an MSI package to install a Web Application by default on the site that used port 8080, and the virtual directory to "TestWeb", Visual Studio 2003 made this easy.

  1. Select the File System view of the Web Setup project for the web application
  2. Select the properties view of the Web Application Folder
  3. Set the Port property to 8080
  4. Set the VirtualDirectory Property to "TestWeb"

If one wanted to pass this as a command line parameters the MSI these MSI properties could also be set.

   TARGETPORT=8080
   TARGETVDIR=TestWeb
   TARGETDIR="C:\Program Files\..."

The property TARGETDIR can also be used to specify the physical path where the application should be installed.

Command Line Options for TARGETSITE

Msiexec /I "Websetup1.msi" TARGETSITE="/LM/W3SVC/1"

The problem with this approach is that the site will not be the same number on every server. We can use this approach to know what site number is used based on the port number assigned to that site. We can use the algorithm noted below in a VB script to find the server number and use the update the TARGETSITE on the command line.

  • serverNumber = FindServerNumber( 9115)
  • tgtSite = "/LM/W3SVC/" & serverNumber
  • shell.Run "msiexec /passive /I """ & argInstallerName & """ TARGETSITE=" & tgtSite

Function FindServerNum(strPort) 
    Dim MD_SERVER_STATE_STARTED, MachineName, IISObjectPath, IISObjects
    Dim ChildObject, ChildObjectName
    'Dim Servers 
    Dim ServerNum, i, strBindings, strSvrPort, BindArray, binding
    Dim svrCount, arrCount, Servers(10), site
    svrCount = 0 
    arrCount = 0 
    i = 0 
    MD_SERVER_STATE_STARTED = 2 
    MachineName = "localhost" 
    IISObjectPath = "IIS://" & MachineName & "/W3SVC" 
 
    Set IISObject = GetObject(IISObjectPath) 
    'Find all the server numbers pu into an array 
    for each ChildObject in IISObject 
        ChildObjectName = ChildObject.AdsPath 
        ChildObjectName = Right(ChildObjectName, Len(ChildObjectName)- InStrRev(ChildObjectName, "/")) 
    on error resume next 
        ServerNum = Clng(ChildObjectName) 
        If (Err = 0) Then 
            Servers(arrCount) = ServerNum 
            arrCount = arrCount + 1 
        End If 
    next 
 
    'Get the port number and compare with the passed port number. If match, return the server number 
    while i <= arrCount 
        set site = IISObject.GetObject("IIsWebServer", Servers(i)) 
        ' Gets the Port Number of the current IISObject. 
        BindArray = site.ServerBindings 
    on error resume next 
        strBindings = BindArray(0) 
        If Err = 0 Then 
            'remove the : char from begining and end of the port number 
            strSvrPort = Left(strBindings, InStrRev(strBindings, ":") - 1) 
            strSvrPort = Right(strSvrPort, Len(strSvrPort) - InStr(strSvrPort, ":"))    
    on error resume next 
            If cint(strPort) = cint(strSvrPort) Then 
                If Err = 0 Then 
                    If IISObject.ServerState = MD_SERVER_STATE_STARTED Then 
                    ' Determines if this is our server. IIS can only have one 
                    ' active port, so if the port is active it is the port where 
                    ' the application is installed.                    
                    FindServerNum = Servers(i) 
                    svrCount = svrCount + 1                    
                    exit function 
                End If 
            End If 
        End If
        i = i + 1 
    wend 
 
    If svrCount = 0 Then 
    ' Err.Raise 9999, "FindServerNum", "No Active Servers with the requested port were found. Port=" & strPort &  ". " 
    End If 
 
End Function 

A copy of the script can be downloaded MSIInstallScript.zip.

Getting the TARGETSITE in a Custom Action

If you follow the basic line of logic from the article at Walkthrough: Passing Data to a Custom Action then getting the TARGESITE value is fairly simple.

  1. Create an Installer Class (Add new item, Installer class)
  2. Put the code in the installer class to process the parameters
    
    Public Overrides Sub Install(ByVal stateSaver As System.Collections.IDictionary) 
        ' Gets the parameter passed across in the CustomActionData. 
        MyBase.Install(stateSaver) 
     
        Dim strSite As String = Nothing 
        strSite = Me.Context.Parameters.Item("SITE").ToString 
        MsgBox("SITE=" & strSite, MsgBoxStyle.Information, "Custom Install")     
        Me.Context.LogMessage("JS Action Before SITE=" & strSite) 
    End Sub 
    
  3. Add the output from the installer class to the web installation (View File System, Add project output, select primary output from the project with the installer class)
  4. Create the custom action (View custom actions, add custom action, select Web Application Folder, select Primary output from the project with the installer class)
  5. 5. Set the CustomActionData property for the custom action as follows:
    
    /SITE=[TARGETSITE] /PORT=[TARGETPORT] /VDIR=[TARGETVDIR] /TDIR="[TARGETDIR]\"
    

If you run the following command, you should see a message box that appears showing the SITE value.


msiexec.exe /qf /norestart /Liwearucmopvx! MSIInstall.log /i WebSetupald.msi

Setting the TARGETSITE in the Custom Action

So now we have read it, we should be able to set it.


Me.Context.Parameters.Item("SITE") = "/LM/W3SVC/1"

Actually, this is misleading, you can’t set the TARGETSITE. Ok, you can, but by the time you set it, the TARGETSITE has already been established and so setting it is a moot point.

If you actually run this, you will see there are two problems with this approach. First, the component that executes the custom action actually gets installed in the TARGETSITE and TARGETVDIR. Second, since the TARGETSITE has already been set in the User Dialog or Acquisition or User Dialog phase of gathering the data to set the properties for the installation.

The Windows Installer goes through a series of sequences for the MSI package: Initialization; User Dialog; Finalization or Rollback. A full explanation of these phases is best explain in the article Installation Phases and In-Script Execution Options for Custom Actions in Windows Installer.

Essentially, this is a chicken and egg problem. One can’t set the property in the custom action because by the time the custom action is invoked, the property for TARGETSITE is already set.

Phil Wilson also explains the same sequencing problem in his article Visual Studio Setup - projects and custom actions.

Conclusion

Essentially if you have to solve the problem of installing an application on a site with a specific port, there are two options. Set the TARGETSITE property and pass this as a parameter prior to invoking msi install, or moving to another tool like Windows Installation Studio 7 by Wise (aka Altiris) or InstallShield from Marco Vision. These products allow the creation of dialog windows which can be customized with further custom actions and logic far beyond what the Visual Studio 2003 or 2005 products allow.

References

Related Items