Thursday, August 13, 2009

I have seen a couple blog entries about executing MSTest unit tests from MSBuild. Most recently I saw the entry by Scott A. Lawrence. So I decided to share how I execute MSTest unit tests from MSBuild

I created a file named Build.Common.UnitTest.targets which contains all the behavior that will execute the test cases. This file can then be imported into whatever scripts that need to execute test cases. The entire file is shown below. We will discuss afterwards.

Build.Common.UnitTest.targets

<?xml version="1.0" encoding="utf-8"?>

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

  <!-- =======================================================================

    TESTING

  ======================================================================= -->

 

  <!-- Default build settings go here -->

  <PropertyGroup>

    <RunMSTest Condition="'$(RunMSTest)'==''">true</RunMSTest>

    <BuildInParallel Condition="'$(BuildInParallel)'==''">true</BuildInParallel>

  </PropertyGroup>

 

  <Target Name="MSTestValidateSettings">

    <!-- Cleare out these items -->

    <ItemGroup>

      <_RequiredProperties Remove="@(_RequiredProperties)"/>

      <_RequiredItems Remove="@(_RequiredItems)"/>

    </ItemGroup>

   

    <ItemGroup>

      <_RequiredProperties Include="BuildContribRoot">

        <Value>$(BuildContribRoot)</Value>

      </_RequiredProperties>

      <_RequiredProperties Include="OutputRoot">

        <Value>$(OutputRoot)</Value>

      </_RequiredProperties>

 

      <_RequiredItems Include="MSTestProjects">

        <RequiredValue>@(MSTestProjects)</RequiredValue>

        <RequiredFilePath>%(MSTestProjects.FullPath)</RequiredFilePath>

      </_RequiredItems>

      <_RequiredItems Include="AllConfigurations">

        <RequiredValue>@(AllConfigurations)</RequiredValue>

      </_RequiredItems>

      <_RequiredItems Include="AllConfigurations.Configuration">

        <RequiredValue>%(AllConfigurations.Configuration)</RequiredValue>

      </_RequiredItems>

    </ItemGroup>

   

    <!-- Raise an error if any value in _RequiredProperties is missing -->

    <Error Condition="'%(_RequiredProperties.Value)'==''"

           Text="Missing required property [%(_RequiredProperties.Identity)]"/>

 

    <!-- Raise an error if any value in _RequiredItems is empty -->

    <Error Condition="'%(_RequiredItems.RequiredValue)'==''"

           Text="Missing required item value [%(_RequiredItems.Identity)]" />

 

    <!-- Validate any file/directory that should exist -->

    <Error Condition="'%(_RequiredItems.RequiredFilePath)' != '' and !Exists('%(_RequiredItems.RequiredFilePath)')"

           Text="Unable to find expeceted path [%(_RequiredItems.RequiredFilePath)] on item [%(_RequiredItems.Identity)]" />

  </Target>

 

  <UsingTask

      TaskName="TestToolsTask"

      AssemblyFile="$(BuildContribRoot)TestToolsTask-1.3\Microsoft.VisualStudio.QualityTools.MSBuildTasks.dll"/>

 

  <!-- TODO: Create a ValidateTestSettings target and put it on this list -->

  <PropertyGroup>

    <MSTestDependsOn>

      BuildMSTestProjects;

      BeforeMSTest;

      CoreMSTest;

      AfterMSTest

      $(MSTestDependsOn);

    </MSTestDependsOn>

  </PropertyGroup>

  <Target Name="MSTest" DependsOnTargets="$(MSTestDependsOn)"/>

  <Target Name="BeforeMSTest"/>

  <Target Name="AfterMSTest"/>

  <Target Name="CoreMSTest" Outputs="%(MSTestProjects.Identity)">

    <Message Text="Running MSTest for project [%(MSTestProjects.Identity)]"/>

    <Message Text="MSTestProjects.Directory: %(MSTestProjects.RootDir)%(MSTestProjects.Directory)" />

 

    <PropertyGroup>

      <_CurrentConfig>Debug</_CurrentConfig>

    </PropertyGroup>

 

    <PropertyGroup>

      <_SearchPath>$(OutputRoot)$(_CurrentConfig)\%(MSTestProjects.Filename)\</_SearchPath>

      <_TestContainers></_TestContainers>

    </PropertyGroup>

 

    <TestToolsTask

      SearchPathRoot="%(MSTestProjects.RootDir)%(MSTestProjects.Directory)"

      TestContainers="$(OutputRoot)$(_CurrentConfig)\%(MSTestProjects.Filename)\bin\%(MSTestProjects.Filename).dll"/>

 

    <!-- TODO: Read in the results of the tests and get all failures and report them here -->

  </Target>

 

  <PropertyGroup>

    <BuildMSTestProjectsDependsOn>

      BeforeBuildMSTestProjects;

      CoreBuildMSTestProjects;

      AfterBuildMSTestProjects;

      $(BuildMSTestProjectsDependsOn);

    </BuildMSTestProjectsDependsOn>

  </PropertyGroup>

  <Target Name="BuildMSTestProjects" DependsOnTargets="$(BuildMSTestProjectsDependsOn)"/>

  <Target Name="BeforeBuildMSTestProjects"/>

  <Target Name="AfterBuildMSTestProjects"/>

  <Target Name="CoreBuildMSTestProjects" Outputs="%(AllConfigurations.Configuration)"

          DependsOnTargets="CleanMSTest">   

    <!-- Make sure to do clean build in case test cases were added -->

   

    <PropertyGroup>

      <_CurrentConfig>%(AllConfigurations.Configuration)</_CurrentConfig>

    </PropertyGroup>

 

    <Message Text="Building (MSTestProjects.Identity) %(MSTestProjects.Identity)" Importance="high"/>

   

    <!-- Build the projects here. -->

    <MSBuild Projects="%(MSTestProjects.Identity)"

             Properties="Configuration=$(_CurrentConfig);

                          OutputPath=$(OutputRoot)$(_CurrentConfig)\%(MSTestProjects.Filename)\bin\;

                          BaseIntermediateOutputPath=$(OutputRoot)$(_CurrentConfig)\%(MSTestProjects.Filename)\obj\;

                          GenerateResourceNeverLockTypeAssemblies=true;

                          %(ProjectsToBuild.Properties);

                          $(AllProjectProperties);"

             BuildInParallel="$(BuildInParallel)"

             >

    </MSBuild>

  </Target>

 

 

  <Target Name="CleanMSTest">

 

    <MSBuild Projects="@(MSTestProjects)"

         Targets="Clean"

         Properties="Configuration=$(_CurrentConfig);

                          OutputPath=$(OutputRoot)$(_CurrentConfig)\%(MSTestProjects.Filename)\bin\;

                          BaseIntermediateOutputPath=$(OutputRoot)$(_CurrentConfig)\%(MSTestProjects.Filename)\obj\;

                          GenerateResourceNeverLockTypeAssemblies=true;"

         BuildInParallel="$(BuildInParallel)"

             />

   

  </Target>

 

</Project>

There are five important targets which are described below.

Name

Description

MSTestValidateSettings

This validates that the file was provided the needed data values to perform its task, to run the unit tests. For more info on this technique see my previous entry Elements of Reusable MSBuild Scripts: Validation.

MSTest

This is the target that you would execute to run the test cases. The target itself is empty but it sets up the chain of dependent targets.

CoreMSTest

This is the target which executes the test cases. This is preformed using the TestToolsTask.

BuildMSTestProjects

This target is responsible for building (i.e. compiling) the projects which contain the test cases. You don't have to call this it is called automagically.

CleanMSTest

This target will execute the Clean target for all the test projects defined.

 

If you take a look at the CoreBuildMSTestProjects target you can see that I am batching (Target batching to be specific) it on each defined configuration. This is achieved with the attribute Outputs="%(AllConfigurations.Configuration)". If you are not familiar with batching, see the links at the end of this post for more details, and you can always grab my book for even more detailed info J. Then inside that target I build each project by batching (Task batching) the MSBuild task on each project defined in the MSTestProjects item list.

Then inside the CoreMSTest target I execute the test cases. This target is batched for every value in the MSTestProjects item. As I'm writing this I have noticed that I've hard-coded the value for the configuration used in that target to be Debug with the statement

<PropertyGroup>

  <_CurrentConfig>Debug</_CurrentConfig>

</PropertyGroup>

This shouldn't be hard coded, but passed in. I will leave it as is for now though. Then the TestToolsTask is invoked to execute the test cases.

Now that we have written the re-usable .targets file to execute the test cases we need to create a file which will "feed" it the necessary data values and let it do its magic. I created a sample solution, which you can download at the end of this post, which demonstrates its usage. The solution is named MSTestExample and you can see the files it contains in the screen shot below.

Here I've highlighted the two MSTest projects as well as a couple build files. I've already shown the contents of the Build.Common.UnitTest.targets file. Here is the contents of the Build.MSTestExample.proj file.

Build.MSTestExample.proj

<?xml version="1.0" encoding="utf-8"?>

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

 

  <!--

  Some properties you may be interested in setting:

    Name              Description

    *****************************************************

    RunMSTest         true/false to run unit tests or not

  -->

 

  <PropertyGroup>

    <Root Condition="'$(Root)'==''">$(MSBuildProjectDirectory)\..\</Root>

 

    <BuildRoot Condition="'$(BuildRoot)'==''">$(Root)Build\</BuildRoot>

    <BuildContribRoot Condition="'$(BuildContribRoot)'==''">$(BuildRoot)Contrib\</BuildContribRoot>

 

    <SourceRoot Condition="'$(SourceRoot)'==''">$(Root)</SourceRoot>

    <BuildArtifactsRoot Condition="'$(BuildArtifactsRoot)'==''">$(BuildRoot)BuildAftifacts\</BuildArtifactsRoot>

    <OutputRoot Condition="'$(OutputRoot)'==''">$(BuildArtifactsRoot)Output\</OutputRoot>

    <TreatWarningsAsErrors Condition=" '$(TreatWarningsAsErrors)'=='' ">true</TreatWarningsAsErrors>

  </PropertyGroup>

 

  <PropertyGroup>

    <CodeAnalysisTreatWarningsAsErrors Condition="'$(CodeAnalysisTreatWarningsAsErrors)'==''">true</CodeAnalysisTreatWarningsAsErrors>

    <BuildInParallel Condition="'$(BuildInParallel)'==''">true</BuildInParallel>

   

    <RunCodeAnalysis Condition="'$(RunCodeAnalysis)'==''">true</RunCodeAnalysis>

    <RunMSTest Condition="'$(RunMSTest)'==''">false</RunMSTest>

    <RunStyleCop Condition="''=='$(RunStyleCop)'">true</RunStyleCop>

  </PropertyGroup>

 

  <!-- Configurations that we want to build for -->

  <ItemGroup>

    <AllConfigurations Include="Debug">

      <Configuration>Debug</Configuration>

    </AllConfigurations>

  </ItemGroup>

 

  <!-- =======================================================================

    MSTest

  ======================================================================= -->

 

  <ItemGroup>

    <MSTestProjects Include="$(SourceRoot)TestProject1\TestProject1.csproj">

    </MSTestProjects>

    <MSTestProjects Include="$(SourceRoot)TestProject2\TestProject2.csproj">

    </MSTestProjects>

  </ItemGroup>

 

  <Import Project="$(BuildRoot)Build.Common.UnitTest.targets"/>

 

  <!-- Inject the MSTest targets into the build -->

  <!--<PropertyGroup Condition="'$(RunMSTest)'=='true'">

    <BuildDependsOn>

      $(BuildDependsOn);

      MSTest;

    </BuildDependsOn>

    <BuildMSTestProjectsDependsOn>

      CoreBuild;

      $(BuildMSTestProjectsDependsOn)

    </BuildMSTestProjectsDependsOn>

  </PropertyGroup>-->

 

</Project>

This file is pretty simple. It just creates some properties, and items and then just imports the Build.Common.UnitTest.targets file to do all the heavy lifting. You will probably notice that there are some properties defined that don't make sense here, like CodeAnalysisTreatWarningsAsErrors, this is because this was taken from a build script which does some other tasks. You can ignore those. To see what properties/items are required for the MSTest just look at the MSTestValidateSettings target. Also in the previous code snippet I showed how you could inject the MSTest target into the build process but it is commented out since there is no real build process in this case.

One thing about this approach that may not be ideal is that it will execute the test cases in one assembly at a time and if there is a failure it will not go on to the other assemblies. In my case this is OK because this is for local builds, public builds are executing test cases by Team Build, but this can be looked at.

Questions? Comments?

 

MSTestExamples.zip

MSBuild Batching Links


Sayed Ibrahim Hashim

Thursday, August 13, 2009 5:11:52 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Tuesday, May 26, 2009

Before I begin this post I must say that I do not have any inside information about the release of Visual Studio 2010 or .NET 4. The post is composed of 100 % speculation and based on information that has already been made publicly available.

I am pretty interested to know the release date for Visual Studio 2010, and .NET Framework 4.0, so I asked around but no one could tell me anything. So I decided to do some research as to the release dates for Visual Studio 2005 and Visual Studio 2008 based on the release of their Beta releases. I came up with the following table.

 

Beta 1

Beta 2

RTM

Beta 1 - Beta 2

Beta 2 - RTM

Visual Studio 2005

06/29/2004

04/18/2005

11/07/2005

43 Weeks

29 Weeks

Visual Studio 2008

04/19/2007

07/26/2007

11/19/2007

14 Weeks

17 Weeks

Visual Studio 2010

05/18/2009

11/30/2009

05/09/2010

28 Weeks

20 Weeks

Yellow cells are 100% based just on the averages of the two previous versions

 

When Visual Studio 2005 came out there were huge changes. I think this is why the amount of time between Beta 1 and Beta 2, and between Beta 2 and RTM were so much longer then their corresponding periods in Visual Studio 2008. I think for the release of Visual Studio 2010 we are going to be somewhere between the two time periods. So maybe a decent indicator would simply be the average of the two. This is what I used to come up with the cells that are shaded in yellow. I'm sure this will be off but hopefully by weeks and not months!

   

Sayed Ibrahim Hashimi

Tuesday, May 26, 2009 4:24:49 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Monday, June 16, 2008

If you are customizing your build process you may need to increase the verbosity that Visual Studio uses when building your projects. It is pretty easy to change this value, you just go to Tools->Options then find the Project and Solutions->Build and Run node. The dialog is shown here

The area where the verbosity setting lies is highlighted. I've got mine set to Detailed, which produces a lot of output. If you set it to Diagnostic, then you should be ready for a lot of output. Most like much more then you need!


Sayed Ibrahim Hashimi

Monday, June 16, 2008 6:50:26 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Thursday, May 08, 2008

After some discussion at the MSBuild MSDN Forum I started investigating building the same project with different Compiler constants defined. I was actually pretty surprised by what I found. I created a simple Windows Form Application with a lone button one it. The image below show the simple app.

Here is the entire code behind file.

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

 

namespace WindowsFormsApplication1

{

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

}

 

private void buttonExample_Click(object sender, EventArgs e)

{

string message = "default message";

#if CONSTANT_ONE

message = "CONSTANT_ONE";

#endif

#if CONSTANT_TWO

message = "CONSTANT_TWO";

#endif

 

MessageBox.Show(message);

}

}

}

Ddd

The parts to take note are the regions highlighted in yellow. So if the constant CONSTANT_ONE is defined the message results to CONSTANT_ONE and if CONSTANT_TWO is defined it will show the message CONSTANT_TWO.

I then created the simple build file.

<?xml version="1.0" encoding="utf-8"?>

<Project DefaultTargets="BuildAll" ToolsVersion="3.5"

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

 

<PropertyGroup>

<ConstOne>CONSTANT_ONE</ConstOne>

<ConstTwo>CONSTANT_TWO</ConstTwo>

</PropertyGroup>

 

<ItemGroup>

<Projects Include="$(MSBuildProjectDirectory)\..\WindowsFormsApplication1\WindowsFormsApplication1.csproj">

</Projects>

</ItemGroup>

 

<!--

Cleans the project before we start the build process.

The only reason I am calling this is to remove artifacts of

previous builds, to clearly demonstrate what's going on.

-->

<Target Name="CleanAll">

<MSBuild Projects="@(Projects)" Targets="Clean" />

</Target>

 

<Target Name="BuildAll" DependsOnTargets="CleanAll;BuildConstOne;BuildConstTwo" />

 

<Target Name="BuildConstOne">

<MSBuild Projects="@(Projects)"

Properties="DefineConstants=$(ConstOne);OutputPath=binOne\;"/>

</Target>

<Target Name="BuildConstTwo">

<MSBuild Projects="@(Projects)"

Properties="DefineConstants=$(ConstTwo);OutputPath=binTwo\;"

 

/>

</Target>

 

<!-- ===========================================================================

This region rebuilds the projects, so after it is built once it is then cleaned

before building it again.

This produces the assemblies with the desired behavior

================================================================================-->

 

<Target Name="ReBuildAll" DependsOnTargets="CleanAll;ReBuildConstOne;ReBuildConstTwo" />

<Target Name="ReBuildConstOne">

<MSBuild Projects="@(Projects)" Targets="Rebuild"

Properties="DefineConstants=$(ConstOne);OutputPath=binOne\;"/>

</Target>

<Target Name="ReBuildConstTwo">

<MSBuild Projects="@(Projects)" Targets="Rebuild"

Properties="DefineConstants=$(ConstTwo);OutputPath=binTwo\;"/>

</Target>

 

<!--==========================================================================

This region calls the default target (Build) but also specifies the

BaseIntermediateOutputPath so it works as well because the projects

are building to different locations on disk.

==============================================================================-->

<Target Name="BuildAllBetter" DependsOnTargets="CleanAll;BuildConstOneBetter;BuildConstTwoBetter" />

<Target Name="BuildConstOneBetter">

<MSBuild Projects="@(Projects)"

Properties="DefineConstants=$(ConstOne);OutputPath=binOne\BaseIntermediateOutputPath=objOne\"/>

</Target>

<Target Name="BuildConstTwoBetter">

<MSBuild Projects="@(Projects)"

Properties="DefineConstants=$(ConstTwo);OutputPath=binTwo\;BaseIntermediateOutputPath=objTwo\;"

 

/>

</Target>

 

</Project>

Ddddd

Let's take a look at what's going on here. The BuildConstOne target passes the CONSTANT_ONE value as a property when invoking the MSBuild task, and builds to the binOne folder. The BuildConstTwo target passes the CONSTANT_TWO value and builds to binTwo. If you execute the target BuildAll, you would expect that the exe in binOne would show the message CONSTANT_ONE and the exe in the binTwo folder has CONSTANT_TWO message showing. What you actually get is that both actually show CONSTANT_ONE. This is because MSBuild doesn't entirely rebuild the project when building it for the second time in BuildConstTwo. The real reason behind this would be because both projects are building to the same BaseIntermediateOutputPath, obj\. There are two ways to of getting around this, one is to call Rebuild instead of Build, and the other is to specify the BaseIntermediateOutputPath as well. In the above I have demonstrated both approaches. The ReBuildAll calls Rebuild, and the BuildAllBetter specifies the BaseIntermediateOutputPath as well as OutputPath.

Questions?!

You can download all the files at:

DefineConst.zip

Sayed Ibrahim Hashimi

Thursday, May 08, 2008 12:52:14 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Tuesday, April 29, 2008

A question that comes up pretty frequently is "How can I debug an MSBuild task"? It's actually pretty simple. In this post I will describe how to easily an effectively debug MSBuild tasks that you are creating. In this example I will be demonstrating a task from my open source tasks at www.codeplex.com/Sedodream. The task is one that was contributed by Grant Holliday.

First in the project where your tasks are contained create a folder that will be used to contain sample MSBuid files that can be used to debug the tasks. This is also a good idea, because it will show people how to use your tasks. If you are don't want to mix samples & code in the same project then just make sure in your build you copy the files to the correct locations. In the sample project, which you can download at the bottom, the folder is named Samples. When you add MSBuild files to the folder make sure you set the file to be copied to the output folder as well. See the image below.

By setting this, the file will be copied to the output folder when the project is built. Since it will be in the output folder we can use a relative path to get to the assembly that contains the task. Take a look at the sample MSBuild file for this task.

<?xml version="1.0" encoding="utf-8"?>

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

   <PropertyGroup>

    <TaskLocation Condition="$(TaskLocation)==''">$(MSBuildProjectDirectory)\..\DebugTask.dll</TaskLocation>

  </PropertyGroup>

   <UsingTask TaskName="CreateGuid" AssemblyFile="$(TaskLocation)"/>

 

  <Target Name="Example">

    <Message Text="Starting example" />

    <CreateGuid>

        <Output PropertyName="Guid" TaskParameter="Output"/>

      </CreateGuid>

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

   </Target>

</Project>

 

The line highlighted contains the path to the assembly that contains the task. This path is relative to the location in the output folder, not the source folder. Also another thing to take note, is that we only write to the TaskLocation property if it is empty. This is useful because you can overwrite the location through command line parameters if necessary. But this shouldn't be needed for what we are trying to accomplish here.

After you create the sample, build the solution. And open a command prompt to verify that it works. Here is a sample of the result of this project file.

Once you've verified that that MSBuild file works then you can right click on the project that contains your task, and select properties. From there go to the debug tab. What we want to do is start the msbuild.exe executable on that sample project file. To do this fill in the full path to it in the Start External program text box, i.e. 'C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe'. In the command line arguments you should pass the name of the project file followed by any msbuild parameters. Typically I will attach a file logger to the process. Finally you should set the working directory to the folder containing the sample. The result should look something like the image shown below.

Following this set a break point in your task, set the project as the startup project and hit F5! Another thing to take note of is that these properties are stored in the .user file so it shouldn't affect any other developers on your team.

Below is the link containing a simple solution that was used here.

 

DebugTask.zip

Sayed Ibrahim Hashimi

Tuesday, April 29, 2008 6:10:03 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Tuesday, April 15, 2008

Recently I was introduced to a tool that was to a tool that would analyze Javascript for syntactical errors as well as usage of bad practices. This tool is JSLint. Douglas Crockford from Yahoo! created, and maintains, JSLint. If you haven't heard of him, he is a well known Javascript expert.

Now how can we verify our Javascript with JSLint & MSBuild? In my open source project Sedodream MSBuild I have created a new JSLint task. You can get the installer by going to http://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=Sedodream&ReleaseId=12530. After you install the msi you can follow these steps to invoke the JSLint on your projects.

Edit your web project file and include the following snippet before the </Project> tag.

<Import Project="$(MSBuildExtensionsPath)\Sedodream\Sedodream.tasks"/>

<Import Project="$(MSBuildExtensionsPath)\Sedodream\JSLint.targets"/>

 

<PropertyGroup>

<BuildDependsOn>

$(BuildDependsOn);

RunJSLint;

</BuildDependsOn>

</PropertyGroup>

 

The first line includes makes the tasks from my project available, and the second line includes the file that knows how to execute the JSLint tool. Following that I extend the build process to execute the RunJSLint target. If you are not familiar with this take a look at my article Inside MSBuild.

After you do this you may be prompted with a security warning from Visual Studio, you should pick 'Load Project Normally'. You can read more about how to disable it at http://msdn2.microsoft.com/en-us/library/ms228217.aspx. I chose to not have my installer set that registry flag. For the time being I think that users should make that decision themselves, although I would like to think I'm trustworthy J

After you do this and allow Visual Studio to load the project normally, if your create Javascript files that have errors, or JSLint doesn't like you will be warned in the error list as shown below.

 

With that being said keep in mind this is a V1 deal, so this may not work perfect. I am working to make sure that it works well, but it was kinda tricky to get this to work period!

As always I welcome your feedback and I will post more info about this later. Oh yeah by default from JSLint the GoodParts are enforced keep an eye on this blog, or send me a message, to see how to change that.

Sayed Ibrahim Hashimi

Tuesday, April 15, 2008 6:25:01 AM (GMT Daylight Time, UTC+01:00)  #    Comments [4]  | 
Wednesday, February 20, 2008

Previously I was thinking of writing some more material on MSBuild. Since then I have written about 60 pages of new material, and have actually decided to focus on some new topics. Because of this I will release a mulit part series that consists of this material that I have written. I think that I've come up with some pretty good content and would like to make it available. It still requires polishing and changes for this new format. So this is the first in this series of post.

These first posts will be from the MSBuild Quick Start chapter

In this post we will discuss the topics highlighted.

MSBuild Quick Start

  • Project file details
  • MSBuild Properties & Targets
  • Items
  • Simple Conditions
  • Default / Initial targets
  • Command line usage
  • Before / After build
  • Clean

MSBuild Quick Start

When you are learning a new subject it's exciting to get just dive right into it and get your hands dirty. In this section I will describe all the key elements you will need to know to get started using MSBuild. If you are already familiar with MSBuild feel free to skip this section, all of the material covered here will be covered in later sections as well.

The topics that we will cover in this section include; structure of an MSBuild file, Properties, Targets, Items and invoking MSBuild. With that being said, let's get started.

Project file details

An MSBuild file, typically called an MSBuild project file is really an XML file. These XML files are described by two XMLSchema Documents (XSD). These are located in the %WINDIR%\Microsoft.NET\Framework\v2+\MSBuild folder. Where v2+ is the version folder for .NET 2 and above. In this text I will assume you are using .NET 2.0 unless specified. In those folders you will find Microsoft.Build.Commontypes.xsd, this is the file that describes commonly found element in Visual Studio generated project files. Also this file includes the Microsoft.Build.Core.xsd document. This document describes all the fixed elements in an MSBuild project file. The simplest MSBuild file would contain the following:

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

</Project>

This is the XML fragment that will identify this as an MSBuild file. Inside these tags is where all your content will be placed. Specifically we will be declaring Properties,Items, Targets and a few other items directly under the Project element. When you build you need to know what you are building, typically contained in Items and values that drive the build process, typically stored in Properties. We will now discuss how to declare properties, and we will cover Items later in this chapter.

MSBuild Properties & Targets

MSBuild properties are simply key value pairs, the key for the property is the name that you will use to refer to the property, and the value is, simply, its value. When you declare static properties they are contained in a PropertyGroup element directly under the Project element. Below is an example.

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

<PropertyGroup>

<AppServer>\\sayedApp</AppServer>

<WebServer>\\sayedWeb</WebServer>

</PropertyGroup>

</Project>

In the snippet above I have declared two properties; AppServer and WebServer with the values '\\sayedApp' and '\\sayedWeb' respectively. You can create as many PropertyGroups under the Project tag as you desire. The above fragment could have been defined as the one shown below.

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

<PropertyGroup>

<AppServer>\\sayedApp</AppServer>

</PropertyGroup>

<PropertyGroup>

<WebServer>\\sayedWeb</WebServer>

</PropertyGroup>

</Project>

If you create a sample project in Visual Studio, you will notice that there are many properties declared. These properties contain values that will be used throughout the build process. Below is a fragment from a Windows Application project that I created using Visual Studio.

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

<PropertyGroup>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

<ProductVersion>8.0.50727</ProductVersion>

<SchemaVersion>2.0</SchemaVersion>

<ProjectGuid>{A71540FD-9949-4AC4-9927-A66B84F97769}</ProjectGuid>

<OutputType>WinExe</OutputType>

<AppDesignerFolder>Properties</AppDesignerFolder>

<RootNamespace>WindowsApplication1</RootNamespace>

<AssemblyName>WindowsApplication1</AssemblyName>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<OutputPath>bin\Debug\</OutputPath>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

....

</Project>

You can see that things such as the output type, name of the assembly and many other values are defined in properties. Defining properties is great, but completely useless without being able to use them. So we will move on to Target declarations.

MSBuild has execution elements, those are Tasks and Targets. A Task is the smallest unit of work in and MSBuild file, and a Target is simply a sequential set of tasks. A Task must always be contained within target. Below is a sample that shows you the simplest MSBuild file which contains a target.

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

<Target Name="HelloWorld">

</Target>

</Project>

In this sample we have created a new Target named HelloWorld, but it actually doesn't perform any work at this point because it is empty. When MSBuild is installed you are given many tasks out of the box. You can find a list of these tasks at MSBuild Task Reference (http://msdn2.microsoft.com/en-us/library/7z253716.aspx). We will now use the Message task. The message task is used to send a message to the logger(s) that are listening the build process. In many cases this means that a message is sent to the console that is executing the build process. When you invoke a task in an MSBuild file you can pass it input parameters by inserting XML attributes with values. These attributes will vary from task to task depending on what inputs the task is expecting. From the documentation for the Message task (http://msdn2.microsoft.com/en-us/library/6yy0yx8d.aspx) you can see that is accepts a string parameter named Text. The snippet below shows you how to use the Message task to send the value 'Hello World' to the logger(s).

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

<Target Name="HelloWorld">

<Message Text="Hello world!"/>

</Target>

</Project>

Now we will verify that this works as expected. To do this place the above text into a file named HelloWorld.proj. Now open a Visual Studio Command Prompt and go to the directory that contains the newly created file. The shortcut for this prompt is in the Visual Studio Tools folder under the Visual Studio folder in the Start menu. The command that you'll use is the msbuild.exe command. The basic usage for the command is

msbuild [INPUT_FILE] /t:[TARGETS_TO_EXECUTE]

So our command would be

msbuild HelloWorld.proj /t:HelloWorld

In the above we are saying, build HelloWorld.proj by executing the HelloWorld target. The result of this command is shown in the image below.

 

In the image above we can see that the HelloWorld target is executed and that a message 'Hello World!' was displayed on the console. Now let's see how we can use properties within other properties. Have a look at the simple project file shown below.

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

<PropertyGroup>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

<DropLocation>\\sayedData\MSBuildExamples\Drops\$(Configuration)\$(Platform)\</DropLocation>

</PropertyGroup>

<Target Name="PrepareFilesForDrop">

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

</Target>

</Project>

In the snippet above we can see that there are three properties that have been declared. On both the Configuration & Platform properties there is a Condition attribute. We will discuss these attributes later in this chapter. The remaining property, DropLocation, is defined using the values of the two previously declared items. Let's take a close look at that. The DropLocation Property has three components; a constant value and two values that are derived from the Configuration & Platform properties. When the MSBuild engine sees the $() notation it will replace that with the value of the specified properties. So the qualified DropLocation value would be: '\\sayedData\MSBuildExamples\Drops\Debug\AnyCPU\'. You can verify that by executing the PrepareFilesForDrop target from the Visual Studio Command Prompt. The reference for properties can be found at http://msdn2.microsoft.com/en-us/library/ms171458(VS.80).aspx.

When you use MSBuild there are a handful of properties that are available to you out of the box, that cannot be modified. These are known as Reserved properties. In the table below you will find all the Reserved properties.

Name

Description

Example

MSBuildProjectDirectory

Full path to the location of project file.

C:\MSBuild\MSBuild1\MSBuild1

MSBuildProjectFile

Filename of the project file, including extension.

MSBuild1.csproj

MSBuildProjectExtension

Extension of the project filename, including the initial dot.

.csproj

MSBuildProjectFullPath

Full path to the project file including the filename.

C:\MSBuild\MSBuild1\MSBuild1\MSBuild1.csproj

MSBuildProjectName

Name of the MSBuild project file excluding the file extension.

MSBuild1

MSBuildBinPath

Full path of the .NET Framework MSBuild bin directory.

%WINDIR%\Microsoft.NET\Framework\v2.0.50727

MSBuildProjectDefaultTargets

The value for the DefaultTargets attribute that is in the Project element.    

Build

MSBuildExtensionsPath

Full path to the MSBuild folder located in the Program Files directory. This is a great place to keep commonly used items.

C:\Program Files\MSBuild

 

You would use these properties just as you would properties that you have declared in your own project file. To see an example of this you can look at Visual Studio generated project files. When you initially create a new C# project you will find the import statement <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> towards the bottom. This import declaration is using the MSBuildBinPath reserved property to resolve the full path to the Microsoft.CSharp.targets file. This is the file that drives the build process for C# projects. We will discuss its contents in more detail later in the book. Now that we have an idea how to use properties we will move on to Items.

Items

Building applications usually means dealing with many files. Because of this there is a specific construct that is used when dealing with files, and that is what's called Items. Items are usually file references, but they actually can be used for other purposes as well. If you create a project using Visual Studio you may notice that you see many PropertyGroup elements as well as ItemGroup elements. The ItemGroup elements contain all the defined items. When you define a Property you are declaring a key/value pair, but when you declare an Item you declare Items that can potentially have many elements inside of them. Let's see the syntax in action.

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

<ItemGroup>

<SolutionFile Include="..\MSBuildExamples.sln"/>

</ItemGroup>

<Target Name="PrintSolutionInfo">

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

</Target>

</Project>

In this file, ItemsSimple.proj, there is an ItemGroup that has a sub element SoultionFile. The ItemGroup is the element type that all statically declared Items must be placed. The name of the sub elements are the names of the actual Items. In the above sample we have declared an Item names SolutionFile. The SolutionFile element has an attribute attached to it, the Include attribute. This attribute determines what actual entries are contained within the Item. This is because an Item can contain multiple entries. The Include attribute can contain a single value or a comma separated list of values. We will see this demonstrated later in this chapter.

In the target, PrintSolutionInfo, we can see the Item at use. Item evaluation syntax varies from Property syntax. To get the value of an item you will use the @() notation. The result of the build file, ItemsSimple.proj, is shown in the image below.

In this case the Item, SolutionFile, contains a single entry. Now let's see the result if there were multiple files included in the item. The snippet below shows the modified version of the ItemsSimple.proj file.

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

<ItemGroup>

<SolutionFile Include="..\MSBuildExamples.sln"/>

</ItemGroup>

<Target Name="PrintSolutionInfo">

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

</Target>

 

<ItemGroup>

<Compile Include="Form1.cs;Form1.Designer.cs;Program.cs;Properties\AssemblyInfo.cs" />

</ItemGroup>

<Target Name="PrintCompileInfo">

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

</Target>

</Project>

When you invoke the PrintCompileInfo target you will get the result 'Compile: Form1.cs;Form1.Designer.cs;Program.cs;Properties\AssemblyInfo.cs'. The Message tasks' Text parameter is a string value so the item is automatically converted, by MSBuild, into a string then passed to the task. This is done by concatenating all the entries separated by a ;. Later in the book I will demonstrate how you can customize this conversion process. Since the Include attribute can accept either one or many entries we could have actually defined the Compile Item as follows:

<ItemGroup>

<Compile Include="Form1.cs"/>

<Compile Include="Form1.Designer.cs"/>

<Compile Include="Program.cs"/>

<Compile Include="Properties\AssemblyInfo.cs"/>

</ItemGroup>

When a Property declaration appears after a previous declaration, this results in the value of the Property to be overridden. Items act differently from this; you cannot remove entries or overwrite values contained in an Item. You can only append to them. This is why both of the above statements are equivalent. Typically Items refer to existing files, if this is the case you can use wild cards to automatically include whatever files meet the conditions contained in the include statement. You can use three wildcard elements with MSBuild: ?, *, and **. You can use ? to replace a single character with any character. For example, the include declaration Include="c?r.cs" would include the files car.cs, cbr.cs, ccr.cs, and so on. The * element can replace any location with zero or more characters. To change the previous example, Include="c*r.cs" would include car.cs, caar.cs, cr.cs, colorer.cs, and so on. The ** notation tells MSBuild to search the directories recursively for the pattern. For example, Include="src\**\*.cs" would include all the files under the src directory with .cs extensions.

Another difference between Properties and Items is that Items can have metadata. Actually when you declare an Item each entry has a set of metadata associated with it out of the box. The table below outlines the well known metadata.

Name

Description

FullPath

Full path of this item including the filename.

RootDir

Root directory to which this item belongs.

Filename

Filename for this item, not including the extension.

Extension

File extension for this item.

RelativeDir

Path to this item relative to the current working directory.

Directory

Directory of the item without the root directory.

RecursiveDir

Used for items that were created using wildcards. This would be the directory that replaces the wildcard(s) statements that determine the directory.

Identity

Value for the item specified in the Include attribute.

ModifiedTime

Time this item was last modified.

CreatedTime

Time the item was created.

AccessedTime

Last time this item was accessed.

 

The basic usage for accessing metadata values is described by the template shown below.

@(ITEM_NAME->'%(METADATA_NAME)')

Where ITEM_NAME is the name of the item and METADATA_NAME is the name of the metadata that you would like to retrieve. Consider the following example. Assume that you have the following files in a directory; Class1.cs,Class2.cs,Class3.cs and Class4.cs. The following project file, MetadataExample01.proj, shows how to access the Compile Items' Fullpath metadata.

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

<ItemGroup>

<Compile Include="*.cs"/>

</ItemGroup>

<Target Name="PrintCompileInfo">

<Message Text="Compile fullpath: @(Compile->'%(Fullpath)')"/>

</Target>

</Project>

If you execute the PrintCompileInfo target you will see the following result.

From the output shown in the figure above we can see that the full path for all the Compile items was sent to the console. Accessing the metadata in this manner is given the term Item Transformation. We will discuss transformations in much more detail in a later chapter. Before we move on to discuss MSBuild Conditions take a look at a simple Windows Application project file that was generated by Visual Studio. You should see many things that you recognize.

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

<PropertyGroup>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

<ProductVersion>8.0.50727</ProductVersion>

<SchemaVersion>2.0</SchemaVersion>

<ProjectGuid>{0F34CE5D-2AB0-49A9-8254-B21D1D2EFFA1}</ProjectGuid>

<OutputType>WinExe</OutputType>

<AppDesignerFolder>Properties</AppDesignerFolder>

<RootNamespace>WindowsApplication1</RootNamespace>

<AssemblyName>WindowsApplication1</AssemblyName>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<OutputPath>bin\Debug\</OutputPath>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">

<DebugType>pdbonly</DebugType>

<Optimize>true</Optimize>

<OutputPath>bin\Release\</OutputPath>

<DefineConstants>TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.Deployment" />

<Reference Include="System.Drawing" />

<Reference Include="System.Windows.Forms" />

<Reference Include="System.Xml" />

</ItemGroup>

<ItemGroup>

<Compile Include="Form1.cs">

<SubType>Form</SubType>

</Compile>

<Compile Include="Form1.Designer.cs">

<DependentUpon>Form1.cs</DependentUpon>

</Compile>

<Compile Include="Program.cs" />

<Compile Include="Properties\AssemblyInfo.cs" />

<EmbeddedResource Include="Properties\Resources.resx">

<Generator>ResXFileCodeGenerator</Generator>

<LastGenOutput>Resources.Designer.cs</LastGenOutput>

<SubType>Designer</SubType>

</EmbeddedResource>

<Compile Include="Properties\Resources.Designer.cs">

<AutoGen>True</AutoGen>

<DependentUpon>Resources.resx</DependentUpon>

</Compile>

<None Include="Properties\Settings.settings">

<Generator>SettingsSingleFileGenerator</Generator>

<LastGenOutput>Settings.Designer.cs</LastGenOutput>

</None>

<Compile Include="Properties\Settings.Designer.cs">

<AutoGen>True</AutoGen>

<DependentUpon>Settings.settings</DependentUpon>

<DesignTimeSharedInput>True</DesignTimeSharedInput>

</Compile>

</ItemGroup>

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

<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

Other similar extension points exist, see Microsoft.Common.targets.

<Target Name="BeforeBuild">

</Target>

<Target Name="AfterBuild">

</Target>

-->

</Project>

Wednesday, February 20, 2008 12:40:11 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Thursday, January 17, 2008

Previously I blogged about an MSBuild Presentation that I was going to conduct at the JaxDug. I'm pleased to say that the presentation went well. I prepared a Powerpoint, but I decided to not use that and to discuss the various elements by example. I think that ended up being a lot more effective, and interesting than the Powerpoint would have been. You can find all the files for download below. The zip file contains all the files. The ppt is the Office 2003 version.

MSBuild_Sayed_I_Hashimi_012008.zip

MSBuild_Sayed_I_Hashimi_01-2008.pdf
MSBuild_Sayed_I_Hashimi_01-2008.ppt.zip
MSBuild_Sayed_I_Hashimi_01-2008.pptx.zip

Sayed Ibrahim Hashimi

Thursday, January 17, 2008 6:57:31 AM (GMT Standard Time, UTC+00:00)  #    Comments [5]  | 
Wednesday, November 21, 2007

MSBuild: How to get all generated outputs

Earlier today a reader of my book emailed me the following question

I am trying to get a list of all the files being generated when MSBuild build a  certain project (they should be all the files in .bin directory).

When I used the TargetOutputs in MSBuild task it gives me the main output only, which is the .exe or .dll. But any other files like dependency assemblies will not be listed in the TargetOutputs collection.

Any advice??

So it sounds like he is looking for a generic way of determining all the files that have been created (moved) during the build process. I’m assuming that he has a few projects and is using a build script to run the process, and then he wants to move the files to another location. If you have a few projects, then obviously the easiest way is to manually examine the output paths of each project and copy the file as necessary. This is not an ideal scenario, because if you add a project to your process you have to make sure to remember to perform the copy manually. Anywayz, this type of behavior would be useful in many different scenarios. Now let’s talk about how to achieve this.

I took another look at the Microsoft.Common.targets file and determined that there is not a straight forward method of performing this action. So, after thinking about this for a little while I think I have come up with a solution. It’s a little confusing, but actually quite simple.

The solution lies in what I’m calling “MSBuild Inheritance”, which I’ve blogged about previously in the entries at:

·        MSBuild Property & Item Overide Problems

·        MSBuild: Find Many Project References

This is where you take an existing MSBuild file and without modifying it you add behavior and or data to it. Now let’s see how this idea can be used to add the desired behavior described above. For this sample I have created a sample Windows Forms app, as well as an external build script, buildWrapper.proj. This build file is shown below.

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

  <PropertyGroup>

    <ProjectFile>$(MSBuildProjectDirectory)\WindowsApplication1.csproj</ProjectFile>

  </PropertyGroup>

 

  <Target Name="GetOutputs">

    <MSBuild Projects="$(MSBuildProjectFullPath)"

             Properties="ImportProjectFile=true" Targets="Build">

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

    </MSBuild>

    <Message Text="%0a%0dProjectOutputs:%0a%0d    @(ProjectOutputs,'%0a%0d    ')" />

  </Target>

 

  <!--

    These elements will only be processed when in the context of the

    above Target. This is because of the Condition constraint on these items

  -->

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

  <!--

    Here we need to override the Build target with our own that will

    generate the correct results.

    -->

  <Target Name="Build"

        Condition="'$(ImportProjectFile)'=='true'"

        DependsOnTargets="$(BuildDependsOn)"

        Outputs="@(AllOutputs->'%(FullPath)')">

   

    <CreateItem Include="$(OutputPath)\**\*">

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

    </CreateItem>

   

    <Message Text="Custom build invoked!" Importance="high"/>

   

  </Target>

</Project>

What this does is twofold:

1.      Defines the GetOutputs target which will drive the process

2.      Extends the behavior of the normal build to create the proper outputs

If you take a look at the Microsoft.Common.targets you will see that the default definition of the Build target is defined as follows:

    <Target

        Name="Build"

        Condition=" '$(_InvalidConfigurationWarning)' != 'true' "

        DependsOnTargets="$(BuildDependsOn)"

        Outputs="$(TargetPath)"/>

The purpose of this target is to define what targets it depends on and to create the outputs, by itself it doesn’t actually perform any action. Because of this we can override it in our buildWrapper.proj file. So the bottom half of the buildWrapper.proj file will conditionally import the project file and then conditionally re-define the Build target. Inside the new Build target we simply examine the output directory and create an item that contains all files that lie underneath it. In the image below you will see the result of this, specifically take a look at the region highlighted in red.

 

 

Now we can see that we have successfully achieved our goals.

Every time I describe this process I feel like it is still un-clear. If you have questions please let me know.

 

Sayed Ibrahim Hashimi

You can download all the files at http://www.sedodream.com/content/binary/OutExample.zip


(Edit: Updated MSBuild task usage to include Targets="Build")


Wednesday, November 21, 2007 6:29:57 AM (GMT Standard Time, UTC+00:00)  #    Comments [1]  | 
Monday, November 19, 2007

The MSBuild team is trying to prioritize their duties for the next release of MSBuild. Because of this they have asked for feedback on how their customers (us) would enhance MSBuild. They are doing this by distributing a virtual $100 across a possible 11 items. Their list actually ends in an item numbered 12, but there is no number 10. Here is a brief overview of their list of options.

1. Higher Preformance
2. VC Support
3. Support for other project types
4. Convert VS solution file to MSBuild format
5. Better dependency tracking
6. Distributed Build
7. Inline Tasks – ie you write a script right in your MSBuild file instead of a compilied .NET task
8. Scoping for items/properties – ie items/properties can exist in a defined scope
9. Extensible functions – ie being able to add items like the Exists function
11. MSBuild debugger
12. MSBuild visualization 

Here is how I chose to distribute my $100. 

4   - $40
6   - $10
7   - $10
11 - $30
12 - $10

About #4: In my experience, you have 2 scenarios that are the correct way to create build scripts for products. Those are

1.      Create a custom MSBuild file that acts like a solution file

2.      Use a solution file to drive the build process

The only reason to go with 1 is because the solution file is not an actual MSBuild file, otherwise that wouldn’t even be an option. As for option 2, this is not ideal either because it is difficult to inject steps into how the solution build process works. All you can do is really extend what happens before/after the process.

As for the debugger, this is a tool that I have actually wanted to write myself for a long time, but never had the time. If there was a debugger for MSBuild, I think that more people would begin to adopt MSBuild as well as get a much better understanding of how it works.

It’s not often that teams from Microsoft ask for straight customer feature feedback, if you’re interested in future versions of MSBuild then you should definetly participate in this at: http://blogs.msdn.com/msbuild/archive/2007/11/17/how-would-you-spend-100-on-msbuild.aspx. 

Sayed Ibrahim Hashimi

Monday, November 19, 2007 6:40:30 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Tuesday, October 16, 2007

I’ve created a new release of MSBuild tasks, at www.codeplex.com/sedodream. This release includes 4 .NET 3.5 tasks built on Visual Studio B2. Those are all Linq MSBuild tasks. They are:

Name

Description

CreateLinqCode

Creates a Linq code file from a database.

CreateLinqDbml

Creates a Linq DBML file from a database.

CreateLinqMap

Creates a Linq Map file from a database.

CreateDatabase

Creates a database from an Linq DataContext which is contained in an assembly.


The first three tasks simply wrap the SqlMetal command line utility. The last, CreateDatabse, will create a database from a Linq DataContext that is contained in an assembly. Right now there is little validation of the parameters passed, but I plan on adding additional validation. Also I will add more meaningful error messages, when invalid parameters are passed through.

Below you’ll find an example of the CreateLinqCode task.

<?xml version="1.0" encoding="utf-8"?>

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

  <Import Project="$(MSBuildExtensionsPath)\Sedodream\Sedodream35.tasks"/>

  <Target Name="Create">

    <!-- sqlmetal /pluralize

              /conn:"data source=(local);initial catalog=ibrtest;integrated security=SSPI;packet size=4096"

              /timeout:15

              /language:C#

              /context:IbrTestDataContext

              /code:ibrtest.cs

              /views

              /functions

              /sprocs

              /serialization:Unidirectional

              /namespace:Sedodream.Linq.Test

              -->

    <CreateLinqCode

      CodeFileName="ibrahimTest.cs"

     

      ConnectionString="data source=(local);initial catalog=ibrtest;integrated security=SSPI;packet size=4096"

      Timeout="15"

      Language="C#"

      ContextClassName="IbrTestDataContext"

      IncludeViews="true"

      IncludeSprocs="true"

      IncludeFunctions="true"

      TargetNamespace="Sedodream.Linq.Test"

      >

    </CreateLinqCode>

</Target>

</Project>

Here is the CreateLinqDbml task usage.

<?xml version="1.0" encoding="utf-8"?>

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

  <Import Project="$(MSBuildExtensionsPath)\Sedodream\Sedodream35.tasks"/>

  <Target Name="Create">

<!-- sqlmetal /pluralize

              /conn:"data source=(local);initial catalog=ibrtest;integrated security=SSPI;packet size=4096"

              /timeout:15

              /language:C#

              /context:IbrTestDataContext

              /dbml:../LinqObjects\ibrtest.dbml

              /views

              /functions

              /sprocs

              /serialization:Unidirectional

              /namespace:Sedodream.Linq.Test

              -->

    <CreateLinqDbml

      DbmlFileName="ibrahimTest.dbml"

      OutDir="."

      ConnectionString="data source=(local);initial catalog=ibrtest;integrated security=SSPI;packet size=4096"

      Timeout="15"

      Language="C#"

      ContextClassName="IbrTestDataContext"

      IncludeViews="true"

      IncludeSprocs="true"

      IncludeFunctions="true"

      TargetNamespace="Sedodream.Linq.Test"

      >

    </CreateLinqDbml>

  </Target> 

</Project>

 

Please let me know if you have any questions/comments about these items.

Sayed Ibrahim Hashimi

Tuesday, October 16, 2007 12:50:48 AM (GMT Daylight Time, UTC+01:00)  #    Comments [7]  | 
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]  | 
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]  | 
Tuesday, July 18, 2006

I've created a project on CodePlex to hold all of the MSBuild tasks/loggers and what not from my book and my MSDN article Inside MSBuild. The project home page is at Sedodream MSBuild CodePlex project. The idea is that I'll keep all of the MSBuild related material that I discuss here there as well. If you have content that you'd like to post there thats great too.
Here is the current list of what is available there:

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

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

Tuesday, July 18, 2006 5:47:05 AM (GMT Daylight Time, UTC+01:00)  #    Comments [3]  | 
Thursday, June 15, 2006

A few days ago I received an email about an MSBuild visual editor! This was something that I was interested in doing a while ago, but never got around to doing it. This tool is called MSBuild Sidekick, and you can download it for free from http://www.attrice.info/msbuild/index.htm. I have downloaded it and played around with it. I think it’s a good tool for people who are getting started with MSBuild, but for those who use MSBuild on a regular basis I don’t think it can replace the productivity that is achieved with Intellisense. They have an interesting way of representing the build file visually, and after some time if the right extensions are added it may be a tool that I’d use over Visual Studio to edit my MSBuild files. One of the challenges of representing a build file is that you have to be aware of where your new declarations are being placed. For instance if you have two properties defined, lets say Property1 and Property2. If Property2 depends on Property1 then you have to make sure that it is declared after Property1. As far as I could tell there was no way to achieve this without viewing the source of the project file through the tool.

Sayed Ibrahim Hashimi

Thursday, June 15, 2006 6:14:45 AM (GMT Daylight Time, UTC+01:00)  #    Comments [2]  | 
Wednesday, June 14, 2006

In previous versions of the Visual Studio the build customizations were basically limited to a PreBuildEvent and a PostBuildEvent. Now we have MSBuild which allows for many very specific and complex build customizations. But Microsoft obvioiusly must continue to support the Pre/Post build event. It is something that already exists, many people are familiar with it, and its very simple. Let’s see how this is implemented within MSBuild. If you add a Pre or Post build event from Visual Studio, then examine yore project file you’ll find something like the following declared.

 

  <PropertyGroup>

    <PostBuildEvent>echo 'This is the post build event!'</PostBuildEvent>

  </PropertyGroup>

 

So these events are actually simple properties that will be executed when the time is right. What does this mean for you? Now lets talk about what’s funny about this. When MSBuild loads your project file it will load all of the properties contained in PropertyGroup elements, followed by that your items will be evaluated. What this means for the Pre/Post build event is that you are able to access properties that have been declared in PropertyGroup element before the Pre/Post build event declaration. After this evaluation is made the value for the Pre/Post build event will not change! What this equates to is that you are able to use properties such as OutDir or AssemblyName, but you’ll never be able to access properties that are created from tasks contained in other targets! If you need to access properties that have been created by other targets then you’ll have to extend your build process outside of the Pre/Post build event. This is outlined in my MSDN article Inside MSBuild in the Extending the Build Process section.

This entry was inspired by a post on the MSDN MSBuild forum.

 

Sayed Ibrahim Hashimi

Wednesday, June 14, 2006 5:52:03 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Friday, June 09, 2006

If you look around the net you'll find a few different Xml loggers for MSBuild. When I was writing my book I decided to write my own Xml Logger for a few different reasons. The source for the Xml logger that I wrote is available at apress.com. I'm currently in the process of trying to get some shared space for me to place my tasks and loggers that I've written for everyone to have a look at. Once I get that squared away I'll make sure to write an entry about it. In the mean time let's talk about my Xml logger. What is different about my Xml logger versus some others that you may find? When I was looking around for one, I noticed that the ones that I found didn't support building solution files very well. So that was a deficiency that I wasn't willing to sacrifice with. Besides that, I noticed that none of them had an append options as most file based loggers do. So I added that to my Xml logger. I think this is a good option if you are performing a nightly ( or continuous ) build. That way you have one place to go to each time you want to see how your build progressed. However this xml file can get quite large if your build is spanning many projects.
At the bottom of this blog you can download the dll for this logger (Contained in a zip file). The source will be made available soon. There are 4 logger arguments:
   

Name

Description

Append

True/false If true then if a log file exists then it will be append to, otherwise any existing log file will be replaced.

Logfile

Name of the log file to write to

Showsummary

True/false if true then a summary will be contained in the xml file for errors and warnings.

Verbosity

The verbosity level of the logger. Same as msbuild.exe values see MSBuild Command Line Reference


   Here is some sample output:

<MSBuild>

  <Build Started="6/9/2006 1:09:51 AM" Verbosity="Detailed"

Finished="6/9/2006 1:09:56 AM" Succeeded="False">

    <Message>Build started.</Message>

    <Project Name="C:\Data\Dreamcatcher_NET\Dreamcatcher\Dreamcatcher.sln"

Message="Project &quot;Dreamcatcher.sln&quot; (Rebuild target(s)):" \

Started="6/9/2006 1:09:51 AM" Finished="6/9/2006 1:09:56 AM">

      <Target Started="6/9/2006 1:09:51 AM" Name="ValidateSolutionConfiguration"

Message="Target &quot;ValidateSolutionConfiguration&quot; in project &quot;Dreamcatcher.sln&quot;" Finished="6/9/2006 1:09:51 AM" Succeeded="True">

        <Task Started="6/9/2006 1:09:51 AM" Name="Message" Finished="6/9/2006 1:09:51 AM" />

      </Target>

<MSBuild>

 

Here is what the summary section looks like:

    <Warnings>

      <Warning Code="CS0162" Subcategory="">

        <Message>Controls\DreamView.cs(450,6): warning CS0162: Unreachable code detected</Message>

      </Warning>

    </Warnings>

    <Errors>

      <Error File="Controls\DreamView.cs" Code="CS1026" Subcategory="">

        <Message>Controls\DreamView.cs(459,44): error CS1026: ) expected</Message>

        <Location Line="459" ColumnNumber="44" />

      </Error>

      <Error File="Controls\DreamView.cs" Code="CS1002" Subcategory="">

        <Message>Controls\DreamView.cs(459,45): error CS1002: ; expected</Message>

        <Location Line="459" ColumnNumber="45" />

      </Error>

      <Error File="Controls\DreamView.cs" Code="CS1525" Subcategory="">

        <Message>Controls\DreamView.cs(459,45): error CS1525: Invalid expression term ')'</Message>

        <Location Line="459" ColumnNumber="45" />

      </Error>

      <Error File="Controls\DreamView.cs" Code="CS1002" Subcategory="">

        <Message>Controls\DreamView.cs(459,46): error CS1002: ; expected</Message>

        <Location Line="459" ColumnNumber="46" />

      </Error>

    </Errors>

The syntax to use this logger at the command prompt is:

msbuild.exe /l:XmlLogger,PATH_TO_ASSEMBLY\Sedodream.MSBuild.Loggers.dll;logfile=build.xml;verbosity=detailed;append=true;showsummary=true

There is no required parameters the defaults are:

  • Logfile=build.log.xml
  • Verbosity=normal
  • Append=false
  • Showsummary=false

Sedodream.MSBuild.Loggers.zip (binaries 9.77 KB)


Sayed Ibrahim Hashimi

Friday, June 09, 2006 6:34:17 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Sunday, June 04, 2006

Ok, there's several posts about whats good/bad about both of these languages, this isn't one of those. At the end of the day it doesn't matter which technology is the best, but which ones are going to employ you. About a year ago, I think there were many more Java jobs here in Jacksonville (FL) then there were C# (.NET) jobs. I've noticed that there has been an increasing need for C# developers here, can't really say about the Java positions though. Recently I've been playing around with the blog search engine technorati.com, they have some really cool features. If you've never been there you should give it a shot. One of the features is to chart the number of blog entries over a period of time, based on keywords. To view the graph of MSBuild related blogs click on the link below

http://technorati.com/chart/%22MSBuild%22#taglink

I decided to compare the results of a search of "C#" and "Java", and I was really surprised to see the results. Below you can see the images that I saw. Note: These images are static and not updating.

From the technorati.com site it stated that there was 668,373 Java related posts and 17,100,527 C# posts! That is an incredible difference. There could be a few reasons for the difference:

  1. Java people arn't blogging as much
  2. Java people arn't registering with technorati.com as much as the C# folk
  3. There is something wrong with the technorati.com processor

Since this is a simple keyword search I think we can safely assume that (3) is not the culprit. I think its really a combinition of (1) and (2). Another aspect there are many Java related blog entries that don't actually have the word Java in it. But I'm sure the same goes for C#. Previously when I was doing mostly Java work, I thought one of the cool things about Java was the community effort. With the open-source side and all. I thought that C# (.NET) would lack this for sure. But now that I know better, I know that it certainly is not like that. I think the community effort for C# (.NET) is just as strong, if not stronger, then the Java side.

I'm not sure what the significance these number have, but it certainly does make be happy to be in the C# camp. :)

To get the live chart for C# visit: http://technorati.com/chart/%22C%23%22#taglink
To get the live chart for Java visti: http://technorati.com/chart/%22Java%22#taglink

Sayed Ibrahim Hashimi

Sunday, June 04, 2006 7:39:13 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Saturday, June 03, 2006
Frequently I hear people asking "How can we use Visual Studio 2005 for 1.1 development". If you are writing managed code (C#,J#, or VB.NET) then you are in luck. There is a toolkit that was released from the guys at Microsoft that can do this for you. Its called MSBee (MSBuild Everett Edition). You can download this toolkit at Codeplex. The creator of MSBee blogs about it at: http://blogs.msdn.com/clichten/default.aspx

Sayed Ibrahim Hashimi

Saturday, June 03, 2006 7:38:13 AM (GMT Daylight Time, UTC+01:00)  #    Comments [2]  | 
Wednesday, May 10, 2006

I received this email from a reader of my MSBuild MSDN Article, here is his question:

I have multiple projects in multiple directories on my machine, I need to compile all of those projects and copy the resulting DLLs into my application folder which id different from the projects. Can i do all this in one MSBuild file? Most the MSBuild file samples i find work only on a single project and assume that the build file is in the same directory as the .CS files.

To answer this question you can create an MSBuild project file which uses the MSBuild task to build other project files. By using this you can extract out the files that were built during the process and place them into an item to be copied to the location of your choice. To demonstrate this I have created the following directory structure:

├BuildSeveralExample

   ├───App1

      └───WindowsApplication1

          └───Properties

   ├───App2

      ├───ClassLibrary1

         └───Properties

      └───ConsoleApplication1

          └───Properties

   └───Deploy

 

The App1 and App2 folders each represent a product that is to be built. App1 contains a C# Win Forms project and App2 contains a C# Class Library and a C# Console App. The Deploy folder is the location that we would like to copy all of the output files to. In the BuildSeveralExample folder I have created a projects file, BuildAll.proj, which can be used to fulfill this need.

The first thing we need to do is to have a way to locate all of the project files, in my solution I created an Item and included all of the project files as such:
  <ItemGroup>

    <ProjectsToBuild Include="**\*proj" Exclude="$(MSBuildProjectFile)"/>

  </ItemGroup>

I include all project files in the current directory or any subdirectory, with the exception of the current project file. The MSBuildProjectFile is an MSBuild Reserved property, for a complete list see:  http://msdn2.microsoft.com/en-us/library/ms164309.aspx.

In your situation you may need to specify what files to include and exclude, to do this you can simply enter their locations separated with a semi-colon. Now that we have all the project files we’d like to build we need to actually build them. Do this by:

  <PropertyGroup>

    <Configuration>Release</Configuration>

  </PropertyGroup>

 

  <Target Name="CoreBuild">

    <MSBuild Projects ="@(ProjectsToBuild)"

             ContinueOnError ="false"

             Properties="Configuration=$(Configuration)">

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

    </MSBuild>

  </Target>

 

This is using the MSBuild Task to each of the project files. Also note the use of the Output element here, the MSBuild Task has an output of TargetOutputs which contains a list of files which were produced by the project files built. These files are placed in the OutputFiles item. Since we didn’t specify a target to be executed on each project file, the default target will be executed. This is how Visual Studio builds your project files as well. By using the MSBuild task we can also specify properties to be passed to the project file(s) in the Properties attribute. If you need to pass different properties for different project files, then you may have to call the MSBuild Task more than once.

Now all we have to do is copy these files over to another location. This is achieved like:

 

  <PropertyGroup>

    <DestFolder>Deploy\</DestFolder>

  </PropertyGroup>

 

  <Target Name="CopyFiles">

    <Copy SourceFiles="@(OutputFiles)"

          DestinationFiles="@(OutputFiles->'$(DestFolder)%(RecursiveDir)%(Filename)%(Extension)')" />

  </Target>

The location to copy the files is specified in the DestFolder property shown above.

As well as performing these actions, we’d also like to create a fresh build when this process is invoked. To do this we can either call the Rebuild target on the project files or call Clean before we build them. I prefer the second approach, but both are equally good. It also serves for a better demonstration for me to implement the second approach.

To clean the projects all we have to do is call the Clean target on each of the project files. I have written about this in more detail in my previous entry Clean many projects and Extending the Clean. As well as that we have to remove any files that have been created by a previous invocation of this process, have a look at my clean target:

  <!--

  This target can be used to clean all of the projects before you build them.

  It will also delete any dll & exe files located in the the Deploy folder. This could be accomplished

  much better than this, but this is quick and easy.

  -->

  <Target Name="CleanAll">

    <!-- Delete any files this process may have created from a previous execution -->

    <CreateItem Include="$(DestFolder)\**\*exe;$(DestFolder)\**\*dll">

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

    </CreateItem>

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

    <MSBuild Projects="@(ProjectsToBuild)" Targets="Clean" Properties="Configuration=$(Configuration);"/>

  </Target>

To tie it all together I’ve created a BuildAll target which is responsible for managing all of this. Here it is:

  <PropertyGroup>

    <BuildAllDependsOn>CleanAll;CoreBuild;CopyFiles</BuildAllDependsOn>

  </PropertyGroup>

  <Target Name="BuildAll" DependsOnTargets="$(BuildAllDependsOn)"/>

I took the Visual Studio approach and defined targets to perform individual steps, created one target to call them in the correct order by using a depends list derived from a property. This is a good approach because this depends on list can be modified outside of this project file to inject other required steps. For more detailed info on that see my other entry Extending the clean.

To see this in action you have to execute the BuildAll target on the BuildAll.proj file. I’ve made the project file available as well as my sample applications that go along with it. The BuildSeveralExample.zip file contains all the necessary files.

 

BuildAll.proj (Link fixed)

BuildSeveralExample.zip (25.17 KB)

 

Sayed Ibrahim Hashimi

 

Wednesday, May 10, 2006 6:39:05 AM (GMT Daylight Time, UTC+01:00)  #    Comments [10]  | 
Wednesday, May 03, 2006

This is the beginning of a new series of blogs that I plan on writing. Its called ‘MSBuild Fun’, the idea is that I’ll discuss some examples of how you might use MSBuild, outside of building applications. MSBuild is a tool that certainly is geared toward building applications, but is not limited to that. Knowledge of MSBuild can help you with some small things that come up quite often.

In this example we will consider a relatively simple task, copying a set of files from one location to another. Let’s say that you would like to locate all image files contained in a particular location and copy those files to another. There are a few options; you can use the Windows Explorer to search for all of the image files, and copy-paste them to the other location. There are many problems associated with this approach:

  • If there are files with the same name in a different folder then you will only be able to get 1 copy of it
  • If there is an error during the course of copying the files it will stop all together
  • This doesn’t preserve directory hierarchy
  • You can only include files to be copied, not exclude them

Another option is to use the xcopy command from the command prompt. This is a good method to employ to solve the problems associated wit the previous approach. Using this approach we can continue on error,  and preserve the directory hierarchy.  We can also exclude files from being copied. If you are to perform this task many times that you can simply insert the call into a .bat file to preserve the command.
The approach we will use here is to use MSBuild
Copy task to copy the files from the source to the destination. It solves all the problems associated with the first approach, and throws in a few bonuses. Those are:

  • Syntax for excluding files is very powerful, much better than that for xcopy
  • You can transform the name of the file as you copy it
  • You can skip files that already exist, and haven’t changed, in the destination location
  • In one MSBuild file you could house various different copies. With the batch file approach you are basically limited to one pre file.

From a performance perspective the xcopy and MSBuild approach are pretty much equivalent, so that is not a factor. Now lets have a look at this in action.

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

    <ItemGroup>

      <FilesToCopy Include="**\*.doc;**\*.txt"

                   Exclude="**\*trash*"/>

    </ItemGroup>

 

    <PropertyGroup>

      <Dest>Dest2</Dest>

    </PropertyGroup>

 

    <Target Name="CopyFiles">

      <Copy SourceFiles="@(FilesToCopy)"

            DestinationFiles="@(FilesToCopy->'$(Dest)\%(RecursiveDir)\%(Filename)%(Extension)')"

            ContinueOnError="true"/>

    </Target>

</Project>

This simple MSBuild project file will copy all Word Documents and text files that don’t contain trash in the filename to another location maintaining the directory hierarchy. As you can see here, in the FilesToCopy item declaration, I’m using the include attribute to include a list of files to be copied, and the exclude attribute to exclude a list of files from being copied. You can place complex wildcard expressions in these attributes to select only the files you are truly interested in.

 

 

Sayed Ibrahim Hashimi

Wednesday, May 03, 2006 6:02:18 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Friday, April 21, 2006

In the June 06 issue of the MSDN magazine you'll find an article that I wrote titled "Inside MSBuild: Compile Apps Your Way With Custom Tasks For The Microsoft Build Engine". As far as I know this will be the first article in print for the release version of MSBuild. Before I wrote this article I noticed a need, that was there was no one good resource for people to go to to learn all the key things they need to really get going with MSBuild. I think this article really does meet that need decently well. The only large topic that wasn't covered in the article is writing custom loggers. There are a few reasons for this; limited number of words, most users won't be interested in writing custom loggers and there is a lot of material about writing custom loggers available. If you are interested in writing a custom logger, I've got examples on this blog and here are a few other places you can visit:
http://msdn2.microsoft.com/en-us/library/ms171471.aspx
http://blogs.msdn.com/msbuild/archive/2005/10/10/479223.aspx

Along with these in my book I present a custom XML logger. If you read the article I welcom your feedback.

Sayed Ibrahim Hashimi

Friday, April 21, 2006 3:13:06 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Wednesday, April 05, 2006
If you are using Team Foundation Server as your SCM tool, I hope you're taking advantage of using the Team Build tool to create re-producable public builds for your projects. Very cool, and very useful, especially for those Enterprise applications. As you use Team Build you'll need to make changes to how your build process is executed. You can make the customizations in the TFSBuild.proj file. This is the file that will drive your team build. In this file there are many properties declared that are generated by the New Build Type Creation Wizard. These properties are contained in the first PropertyGroup element in that file. Some of these properties include; Description, DropLocation,BuildDirectoryPath,... which are used internally by Team Build to setup the build. Of course Team Build uses MSBuild to actually execute the build, but some things need to happen before/after MSBuild gets involved. For instance creating the Build report, which require those values. In the ideal world you'd expect for those values to be gathered using the MSBuild Object Model, but this is not the case. Team Build is extracting those values as they are declared.
There has been a few posts about this particular problem on the MSBuild MSDN Forum. Those posts are at:
Post: Property Evaluation Order (beta 3)
Properties in properties
In the post Property Evaluation Order (beta 3) I provide a workaround for most situations, but its not always perfect. Here it is for you to see:

It seems like Team build is not using the msbuild object model to get those evaluated properties, but instead are simply extracting the text value from those property declarations.

I tried to overwirte it in different places in the project file as well, with no luck. It always took the first declaration. I'm guessing that the property is not able to be overwritten because it is passed in as a command line parameter to msbuild.exe by team build. But there is a workaround that solves some of the issues. In your TFSBuild.proj file, modify the DropLocation to one that doesn't use any properties and insert the following towards the bottom:

  <PropertyGroup>

    <RootProjectFolder>ProjectX</RootProjectFolder>

 

    <DropBuildDependsOn>

      FixDropLocation;

      $(DropBuildDependsOn);

    </DropBuildDependsOn>

  </PropertyGroup>

 

  <Target Name="FixDropLocation">

    <CreateProperty Value="$(DropLocation)\$(RootProjectFolder)">

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

    </CreateProperty>

    <Message Text="Drop loc: $(DropLocation)" Importance="high"/>

  </Target>

This prepends the FixDropLocation to the DropBuildDependsOn list of targets, so it will be executed before the DropBuild target. This cause your project drop files to be dropped in the location tat you want. This is able to overwrite the DropLocation property becuase we are using the CreateProperty task to overwrite this instead of the normal property declaration.

This is not however a perfect solution, because your build log will always be located in the DropLocation that was originally declared (the one w/o the property embedded within it). And in the Team build type history the drop location will also have this location as well. But it does place the files where you want them :)

Unfortunately it looks like the DropLocation that is given to TFS is happening outside the scope of MSBuild so I'm not sure you have a lot of options. You can't even pass this as a command line parameter to the tfsbuild.exe utility either. Let me know if this  is not clear.


Sayed Ibrahim Hashimi


Wednesday, April 05, 2006 8:09:52 AM (GMT Daylight Time, UTC+01:00)  #    Comments [2]  | 
Friday, March 31, 2006
In case you haven't already heard a service pack will be released for both VS 2003 & VS 2005 this year! SP1 for VS 2003 is scheduled to ship during the second quarter, and SP1 for VS 2005 in the third. You can read more about this at: http://msdn.microsoft.com/vstudio/support/servicing/. I think its kind of interesting that VS2003 didn't have a service pack for 3 years, yet 2005 is having one not even 1 year later. Perhaps because of all the new features that were shipped with this version, however VS 2003 shipped with many new features as well. Since MSBuild is technically a part of the .NET Redistributable, I'm not sure if the service packs will impact anything related to MSBuild. If I find out more I'll be sure to post more.

Sayed Ibrahim Hashimi

Friday, March 31, 2006 7:07:45 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Thursday, March 30, 2006

Previoiusly I wrote about the Microsoft SDC Tasks library, there is another great resource for MSBuild tasks. This is the MSBuild Tigris Tasks project. This is an open source project, so you can submit your tasks to this library as well. Tigris.org is known for their Source Control Management (SCM) tool, Subversion. I've used Subversion previously and was pleased with it, but my favorite is Team Foundation Server :) If you're using Subversion as your source control you'll be glad to find many task for accessing your repository. From the project's site here is a list of the MSBuild tasks that are either completed or currently under development.

Task

Description

AppPoolController*

Allows control for an app pool on a local or remote machine with IIS installed.

AppPoolCreate*

Creates a new application pool on a local or remote machine.

AppPoolDelete*

Deletes an existing application pool on a local or remote machine.

AssemblyInfo

Generates an AssemblyInfo file using the attributes given.

Attrib*

Changes the attributes of files and/or directories

FileUpdate*

Replace text in file(s) using a Regular Expression.

FtpUpload

Uploads a file using File Transfer Protocol (FTP).

FxCop*

Uses FxCop to analyze managed code.

Mail

Sends an email message.

Math.Add

Add numbers.

Math.Divide

Divide numbers.

Math.Multiple

Multiple numbers.

Math.Subtract

Subtract numbers.

Move*

Moves files on the filesystem to a new location.

NDoc

Runs NDoc to create documentation.

NUnit

Runs tests using the NUnit.

RegistryRead

Reads a value from the Registry.

RegistryWrite

Writes a value to the Registry.

Script*

Executes code contained within the task.

ServiceController*

Task that can control a Windows service.

ServiceQuery*

Task that can determine the status of a service.

Sleep*

A task for sleeping for a specified period of time.

SqlExecute*

Executes a SQL command

SvnCheckout

Checkout files from Subversion

SvnClient

Subversion Client

SvnCommit

Commit files to Subversion

SvnExport

Export files from Subversion

SvnInfo*

Get Subversion information for a file or directory.

SvnUpdate

Update files from Subversion

SvnVersion

Get Subversion revision number of a local copy

TaskSchema*

Generates a XSD schema of the MSBuild tasks in an assembly.

Time*

Gets the current date and time.

Unzip

Unzip a file to a target directory.

Version

Increments a four-part version number stored in a text file

VssAdd*

Adds files to a Visual SourceSafe database.

VssCheckin*

Checks in files to a Visual SourceSafe database.

VssCheckout*

Checks out files from a Visual SourceSafe database.

VssClean*

Removes VSS binding information and status files from a solution tree.

VssDiff*

Generates a diff between two versions of an item in a Visual SourceSafe database.

VssGet*

Gets the latest version of a file or project from a Visual SourceSafe database.

VssHistory*

Generates an XML file containing the history of an item in a VSS database.

VssLabel*

Labels an item in a Visual SourceSafe database.

VssUndoCheckout*

Cancels a checkout of an item from a Visual SourceSafe database.

WebDirectoryCreate*

Creates a new web directory on a local or remote machine.

WebDirectoryDelete*

Deletes a web directory on a local or remote machine

WebDownload*

Downloads a resource with the specified URI to a local file.

XmlRead

Reads a value from a XML document using a XPath.

XmlWrite

Updates a XML document using a XPath.

Xslt*

Merge and transform a set of xml files.

Zip

Create a zip file with the files specified.

 * Items not complete or have not been released.

Sayed Ibrahim Hashimi

Thursday, March 30, 2006 4:21:48 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Saturday, March 18, 2006

One of the Microsoft teams based in the UK has created a library of MSBuild tasks that is available for you to download at: http://www.codeplex.com/sdctasks You can download binary versions as well as all the source code for these tasks. There is almost 170 tasks that they have made available! Below is a list of them all if you are interested in what they are.

 

ActiveDirectory.Group.AddUser SourceDepot.ReverseIntegrate
ActiveDirectory.Group.Create SourceDepot.Sync
ActiveDirectory.User.Create SourceSafe.Get
ActiveDirectory.User.GrantPrivilege SourceSafe.LabelGet
AssignCulture SourceSafe.LabelLatest
BizTalk2002.Configure SourceSafe.Changes
Cab.AddFile Sql.DisconnectUsers
Cab.Create Sql.Execute
Cab.ExtractFile Sql.Access.Revoke
Certificates.AddCertificate Sql.Role.AddUser
CheckComponentInstalled Sql.Access.Grant
CheckProductInstalled ShortenPath"/>
CodeCoverage.AddAssembly StringToItemList"/>
CodeCoverage.Create StringComparison"/>
CodeCoverage.MergeReports StringReplace"/>
CodeCoverage.Start Summary.AddToReport
CodeCoverage.Stop Time.Get
CompileTestSummary Time.Diff
ComponentServices.Application.AddComponent Time.Report
ComponentServices.Application.Delete Tools.DevEnv
ComponentServices.Application.RemoveComponent Tools.FxCop
ComponentServices.Application.Shutdown Tools.Installshield
ComponentServices.Application.Update Tools.MsTest"/>
ComponentServices.Component.Update Tools.Ndoc
ConsoleReadLine Tools.Nunit
CreateGuid Tools.PreSharp
Email Tools.PsExec
EventSource.Create Tools.StrongName.AddSkipVerification
EventSource.Log Tools.StrongName.ReSign
File.Delete Tools.StyleCop
File.GetFiles TrimJavascript
File.RegEx VersionNumber.CreateSourceFiles
Folder.Copy VersionNumber.Load
Folder.GetInfo VersionNumber.SplitBuildNumber
Folder.Share.Connect VersionNumber.Update
Folder.Share.Create VersionNumber.VSSUpdate
Folder.Share.Delete VirtualServer.VirtualMachine.OS.CheckHeartBeat
Folder.Share.Disconnect VirtualServer.VirtualMachine.OS.Shutdown
Folder.Share.Exists VirtualServer.VirtualMachine.Start
GetInstalledComponents VirtualServer.VirtualMachine.Stop
GetInstalledProducts Web.AppPool.Create
GetMetadataValueFromList Web.AppPool.Delete
GlobalAssemblyCache.AddAssembly Web.FtpSite.Create
GlobalAssemblyCache.RemoveAssembly Web.FtpSite.CreateVirtualDirectory
Help.Compile Web.FtpSite.Delete
Help.CreateProject Web.FtpSite.DeleteVirtualDirectory
Help.Decompile Web.FtpSite.Start
Help.DocumentExceptions Web.FtpSite.Stop
Help.InsertAfter Web.ServiceExtension.AddFile
Help.InsertBefore Web.ServiceExtension.DeleteFile
Help.InsertParent Web.WebSite.AddBinding
LogicalComparison Web.WebSite.AddFilter
MessageQueue.Create Web.WebSite.AddHttpCustomHeader
MessageQueue.Delete Web.WebSite.AddMimeType
MessageQueue.SetPermissions Web.WebSite.Continue
Msi.EmbedInstallProperties Web.WebSite.Create
Msi.EmbedUninstallProperties Web.WebSite.CreateVirtualDirectory
Msi.GetProperty Web.WebSite.Delete
Msi.Install Web.WebSite.Modify
Msi.Repair Web.WebSite.DeleteFilter
Msi.Uninstall Web.WebSite.DeleteVirtualDirectory
PerformanceCounters.Add Web.WebSite.FilterExists
PerformanceCounters.Remove Web.WebSite.Pause
Registry.CreateKey Web.WebSite.Start
Registry.DeleteKey Web.WebSite.Stop
Registry.DeleteKeyTree Web.WebSite.UnloadVirtualDirectory
Registry.Get Web.WebSite.UpdateHttpErrorSetting
Registry.Set Web.WebSite.UpdateLogSettings
Security.AddAcl Web.WebSite.UpdateServerCertificate
Security.RemoveAcl Web.Smtp.Create
ServiceProcess.Exists Web.Smtp.Start
ServiceProcess.Start Web.Smtp.Stop
ServiceProcess.Stop Wix.CompileMsi
ServiceProcess.UpdateIdentity Wix.LinkMsi
Sleep Wix.Fragment
SourceDepot.Changes Xml.CanonicalizeFile
SourceDepot.ChangesInInterval Xml.ModifyFile
SourceDepot.CreateBranch Xml.GetValue  
SourceDepot.CreateClientFromTemplate Xml.XslTransform
SourceDepot.DeleteBranch Zip.AddFile
SourceDepot.GetChangelistFromDateTime Zip.Create
SourceDepot.GetChangelistFromLabel Zip.ExtractFile
SourceDepot.GetUsersForChangelists MergeByOrder
SourceDepot.Integrate MergeByRef
SourceDepot.LabelSync  

 

I’m sure that some of these tasks you’ll find completely useless. For example I presume that most of you will not be needing the SourceDepot.* tasks. But there are several very cool tasks that are made available here. Tasks for code coverage, tasks for invoking common tools and other things. I think its great that they have made this available and hope to see some of these tasks integrated into the next realease of MSBuild.

Sayed Ibrahim Hashimi


(Edit: Updated SDC tasks link)

Saturday, March 18, 2006 7:23:16 AM (GMT Standard Time, UTC+00:00)  #    Comments [1]  | 
Monday, March 06, 2006
A friend of mine was installing SQL Server 2005 & Visual Studio 2005 on a new machine he received. After installing SQL Server 2005, he tried to install VS 2005 and received an error stating that he couldn't install VS 2005 becase beta versions were present on his machine. I had this problem earlier when I needed to upgrade my machine from RC to RTM. The problem was that he accidently installed SQL Server 2005 CTP. I told him to run the him to download and run the Visual Studio Beta Removal tool. When I had this problem it solved it right up for me. Unfortunately, I think Windows got confused with all the installing/un-installing that was going on. He got his machine to the point where SQL Server wouldn't show up in the add/remove programs list, he couldn't run the un-installer for it because it wasn't present, and he couldn't re-install the same CTP version to uninstall it correctly. And of course he couldn't install VS 2005. It seemed like he was in a real tough spot, we searched around for more information about how to get around this situation. I found a tool, the Windows Installer Clean Up tool, that can be used to removal applications and installer files from failed apps that don't show up in the add/remove programs list. When he ran this tool he was able to see and remove the SQL related components. Following this he was able to install the correct versions of SQL Server 2005 & Visual Studio 2005.

Sayed Ibrahim Hashimi

Monday, March 06, 2006 4:57:24 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Tuesday, February 28, 2006

When using MSBuild you can import external declarations into your project by using the Import element. This is very useful for distributing your shared targets across your organization, and is a way to segregate where things are located. When MSBuild encounters this tag, a few things happen. First the current directory is changed to that of the file that is being imported. This is done so assembly locations specified by the Using Task and other imports are resolved correctly. What you may be surprised to find out is that if the importing project file declares any items, those item declarations are always relative to the top project file location. To clarify this if you have the following directory structure:

C:.

├───one

   └───Two

           YourProject.proj

├───Shared

      CurrentDirectory.dll

      SharedTargets.targets

  

   └───Another

           SharedTargets_2.targets

└───utils

        CommandLine.txt

In this scenario YourProject.proj is the top project file, it imports both SharedTargets.targets and SharedTargets_2.targets files. If the SharedTargets.targets had an item declaration of

<ItemGroup>
    <Test Include="test.txt"/>
</ItemGroup>

When imported by YourProject.proj this item declaration would actually resolve to C:\one\two\test.txt instead of the expected C:\Shared\test.txt value. In 95% of the time this is not an issue at all. But for that other 5% how can we accomplish this?

Well, there is no magic reserved MSBuild property for this. So we're gonna have to do some work here. To accomplish this we'll have to create a custom MSBuild task. If you've never created an MSBuild task you might be surprised how easy it is!

I created a new project named DirectoryTask which will house this task. Following this I added a reference to Microsoft.Build.Framework and Microsoft.Build.Utilities assemblies. Following this I wrote the task. It is shown below

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Build.Framework;

using Microsoft.Build.Utilities;

namespace CurrentDirectory

{

    /// <summary>

    /// Task that will return the folder that contains the current project.

    /// Inheriting from AppDomainIsolatedTask causes this task to execute in its own

    /// App domain. Not necessary here, only for demonstration.

    ///

    /// Sayed Ibrahim Hashimi

    /// www.sedodream.com

    /// </summary>

    public class CurrentDir : AppDomainIsolatedTask

    {

        private ITaskItem currentDir;

        [Output]

        public ITaskItem CurrentDirectory

        {

            get

            {

                return this.currentDir;

            }

        }

        public override bool Execute()

        {

            System.IO.FileInfo projFile = new System.IO.FileInfo(base.BuildEngine.ProjectFileOfTaskNode);

            this.currentDir = new TaskItem(projFile.Directory.FullName);

           

            return true;

        }

    }

}

As you can see this is a pretty simple task. When Execute is called the directory is gathered from the project file that contains the task invocation. The name of the assembly that I built this into is CurrentDirectory.dll.

Now to see this in action I will use the same directory structure shown above. The contents of the SharedTargets.targets file is:

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

 

  <UsingTask AssemblyFile="CurrentDirectory.dll" TaskName="CurrentDir"/>

 

  <Target Name="SharedTarget">

    <CurrentDir>

      <Output ItemName="CurrentDir" TaskParameter="CurrentDirectory" />

    </CurrentDir>

 

 

    <Message Text="Inside the SharedTargets.targets" Importance="high"/>

    <Message Text="Location: @(CurrentDir->'%(Fullpath)')"/>

  </Target>

 

</Project>

This uses the CurrentDirectory task to determine what the current directory is. The SharedTargets_2.targets file is very similar and is:

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

 

  <Target Name="SharedTarget2">

 

    <CurrentDir>

      <Output ItemName="CurrentDir2" TaskParameter="CurrentDirectory" />

    </CurrentDir>

 

 

    <Message Text="Inside the SharedTargets_2.targets" Importance="high"/>

    <Message Text="Location: @(CurrentDir2->'%(Fullpath)')"/>

  </Target>

 

</Project>

 

Now let’s take a look at the very simple YourProject.Proj file.

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

  <Import Project="..\..\Shared\SharedTargets.targets"/>

  <Import Project="..\..\Shared\Another\SharedTargets_2.targets"/>

 

  <PropertyGroup>

    <PrintDependsOn>

      SharedTarget;

      SharedTarget2

    </PrintDependsOn>

  </PropertyGroup>

 

  <Target Name="Print" DependsOnTargets="$(PrintDependsOn)">

  </Target>

</Project>

 

This file simply imports the two other files and defines the print target. Now we want to invoke the Print target on this project file. To do this open the Visual Studio2005 command prompt and go the directory containing the YourProjec.proj file. Then execute the following:

>msbuild.exe YourProject.proj /t:Print

 

The results of this invocation are:

__________________________________________________

Project "C:\Data\Community\msbuild\MSBuildDirectoryExample\one\Two\YourProject.proj" (default targets):

 

Target SharedTarget:

    Inside the SharedTargets.targets

    Location: C:\Data\Community\msbuild\MSBuildDirectoryExample\Shared

Target SharedTarget2:

    Inside the SharedTargets_2.targets

    Location: C:\Data\Community\msbuild\MSBuildDirectoryExample\Shared\Another

 As you can see the correct location was resolved for both of these items. Previously if you had command line utilities in source control and .targets files that would manage invoking those tools, it was error prone. This is because the .targets file wouldn’t be able to resolve the location of the command line util, even if in the same directory. The solution to this problem it to rely on the developer to set a property (or environment variable) which states where this tool can be located; or some other similar means. With this task we no longer have to rely on such a solution. The .targets file is able to resolve the location of the command line utility. 

I have bundled all related files into a zip file which you can download below.

MSBuildDirectoryExample.zip (you may have to right-click->Save As)

 http://www.sedodream.com/content/binary/MSBuildDirectoryExample.zip

Sayed Ibrahim Hashimi

Tuesday, February 28, 2006 6:23:33 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Wednesday, February 08, 2006

The other day I received an intersting comment on my previous blog enty. The comment was asking how to send the details of a build error via email to a recipient(s). When I was starting with MSBuild I was also wondering how to do this, so I would like to address this topic now. This is a very useful scenario to implement if you builds are automated. If you are lucky enough to be using Team Foundation Server then you job is a lot easier because this is taken care of for you. But if you are like most .NET developers odd are that you aren't. So we need a good way to do this.
There is an SmtpTask that you can use to send an email through an MSBuild task. At first thought you might think that this will work, but you'll be saddended to find out that it doesn't. This is because the only way to trigger this task to be executed is from the project file. The only way to achieve that is by using the MSBuild OnError element to state what to do in the case of an error occuring during the course of a target executing. When you build a VS project you are most likely to execute the Build (or Rebuild which calls Build) target. This target actually perfroms no work itself, it is delegated to other targets. So having and OnError element in that target is of no use. You'd have to place one in each of the other targets that get executed from its dependencies. This means sticking that element in a lot of different places, which is no fun. So there is no way to say "If an error occurs at any time during my build execute this target". But if we change that phrase to "If an error occurs anywhere during my build then do ...", we can achive that. We do that by writing and using a custom logger!

Writing a custom logger may sound to be daunting at first, but rest assured it is not! Once you write one logger you can write many, and I'll show you how to write your first one right here! Let's cover some basics first. MSBuild ships with 2 loggers, the ConsoleLogger and the FileLogger. These classes are a part of the Microsoft.Build.BuildEngine namespace, and you can extend them. So this means that we can use the already written functionality and extend it to fit our needs. This is exactly what we are going to do in this example. We will extend the FileLogger to email the log file if an error has occured. Let's get started on this.

First create a new C# (or other managed lang of you choice) Class Library project. I named this project EmailLogger. You'll first want to add references to the following libraries.

  1. Microsoft.Build.BuildEngine
  2. Microsoft.Build.Utilities
  3. Microsoft.Build.Framework

At this point we are ready to start coding. We want to make the EmailLogger class a sub-class of the FileLogger to get its behavior. Here are some logger basics now. The logging mechanism in MSBuild is an event based system. Events include such things as ProjectStarted, BuildFinished, ErrorRaised and others. We're obviously very interested in the last one. The method

     public void Initialize(Microsoft.Build.Framework.IEventSource eventSource)

will be called before MSBuild starts to build your project. In this method is where you tell MSBuild what events you are interested in handling. In our case we will be handling only two events. Those events are; ProjectStarted and ErrorRaised. We handle the ProjectStarted because we need to grab all of the properties defined in the project file and put them somewhere. And we handle ErrorRaised to make a flag that an error has occured. Along with Initilize() we are promised that

    public override void Shutdown()

will be called after the build has finished and its time for the logger to say goodnight. This is where we will actually send the email. Let's have a look at the Initalize method shown below.

        /// <summary>

        /// This is guarenteed to be called by MSBuild before any build steps occur

        /// </summary>

        /// <param name="eventSource"></param>

        public override void Initialize(Microsoft.Build.Framework.IEventSource eventSource)

        {

            //This call will set the correct values from the parameters

            base.Initialize(eventSource);

            this.SetParameters(base.Parameters);

            //add the logger delegates here

            eventSource.ErrorRaised += new BuildErrorEventHandler(this.BuildError);

            eventSource.ProjectStarted += new ProjectStartedEventHandler(this.ProjectStarted);

        }

In this method we aren't doing anything fancy, we call SetParameters because we need to get the file location where the log is going to be saved to. When you are writing loggers remember it is upto you to parse and validate any parameters that are passed to it. If you are using the Logger class as your base class then the Verbosity is handled for you. You should chose this over implementing the ILogger interface directly. Anywayz back to the topic at hand, besides that we are registering our delegates to the appropriate events. After this the build starts, our ProjectStarted delegate gets invoked. In this method we are storing all the properties defined in the build file to a dictionary so we know how to send the email.

This logger works like this, it will gather the settings for sending the email from the project's properties. These can either be defined in the project file itself, or passed in as command line arguments to msbuild.exe using the /p switch. The properties that it will look for are named:

        EmailLogger_SmtpHost

        EmailLogger_From

        EmailLogger_To

        EmalLogger_CC

        EmailLogger_SmtpPort

        EmailLogger_TimeOut

        EmailLogger_SendOnSuccess

The alternative to this approach which would actually be more correct is to send all of these in a parameters to the logger. But that would be really annoying to have to type all of these values in at the command line each time you want to perform a build. Even putting these into an msbuild.rsp file is not as convienent. So I figured to put them into the project is not such a bad idea.

 

The ProjectStarted handler is shown below.

        /// <summary>

        /// The event that is raised when the project is started.

        /// This will examine all of the properties defined in the project file

        /// and store them in the <code>properties</code> dictionary

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        void ProjectStarted(object sender, ProjectStartedEventArgs e)

        {

            if (e.Properties != null)

            {

                foreach (DictionaryEntry entry in e.Properties)

                {

                    this.properties.Add(entry.Key.ToString(), entry.Value.ToString());

                }

            }

        }

The BuildError method is very simple, it is shown below.

        /// <summary>

        /// This is called in the event of an error.

        /// In this example we just mark that there was an error and the email is sent

        /// at the end of the build in the <code>Shutdown</code> method.

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        void BuildError(object sender, BuildErrorEventArgs e)

        {

            this.errorOccurred = true;

        }

Besides this we are pretty much just left with the Shutdown() method. It is shown below.

        /// <summary>

        /// This is called before msbuild exits.

        /// This is the method that will actually send the email out if there was an error

        /// or if send on success was specified as a property to 'true'

        /// </summary>

        public override void Shutdown()

        {

            //this should close down the log file

            base.Shutdown();

 

            //get the values for the email from the properties           

 

            if (this.properties == null || this.properties.Count <= 0)

            {

                return;

            }

 

            Emailer emailer = new Emailer();

 

            emailer.SmtpHost = this.GetProperty(EmailLoggerStrings.SmtpHostString);

            emailer.From = this.GetProperty(EmailLoggerStrings.FromString);

            emailer.To = this.GetProperty(EmailLoggerStrings.ToStr);

            emailer.Cc = this.GetProperty(EmailLoggerStrings.CcString);

            emailer.SetSmtpPort(this.GetProperty(EmailLoggerStrings.SmtpPortString));

            emailer.SetTimeout(this.GetProperty(EmailLoggerStrings.TimeoutString));

 

            bool emailOnSuccess = false;

            string emailOnSuccessStr = this.properties[EmailLoggerStrings.EmailOnSuccessString];

            if (!string.IsNullOrEmpty(emailOnSuccessStr))

            {

                emailOnSuccess = bool.Parse(emailOnSuccessStr);

            }

            if ( errorOccurred || emailOnSuccess )

            {

                //we need to send to email the log file to recipient here

                emailer.Subject = "Build log " + DateTime.Now.ToShortDateString();

                emailer.Body = "Log file attached";

                emailer.Attachment = this.logFile;

 

                emailer.SendEmail();

            }

        }

In this method we let the base (FileLogger) shutdown first. This will cause the logfile to be written out to disk, then we create the email and send if if there was an error, or if it was stated to send even on success. This is pretty much it from an MSBuild perspective. We have the Emailer class, but there is no MSBuild stuff there, just stuff to send the email.

Now we need to know how to use this logger. You are going to want to place the dll, in my case it's named EmailLogger.dll in a location that is accessible by other projects during the build process. In my case I have a SharedLoggers directory that contains these guys. So I copied it there. Then at the command line when we perform a build we need to tell MSBuild to use this logger. Do that by:

>msbuild.exe

PROJECT_FILE_NAME /l:EmailLogger,(PATH_TO_LOGGER)\EmailLogger.dll;logfile=test.log;verbosity=detailed

And you'll have to specify those above properties in your project file that you are building like so:

  <PropertyGroup>

    <EmailLogger_SmtpHost>fill this in</EmailLogger_SmtpHost>

    <EmailLogger_From>sayed.hashimi@gmail.com</EmailLogger_From>

    <EmailLogger_To> sayed.hashimi@gmail.com </EmailLogger_To>

    <EmalLogger_CC></EmalLogger_CC>

    <EmailLogger_SendOnSuccess>true</EmailLogger_SendOnSuccess>

  </PropertyGroup>

Then start your build. If you are building a solution, then these properties have to be defined in a project file that is being built, or at the command line. If you have multiple definitions this could get confusing from this implementation. Maybe more on that at a later date, too late now. That should be about all you need to know. The files are made available here for your convienence. Oh yeah, this hasn't been throughly tested, I just put this together so use it at your own risk. If you have modifications I'd appreciate you letting me know so I can fix my version as well.

 

EmailLoggerProjectFiles.zip (23.26 KB)   You may have to right-click Save as to get this file.

EmailLogger.dll (20 KB)  You may have to right-click Save as to get this file


Sayed Ibrahim Hashimi

 

Wednesday, February 08, 2006 7:22:28 AM (GMT Standard Time, UTC+00:00)  #    Comments [2]  | 
Tuesday, January 31, 2006
I've seen this question asked a few times, here and there. I'm sure the answer is in a few other places as well as here, but I don't have the link. Anywayz the topic is how to create a file that contains the biuld log contents. This is a pretty simple question because MSBuild ships with a file logger. So all we have to do is to tell MSBuild to use it and pass it the options that we want to use.
First you'll need to open the Visual Studio 2005 Command Prompt and navigate to the directory that contains your project. Lets call this project, DreamcatcherUI.proj. To specify to use the file logger we will have to use the /l switch when we invoke msbuild on it. This is the logger switch, the long version of this is /logger.
The syntax for using this switch is:
     /l:<logger class>,<Assembly>[;Parameters 4 logger]
In order to use the MSBuild file logger we can use the following syntax
    /l:FileLogger,Microsoft.Build.Engine;verbosity=detailed;logfile=mylog.log
So the msbuild command to build the DreamcatcherUI.proj would be:
>msbuild.exe /t:Build /l:FileLogger,Microsoft.Build.Engine;verbosity=detailed;logfile=mylog.log
The verbosity is the level of detail that you want in your log file, there are 5 settings: quiet[q]; minimal[m];normal[n];detailed[d]; and diagnostic[diag]. The name in brackets is the short name, you can use these in place of the full name if desired. The logfile parameter is the location that you want the file saved to. If not specified then a file msbuild.log will be created with the log in the current directory.
It is important to mention that the parameters section of the logger is upto the logger implementation to parse and make sense of. For the FileLogger you have 2 other parameters that you can pass in those append and encoding. If append=true then the file will be appended to and overwriten if otherwise. You can use the encoding to specify a certain encoding for the log file that is to be created.

Sayed Ibrahim Hashimi

Tuesday, January 31, 2006 5:16:12 AM (GMT Standard Time, UTC+00:00)  #    Comments [3]  | 
Thursday, January 26, 2006

Ok,

I thought this was pretty straight forward but I had two people ask me about this recently so I thought I'd post a blog discussing it. There are many occasions where you'd like to create a Form that and use it as the base for inheritance by other forms. At the same time you'd like to still be able to use the Visual Studio Designer to design your Forms that inherit from that. In order to achieve this you'll have to create what's called an Inherited Form. You can use this form many things, including Visual Inheritance.

For this entry I have created a very simple example, you can download the files at the bottom of this entry if you are interested. To re-create the sample follow along. I created a new C# Windows Form Application. Then renamed the Form1.cs file to MyBaseForm.cs. This is the class that I would like to use as the base class for other forms. At this point I added a docked label to the form and add some specific stylistic changes to it. The resultant form is shown below.

As you can see from the image above there's not much to this form, but this is just a sample :-).
Now I'd like to provide an easy way to customize the description in my new label. To do this I'll create a new property and give it some attributes to be used by the Designer. That property is shown below.

[Description("Description")]
[Category("Form Base")]
public string Description
{
    get
    {
        return this.labelDescription.Text;
    }
    set
    {
        this.labelDescription.Text = value;
    }
}

This is just a normal property with the exception that It has the System.ComponentModel.Description attribute as well as the System.ComponentModel.Category attribute. The Description the name of this attribute that will be shown in the properties grid of the designer, and the category is the category that it will be in. If the Category is not present then it will default to the Misc category. Let's see how this makes a difference. Keep in mind that this is the simplest type of integration with the designer that you can achieve. The possibilities are endless.

At this point we should build the project. Before we can create the Inherited Form we must build the project, otherwise you'll get an error stating that no assemblies are present which contain a Form. So after the build has completed we want to add a new item to the project. One method for this is Right Click on the Project->Add->New Item. At this point you'll be presented a dialog to select the template type that you want to create. From this list select "Inherited Form". Have a look at the image below.

I'll call the new form MyInheritedForm.cs. After that you're given a dialog to select which Form class it should inherit from, then your form is created. For clarity about the designer integration here is a view of the properties grid from the MyInheritedForm designer view.

As you can see we have the Description property available in the Form Base category, just what we wanted! Now if the developer changes the value on this property then the text in the description label will be updated. Like I said earlier the designer integration is very capable, and this is the simplest form. Using these Inherited Forms for simple things like Visual Inheritance is a very clean way of mainting visual changes in a single form. But don't view this as the only use of Inherited Forms.

InheritedFormEx.zip (19.8 KB)

 

Sayed Ibrahim Hashimi

Thursday, January 26, 2006 5:26:38 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Tuesday, January 17, 2006

Over the past few months I have noticed a need by many people to place some specific steps into the process that is used when MSBuild is invoked on solution files. When people research this topic they quickly discover that the Visual Studio Solution file is NOT in the MSBuild format. MSBuild is able to consume these files, but you are not able to actually extend the process used for the solution as a whole.
Recently there was an entry at the MSDN MSBuild Forum related to this issue. So I figured I'd put my two cents into the discussion as well.

For some background, when MSBuild needs to build a solution file, it is converted in memory to an MSBuild project file. If you have the envrionment variable msbuildemitsolution set to the value of 1. Then this file will be written out to a file. The file name will be SOLUTION_NAME.sln.proj.

Ok, previously there was another entry on the MSDN Forum about how to set an Envrionment variable using MSBuild. To let you know where I'm going with this, I'd like to have MSBuild be responsible for creating this file and for me to add steps before and after the building of my solution. The task to create the envrionment variable is shown below, this was written by Keith Hill and is at the previous link

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Sedodream.MSBuild
{
    /// <summary>
    /// Taken from http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=73225&SiteID=1
    /// Written by Keith Hill
    /// </summary>
    public class SetEnvVar : Task
    {
        private string _variable;
        private string _value;
        [Required]
        public string Variable
        {
            get { return _variable; }
            set { _variable = value; }
        }
        [Required]
        public string Value
        {
            get { return _value; }
            set { _value = value; }
        }
        public override bool Execute()
        {
            Environment.SetEnvironmentVariable(_variable, _value);
            return true;
        }
    }
}

You can download this file at the bottom of this entry (SetEnvVar.cs).

So we need to take this file and create an assembly from it. Make sure to add Microsoft.Build.Framework and Microsoft.Build.Utilities to your project's references. Oh yeah you could skip this part of the process if you just make sure to set that envrionment variable, but that's no fun.

After you create this assembly place in a known location. I called this assembly Sedodream.MSBuild.dll and placed it in a folder named tasks one folder above the solution I was trying to build.

Now that we are done with that we need to create the msbuild file that will do the rest of the work for us.

The whole file is shown below and can be downloaded at the end of this blog (DreamCatcher_SlnBuild.proj)

<!--============================

MSBuild file which can be used to inject steps into the building of solution files

===============================-->

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

<PropertyGroup>

<!-- Location that holds all needed assemblies for the shared tasks -->

<SharedTasksDir>..\tasks\</SharedTasksDir>

<!-- Assembly that contains the custom tasks -->

<EnvAssemblyFilename>Sedodream.MSBuild.dll</EnvAssemblyFilename>

<!-- Where is the solution file(s) located -->

<BuildSolutionDir>.\</BuildSolutionDir>

</PropertyGroup>

<ItemGroup>

<!-- Item for solutions file(s) -->

<SlnFiles Include="$(BuildSolutionDir)*.sln"/>

</ItemGroup>

<!--Let MSBuild know where to find our tasks-->

<UsingTask AssemblyFile="$(SharedTasksDir)$(EnvAssemblyFilename)" TaskName="SetEnvVar"/>

<!--

Set the envrionment variable msbuildemit solution to 1 to create the msbuild

project file that represents the solution.

-->

<Target Name="SetMSBuildEmit">

<SetEnvVar Variable="msbuildemitsolution" Value="1"/>

</Target>

<Target Name="BuildSolution" DependsOnTargets="SetMSBuildEmit">

<!-- Have MSBuild emit the solution -->

<MSBuild Projects="@(SlnFiles)" Targets="Build"/>

<!-- Create a new item to pick up the newly generated file -->

<CreateItem Include="*.sln.proj">

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

</CreateItem>

<!--

Call MSBuild for each solution file. Pass the property:

theSolution=SOLUTION_FILE_TO_BUILD.

In the DoBuildSolution we use this to determine which file to build.

-->

<MSBuild Projects="$(MSBuildProjectFile)"

Targets="DoBuildSolution"

Properties="@(SolutionMSBuildFiles->'theSolution=%(Filename)%(Extension)')"/>

</Target>

<!--==========================

Content used when this file is invoked above

==============================-->

<!--

Define dependencies for the solution build. This can be extended like other XXXDependsOn Properties

-->

<PropertyGroup>

<DoBuildSolutionDependsOn>

BeforeDoBuildSolution;

CoreBuildSolution;

AfterDoBuildSolution

</DoBuildSolutionDependsOn>

</PropertyGroup>

<Target Name="DoBuildSolution" DependsOnTargets="$(DoBuildSolutionDependsOn)" />

<Target Name="BeforeDoBuildSolution">

<Message Text="*****Before building your solution*****" Importance="high"/>

</Target>

<!--

Here is where we actually build the solution passed to us.

-->

<Target Name="CoreBuildSolution">

<MSBuild Projects="$(theSolution)" Targets="Build"/>

</Target>

<Target Name="AfterDoBuildSolution">

<Message Text="###After building your solution######" Importance="high"/>

</Target>

</Project>

 

Now all you need to do is to place this file into the folder that contains your solution and call msbuild on it with:
    >msbuild.exe DreamCatcher_SlnBuild.proj /t:BuildSolution
The DefaultTargets is set to BuildSolution so you can take that off actually.

The are many advantages to this solution like:
    1) You extend the solution build process in similar ways as extending the project build proces
    2) You can re-use any existing MSBuild tasks
    3) You can add more steps to the build process easily
    4) You can place this file into source control and all team members can use it.

If you need more info please let me know, I would explain further now but I'm not feeling very well currently.

 

Sayed Ibrahim Hashimi