I had been asked by a client to find a way to "silently" auto-deploy a web app to a remote server everyday as a result of a daily build process. The objective of this was to regularly deliver the application to a testing team for regression testing. I decided I wanted to use MSI (Windows Installer) 2.0 technology to do it, mainly because that's what is built in to the Visual Studio .NET 2003 deployment projects. There was also the requirement to have multiple versions of the same application auto-deployed to the web server, with only one version being active at any given time, and a simple way to switch between versions. This is what the Web Server would look like:
App1 website would point to one of its underlying version virtual folders, and App2 would do the same.
During the course of developing a solution to automate this, I had discovered that Windows Installer (as implemented in Visual Studio .NET) does have some limitations that I would need to workaround. For instance, it is not capable of "steering" the deployed files to a particular directory simply by referring only to the target Web Site's name. Another limitation was getting around the product code stamping of MSI's which prevent you from installing another version of the same application.
Of course there are many ways to skin a cat, but this article is a step-by-step account of how I solved the problems I encountered in implementing the auto-deployment process, and how it can be used as the basis of a stable deployment process for any development team that wants to have an automated remote .NET deployment capability featuring "Side-by-side" capability.
Just a note on Build Tools: I have mostly been using a product called Visual Build Professional (www.kinook.com) to carry out my automated software builds, and being a specialist in the field of Auto-Build/Auto-Deploy I have to say it is an excellent tool (Kinook can send me an appropriate reward for this free plug later!), however this article will remain "build tool agnostic" so that you can use the concepts presented here with any (or no) build tool.
To work through this article you'll need the following:
Visual Studio .NET 2003
an understanding of C# and VBScript
a remote IIS server, running IIS 6.0, on the same network as your development machine (you could also use IIS 5.0, but this article will concentrate on 6.0)
You'll also need to install PSExec and Guid Maker on the local development (Build) machine. Follow the instructions from the respective providers.
So, let's get started:
Demonstration
Create the web site.
On your remote IIS server, create a web site at the same level as "Default Web Site", and call it "AutoWeb". Point its home directory at C:\Inetpub\wwwroot\AutoWeb (you will have to create this directory).
Adding the WebSite:
Once you have created the site, right click it in IIS manager, select Properties, click the "Advanced" button, and add a port number of 8000, so that you have both port 80 and 8000:
We are going to use port 8000 to act as a target for the MSI. The port number can be anything really but its best to use high numbers so as not to encroach on important ports. Once you have done that, start the web site (if it is not already started).
Create the deployment directory.
Also on your remote server, create a shared directory: C:\Packages (share name = "Packages"), and just for the purposes of this demonstration, set the "everyone" user to full access. (Disclaimer: Of course you would not do that in a real environment; you would instead use tighter permissions, but remember this is just for the purposes of this demo.) Now create another directory under C:\Packages called AutoWeb:
Create the Solution.
On your local (development) machine, open Visual Studio .NET 2003 and create a new ASP.Net Web project called "AutoWeb", then add a web deployment project to it called AutoWebSetup. Add both the Content and Primary Output of AutoWeb to the AutoWebSetup project. (There won't be any code in this site, we're just going to use it for demonstrating the automated deployment process). The solution explorer should now look like this:
Enable Side-By-Side Deployment.
Changing the Product Code of a deployment project.
For "Side by side" to work, the MSI's "Product Code" GUID needs to be changed inside the deployment project every time you build. This means changing it to a new one just prior to building the MSI.
Here's how to automate the generation of a new GUID using VBScript and GUIDMaker:
Listing 1: "ChangeProductCodeGUID.vbs"
Dim strVersion, strNewGuid, objGuid, strDeployScriptDir
strDeployScriptDir="C:\Inetpub\wwwroot\AutoWeb\AutoWebSetup\AutoWebSetup.vdproj"
Set objGuid = CreateObject("GuidMakr.GUID")
strNewGuid= (objGuid.GetGUID)
'stamp the new guid into the deployment project file
StampProductCode strDeployScriptDir, strNewGuid
Sub StampProductCode(FileSpec,Guid)
Const FOR_READING=1
Const FOR_WRITING=2
Const TRISTATE_FALSE=0
Const TRISTATE_TRUE=-1
Const TRISTATE_USE_DEFAULT=-2
'open the file
Set fs=CreateObject("Scripting.FileSystemObject")
Set f1=fs.GetFile(FileSpec)
Set textstream= f1.OpenAsTextStream(FOR_READING,TRISTATE_FALSE)
' loop thru line by line and locate/replace the Productcode line
Do While textstream.AtEndOfStream = False
strLine = textstream.readline
If instr(strLine,"ProductCode") >0 Then
iStart = instr(strLine,"8:")
If iStart Then
strLine=mid(strLine,iStart + 2)
iStart=1
iFinish = instr(strLine,Chr(34))
If iFinish Then
strNewLine = " " & Chr(34) & "ProductCode" & Chr(34)
strNewLine = strNewLine & " = " & Chr(34) & "8:" & Guid & Chr(34)
strBuffer = strBuffer & strNewLine & vbcrlf
End If
End If
Else
strBuffer = strBuffer & strLine & vbcrlf
End If
Loop
textstream.Close
'now overwrite the file
Set f2=fs.GetFile(FileSpec)
Set textstream=f2.OpenAsTextStream(FOR_WRITING,TRISTATE_FALSE)
textstream.Write strBuffer
textstream.Close
Set f1=Nothing
Set f2=Nothing
Set fs=Nothing
End Sub
Changing the Virtual Folder name
You also need to change the virtual folder name before you compile the MSI. This is because the MSI will always deploy its files under the same website every time. I always add the build number at the end of the virtual folder name to identify it. We are using the "starting" build number of 1000. So if your virtual folder name is "AutoWeb" then the new Virtual folder name will be "AutoWeb1000"
To automate that using VBScript, see the VBScript file called "ChangeVirtualDir.vbs":
Listing 2: "ChangeVirtualDir.vbs"
Dim strVersion, strDeployScriptDir
strDeployScriptDir="C:\Inetpub\wwwroot\AutoWeb\AutoWebSetup\AutoWebSetup.vdproj"
strVersion = "1000"
'stamp the file
StampVirtualDir strDeployScriptDir, strVersion
Sub StampVirtualDir(FileSpec,Version)
Const FOR_READING=1
Const FOR_WRITING=2
Const TRISTATE_FALSE=0
Const TRISTATE_TRUE=-1
Const TRISTATE_USE_DEFAULT=-2
'open the file
Set fs=CreateObject("Scripting.FileSystemObject")
Set f1=fs.GetFile(FileSpec)
Set textstream= f1.OpenAsTextStream(FOR_READING,TRISTATE_FALSE)
' loop thru line by line and locate/replace the Virtual Dir line
Do While textstream.AtEndOfStream = False
strLine = textstream.readline
If instr(strLine,"VirtualDirectory") >0 Then
iStart = instr(strLine,"8:")
If iStart Then
strLine=mid(strLine,iStart + 2)
iStart=1
iFinish = instr(strLine,Chr(34))
If iFinish Then
strVirtualDir = mid(strLine,(iStart), iFinish - (iStart))
strNewLine = " " & Chr(34) & "VirtualDirectory" & Chr(34)
strNewLine = strNewLine & " = " & Chr(34) & "8:"
strNewLine = strNewLine & strVirtualDir & Version & Chr(34)
strBuffer = strBuffer & strNewLine & vbcrlf
End If
End If
Else
strBuffer = strBuffer & strLine & vbcrlf
End If
Loop
textstream.Close
'now overwrite the file
Set f2=fs.GetFile(FileSpec)
Set textstream=f2.OpenAsTextStream(FOR_WRITING,TRISTATE_FALSE)
textstream.Write strBuffer
textstream.Close
Set f1=Nothing
Set f2=Nothing
Set fs=Nothing
End Sub
Changing The "Add/Remove Programs" Title
You also need to change the name that is shown in "Add/Remove Programs" on the remote server, so that you can identify the version.. This script adds the build number at the end of the existing Product Name to identify it. We are using the "starting" build number of 1000. So if the existing Product Name is "AutoWebSetup" then the new Product Name will be "AutoWebSetup 1000"
To automate that using VBScript, see the VBScript file called "ChangeProductName.vbs":
Listing 3: "ChangeProductName.vbs"
Dim strVersion, strDeployScriptDir
strDeployScriptDir="C:\Inetpub\wwwroot\AutoWeb\AutoWebSetup\AutoWebSetup.vdproj"
strVersion="1000"
'stamp the new product name into the deployment project file
StampProductName strDeployScriptDir, strVersion
Sub StampProductName(FileSpec,strVersion)
Const FOR_READING=1
Const FOR_WRITING=2
Const TRISTATE_FALSE=0
Const TRISTATE_TRUE=-1
Const TRISTATE_USE_DEFAULT=-2
'open the file
Set fs=CreateObject("Scripting.FileSystemObject")
Set f1=fs.GetFile(FileSpec)
Set textstream= f1.OpenAsTextStream(FOR_READING,TRISTATE_FALSE)
' loop thru line by line and locate/replace the ProductName line
Do While textstream.AtEndOfStream = False
strLine = textstream.readline
If instr(strLine,"ProductName") >0 Then
iStart = instr(strLine,"8:")
If iStart Then
strLine=mid(strLine,iStart + 2)
iStart=1
iFinish = instr(strLine,Chr(34))
If iFinish Then
strProductName = mid(strLine,(iStart), iFinish - (iStart))
strNewLine = " " & Chr(34) & "ProductName" & Chr(34)
strNewLine=strNewLine & " = " & Chr(34) & "8:" & strProductName
strNewLine=strNewLine & " " & strVersion & Chr(34)
strBuffer = strBuffer & strNewLine & vbcrlf
End If
End If
Else
strBuffer = strBuffer & strLine & vbcrlf
End If
Loop
textstream.Close
'now overwrite the file
Set f2=fs.GetFile(FileSpec)
Set textstream=f2.OpenAsTextStream(FOR_WRITING,TRISTATE_FALSE)
textstream.Write strBuffer
textstream.Close
Set f1=Nothing
Set f2=Nothing
Set fs=Nothing
End Sub
Compile the solution.
You can do this in the IDE , or use your build tool if you have one set up, or you can call Devenv.exe from the command line. The easiest way is to right-click the deployment project ("AutoWebSetup") and then select "Rebuild". Either way, you end up with an MSI called AutoWebSetup.MSI in the debug directory under your deployment project. Note that we are not stamping the DLL's in this project with the build number. We should be doing that of course, but that is not the subject of this article.
Now its time to deploy.
For this we'll need a couple of extra script and batch files,
ChangeWebSitePath.vbs is a VBScript file, which changes the remote web site's home directory.
Listing 4: "ChangeWebSitePath.vbs"
ThisScriptName="ChangeWebSitePath.vbs"
if WScript.Arguments.Count <> 0 then
strWebStartPath = WScript.arguments(0)
strWebSiteName = WScript.arguments(1)
strNewHomePath = WScript.arguments(2)
strLogFile = WScript.arguments(3)
else
'*********************
'Get current Directory
'*********************
Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.GetFile(ThisScriptName)
for iCtr = Len(file.Path) to 1 step -1
lPos = Instr(iCtr,file.path,"\")
if lPos > 0 then
strLogFile=left(file.Path,lPos) & "ChangeWebSitePath.log"
Exit For
End If
Next
'Log command line error
Call LogText(strLogFile,"One or more missing parameters on command line. " _
& WScript.ScriptFullName & " started on " & formatdatetime(Date,0) _
& " " & formatdatetime(Time,3))
WScript.Quit
end if
'Log start of script
Call LogText(strLogFile,"***** " & WScript.ScriptFullName & " started on " _
& formatdatetime(Date,0) & " " & formatdatetime(Time,3) & " *****")
Set objIIsWebService = GetObject(strWebStartPath)
For Each objSITE In objIIsWebService
If objSITE.class = "IIsWebServer" Then
if UCase(objSITE.ServerComment) = UCase(strWebSiteName) then
strIndex = objSITE.Name
Call LogText(strLogFile,"Found Website " & strWebSiteName _
& " with index of " & strIndex)
Set objIIsWebSite = GetObject(strWebStartPath & "/" & strIndex _
& "/ROOT")
objIIsWebSite.Path = strNewHomePath
objIIsWebSite.SetInfo
Call LogText(strLogFile,"Changed Website " & strWebSiteName _
& " home directory to " & strNewHomePath)
Exit For
end if
End If
Next
'Log end of script
Call LogText(strLogFile,"***** " & WScript.ScriptFullName & " completed on " _
& formatdatetime(Date,0) & " " & formatdatetime(Time,3) & " *****")
WScript.Quit
Sub LogText(strFileSpec,strLogLine)
dim fs, f1
Const FOR_READING=1
Const FOR_WRITING=2
Const FOR_APPENDING=8
Const TRISTATE_FALSE=0
Const TRISTATE_TRUE=-1
Const TRISTATE_USE_DEFAULT=-2
Set fs=CreateObject("Scripting.FileSystemObject")
Set f1 = fs.OpenTextFile(strFileSpec, FOR_APPENDING, True)
f1.WriteLine(strLogLine)
f1.Close
Set f1=Nothing
Set fs=Nothing
End Sub
Install.bat is a batch file, which calls the ChangeWebSitePath.vbs script, and also installs the MSI. ("Install.bat" is created by "Deploy.bat")
ResetWebPath.bat is a batch file that calls ChangeWebSitePath.vbs. ("ResetWebPath.vbs" is created by "Deploy.bat")
SetnewWebPath.bat is a batch file that also calls ChangeWebSitePath.vbs. ("SetNewWebSitePath.vbs" is created by "Depoy.bat")
Copy the MSI, the VBS and the three BAT files to the shared \\<RemoteMachine>\Packages\AutoWeb\1.0.0.1000 folder using XCopy, then using PSExec, run them.
Listing 5: "Deploy.bat" (note that you have to substitute your own values at the top of the script)
SET BUILD_NUMBER=1000
SET YOUR_REMOTE_SERVER=YourRemoteServerName
SET YOUR_REMOTE_USER=YourRemoteUserName
SET YOUR_REMOTE_PASSWORD=YourRemotePassword
IF NOT EXIST \\%YOUR_REMOTE_SERVER%\Packages\AutoWeb\1.0.0.%BUILD_NUMBER% MD \\%YOUR_REMOTE_SERVER%\Packages\AutoWeb\1.0.0.%BUILD_NUMBER%
Once you have run Deploy.bat, look at the "Add/Remove Programs" Applet on your remote machine. It should contain an entry similar to this:
Also, it should have deployed the website to C:\Inetpub\wwwroot\AutoWeb\AutoWeb1000
But wait, there's more ! .... if you open IIS manager, and look at the home directory of the AutoWeb site, it should be pointing to:
C:\Inetpub\wwwroot\AutoWeb\AutoWeb1000
Cool isn't it ?
Conclusion
You can see how all of this can be totally automated, from say a Scheduled Task or similar. The really nice thing about
this style of deployment is that, for production systems, it allows you to roll back to the previous version simply by
changing the home directory of the web site, no further deployment required! This can be very handy when you've just
deployed a new version and something has gone horribly wrong. It does in fact satisfy all of my original requirements:
It allows testers to switch easily between versions and compare test results
It allows easy rollback in a production environment
It allows Side-By-Side deployment
It's totally automated
Of course, you do end up with a new version of the application every day on the IIS server, so I am in the process
of writing a script that removes old versions. Also this article does not cover automating the migration of
databases, or the configuration of the application. So there are some ideas that I can perhaps discuss in part
2 of this article.
Please feel free to email me with any questions... Enjoy !
In the second part of his series on building N-tier web applications using ASP.NET 2.0 and SQL Server 2005, Thiru Thangarathinam covers the business logic and user interface layers. In the process, he also examines some new features in ASP.NET 2.0 that greatly simplify the development process.
[Read This Article][Top]
Code reusuability is one of the major goals of any good object-oriented programmer. While the ASP.NET framework has made code reusuability easier and more elegant than it was in classic ASP, one area where reusuability could be improved is at the UI level. This article outlines a technique that you can use in ASP.NET 1.x that allows every page in your web application to inherit not only the functionality of a base page, but its UI as well.
[Read This Article][Top]
In this article, Gayan Peiris looks at creating an ASP.NET web application that will display the usage details of a selected SharePoint site. Building such an application enables SharePoint administrators to gather all SharePoint usage data from a central location. [Read This Article][Top]
In this article, Gayan Peiris examines using the SharePoint Object Model
to access SharePoint site information from an ASP.NET web application.
It should be of particular interest to SharePoint administrators who
can use the included code as a starting point for development of
their own web-based SharePoint administration application.
[Read This Article][Top]
Most default SharePoint Server Web Parts can be connected across organizations. The third article in this series shows how to develop connectable Web Parts that consume information provided by other Web Parts. [Read This Article][Top]
Automatic daily builds is a well known software engineering best practice. This article introduces a strategy for implementing and promoting daily builds and offers tips and tricks for preventing and fixing breaks. [Read This Article][Top]
Building an application can be more than pressing F5. With an increasing
number of quality packages being released, developers for the .NET platform now have options to create a very sophisticated build process. Aaron Junod describes a sample build environment and shows how a number of tools can work together to make reliable, predictable, and value-added builds. [Read This Article][Top]
The first part of this three part series walks through the process of creating a mobile blog application using a BASIC development environment for Palm OS devices called NS Basic.
Subsequent articles will focus on synchronizing the data to the desktop using C# and creating an installer. [Read This Article][Top]
This short article provides source code for a classic ASP online database functions testing application and shows how to configure and use the tool for either SQL Server or Oracle. [Read This Article][Top]
In this article Robert Chartier shows you how to use functionality in the .NET Framework to rewrite requested URLs on the fly. [Read This Article][Top]
Mailing List
Want to receive email when the next article is published? Just Click Here to sign up.