Monday, March 5, 2012

Deploying Web Sites with an MSI

I have blogged before about using MSIs to deploy but this post deals specifially with deploying a web site and its files to a target server.  This post assumes you are deploying to a Windows Server 2008 (R2) server with IIS 7.5 already installed.  The beauty about using this MSI is it will create a functioning web site for you by the time you click Finish.  Uninstall will remove the web site.  It uses the APPCMD utility which is located in  system32\inetsrv\ on the target server. 
I started with a generic Setup project that I could re-use.  I call this the WebSiteInstaller. In the class that inherits from System.Configuration.Install.Installer, I added an OnAfterInstall override procedure. What it does is uses 2 parameters that are prompted for during installation and passes them to a CreateWebsSite batch file. These 2 paramaters are the Physical path for the web site directory (e.g. c:\inetpub\wwwroot) and the port number. I also added an Uninstall procedure that will call DeleteWebsSite.BAT.

protected override void OnAfterInstall(IDictionary savedState)
{
base.OnAfterInstall(savedState);

// Retrive the target path of the Installation directory
string targetPath = this.Context.Parameters["targetdir"];

// Retrive the Physical path of the web Site
string phyPath = this.Context.Parameters["phyPath"];

// Save the Physical Path in the save state.
savedState.Add("phyPath", phyPath);

// Retrieve the Port of the WebSite.
string websitePort = this.Context.Parameters["webSitePort"];
string arg = @"""" + phyPath + @"""" + " " + websitePort + " " + @"""" + targetPath;

// Execute the CreateWebsSite batch process to create all the components related to WebSIte
ExecuteProcess(targetPath + "CreateWebsSite.BAT", arg);
}

public override void Uninstall(IDictionary savedState)
{
base.Uninstall(savedState);

// Retrive the target path of the Installation directory
string targetPath = this.Context.Parameters["targetdir"];

// Retrive the Physical path of the web Site
string phyPath = Convert.ToString(savedState["phyPath"]);
string arg = @"""" + phyPath;

// Execute the DeleteWebSite batch process to delete all the components related to WebSIte
ExecuteProcess(targetPath + "DeleteWebsSite.BAT", arg);
}
For the source to the ExecuteProcess function, see my earlier post .Thats it for the generic WebSiteInstaller project.
So now I can go to my Web Site Solution and add the WebSiteInstaller project and another Setup project. In my Setup project I add the Primary Output of my web site project and the WebSiteInstaller. By the way, when creating your web site don;t use File -> New -> Web but instead use File -> New -> Project -> Web -> ASP.NET Web Application. The latter will create a DLL and so gives you a Primary Output.

In my earlier post I explained how to add a Custom Dialog screen. You will need 2 parameters called PHYPATH and WEBSITEPORT. For Custom Actions add, and this is important, the Primary Output from WebSiteInstaller. The CustomActionData needs to be
For the Install Action

/phyPath="[PHYPATH]" /webSitePort="[WEBPORT]" /targetdir="[TARGETDIR]\
For the Uninstall Action

/targetdir="[TARGETDIR]\"
Last thing to do in this setup project is to add the two batch files.
Here is the content of the CREATEWEBSSITE.BAT. Note here my website name is WCFSaveFile which you will have to change. And you have to specify which files you want to copy to the target directory. In my case its an SVC and the web.config and the contents of the bin directory.

@echo OFF

set PHYPATH=%1
set PORT=%2
set SOURCEPATH=%3

set "FULLPATH=%PHYPATH%\WCFSaveFile"

md %FULLPATH%
md %FULLPATH%\bin

copy %SOURCEPATH%SaveFileService.svc" %FULLPATH%
copy %SOURCEPATH%web.config" %FULLPATH%
copy %SOURCEPATH%bin\*.*" %FULLPATH%\bin

REM Create Application Pool

%systemroot%\system32\inetsrv\APPCMD add apppool /name:WCFSaveFile
%systemroot%\system32\inetsrv\APPCMD set apppool "WCFSaveFile" /managedRuntimeVersion:v4.0
%systemroot%\system32\inetsrv\APPCMD set apppool "WCFSaveFile" /managedpipelineMode:Classic
%systemroot%\system32\inetsrv\APPCMD set apppool "WCFSaveFile" -processModel.identityType:NetworkService

REM Create WebSite

%systemroot%\system32\inetsrv\APPCMD add site /name:WCFSaveFile /bindings:"http/*:%PORT%:" /physicalPath:%FULLPATH%
%systemroot%\system32\inetsrv\APPCMD set app "WCFSaveFile/" /applicationPool:WCFSaveFile


The content of DELETEWEBSSITE.BAT is as follows.

@echo OFF

set PHYPATH=%1
set "FULLPATH=%PHYPATH%\WCFSaveFile"

REM Delete WebSite

%systemroot%\system32\inetsrv\APPCMD delete site /site.name:WCFSaveFile

REM Delete Application Pool

%systemroot%\system32\inetsrv\APPCMD delete apppool /apppool.name:WCFSaveFile

REM Delete virtual path

echo %FULLPATH%\bin\

del %FULLPATH%\bin\*.*" /Q
del %FULLPATH%\*.*" /Q
rd %FULLPATH%\bin
rd %FULLPATH%
Thats it. Your MSI should install the web site, create the App Pool and set it to .Net FW 4.0. I have just reused this for another web site and it took me only a few minutes to have a working MSI.
Hope you find it useful.
One last word of warning. NEVER add a final \ to the physical path when prompted by the MSI because it gives an obscure error.

No comments: