Wednesday, June 27, 2007

Ok, I know there are many MSBuild loggers available so why create a new one? Because for a while I’ve been thinking that it would be cool to have a logger that would transform an MSBuild log into a log4net log. This is because obviously log4net has many appenders and tools that you can use out of the box. For example if you wanted to put your logs into a database, then attach the AdoNetAppender. Because of this, and because I couldn’t already find one, I wrote one. You can find it on my Sedodream MSBuild project. This logger is called SedoLogger. I figured this would be a good name, in the case that later on I wanted to abstract out the specific logging mechanism behind it. I have just created a new release that contains all the necessary files. I have posted some usage information about this logger on the SedoLogger WiKi page. I’ll be posting some more details about this logger here in the near future.

Sayed Ibrahim Hashimi

Wednesday, June 27, 2007 6:09:10 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Friday, April 27, 2007

Hi,

Someone contacted me regarding some issues he was having with respect to using the Web Deployment Project s and that was the inspiration for this post.
There is an inherent problem with the way that Properties and Items are declared, created, overridden and used. There are a few problems those are:

·         PropertyGroup & ItemGroup elements are evaluated before any target executes.

·         Items are append only, meaning that you can’t delete items or any reference contained within them.

We will now start by examining this issue. To simplify the issue I won’t use the Web Deployment Project files, or C# project files. But simpler files that let us focus on what is going on. Below is the file that represents the Microsoft.WebDeployment.targets file. This file is called Shared.proj.  

<Project

  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!--

  This represents the Microsoft.WebDeployments.targets file

  -->

  <PropertyGroup>

    <_AspNetCompilerVirtualPath>$(SourceWebVirtualPath)</_AspNetCompilerVirtualPath>

  </PropertyGroup>

  <ItemGroup>

    <_SourceWebPathItem Include="$(SourceWebPhysicalPath)"/>

  </ItemGroup>

 

  <Target Name="Build">

    <Message Text="Inside build shared.proj"/>

  </Target>

 

  <Target Name="PrintValues">

    <Message Text="SourceWebVirtualPath: $(SourceWebVirtualPath)"/>

    <Message Text="SourceWebPhysicalPath: $(SourceWebPhysicalPath)"/>

 

    <Message Text="_AspNetCompilerVirtualPath: $(_AspNetCompilerVirtualPath)"/>

    <Message Text="_SourceWebPathItem: @(_SourceWebPathItem)"/>

  </Target>

 

</Project>

Now let’s see the project file that represents the project that Visual Studio would create for you when you use the Web Deployments Template. My representative file is called Your.proj, it is shown below.

<Project

  InitialTargets="PreBuild"

  DefaultTargets="PrintValues"

  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!-- This represents the .wdproj file -->

 

  <PropertyGroup>

    <SourceWebVirtualPath>WebPathItems</SourceWebVirtualPath>

    <SourceWebPhysicalPath>WebPathItems</SourceWebPhysicalPath>

  </PropertyGroup>

 

  <Target Name="PreBuild">

    <Message Text="Changing path to be 'OtherPath' instead of 'WebPathItems'"/>

    <CreateProperty Value="OtherPath">

      <Output PropertyName="SourceWebVirtualPath" TaskParameter="Value"/>

    </CreateProperty>

    <CreateProperty Value="OtherPath">

      <Output PropertyName="SourceWebPhysicalPath" TaskParameter="Value"/>

    </CreateProperty>

  </Target>

 

  <Import Project="Shared.proj"/>

 

</Project>

There are a couple of things to notice here, the first is that we are importing the Shared.proj file with the statement:

    <Import Project="Shared.proj"/>

The other is that we are overriding the SourceWebVirtualPath and the SourceWebPhysicalPath values, in the PreBuild event which is on the InitialTargets list. Because of this we know that the PreBuild target will be executed before any target that is not on the InitialTargets list.

Now going back to the Shared.proj file, we can see

  <PropertyGroup>

    <_AspNetCompilerVirtualPath>$(SourceWebVirtualPath)</_AspNetCompilerVirtualPath>

  </PropertyGroup>

  <ItemGroup>

    <_SourceWebPathItem Include="$(SourceWebPhysicalPath)"/>

  </ItemGroup>

So from this we are creating a new property _AspNetCompilerVirtualPath based on the original SourceWebVirtualPath, and and item _SourceWebPathItem based on SourceWebPhysicalPath. These new properties and items are used throughout the Microsoft.WebDeployment.targets  file, and is a common practice amongst other .targets files. You’ll find something similar in the Microsoft.Commons.targets files. Now is where the problem arises, these new properties and items that have been declared in the PropertyGroup & ItemGroup, so they will be evaluated and assigned before the PreBuild target executes. Because of this you don’t really have a chance to actually override these values using any logic. The only (until you read further) way you can override them is to use static elements like PropertyGroup  and ItemGroup.

You can see this is the case from the output of the PrintValues target below.

Target PreBuild:
    Changing path to be 'OtherPath' instead of 'WebPathItems'

Target PrintValues:
    SourceWebVirtualPath: OtherPath
    SourceWebPhysicalPath: OtherPath
    _AspNetCompilerVirtualPath: WebPathItems
    _SourceWebPathItem: WebPathItems

Build succeeded.

So how can we fix this? We need to be able to dynamically create properties & items and have them override static items? We can create a wrapper MSBuild file that does the logic to determine the correct values and the use the MSBuild Task. Have a look below at the file Fix.proj.

 

 

<Project

  InitialTargets="PreBuild"

  DefaultTargets="Build"

  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!-- This represents the .wdproj file -->

 

  <PropertyGroup>

    <SourceWebVirtualPath>WebPathItems</SourceWebVirtualPath>

    <SourceWebPhysicalPath>WebPathItems</SourceWebPhysicalPath>

  </PropertyGroup>

 

  <Target Name="PreBuild">

    <Message Text="Changing path to be 'OtherPath' instead of 'WebPathItems'"/>

    <CreateProperty Value="OtherPath">

      <Output PropertyName="SourceWebVirtualPath" TaskParameter="Value"/>

    </CreateProperty>

    <CreateProperty Value="OtherPath">

      <Output PropertyName="SourceWebPhysicalPath" TaskParameter="Value"/>

    </CreateProperty>

 

    <Message Text="SourceWebVirtualPath: $(SourceWebVirtualPath)"/>

    <Message Text="SourceWebPhysicalPath: $(SourceWebPhysicalPath)"/>

  </Target>

 

  <Target Name="Build">

    <MSBuild Projects="Your.proj"

             Properties="SourceWebVirtualPath=$(SourceWebVirtualPath);

                SourceWebPhysicalPath=$(SourceWebPhysicalPath)"

             Targets="PrintValues"

             />

  </Target>

 

</Project>

Take a look at the highlighted portion, it simply calls MSBuild on the Your.proj fle. Here is the output when executing this file.

Target PreBuild:
    Changing path to be 'OtherPath' instead of 'WebPathItems'
    SourceWebVirtualPath: OtherPath
    SourceWebPhysicalPath: OtherPath
Target Build:

    __________________________________________________
    Project "C:\Data\Community\msbuild\WebDeploymentIssues\ASPNETix.proj" is building "C:\Data\Community\msbuild\WebDeploymentIssu\Sample\Your.proj" (PrintValues target(s)):

    Target PreBuild:
        Changing path to be 'OtherPath' instead of 'WebPathItems'
    Target PrintValues:
        SourceWebVirtualPath: OtherPath
        SourceWebPhysicalPath: OtherPath
        _AspNetCompilerVirtualPath: OtherPath
        _SourceWebPathItem: OtherPath

Build succeeded.

So you can see that all the desired values have been overridden. But having 2 files is really annoying, and in some cases you don’t really have this option. So because of this we need to find a solution that will modify the original file only. And here is that file, it is shown below its called YourFix.proj.

<Project

  InitialTargets="PreBuild"

  DefaultTargets="PrintValues"

  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!-- This represents the .wdproj file -->

 

  <PropertyGroup>

    <SourceWebVirtualPath>WebPathItems</SourceWebVirtualPath>

    <SourceWebPhysicalPath>WebPathItems</SourceWebPhysicalPath>

  </PropertyGroup>

 

  <Target Name="PreBuild">

    <Message Text="Changing path to be 'OtherPath' instead of 'WebPathItems'"/>

    <CreateProperty Value="OtherPath">

      <Output PropertyName="SourceWebVirtualPath" TaskParameter="Value"/>

    </CreateProperty>

    <CreateProperty Value="OtherPath">

      <Output PropertyName="SourceWebPhysicalPath" TaskParameter="Value"/>

    </CreateProperty>

  </Target>

 

  <PropertyGroup>

    <DoingFix>true</DoingFix>

  </PropertyGroup>

 

  <Import Project="Shared.proj"/>

 

  <!-- Here we conditionally override the PrintValues -->

  <Target Name="PrintValues" Condition="$(DoingFix)=='true'">

    <!-- Make a call to this file -->

    <MSBuild Projects="$(MSBuildProjectFullPath)"

             Properties="DoingFix=false;

              SourceWebVirtualPath=$(SourceWebVirtualPath);

              SourceWebPhysicalPath=$(SourceWebPhysicalPath)"

             Targets="PrintValues"

             />

  </Target>

</Project>

In this approach we need to modify the behavior of the PrintValues target so we override it and have it call itself with the correct values. The MSBuild ProjectFullPath is an MSBuild Reserved Property, and it will return the full path of the current project file. The trick here is the conditional override of this target based on the DoingFix value. So we can see that the DoingFix property will default to true, so the PrintValues target will be overridden. But when the file is called back into with DoingFix=false it will not be overridden, and the PrintValues in Shared.proj will be used. In your use this target will most likely be Build. The drawback of the approach is that you’d have to overwrite each target that you are interested in. The other drawback is that it is confusing if you are not paying close attention to what this does. I posted something previously that used a similar technique read it if this interests you http://www.sedodream.com/PermaLink,guid,9c235811-3078-45e7-b122-6a239fb33fc0.aspx.

Here is an outline of the files I have attached:

                Shared.proj        => Represents a shared file (ie Miscrosoft.WebDeployments.targets)

                Your.proj             => Represents your extension of the shared.proj file (ie SiteDeployment.wdproj)

                Fix.proj                 => The wrapper that corrects the problems

                YourFix.proj       => The real solution to this problem.

Sayed Ibrahim Hashimi

Friday, April 27, 2007 6:18:53 AM (GMT Daylight Time, UTC+01:00)  #    Comments [11]  | 
Monday, February 19, 2007

I’ve got a new article titled “WiX Tricks: Automate Releases With MSBuild And Windows Installer XML”. This article covers in detail how to create an automated build & release process using MSBuild & the Windows Installer Xml Toolset. Not only does it describe how to achieve an automated process, but I actually provide you will an extensible set of MSBuild files that can be used to easily do it for you! If you are already using WiX to create your installation packages then integrating my targets into your process should be very straight forward. You can download all the sources from article site, but you may be better off getting it from my Sedodream MSBuild project site. I’ll be maintaining the files on that site, so it is better to get the latest directly from there. If you do use my targets, please let me know what your experiences are. I’ll be continuing to expand on those and your feedback is important.  One of the items that are pretty important for me is to integrate Install Shield installations into this process. I’ve created an internal process to do this for me, but it is not re-usable as the WiX ones are. Soon I hope to be able to complete this.

Also in this article I do cover some more advanced MSBuild topics such as MSBuild batching in pretty good detail. As far as I know this is the only published article that covers batching in any kind of detail.

 

Sayed Ibrahim Hashimi

Monday, February 19, 2007 3:15:58 AM (GMT Standard Time, UTC+00:00)  #    Comments [1]  | 
Thursday, January 18, 2007

Ok, here is the challenge you have several project files that comprise a set of products. Now you want list every reference that each project file has. How do you do it? I came up with a pretty nifty way of doing it. If you look a project file for a managed project (I’ll be using C# projects for this example) you’ll see that you can see all the references listed in an Item named Reference. So there’s got to be a way to extract out that information for each project. I was thinking that it would be nice if each project had a target that would simply output that item. But that would involve changing each project to add the target to perform that. Which obviously is not an option. But what we can do is achieve the exact same thing, by using the Import element. Take a look at the simple targets file below:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

 

   <Import Project="$(ProjectFile)"/>

 

   <Target Name="GetReferences" Outputs ="@(ProjReferences)">

      <Message Text="Getting references for project $(ProjectFile)"/>

      <CreateItem Include="@(Reference)">

         <Output ItemName="ProjReferences" TaskParameter="Include"/>

      </CreateItem>

   </Target>

  

</Project>

What this will do is import an MSBuild project using the Import statement, this file is driven off of a property file. After that it will merge a new target GetReferences with the project declared in ProjectFile. Now we simply need a way to specify that ProjectFile. To achieve this we will actually change the above statements to include the condition Condition="$(ProjectFile)!=''". And we add the following statements to find all project files, enumerate references and write a file containing all of them.

<!-- Project files to examine -->

<ItemGroup>

   <ProjectFiles Include="**\*.csproj"/>

</ItemGroup>

<!-- File to save project references to -->

<PropertyGroup>

   <RefFile>SolutionReferences.txt</RefFile>

</PropertyGroup>

  

<Target Name="SetupFindRef">

   <Delete Files="$(RefFile)"/>

   <WriteLinesToFile File="$(RefFile)" Lines="ProjectName%09Reference"/>

</Target>

 

<Target Name="FindReferences" Outputs="%(ProjectFiles.FullPath)" DependsOnTargets="SetupFindRef">

   <MSBuild Projects="$(MSBuildProjectFile)" Properties="ProjectFile=%(ProjectFiles.FullPath)" Targets="GetReferences">

      <Output ItemName="ProjReferences" TaskParameter="TargetOutputs"/>

   </MSBuild>

           

   <WriteLinesToFile File="$(RefFile)" Lines="%(ProjectFiles.FullPath)"/>

   <WriteLinesToFile File="$(RefFile)" Lines="@(ProjReferences->'%09%(Identity)')"/>

</Target>

 

If we execute the FindReferences target on this project file it will batch over each project file. It will then execute the same project file with the property ProjectFile defined, and execute the GetReferences target. So what is really happening is that we are effectively extending the existing MSBuild project file and injecting a new target to an existing  project file. Pretty nifty.

Here is the entire project file so you can see the end result, also you can download it below.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="FindReferences">

   <!-- Project files to examine -->

   <ItemGroup>

      <ProjectFiles Include="**\*.csproj"/>

   </ItemGroup>

   <!-- File to save project references to -->

   <PropertyGroup>

      <RefFile>SolutionReferences.txt</RefFile>

   </PropertyGroup>

  

   <Target Name="SetupFindRef">

      <Delete Files="$(RefFile)"/>

      <WriteLinesToFile File="$(RefFile)" Lines="ProjectName%09Reference"/>

   </Target>

 

   <Target Name="FindReferences" Outputs="%(ProjectFiles.FullPath)" DependsOnTargets="SetupFindRef">

      <MSBuild Projects="$(MSBuildProjectFile)" Properties="ProjectFile=%(ProjectFiles.FullPath)" Targets="GetReferences">

         <Output ItemName="ProjReferences" TaskParameter="TargetOutputs"/>

      </MSBuild>

           

      <WriteLinesToFile File="$(RefFile)" Lines="%(ProjectFiles.FullPath)"/>

      <WriteLinesToFile File="$(RefFile)" Lines="@(ProjReferences->'%09%(Identity)')"/>

   </Target>

 

  

   <Import Project="$(ProjectFile)" Condition="$(ProjectFile)!=''"/>

   <Target Name="GetReferences" Outputs ="@(ProjReferences)" Condition="$(ProjectFile)!=''">

      <Message Text="Getting references for project $(ProjectFile)"/>

      <CreateItem Include="@(Reference)">

         <Output ItemName="ProjReferences" TaskParameter="Include"/>

      </CreateItem>

   </Target>

</Project>

Here is what the result looks like (for the some of the Enterprise Library projects):

ProjectName           Reference

C:\TEMP\EnterpriseLibrary\src\Caching\Caching.csproj

   System

   System.configuration

   System.Configuration.Install

   System.Data

   System.Management

   System.Xml

C:\TEMP\EnterpriseLibrary\src\Caching\Configuration\Design\Caching.Configuration.Design.csproj

   System

   System.configuration

   System.Data

   System.Design

   System.Drawing

   System.Windows.Forms

   System.XML

C:\TEMP\EnterpriseLibrary\src\Caching\Cryptography\Caching.Cryptography.csproj

   System

   System.configuration

C:\TEMP\EnterpriseLibrary\src\Caching\Cryptography\Configuration\Design\Caching.Cryptography.Configuration.Design.csproj

   System

   System.configuration

   System.Data

   System.Drawing

   System.Xml

The above project file will actually generate a tab delimited file, so you can open it up in Excel and take a closer look.

 GetAllReferences.proj

Sayed Ibrahim Hashimi

Thursday, January 18, 2007 7:05:31 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Wednesday, November 22, 2006

Just wanted to let everyone know that my Sedodream MSBuild Project now has an MSI that can be downloaded. You can find that latest downloads at http://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=Sedodream. The installer will place all MSBuild tasks in the %Program Files%\MSBuild\Sedodream directory. In your MSBuild project files you  can refer to this location as $(MSBuildExtensionsPath)\Sedodream\.

Here is a list of what is currently included in the project

Tasks Included

Name

Description

AddMetadata

Allows you to add metadata to an item

FindUnder

Finds and returns all directories (empty or not) under a specified path

GetDate

Returns a string representation of the date in a specified format

GetRegKey

Returns the value of a registry key

Move

Task to move a file (or set of files)

NUnitTask

Task to run NUnit test cases as a part of the build process

TempFile

Returns the path to a new temporary file

ReplaceInFile

Can be used to replace specified text in a file.

GetExternalIp

Can be used to determine the external IP address of the executing machine

GetInternalIp

Can be used to determine all the Internal IP addresses of the executing machine

Loggers Included

Name

Description

EmailLogger

An MSBuild logger capable of emailing the resulting log

SimpleFileLogger

A simple MSBuild file based logger

XmlLogger

An MSBuild logger which creates an Xml file

Sayed Ibrahim Hashimi

Wednesday, November 22, 2006 3:14:56 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Monday, November 13, 2006

There were a couple of posts in the MSBuild forum recently related to customizing the build process. The specific thread is, Forcing a Clean build. The question boils down to, "How do you force a clean each time I build?" The answer lies in adding the Clean target to the build targets dependency list. You can read details on how this works in my MSDN article Inside MSBuild.
One of the participants asked me to post a complete solution of how to do this. So that response is this entry. I created a sample Windows Application that has the build process customized. I called this BeforeBuildEx, you can download all the files at the end of this entry. I modified the WindowsApplication1.csproj file and added the following lines:

<PropertyGroup>

   <BuildDependsOn>

      Clean;

      MyBeforeBuild;

      $(BuildDependsOn);

      MyAfterBuild

   </BuildDependsOn>

</PropertyGroup>

 

<Target Name="MyBeforeBuild">

   <Message Text="This is before the build." Importance="high"/>

</Target>

 

<Target Name="MyAfterBuild">

   <Message Text="This is after the build." Importance="high"/>

</Target>

The list of targets that must be executed for a build is contained in the BuildDependsOn list. So if you change that list, you change to build process. If you want to add a target before the build process, add that target to the begining of the list. In the above statement, I'm actually extending the previous list by adding my targets. I add 2 targets before and 1 after the already existing definiton for BuildDependsOn. Much more detail about this in my article. The snippet above was inserted after the statement

<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

This is very important, otherwise your customizations may be overwritten. Now if you build this you will see that the Clean target is executed when the application is built. If you want to see this in Visual Studio you have to increase the verbosity of the MSBuild output that is shown on the Output window. Do this by: Tools->Options->Projects and Solutions->Build and Run and set the value for MSBuild project build output verbosity to atleast Normal. The link to the zip file is below. In some cases this could cause some problems for Visual Studio.

BeforeBuildEx.zip

Sayed Ibrahim Hashimi

Monday, November 13, 2006 4:11:06 AM (GMT Standard Time, UTC+00:00)  #    Comments [5]  | 
Tuesday, August 29, 2006

A twist on the previous entry. Here’s a very similar, but different scenario. You have a set of files, that you want to push to a set of folders. But you have to “discover” these folders. In this scenario there are really 2 different situations you can be in.

1.       Each folder contains a file that you are able to identify by file name

2.       Each folder contains no files, but you are able to identify the folder by its name

For 2) you’ll have to rely on a custom task to get locate these folders. This is because if the folder is empty then you are not able to put these in an Item the normal way. I’ve previously written a task FindUnder that you can use for this purpose. You can find this task, and a handful of others at http://www.codeplex.com/Wiki/View.aspx?ProjectName=Sedodream. Let’s go over scenario 2 because I think it’s a little more straight forward.

Here is the directory structure that you have to work with:

   AssemblyInfo.cs

   DeploySample.proj

   Other1.cs

   Other2.cs

├───Deploy

   ├───five.control

   ├───four.control

   ├───one.control

   ├───six.control

   ├───six.something

   ├───three.control

   ├───three.something

   └───two.control

In this example the folders that you want to copy into end with .control, and the files you want to copy include AssemblyInfo.cs, Other1.cs and Other2.cs. In this situation we want to find all folders under Deploy that end with control. So our search pattern will be *control under the Deploy folder. Here’s how we find those folders using the FindUnder task, and copy the files into them.

   <PropertyGroup>

      <DeployRoot>Deploy</DeployRoot>

      <SearchPath>*.control</SearchPath>

   </PropertyGroup>

   <ItemGroup>

      <FilesToCopy Include="AssemblyInfo.cs;Other1.cs;Other2.cs"/>

   </ItemGroup>

   <Target Name="Deploy">

      <Copy SourceFiles="" DestinationFolder=""/>

      <!-- Find all the folders we need to place the AssemblyInfo.cs file in -->

      <FindUnder Path="$(DeployRoot)" FindDirectories="true" SearchPattern="$(SearchPath)">

         <Output ItemName="DeployFolders" TaskParameter="FoundItems"/>

      </FindUnder>

     

      <Message Text="Found items: @(DeployFolders,'%0d%0a')" Importance="high"/>

      <Copy SourceFiles="@(FilesToCopy)" DestinationFolder="%(DeployFolders.FullPath)"/>

   </Target>

Let’s talk about the Copy task above. This shows how we can copy a set of folders to a set of different folders. This is possible through batching which was discussed in more detail in the previous post. As you use batching keep in mind that batching isn’t limited to custom meta-data. But can also be used with Well-known item metadata. That is what we are doing in this example.

Now let’s see how can we handle situation 1). To review, this is the situation that we have files in a set of directories that we can identify. In this example let’s say that file is named app.config. So we want to find all folders under a given location that have a file named app.config inside of it. So here is your directory structure

   AssemblyInfo.cs

   DeploySample.proj

   Other1.cs

   Other2.cs

└───Deploy2

      ├───five.control

             app.config

      ├───four.control

             app.config

      ├───one.control

             app.config

      ├───six.control

             app.config

      ├───six.something

      ├───three.control

             app.config

      ├───three.something

      └───two.control

            app.config

So the result should actually be the same as the previous example. This is how to achieve that.

   <PropertyGroup>

      <DeployRoot2>Deploy2</DeployRoot2>

   </PropertyGroup>

  

   <ItemGroup>

      <KeyFiles Include=".\$(Deploy2)\**\app.config"/>

   </ItemGroup>

 

   <Target Name="Deploy2">

      <Message Text="Folders to deploy to:%0d%0a@(KeyFiles->'%(RecursiveDir)','%0d%0a')"/>

 

      <!-- Now copy each file to each destination -->

      <Copy SourceFiles="@(FilesToCopy)" DestinationFolder=".\%(KeyFiles.RecursiveDir)"/>

   </Target>

 

So the in this example I’m using the KeyFiles item to determine which folders to copy all the files to. From the Deploy2 target you can see that I’m using batching to copy all the FilesToCopy into each of the locations determined by the KeyFiles item. The directory that contains each KeyFile is determined by the RecursiveDir well-known meta-data value.

 

You can download the entire sample at: www.sedodream.com/content/binary/BatchingSample.zip

Or you can have a look at the project file at:  www.sedodream.com/content/binary/DeploySample.proj.txt

 

Sayed Ibrahim Hashimi

Tuesday, August 29, 2006 3:17:46 AM (GMT Daylight Time, UTC+01:00)  #    Comments [11]  | 
Tuesday, August 15, 2006

Today someone was telling me about a co-worker who was having issues with MSBuild. He told me that he was trying to copy a set of files to a set of different servers. But the issue was that he didn’t know how to achieve this without performing multiple Copy task invocations. I told him that he could achieve this using MSBuild Batching. Batching is a process of performing a task (or target) on a set of items (batches) at a time. A batch can also include a single item. So in this scenario we need to perform the copy one time for each server that he wanted to deploy to. I’ve created a simple msbuild file which demonstrates this in two different ways. The first way uses task batching, which can bee seen in the Test target. And the other uses Target batching which can be seen in the DoItCore target. I've also created a clean target, which has nothing to do with batching.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Test">

     

      <ItemGroup>

            <SourceFiles Include="*.txt"/>

            <Dest Include="One;Two;Three;Four;Five"/>

      </ItemGroup>

     

      <Target Name="Test">

            <Copy SourceFiles ="@(SourceFiles)" DestinationFolder="%(Dest.FullPath)"/>

            <Message Text="Fullpath: %(Dest.FullPath)"/>

      </Target>

 

      <!-- These targets demonstrate target batching -->

      <Target Name="DoIt" DependsOnTargets="DoItCore"/>

      <Target Name="DoItCore" Inputs="@(SourceFiles)" Outputs="%(Dest.FullPath)">

            <Copy SourceFiles="@(SourceFiles)" DestinationFolder="%(Dest.FullPath)"/>

      </Target>

 

      <!-- This will clean up the files -->

      <Target Name="Clean">

            <CreateItem Include="%(Dest.FullPath)\**\*">

                  <Output ItemName="FilesToDelete" TaskParameter="Include"/>

            </CreateItem>

            <Delete Files="@(FilesToDelete)"/>

      </Target>

</Project>

Batching is an advanced topic of MSBuild, and is defintely neglected. I have to admit I’m guilty of not writing about it enough myself. There are some good batching resources, they are listed below.

MSBuild Batching

How To : Batch Targets with Item Metadata

Batching Brainteaser Explained

Channel 9 : Batching Examples

Channel 9 : How does MSBuild’s Batching Algorigth Work?

 

Sayed Ibrahim Hashimi

Tuesday, August 15, 2006 4:59:32 AM (GMT Daylight Time, UTC+01:00)  #    Comments [2]  | 
Wednesday, August 09, 2006

In case you haven't already heard the MSDN Library is now free to download! You can download the May 2006 version at: http://www.microsoft.com/downloads/details.aspx?FamilyId=373930CB-A3D7-4EA5-B421-DD6818DC7C41&displaylang=en

Previously you could only get the MSDN Library if you were an MSDN Subscriber. I think its great that Microsoft made the MSDN Library free to download. It's a great resource and contains all kinds of goodies for .NET developers.

Sayed Ibrahim Hashimi

Wednesday, August 09, 2006 5:32:45 AM (GMT Daylight Time, UTC+01:00)  #    Comments [2]  | 
Tuesday, July 25, 2006

Recently a chapter from my book Deploying .NET Applications: Learning MSBuild and ClickOnce has been posted. You can read the sample chapter MSBuild By Example. Here is the TOC for that chapter.

MSBuild: By Example

Introducing Well-Known Metadata

Formatting Your Output

Editing MSBuild Files with IntelliSense

Integrating MSBuild into Visual Studio

Introducing Custom Metadata

Understanding the Difference Between @ and %

Using Environment Variables in Your Project

Reusing MSBuild Project Elements

Dealing with MSBuild Errors

Summary

Thanks to the guys at Apress and C# Online for making this chapter available online for everyone to have a look at!

Sayed Ibrahim Hashimi 

 

 

Tuesday, July 25, 2006 5:31:32 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 

Theme design by Jelle Druyts