MSBUILD: Creating Professional Deployment Scripts – Part 1

 

Introduction

I recently published a blog in regards to developing a custom application that will configure IIS using Directory Services. The tool is capable of:

  • Creating Application Pools
  • Creating Virtual Directories
  • Creating Web Sites
  • Configuring settings for the above (Application Pool Identity, Anonymous User, Virtual Directory Application Name)

To be honest, I was not entirely happy with this tool, since it would not work on Vista or later versions of IIS. Another problem, was that the XML file holding the configuration was my own ‘Language’ of expressing how to configure IIS and is not a standard, so could be hard for administrators to learn if I depart from the company, so we need something that a community supports.

A colleague of mine (Chris Hagens) introduced me to MSBuild, and I was blown away by this tool and how easy it is to use. This article will introduce a simple build script that will get you going so that you can blow the mind of your administrator when the next deployment schedule is nigh.

Before we continue, please take a moment to read this article, I certainly found it helpful in fast tracking my learning curve, I enjoy to understand processes instead of trying to hack. So put a sign on the door "Do not disturb" and grab a cup of tea and lets get down and dirty!

Patrick Smacchia: http://www.codeproject.com/KB/books/msbuild.aspx

Internet Information Server

Scenario

We are implementing a SOA (Service Orientated Architecture) solution which requires various web services to be deployed on various servers and different environments (Pre-Production, Production, Test and Development).

NTFS permissions will also be created and active directory account will be added to a group.

Solution

We going to geek it to the max and create a simple MSBuild script that will run at configure the web services automatically for us. There are many ways to deploy zipped applications, so in this solution, I want to keep it simple and assume you have a separate script to unzip the web service web files e.g asmx, web.config and dll’s to the file system.

The solution consists of two files:

  • WebServers.cmd
  • WebServices.build

There is also assemblies and schemas used:

  • Microsoft.Sdc.Common.tasks
  • MSBuild.Community.Tasks.Targets
  • Microsoft.Sdc.Tasks.BizTalk.dll
  • MSBuild.Community.Tasks.dll

I have created a zip file that you can download.

Download

http://grounding.co.za/files/folders/documents/entry1841.aspx

Prerequisite

.Net Framework

The prerequisite for this to work is the .NET Framework version 2 is installed, just the runtime

image

Here is a link for version 2 on a x86 platform

 http://www.microsoft.com/downloads/details.aspx?familyid=0856eacb-4362-4b0d-8edd-aab15c5e04f5&displaylang=en

if you on 64-bit windows, then download

http://www.microsoft.com/downloads/details.aspx?familyid=B44A0000-ACF8-4FA1-AFFB-40E78D788B00&displaylang=en

Libraries, XSD and import files

  • Microsoft.Sdc.Common.tasks
  • MSBuild.Community.Tasks.Targets
  • Microsoft.Sdc.Tasks.dll
  • MSBuild.Community.Tasks.dll

image

image

Note: If you run this on XP you may not get Application Pools! IIS 6.0 and higher uses Application Pools.

Analysis

Main Batch File

Here is the main batch file, it’s sole purpose is to pass the correct environment variable to the MSBuild tool, so it knows which environment it is building:

WebServices.cmd

———————————————————————————————-

@echo off
set /P ENVIRONMENT="Choose environment (DEV, TEST, PREP, PROD) : "
IF %ENVIRONMENT% == DEV GOTO deploy
IF %ENVIRONMENT% == TEST GOTO deploy
IF %ENVIRONMENT% == PREP GOTO deploy
IF %ENVIRONMENT% == PROD GOTO deploy
ECHO Unknown environment
GOTO end

:deploy
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\msbuild.exe WebServices.build /property:ENVIRONMENT=%ENVIRONMENT%

:end
pause

———————————————————————————————-

The first thing we need to do is store the answer to the  question into an environment variable, in this case the question or prompt is going to be "Choose environment…."

The set /P switch allows you to set the value of a variable to a line of input entered by the user.  Displays the specified promptString before reading the line of input.  The promptString can be empty.

Once we have successfully we can now call the msbuild utility and parse it the correct argument for the environment.

"msbuild.exe WebServices.build /property:ENVIRONMENT=%ENVIRONMENT%"

WebServices.Build

———————————————————————————————-

<Project DefaultTargets="All"  xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
    <Import Project="C:\MSBUILD\Tools\Microsoft.Sdc.Common.tasks" />
    <Import Project="C:\MSBUILD\Tools\MSBuild.Community.Tasks.Targets"/>

    <!– Set the application name as a property –>
    <PropertyGroup Condition="$(ENVIRONMENT)==’DEV’">
        <ServerName>.</ServerName>
        <!– Gateway settings –>
        <GatewayAppPool>MMITGatewayPool</GatewayAppPool>
        <GatewayIdentityUsername>Administrator</GatewayIdentityUsername>
        <GatewayIdentityDomain>guru-f09deb3e</GatewayIdentityDomain>
        <GatewayIdentityFQUser>$(GatewayIdentityDomain)\$(GatewayIdentityUsername)</GatewayIdentityFQUser>
        <GatewayIdentityPassword>password</GatewayIdentityPassword>
        <GatewayBackupFolder>C:\MSBuild\Example\Gateway\Backup</GatewayBackupFolder>
        <GatewayVirtualPath>C:\MSBuild\Example\Gateway\WebServices</GatewayVirtualPath>
        <GatewayVirtualDirName>Gateway.WebServices</GatewayVirtualDirName>
    </PropertyGroup>
    <PropertyGroup Condition="$(ENVIRONMENT)==’TEST’">
        <ServerName>.</ServerName>
        <!– Gateway settings –>
        <GatewayAppPool>MMITGatewayPool</GatewayAppPool>
        <GatewayIdentityUsername>_SA_WebService</GatewayIdentityUsername>
        <GatewayIdentityDomain>TEST</GatewayIdentityDomain>
        <GatewayIdentityFQUser>$(GatewayIdentityDomain)\$(GatewayIdentityUsername)
</GatewayIdentityFQUser>
        <GatewayIdentityPassword>PassworD</GatewayIdentityPassword>
        <GatewayBackupFolder>c:\backup</GatewayBackupFolder>
        <GatewayVirtualPath>C:\Gateway\WebServices</GatewayVirtualPath>
        <GatewayVirtualDirName>Gateway.WebServices</GatewayVirtualDirName>
    </PropertyGroup>
    <PropertyGroup Condition="$(ENVIRONMENT)==’PREPROD’">
        <ServerName>.</ServerName>
        <!– Gateway settings –>
        <GatewayAppPool>MMITGatewayPool</GatewayAppPool>
        <GatewayIdentityUsername>_SA_WebService_PP</GatewayIdentityUsername>
        <GatewayIdentityDomain>TEST</GatewayIdentityDomain>
        <GatewayIdentityFQUser>$(GatewayIdentityDomain)\$(GatewayIdentityUsername)</GatewayIdentityFQUser>
        <GatewayIdentityPassword>pASSWORd</GatewayIdentityPassword>
        <GatewayBackupFolder>c:\backup</GatewayBackupFolder>
        <GatewayVirtualPath>C:\Gateway\WebServices</GatewayVirtualPath>
        <GatewayVirtualDirName>Gateway.WebServices</GatewayVirtualDirName>
    </PropertyGroup>
    <PropertyGroup Condition="$(ENVIRONMENT)==’PROD’">
        <ServerName>.</ServerName>
        <!– Gateway settings –>
        <GatewayAppPool>MMITGatewayPool</GatewayAppPool>
        <GatewayIdentityUsername>_SA_WebService</GatewayIdentityUsername>
        <GatewayIdentityDomain>PROD</GatewayIdentityDomain>
        <GatewayIdentityFQUser>$(GatewayIdentityDomain)\$(GatewayIdentityUsername)</GatewayIdentityFQUser>
        <GatewayIdentityPassword></GatewayIdentityPassword>
        <GatewayBackupFolder>c:\backup</GatewayBackupFolder>
        <GatewayVirtualPath>C:\Gateway\WebServices</GatewayVirtualPath>
        <GatewayVirtualDirName>Gateway.WebServices</GatewayVirtualDirName>
    </PropertyGroup>

   <Target Name="AppPool" >
        <Prompt Condition="$(ENVIRONMENT) == ‘PROD’" Text="Enter the application pool password">
            <Output TaskParameter="UserInput" PropertyName="GatewayIdentityPassword"/>
        </Prompt>
        <Message Text="Creating Gateway Application Pool" />
        <Web.AppPool.Create ContinueOnError="true" AppPoolName="$(GatewayAppPool)" Identity="$(GatewayIdentityFQUser)" Password="$(GatewayIdentityPassword)" IdentityType="3" PeriodicRestartTime="12"/>
    </Target>

    <Target Name="VirtualDirectory">
        <Message Text="Creating Gateway Virtual Directory" />
        <Web.WebSite.DeleteVirtualDirectory VirtualDirectoryName="MMIT.Gateway.WebServices" />
        <Web.WebSite.CreateVirtualDirectory AppPoolId="$(GatewayAppPool)" AppCreate="true" WebAppName="$(GatewayVirtualDirName)" Path="$(GatewayVirtualPath)" VirtualDirectoryName="$(GatewayVirtualDirName)" />
    </Target>

    <Target Name="FolderPermissionsBackup">
        <MakeDir Directories="$(GatewayBackupFolder)"/>
        <Exec Command="cacls $(GatewayBackupFolder)  /G $(GatewayIdentityFQUser):F /T /E" />
        <Exec Command="cacls c:\windows\temp  /G $(GatewayIdentityFQUser):F /T /E" />
    </Target>

    <Target Name="ASPWorkerProcess">
        <ActiveDirectory.Group.AddUser ContinueOnError="true" GroupName="IIS_WPG" GroupMachine ="$(ServerName)" UserName="$(GatewayIdentityUsername)" UserDomain ="$(GatewayIdentityDomain)" />
    </Target>
    <Target Name="All">
        <CallTarget Targets="AppPool" />
        <CallTarget Targets="VirtualDirectory" />
        <CallTarget Targets="FolderPermissionsBackup" />
        <CallTarget Targets="ASPWorkerProcess" />
    </Target>
</Project>

———————————————————————————————-

XML Declarations and Imports

<Project DefaultTargets="All"  xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
    <Import Project="C:\MSBuild\Tools\Microsoft.Sdc.Common.tasks" />
    <Import Project="C:\MSBuild\Tools\MSBuild.Community.Tasks.Targets"/>

    <!– Set the application name as a property –>

This is an advanced XML file that actually conforms to an XSD, which is referenced in this namespace:

"http://schemas.microsoft.com/developer/msbuild/2003"

I recommend you reference it, it will allow you to use type safety when writing the XML configuration file, assuming you using an XML editor (notepad is not recommended, your eyes will go dizzy). Notice my files are located in my C:\MSBuild\Tools\ folder, you can modify this and choose your path.

Property Group

This section in the XML is very powerful, here you can define variables, like when writing a class, and then reference them, what is really nice you can use conditional statements to initialise the properties depending on a state of a variable e.g.

Condition="$(ENVIRONMENT)==’DEV’

In my example, I have properties to store for Username, Password, Domain and also the fully qualified username e.g. Domain\UserName

<PropertyGroup Condition="$(ENVIRONMENT)==’DEV’">
        <ServerName>.</ServerName>
        <!– Gateway settings –>
        <GatewayAppPool>MMITGatewayPool</GatewayAppPool>
        <GatewayIdentityUsername>Gateway</GatewayIdentityUsername>
        <GatewayIdentityDomain>DEV</GatewayIdentityDomain>
        <GatewayIdentityFQUser>$(GatewayIdentityDomain)\$(GatewayIdentityUsername)</GatewayIdentityFQUser>
        <GatewayIdentityPassword>mmitdev</GatewayIdentityPassword>
        <GatewayBackupFolder>c:\backup</GatewayBackupFolder>
        <GatewayVirtualPath>C:\Code\MMIT.Gateway\MMIT.Gateway.WebServices</GatewayVirtualPath>
        <GatewayVirtualDirName>MMIT.Gateway.WebServices</GatewayVirtualDirName>
</PropertyGroup>

Target Group

In this section you put the implementation of your scripts, like running commands.

I ran cacls.exe which confgures NTFS permissions for a backup folder and various other tasks, many of which are in libraries, the command "Web.WebSite.CreateVirtualDirectory" actually calls the dll referenced by the XML and passes the parameters in.

Below is a sample target Action which adds a user to a group:

    <Target Name="ASPWorkerProcess">
        <ActiveDirectory.Group.AddUser ContinueOnError="true" GroupName="IIS_WPG" GroupMachine ="$(ServerName)" UserName="$(GatewayIdentityUsername)" UserDomain ="$(GatewayIdentityDomain)" />
    </Target>

Running the tool

Double click WebServices.cmd

Type in DEV (The other environments will fail, unless you configure the properties for them)

image

 

Output of Sample Run

—————————————————————–

Choose environment (DEV, TEST, PREP, PROD) : DEV
Microsoft (R) Build Engine Version 2.0.50727.832
[Microsoft .NET Framework, Version 2.0.50727.832]
Copyright (C) Microsoft Corporation 2005. All rights reserved.

Build started 20/09/2008 10:03:29.
__________________________________________________
Project "C:\MSBuild\WebServices.build" (default targets):

Target All:
    Target AppPool:
        Creating Gateway Application Pool
        Creating app pool "MMITGatewayPool".
        MSBUILD : warning : A task error has occured.
        MSBUILD : warning : Message             = App Pool already exists.
        MSBUILD : warning : MachineName         = localhost
        MSBUILD : warning : AppPoolName         = MMITGatewayPool
        MSBUILD : warning : IdentityType        = 3
        MSBUILD : warning : Identity            = guru-f09deb3e\Administrator
        MSBUILD : warning : Password            = password
        MSBUILD : warning : IdleTimeout         = 20
        MSBUILD : warning : PeriodicRestartTime = 12
        MSBUILD : warning : WorkerProcesses     = 1
        MSBUILD : warning : RestartSchedule     = <String.Empty>
        MSBUILD : warning : RequestQueueLimit   = 1000
        MSBUILD : warning :
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.Web.AppPool.
EnsureAppPool()
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.Web.AppPool.
Save()
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.Web.AppPool.Create.Interna
lExecute()
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.TaskBase.Execute()
        MSBUILD : warning : The system cannot find the path specified.
        MSBUILD : warning :
        MSBUILD : warning :    at System.DirectoryServices.DirectoryEntry.Bind(B
oolean throwIfFail)
        MSBUILD : warning :    at System.DirectoryServices.DirectoryEntry.Bind()

        MSBUILD : warning :    at System.DirectoryServices.DirectoryEntry.get_Is
Container()
        MSBUILD : warning :    at System.DirectoryServices.DirectoryEntries.Chec
kIsContainer()
        MSBUILD : warning :    at System.DirectoryServices.DirectoryEntries.Add(
String name, String schemaClassName)
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.Web.AppPool.
EnsureAppPool()
        The previous error was converted to a warning because the task was calle
d with ContinueOnError=true.
        Build continuing because "ContinueOnError" on the task "Web.AppPool.Crea
te" is set to "true".
    Done building target "AppPool" in project "WebServices.build".
    Target VirtualDirectory:
        Creating Gateway Virtual Directory
        Deleting virtual directory "MMIT.Gateway.WebServices".
        Creating virtual directory "Gateway.WebServices".
    Target FolderPermissionsBackup:
        cacls C:\MSBuild\Example\Gateway\Backup  /G guru-f09deb3e\Administrator:
F /T /E
        processed dir: C:\MSBuild\Example\Gateway\Backup
        cacls c:\windows\temp  /G guru-f09deb3e\Administrator:F /T /E
        processed dir: c:\windows\Temp
        processed file: c:\windows\Temp\avg8info.id
        processed file: c:\windows\Temp\DMI6DC.tmp
        processed file: c:\windows\Temp\ehprivjob.log
        processed file: c:\windows\Temp\ehprivjob1.log
        processed file: c:\windows\Temp\MpSigStub.log
        processed file: c:\windows\Temp\WinSAT_DX.etl
        processed file: c:\windows\Temp\WinSAT_KernelLog.etl
        processed file: c:\windows\Temp\WinSAT_StorageAsmt.etl
    Target ASPWorkerProcess:
        MSBUILD : warning : A task error has occured.
        MSBUILD : warning : Message             = The specified domain either do
es not exist or could not be contacted.
        MSBUILD : warning :
        MSBUILD : warning : GroupMachine[0]     = .
        MSBUILD : warning : EnsureUserIsInGroup = False
        MSBUILD : warning : UserName            = Administrator
        MSBUILD : warning : GroupName[0]        = IIS_WPG
        MSBUILD : warning : UserDomain          = guru-f09deb3e
        MSBUILD : warning :
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.ActiveDirect
ory.User.Exists(String username, String domainName)
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.ActiveDirectory.Group.AddU
ser.InternalExecute()
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.TaskBase.Execute()
        The previous error was converted to a warning because the task was calle
d with ContinueOnError=true.
        Build continuing because "ContinueOnError" on the task "ActiveDirectory.
Group.AddUser" is set to "true".
    Done building target "ASPWorkerProcess" in project "WebServices.build".
Done building target "All" in project "WebServices.build".

Done building project "WebServices.build".

Build succeeded.
MSBUILD : warning : A task error has occured.
MSBUILD : warning : Message             = App Pool already exists.
MSBUILD : warning : MachineName         = localhost
MSBUILD : warning : AppPoolName         = MMITGatewayPool
MSBUILD : warning : IdentityType        = 3
MSBUILD : warning : Identity            = guru-f09deb3e\Administrator
MSBUILD : warning : Password            = password
MSBUILD : warning : IdleTimeout         = 20
MSBUILD : warning : PeriodicRestartTime = 12
MSBUILD : warning : WorkerProcesses     = 1
MSBUILD : warning : RestartSchedule     = <String.Empty>
MSBUILD : warning : RequestQueueLimit   = 1000
MSBUILD : warning :
MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.Web.AppPool.EnsureAp
pPool()
MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.Web.AppPool.Save()
MSBUILD : warning :    at Microsoft.Sdc.Tasks.Web.AppPool.Create.InternalExecute
()
MSBUILD : warning :    at Microsoft.Sdc.Tasks.TaskBase.Execute()
MSBUILD : warning : The system cannot find the path specified.
MSBUILD : warning :
MSBUILD : warning :    at System.DirectoryServices.DirectoryEntry.Bind(Boolean t
hrowIfFail)
MSBUILD : warning :    at System.DirectoryServices.DirectoryEntry.Bind()
MSBUILD : warning :    at System.DirectoryServices.DirectoryEntry.get_IsContaine
r()
MSBUILD : warning :    at System.DirectoryServices.DirectoryEntries.CheckIsConta
iner()
MSBUILD : warning :    at System.DirectoryServices.DirectoryEntries.Add(String n
ame, String schemaClassName)
MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.Web.AppPool.EnsureAp
pPool()
MSBUILD : warning : A task error has occured.
MSBUILD : warning : Message             = The specified domain either does not e
xist or could not be contacted.
MSBUILD : warning :
MSBUILD : warning : GroupMachine[0]     = .
MSBUILD : warning : EnsureUserIsInGroup = False
MSBUILD : warning : UserName            = Administrator
MSBUILD : warning : GroupName[0]        = IIS_WPG
MSBUILD : warning : UserDomain          = guru-f09deb3e
MSBUILD : warning :
MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.ActiveDirectory.User
.Exists(String username, String domainName)
MSBUILD : warning :    at Microsoft.Sdc.Tasks.ActiveDirectory.Group.AddUser.Inte
rnalExecute()
MSBUILD : warning :    at Microsoft.Sdc.Tasks.TaskBase.Execute()
    2 Warning(s)
    0 Error(s)

Time Elapsed 00:00:02.89
Press any key to continue . . .

—————————————————————–

image

image

Download

http://grounding.co.za/files/folders/documents/entry1841.aspx

Conclusion

 

I think this article gives enough substance to get you going in creating deployment scripts that are kick ass. Play with it, and remember this is designed for IIS 6.0 and above. I did not package in a sample web service file, but I am sure you folks can test it with a real virtual directory, my sample for the dev environment is empty, to keep things simple. Remember, when you run the tool to type DEV and dev etc, the prompts are case sensitive.

This tool is really fun to play with and adds an extra edge to your applications at deployment time. In Part 2, we will take this tool to the limits and automate BizTalk 2006 deployments, which I can assure you, requires some super Geeking!

Advertisements
Uncategorized

2 thoughts on “MSBUILD: Creating Professional Deployment Scripts – Part 1

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s