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
Here is a link for version 2 on a x86 platform
if you on 64-bit windows, then download
Libraries, XSD and import files
- Microsoft.Sdc.Common.tasks
- MSBuild.Community.Tasks.Targets
- Microsoft.Sdc.Tasks.dll
- MSBuild.Community.Tasks.dll
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)
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 . . .
—————————————————————–
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!
- Uncategorized
any updates ?? using IIS 7 and MSBuild Extension Pack.
Hi,
Good Question, will have a look into it, things have changed allot since that post π