Image may be NSFW.
Clik here to view.
If you missed part one please check it out and then come back here. You can also download the sample code from CodePlex.
Our next job is to get MSBuild to copy the files needed for deployment into a new directory. This is effectively the same as using the Publish Web Site option in Visual Studio. However, I couldn’t find any way to do that via MSBuild – if anyone knows how, please leave a comment. UPDATE I found a way to do this in the end, check out the post How to publish a web site with MSBuild.
Instead I’ve used the Copy task, and defined a couple of ItemGroups, one for the aspx pages and another for the contents of the bin folder. The XML below shows this.
<Target Name="Publish"> <RemoveDir Directories="Output" ContinueOnError="true" /> <ItemGroup> <Pages Include="**\*.aspx" /> <Binaries Include="bin\*.dll" /> </ItemGroup> <Copy SourceFiles ="@(Pages)" DestinationFolder="Output\%(RecursiveDir)" /> <Copy SourceFiles ="@(Binaries)" DestinationFolder="Output\bin" /> <Copy SourceFiles ="Web.config" DestinationFolder="Output" /> </Target>
Along with the web.config file, these files are all that are needed for our simple website to run.
This new target introduces some more features of MSBuild. First off the RemoveDir task runs with the ContinueOnError attribute set to true. The effect of this is that, should we try to remove a directory that does not exist, the resulting error will not stop the script. This attribute is actually available for all tasks and defaults to false.
When specifying the Pages ItemGroup, using “**\*.aspx” allows us to pick up all aspx files in the project, rather than just those in the root. This is important as our web site has an Admin folder containing an aspx.
Having defined the ItemGroups it is quite simple to use the Copy task to place them in the Output folder. We do not need to explicitly create the folder either – the act of copying into it will do so.
In order to preserve the directory structure of the web site, the pages are copied using the %(RecursiveDir) syntax. Had this not been used then all pages would have been placed in the root of the Output folder.
To give the new target a quick spin, execute the following command
msbuild Build.xml /t:Publish
and you should find the Output folder populated with the binaries, aspx pages and the web.config. The site is almost ready to be deployed, however we still need to copy the environment-specific settings over. Let’s tackle that now.
As you may recall from part one, in addition to the web.config file, there are live.config and test.config files. Whilst web.config contains all settings for the web site, live.config and test.config contain only those settings that are different for that environment. It is then the job of the build script to copy the correct values over.
To achieve our goal, the general plan is to read settings out of one file, using an XPath query, and then write them back to another file, using the same XPath. A quick scan of the built-in tasks didn’t find anything useful, however the MSBuild Community Tasks Project came to the rescue. Having downloaded and installed the extra tasks they provide, we now have access to XmlRead and XmlUpdate tasks. Excellent!
Before diving in and using them, make sure the following XML is inserted below the root Project node
<Import Project="C:\Program Files\MSBuild\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
This ensures that MSBuild knows about the extra tasks. We also need some intermediary variables to store the settings between reading and writing. MSBuild allows a script to declare a PropertyGroup, which can then contain a bunch of properties. The following XML illustrates this
<PropertyGroup> <Environment>Test</Environment> <CompilationDebug /> <CustomErrorsMode /> <ContentEditorsEmail /> <AdministratorsEmail /> </PropertyGroup>
Note that a property has also been added for indicating which environment we are working with. At the moment this is hard-wired as Test but that will change later on.
The first step is to get those properties populated correctly, using the XmlRead task. This is pretty simple as can be seen below
<Target Name="GetConfig"> <XmlRead XPath="configuration/system.web/compilation/@debug" XmlFileName="$(Environment).config"> <Output TaskParameter="Value" PropertyName="CompilationDebug" /> </XmlRead> </Target>
I’ve only shown one instance of XmlRead here, to avoid repetition. The full script contains all four. The points to make here are
- A GetConfig target has been declared to keep all our XmlRead tasks together
- The XmlFileName attribute has a dynamic value. Using MSBuild’s $(PropertyName) syntax it is easy to point at the correct config file dependant on the value of the Environment property
- The Output tag is used in order to store the return value of XmlRead in the CompilationDebug property
We’re almost there now. As you may have guessed, the syntax for XmlUpdate is very similar. Again, it makes sense to group all four updates into a SetConfig target, as follows
<Target Name="SetConfig" DependsOnTargets="GetConfig"> <XmlUpdate XPath="configuration/system.web/compilation/@debug" XmlFileName="Output\web.config" Value="$(CompilationDebug)" /> </Target>
The same XPath is used to find the target node in the web.config file and the Value attribute is set using the $(PropertyName) substitution technique. I also added DependsOnTargets=”GetConfig” to enforce that settings are read before they are written.
Phew! OK, we can now run our shiny new target by switching back to the command window and executing this statement
msbuild Build.xml /t:SetConfig
To test everything has worked, open up the web.config file in the Output folder and you will find that its values match those in Test.config.
At this point the script is in need of a little refactoring. It is also awkward to run all of the targets in one go. In the next part we will resolve these issues and create the deployment target. Until then please feel free to provide feedback in the comments. You can also download the script in its current state from CodePlex.
Technorati tags: msbuild msbuild tutorial build script .NET