Last week I came across an interesting problem with MsBuild. I recently blogged about Web Deployment projects in a Team Foundation build. A big advantage of the Web Deployment projects is, that the website/web application output files are copied to a sub directory rather than being dropped in the build output directory which is the case for all other project types.
Unfortunately this behavior was not enough for the build I was working on.
In a TeamBuild, only containing Web Application projects and Web Deployment projects, I needed to copy the published websites to a new directory and add an extra subdirectory into the path.
Let me clarify this with an example: I needed to copy “c:Build_PublishedWebsitesWebSite1***.*” to “c:MyWebApplicationsWebSite1wwwroot***.*”.
The key to solving this problem is getting a list of all the websites in the _PublishedWebSites directory and linking them to a collection of files that are contained in those website directories. Unfortunatly this can’t be accomplished in a single MsBuild statement. But I can be solved by using metadata on items and item collections.
Metadata is extra information about items in a collection. This metadata can be used to manipulate the item collection.
Each item in an item collection has some metadata that can be used. For instance each item has a metadata property FullPath specifying the full path to the item on disk. Here you can find a list of the well-known item metadata that is associated with each item in an item collection.
It is also possible to add your own metadata to items in a collection.
I used this feature in the build I was working on to create an item collection in which each item had some custom metadata.
Lets take a look at the example again. The first step to take is getting a list of all the websites in the _PublishedWebSites directory.
Just create a new item collection, selecting all the (first level) directories under the _PublishedWebSites directory. (A pre condition is that the directories you want to select must contain at least 1 file. Empty directories or directories only containing directories themselves are ignored by the CreateItem task.)
The new item collection contains all the files that are available in the directories. Each item will have some metadata we need to use. One being: RelativeDir. This metadata property holds the directory (part of a path) that corresponds with the * or ** specified in the Include filter when creating the item collection. In the example, it will contain “WebSite1”.
Next we need an item collection of all the files that are part of the websites, grouped per website. This can be accomplished by selecting all the files and directories in the directories we collected in the PublishedWebSites item collection.
By only using the RootDir and Directory metadata properties, it is possible to create an item collection containing all the files. When creating a new item collection based on a previous created item collection, the metadata properties of the previous created item collection must be copied to the new collection (if you need to use them again). In case of the example, the RecursiveDir property must be copied.
It is not possible to overwrite the well-known metadata properties so a new metadata property must be created.
By adding this metadata it is possible to ensure that the directory name of the website directory is preserved.
The FilesToCopy item collection will contain all the files that must be copied, and will have all the relevant metadata to create the destination path.
This is just one example of how you can use metadata on items and it shows that the use of metadata can be a powerful way to solve specific problems.