MSBuild Content You Want

Comments [12]

Lately I've been thinking of writing a book strictly on MSBuild. My previous book Deploying .NET Applications has devoted about half of the book to the topic. But I feel that there is still a lot of material missing even from that text. When the book was published I thought that by this time there would be such a book, but there is not. Because of this need, I'm considering writing the book myself! Now I'd like to hear from you, do you think such a book would be useful? What content would you like to see covered in a book like this? You can either contact me through this website of by email at sayed [DOT] hashim [AT] NOSPAMgmail [DOT]com. All feedback is greatly appreciated.

Sayed Ibrahim Hashimi

 


Comment Section

Comments are closed.


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


Comment Section

Comments are closed.


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">

 

  <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">

 

 

  <PropertyGroup>

    <SourceWebVirtualPath>WebPathItemsSourceWebVirtualPath>

    <SourceWebPhysicalPath>WebPathItemsSourceWebPhysicalPath>

  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">

 

 

  <PropertyGroup>

    <SourceWebVirtualPath>WebPathItemsSourceWebVirtualPath>

    <SourceWebPhysicalPath>WebPathItemsSourceWebPhysicalPath>

  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">

 

 

  <PropertyGroup>

    <SourceWebVirtualPath>WebPathItemsSourceWebVirtualPath>

    <SourceWebPhysicalPath>WebPathItemsSourceWebPhysicalPath>

  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>trueDoingFix>

  PropertyGroup>

 

  <Import Project="Shared.proj"/>

 

 

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

   

    <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


Comment Section

Comments are closed.


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

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

 

Sayed Ibrahim Hashimi


Comment Section

Comments are closed.


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.

<ItemGroup>

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

ItemGroup>

<PropertyGroup>

   <RefFile>SolutionReferences.txtRefFile>

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">

  

   <ItemGroup>

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

   ItemGroup>

  

   <PropertyGroup>

      <RefFile>SolutionReferences.txtRefFile>

   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


Comment Section

Comments are closed.


<< Older Posts | Newer Posts >>