- | rssFeed | My book on MSBuild and Team Build | Archives and Categories Saturday, June 01, 2013

Hijacking the Visual Studio Build Process

Have you ever wanted to use Visual Studio to manage project artifacts but wanted to have a fully custom build process? The recommend way to do this is to build a Custom Visual Studio Project System, but there is a much easier way for lightweight needs. In this post I’ll show you how to take an existing project and “replace” the build process used? For example, wouldn’t it be cool if you could develop a Chrome Extension with VS? When you do a build it would be great to generate the .zip file to for the Chrome Gallery in the output folder. Doing this is way easier than you might think.

Before I go over the steps to do this let me explain the “contract” between Visual Studio and MSBuild. The primary interactions around build in Visual Studio include the following actions in VS. Including what VS does when the action is invoked.

For more details you can read Visual Studio Integration (MSBuild). The easiest way to completely customize the VS build process is to do the following:

  1. Create the correct project based on the artifacts you plan on using (i.e. if you’re going to be editing .js files then create a web project)
  2. Edit the project file to not import any of the .targets files
  3. Define the following targets; Build, Rebuild and Clean

After that when you invoke the actions inside of VS the correct action will be executed in your project.

Let’s look at a concrete example. I’ve been working on a Chrome extension with Mads Kristensen the past few days. The entire project is available on github at https://github.com/ligershark/BestPracticesChromeExtension. When it came time to try out the extension or to publish it to the Chrome store we’d have to manually create it, which was annoying. We were using a standard web project to begin with. Here is how we made the workflow simple. Edited the project file to disable the Import statements. See below.

<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" 
    Condition="false" />
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" 
    Condition="false and '$(VSToolsPath)' != ''" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" 
    Condition="false" />

For each of these targets I added/updated the condition to ensure that the .targets file would never be loaded. The reason why I did not simply remove these is because in some cases VS may get confused and try to “upgrade” the project and re-insert the Import statements.

After that I added the following statements to the .csproj file.

  <PropertyGroup>
    <LigerShareChromeTargetsPath>$(BuildFolder)\ligersharek.chrome.targets</LigerShareChromeTargetsPath>
  </PropertyGroup>
  <Import Project="$(LigerShareChromeTargetsPath)" Condition="Exists($(LigerShareChromeTargetsPath))" />

Here I defined a new property to point to a custom .targets file and a corresponding Import statement. Now let’s take a look at the ligershark.chrome.targets file in its entirety.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <UseHostCompilerIfAvailable>false</UseHostCompilerIfAvailable>
  </PropertyGroup>
  <ItemDefinitionGroup>
    <AppFileNameItem>
      <Visible>false</Visible>
    </AppFileNameItem>
    <AppFolderItem>
      <Visible>false</Visible>
    </AppFolderItem>
  </ItemDefinitionGroup>
  
  <UsingTask AssemblyFile="$(BuildLib)MSBuild.ExtensionPack.dll" TaskName="MSBuild.ExtensionPack.Compression.Zip" />
  
  <Target Name="Build">
    <MakeDir Directories="$(OutputPath)" />
    
    <ItemGroup>
      <_AppCandidateFilesToZip Remove="@(_AppCandidateFilesToZip)" />
      <_AppCandidateFilesToZip Include="@(Content);@(None)" />
    </ItemGroup>
    
    <FindUnderPath Path="$(AppFolder)" Files="@(_AppCandidateFilesToZip)">
      <Output TaskParameter="InPath" ItemName="_AppFilesToZip" />
    </FindUnderPath>
    <Message Text="App files to zip: @(_AppFilesToZip->'%(RelativeDir)%(Filename)%(Extension)')" />

    <PropertyGroup>
      <_AppFolderFullPath>%(AppFolderItem.FullPath)</_AppFolderFullPath>
    </PropertyGroup>

    <Message Text="Creating package .zip at [%(AppFileNameItem.FullPath)]" Importance="high" />
    <MSBuild.ExtensionPack.Compression.Zip 
      TaskAction="Create" 
      CompressFiles="@(_AppFilesToZip)" 
      ZipFileName="%(AppFileNameItem.FullPath)" 
      RemoveRoot="$(_AppFolderFullPath)" 
      CompressionLevel="BestCompression" />
  </Target>
  
  <PropertyGroup>
    <RebuildDependsOn>
      Clean;
      Build;
    </RebuildDependsOn>
  </PropertyGroup>
  <Target Name="Rebuild" DependsOnTargets="$(RebuildDependsOn)" />
  <Target Name="Clean">
    <!-- delete all the files in the output folder -->
    <ItemGroup>
      <_FilesToDelete Remove="@(_FilesToDelete)" />
      <_FilesToDelete Include="$(OutputPath)**\*" />
    </ItemGroup>
    <Message Text="Deleting files: @(_FilesToDelete)" />
    <Delete Files="@(_FilesToDelete)" />
  </Target>
</Project>

As you can see I defined the following targets; Build, Rebuild, and Clean. This is what we discussed earlier as the requirements. Now when I click the Build button in VS a .zip file containing the Chrome Extension is created in the bin\ folder. Additionally no other step (i.e. any typical build step) is performed here. The only thing happening is what is defined in the Build target here. Similarly when I Rebuild or Clean the Rebuild or Clean target is invoked respectively. This is  a pretty neat way to modify an existing project to completely replace the build process to suit your needs.

There are a few things in the .targets file that I’d like to point out.

UseHostCompilerIfAvailable

In the .targets file I have set the property declaration <UseHostCompilerIfAvailable>false</UseHostCompilerIfAvailable>. When you are working with a project in Visual Studio there is a compiler, the host compiler, that Visual Studio has which can be used. For performance reasons in most cases this compiler is used. In this case since we do not want any standard build actions to be executed this performance trick is undesired. You can easily disable this by setting this property to false.

After this MSBuild will always be invoked when performing any build related action.

Visible metadata for Item Lists

When using MSBuild you typically represent files as values within an item list. One issue that you may encounter when modifying a project which will be loaded in VS is that the files inside of those item lists will show up in Visual Studio. If you want to disable this for a particular value in an item list you can add the well known metadata <Visible>false</Visible>. For example you could have the following.

<ItemGroup>
    <IntermediateFile Include="cache.temp">
        <Visible>false</Visible>
    </IntermediateFile>
</ItemGroup>

If you don’t want to add this to every value in the item list then you can set the default value using an ItemDefinitionGroup.  For example take a look at the elements below.

<ItemDefinitionGroup>
  <AppFileNameItem>
    <Visible>false</Visible>
  </AppFileNameItem>
  <AppFolderItem>
    <Visible>false</Visible>
  </AppFolderItem>
</ItemDefinitionGroup>

After this declaration is processed if the Visible metadata is accessed for AppFileNameItem or AppFolderItem on a value which does not declare Visible, then false is returned.

 

The rest of the content of the .targets file is pretty straightforward. This post shows a basic example of how you can take an existing Visual Studio project and completely replace the build process.

 

This project is open source at https://github.com/ligershark/BestPracticesChromeExtension.

 

Sayed Ibrahim Hashimi | http://msbuildbook.com | @SayedIHashimi

msbuild | MSBuild 4.0 | Visual Studio | Visual Studio 2012 Saturday, June 01, 2013 6:47:16 PM (GMT Daylight Time, UTC+01:00)  #     | 
Tuesday, February 12, 2013

MSBuild: DependsOnTargets versus BeforeTargets, AfterTargets

Today I saw a question on StackOverflow asking about the difference between DependsOnTargets and BeforeTargets/AfterTargets. Below is my response.

In my MSBuild Book I have a section based on how to create reusable elements in MSBuild, if you are interested. I'll give some comments here as well though. This content is different from what the book has.

When creating MSBuild files you should be careful to isolate what versus how. To explain that a bit, lets examine how the managed VS projects work out of the box (which is a great model for reusable elements).

When you create a C#/VB project you get a .csproj file, this .csproj file primarily contains Properties and Items. You will not find a single target in that file. These files contains what will get built (along with some settings relating to the build). At the bottom of the project file you will find an import statement. This import bring in How the project is built.

The files which are imported include:

In this case Microsoft.common.targets defines the overall build process for all the managed languages. Then Microsoft.CSharp.targets (or one of the other lang specific .targets file) fills in the blanks of how to invoke the specific language specific tools.

DependsOnTargets versus Before/AfterTargets

In your answer above you state "I recommend to avoid DependsOnTargets unless it is really really necessary, for instance if two". I disagree with this. Here is my take on DependsOnTargets versus Before/AfterTargets.

Use DependsOnTargets when

Use Before/AfterTargets when

To tease out the difference a bit consider web projects. For web projects there are two workflows that the .csproj/.vbproj take care of: 1. Build 1. Publish

If I want to add a target to the list of targets to be executed before the Build target I can dynamically update the BuildDependsOn property for publish scenarios only. You cannot do this with Before/AfterTargets.

In an ideal world each target would have the following wrt DependsOnTargets.

For example

<MyTargetDependsOn>
    $(MyTargetDependsOn);
    Target1;
    Target2
</MyTargetDependsOn>

Unfortunately many targets do not follow this pattern, so DependsOnTargets is dead in the water for many cases.

When I am authoring MSBuild scripts I always use DependsOnTargets unless there is a solid reason why I should chose to use Before/AfterTargets. I feel that (I have no insight on the real reasons to the design as I wasn't with Microsoft at the time) Before/AfterTargets was really created to allow users to inject targets to be executed before/after a target which they did not own, and the creators did not use the pattern above.

 

 

dddd

msbuild | MSBuild 4.0 Tuesday, February 12, 2013 2:16:30 AM (GMT Standard Time, UTC+00:00)  #     | 
Sunday, January 06, 2013

Command line web project publishing

With the release of VS2012 we have improved the command line publish experience. We’ve also made all the web publish related features available for VS2010 users in the Azure SDK.

The easies way to publish a project from the command line is to create a publish profile in VS and then use that. To create a publish profile in Visual Studio right click on the web project and select Publish. After that it will walk you though creating a publish profile. VS Web publish profile support the following publish methods.

Command line publishing is only supported for Web Deploy, Web Deploy Package, and File System. If you think we should support command line scenarios for other publish methods the best thing to do would be to create a suggestion at http://aspnet.uservoice.com. If there is enough interest we may work on that support.

Let’s first take a look at how you can publish a simple Web project from the command line. I have created a simple Web Forms project and want to publish that. I’ve created a profile named SayedProfile. In order to publish this project I will execute the following command.

msbuild MyProject.csproj /p:DeployOnBuild=true /p:PublishProfile=<profile-name> /p:Password=<insert-password> /p:VisualStudioVersion=11.0

In this command you can see that I have passed in these properties;

You may not have expected the VisualStudioVersion property here. This is a new property which was introduced with VS 2012. It is related to how VS 2010 and VS 2012 are able to share the same projects. Take a look at my previous blog post at http://sedodream.com/2012/08/19/VisualStudioProjectCompatabilityAndVisualStudioVersion.aspx. If you are building the project file, instead of the solution file then you should always set this property.

If you are publishing using the .sln file you can omit the VisualStudioVersion property. That property will be derived from the version of the solution file itself. Note that there is one big difference when publishing using the project or solution file. When you build an individual project the properties you pass in are given to that project alone. When you build from the command line using the solution file, the properties you have specified are passed to all the projects. So if you have multiple web projects in the same solution it would attempt to publish each of the web projects.

FYI in case you haven’t already heard I’m working on an update to my book. More info at msbuildbook.com

Sayed Ibrahim Hashimi | @SayedIHashimi

msbuild | MSBuild 4.0 | MSDeploy | web | Web Deployment Tool Sunday, January 06, 2013 2:56:37 AM (GMT Standard Time, UTC+00:00)  #     | 
Sunday, May 13, 2012

VS Web Publish: How to parameterize connection strings outside of web.config

If you have used the Visual Studio web publish in either VS 2010 or VS 11 to create Web Deploy packages then you probably know that we parameterize connection strings in web.config automatically. In case you are not familiar with Web Deploy parameters, they are a way to declare that you want to easily be able to update a value of something when publishing the package later on. Connection strings are good examples of something which typically needs to be updated during publish.

As I previously stated if you create a Web Deploy package in Visual Studio we will automatically create Web Deploy parameters for all your connection strings in web.config. Earlier today I saw a question on StackOverflow asking how to parameterize connection strings in non-web.config files (question actually asked something else, but I think this is what he’s really wanting). I created a sample showing how to do this. Below is what the connectionStrings element looks like in web.config.

<connectionStrings configSource="connectionStrings.config" />

And here is connectionStrings.config

<?xml version="1.0" encoding="utf-8" ?>
<connectionStrings>
  <clear/>
  <add name="ApplicationServices"
           connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnetdb.mdf;User Instance=true"
           providerName="System.Data.SqlClient" />
  <add name="OtherConnectionString"
       connectionString="data source=.\SQLExpress;Integrated Security=SSPI;Initial Catalog=foo"
       providerName="System.Data.SqlClient"/>
</connectionStrings>

In order to parameterize these connection strings you will have to extend the Web Publish Pipeline. To do that create a file named {project-name}.wpp.targets in the root of the project in which you are working (for VS 11 projects you can place all this directly inside of the .pubxml files). This will be an MSBuild file which will get imported into the build/publish process. Below is the file which needs to be created.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <ItemGroup>
    <!-- Here we need to declare MSDeploy parameters for connection strings in connectionStrings.config -->
    <MsDeployDeclareParameters Include="ApplicationServices-ConnectionString" >
      <Kind>XmlFile</Kind>
      <Scope>connectionStrings.config$</Scope>
      <Match>/connectionStrings/add[@name='ApplicationServices']/@connectionString</Match>
      <Description>Connection string for ApplicationServices</Description>
      <DefaultValue>data source=(localhost);Initial Catalog=AppServices</DefaultValue>
      <Tags>SqlConnectionString</Tags>
    </MsDeployDeclareParameters>

    <MsDeployDeclareParameters Include="OtherConnectionString-ConnectionString" >
      <Kind>XmlFile</Kind>
      <Scope>connectionStrings.config$</Scope>
      <Match>/connectionStrings/add[@name='OtherConnectionString']/@connectionString</Match>
      <Description>Connection string for OtherConnectionString</Description>
      <DefaultValue>data source=(localhost);Initial Catalog=OtherDb</DefaultValue>
      <Tags>SqlConnectionString</Tags>
    </MsDeployDeclareParameters>
  </ItemGroup>

</Project>

Here you can see that I am creating values for MSDeployDeclareParameters. When you package/publish this item list is used to create the MSDeploy parameters. Below is an explanation of the metadata values each contain.

After you create this file you will need to close/re-open VS (it caches imported .targets files). Then you can create a web deploy package. When you do so these new parameters will be declared. In my case I then imported this in the IIS manager and here is the dialog which shows up for the parameters.

SNAGHTML94cce08

As you can see the Application Path parameter is shown there as well as my custom connection string values. When I update the values in the text box and opened connectionStrings.config on my web server they were the values I entered in the dialog box.

FYI I have uploaded this sample to my github account at ParameterizeConStringConfig.

Sayed Ibrahim Hashimi @SayedIHashimi

msbuild | MSBuild 4.0 | MSDeploy | Visual Studio | Web Deployment Tool | Web Publishing Pipeline Sunday, May 13, 2012 10:18:03 PM (GMT Daylight Time, UTC+01:00)  #     | 
Monday, March 21, 2011

Property Functions: GetFolderPath

Today someone sent me an email asking how to call the System.Environment.GetFolderPath method passing in the value of MyDocuments for the value of the folder parameter. I was expecting the below to do the trick.

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

  <PropertyGroup>
    <Tempfile02>$([System.Environment]::GetFolderPath(System.Environment.SpecialFolder.MyDocuments))</Tempfile02>
  </PropertyGroup>

  <Target Name="Demo">
    <Message Text="TempFile01: $(TempFile02)"/>
  </Target>

</Project>

To my surprise I was faced with the following error.

Build started 3/20/2011 6:20:36 PM.

Project "C:\temp\_NET\msbuild\PropFunction01.proj" on node 1 (default targets).

C:\temp\_NET\msbuild\PropFunction01.proj(20,5): error MSB4186: Invalid static method invocation syntax: "[System.Environment]::GetFolderPath(System.Environment.Spec ialFolder.MyDocuments)". Requested value 'System.Environment.MyDocuments' was not found. Static method invocation should be of the form: $([FullTypeName]::Method()), e.g. $([System.IO.Path]::Combine(`a`, `b`)).

Done Building Project "C:\temp\_NET\msbuild\PropFunction01.proj" (default targets) -- FAILED.

Build FAILED.

So I sent an email to the MSBuild team asking “WTF why doesn’t this work?!”, and Dan Moseley (a lead dev there) sent me a snippet that worked, its below.

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

  <PropertyGroup>
    <Tempfile02>$([System.Environment]::GetFolderPath(SpecialFolder.MyDocuments))</Tempfile02>
  </PropertyGroup>

  <Target Name="Demo">
    <Message Text="TempFile01: $(TempFile02)"/>
  </Target>

</Project>

 

In that snippet instead of using the fully qualified class name of System.Environment.SpecialFolder.MyDocuments, for some reason you have to use the shortened name of just SpecialFolder.MyDocuments. It seems like a bug to me, but at least there is a work around!

Resources

Sayed Ibrahim Hashimi – @sayedihashimi

msbuild | MSBuild 4.0 | Visual Studio 2010 Monday, March 21, 2011 1:27:44 AM (GMT Standard Time, UTC+00:00)  #     | 
Thursday, November 18, 2010

XDT (web.config) Transforms in non-web projects

One of the really cool features that we shipped for Visual Studio 2010 was web.config (XDT) transformations. Because the transformations are so simple and straightforward one of the first questions that someone asks after using it is “how can I use this in my other projects?” Unfortunately this feature is only built into the Web Application Projects (WAP). But it is very easy to reuse this because we just rely on an MSBuild task to do the heavy lifting for us. I received an email from that basically went like this

“Hi, I would like to use XDT transformations on my WPF project for both the app.config file as well as my unity.xml file. How can I do this?”

So one answer is to modify your project file to use the TransformXml task as I have blogged previously about (link below). But I thought that since this was such a common problem that I should go ahead and create a .targets file which would solve the above problem and could be re-used by anyone.

Let me clarify the scenario a bit before we dive into the details of the solution. I have create a sample Wpf project, named Wpf01. Inside of that project I have created these files:

Take a look at the image below (note: I manually edited the project file to make the file nest under each other, I will explain that shortly)

image

The files with .debug/.release are transform files. When I build I expect the following to happen:

  1. Transform app.config with app.{Configuration}.config and write file to output folder with the correct name i.e. Wpf01.exe.config instead of just app.config
  2. Transform Sample01.xml with Sample01.{Configuration}.config and write it to output folder with the name Sample01.config
  3. Transform Sub\Sub\Sub01.xml with Sub\Sub\Sub01.{Configuration}.config and write it to the output folder with the name Sub\Sub\Sub01.xml
  4. None of my source files should change

Usage

Before I get into the solution let me explain how to use the solution first because if you are not interested in the MSBuild details you can skip over that Smile

  1. You must have installed Web projects with Visual Studio on the machine (it contains the TransformXmll task).
  2. Create the folder %ProgramFiles (x86)%\MSBuild\Custom. If you want to share this across team members then see my note at the end of this blog.
  3. Download TransformFiles.targets (link below) and place the file into the folder %ProgramFiles (x86)%\MSBuild\Custom.
  4. Edit your project file (right click on the project Unload Project, right click again and pick edit)
  5. At the end of the project file place the element <Import Project="$(MSBuildExtensionsPath)\Custom\TransformFiles.targets" /> immediately above the closing </Project> tag
  6. For files that you want transformed a metadata value of TransformOnBuild to true. See below on what this means.
  7. Build and take a look at files in your output directory

For #5 lets examine the sample that I created. In this sample I had an app.config file. When I first created the project the entry in the project file for app.config looked like the following.

<None Include="app.config" />

So what you need to do is to add a new metadata value as described above for that. So it will turn into the following.

<None Include="app.config">
  <TransformOnBuild>true</TransformOnBuild>
</None>

The transform targets will look for items that have this value declared on them and then during build it will transform them, if the transform file exists in the same folder as the file itself. You will need to add TransfromOnBuild to all the files that you want to transform. So in my case I added it to app.config, Sample01.xml and Sub01.xml. Note you should not add this to the transform files themselves because you will just waste your own time. After you do this you should perform a build then take a look at the output directory for your transformed files. The app.config should write out the the correct file and the others as expected.

Nest transforms under the source file

You might have noticed that in the image above that the transform files are nested under the files themselves. To do this you need to add the DependentUpon metadata value to the child items. For instance for app.config the child items look like the following.

<None Include="app.debug.config">
  <DependentUpon>app.config</DependentUpon>
</None>
<None Include="app.release.config">
  <DependentUpon>app.config</DependentUpon>
</None>

Implementation

If you are wondering how this works then this is the section for you. TransformFile.targets has 2 targets; DiscoverFilesToTransform and TransformAllFiles. DiscoverFilesToTransform looks through a set of items (None, Content, and Resource). Inside of DiscoverFilesToTransform I look for values with the %(TransformOnBuild)==true. After all of those are collected I identify if there is an app.config file being transformed and if so it is placed into a specific item list and all others go into another item list.

Inside of TransformAllFiles the TransformXml task is used to transform all of the files. This target injects itself into the build process by having the attribute AfterTargets="Build;_CopyAppConfigFile". So whenever the Build or _CopyAppConfigFile targets are called the TransformAllFiles target will execute.

Here if the full code for this file.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="TransformXml"
         AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
  
  <ItemDefinitionGroup>
    <!-- Set the default value to false here -->
    <None>
      <TransformOnBuild>false</TransformOnBuild>
    </None>    
    <Content>
      <TransformOnBuild>false</TransformOnBuild>
    </Content>    
    <Resource>
      <TransformOnBuild>false</TransformOnBuild>
    </Resource>
    <EmbeddedResource>
      <TransformOnBuild>false</TransformOnBuild>
    </EmbeddedResource>
    
    <_FilesToTransform>
      <IsAppConfig>false</IsAppConfig>
    </_FilesToTransform>
  </ItemDefinitionGroup>

  <PropertyGroup>
    <TransformAllFilesDependsOn>
      DiscoverFilesToTransform;
    </TransformAllFilesDependsOn>
  </PropertyGroup>
  <Target Name="TransformAllFiles" DependsOnTargets="$(TransformAllFilesDependsOn)" AfterTargets="Build;_CopyAppConfigFile">
    <!-- Now we have the item list _FilesToTransformNotAppConfig and _AppConfigToTransform item lists -->
    <!-- Transform the app.config file -->    
    <ItemGroup>
      <_AppConfigTarget Include="@(AppConfigWithTargetPath->'$(OutDir)%(TargetPath)')" />
    </ItemGroup>
    
    <PropertyGroup>
      <_AppConfigDest>@(_AppConfigTarget->'%(FullPath)')</_AppConfigDest>
    </PropertyGroup>

    <MakeDir Directories="@(_FilesToTransformNotAppConfig->'$(OutDir)%(RelativeDir)')"
             Condition="Exists('%(RelativeDir)%(Filename).$(Configuration)%(Extension)')"/>
    
    <TransformXml Source="@(_AppConfigToTransform->'%(FullPath)')"
                  Transform="%(RelativeDir)%(Filename).$(Configuration)%(Extension)"
                  Destination="$(_AppConfigDest)"
                  Condition=" Exists('%(RelativeDir)%(Filename).$(Configuration)%(Extension)') " />

    
    <TransformXml Source="@(_FilesToTransformNotAppConfig->'%(FullPath)')"
                  Transform="%(RelativeDir)%(Filename).$(Configuration)%(Extension)"
                  Destination="@(_FilesToTransformNotAppConfig->'$(OutDir)%(RelativeDir)%(Filename)%(Extension)')"
                  Condition=" Exists('%(RelativeDir)%(Filename).$(Configuration)%(Extension)') " />
  </Target>
  
  <Target Name="DiscoverFilesToTransform">
    <!-- 
    This will look through items list: None & Content for those
    with Metadata <TransformOnBuild>True</TransformOnBuild>
    -->
    <ItemGroup>
      <_FilesToTransform Include="@(None);@(Content);@(Resource);@(EmbeddedResource)"
                         Condition=" '%(TransformOnBuild)' == 'true' "/>
    </ItemGroup>    

    <PropertyGroup>
      <_AppConfigFullPath>@(AppConfigWithTargetPath->'%(RootDir)%(Directory)%(Filename)%(Extension)')</_AppConfigFullPath>
    </PropertyGroup>

    <!-- Now look to see if any of these are the app.config file -->
    <ItemGroup>
      <_FilesToTransform Condition=" '%(FullPath)'=='$(_AppConfigFullPath)' ">
        <IsAppConfig>true</IsAppConfig>
      </_FilesToTransform>
    </ItemGroup>
          
    <ItemGroup>
      <_FilesToTransformNotAppConfig Include="@(_FilesToTransform)"
                                     Condition=" '%(IsAppConfig)'!='true'"/>
      
      <_AppConfigToTransform  Include="@(_FilesToTransform)"
                              Condition=" '%(IsAppConfig)'=='true'"/>
    </ItemGroup>
  </Target>
</Project>

Gaps

With most things found on blogs there are some gaps Those are described here.

Clean build => It’s a best practice to delete files upon clean, but in this case I am not. This would be pretty easy to add, if you are interested let us know and I will update the sample.

Incremental build => The transforms will run every time you build even if the outputs are up to date, if this is an issue for you let us know and I will update the sample.

Sharing with team members

If you want to share with team members instead of placing this into %ProgramFiles (x86)% just place it into a folder in version control then change the <Import statement to point to that file instead of using MSBuildExtensionPath.

 

If you end up using this please let us know what is your experience with it.

Resources

Sayed Ibrahim Hashimi @sayedihashimi

msbuild | MSBuild 4.0 | MSDeploy | Web Publishing Pipeline Thursday, November 18, 2010 5:41:09 AM (GMT Standard Time, UTC+00:00)  #     | 
Thursday, November 11, 2010

ASP.NET Web Application: Publish/Package Tokenizing Parameters

Today I just saw a question posted on stackoverflow.com asking Why are some Web.config transforms tokenised into SetParameters.xml and others are not? Let me give some background on this topic for those who are not aware of what the question is.

With Visual Studio 2010 when you package your application using the Build Deployment Package context menu option, see image below.

image

When build the package by default the package will be created in obj\{Configuration}\Package\{ProjectName}.zip where {Configuration} is the current build configuration, and {ProjectName} is the name of the project. So in this case I since I’m building with Debug and the project name is MvcApplication1 the package will be placed at obj\Debug\Package\MvcApplication1.zip. If you take this package and then import into IIS 7 with the “Import Application” option shown below. Note: The machine must have the Web Deployment Tool (aka MSDeploy) installed.

image

Once you click on Import Application then browse out to the package you will be shown a screen which prompts your for parameters. Its shown below.

SNAGHTML2d2664

On this screen you can see that we are prompting for a couple parameter values here. One is an IIS setting, Application Path, and the other is a connection string which will be placed inside the web.config file. If your Web Application Project (WAP)  had 5 different connection strings then they would automatically show up here on this page. Since connection strings are replaced so often we create parameters for all connection strings by default. You can define new parameters on your own, quite easily actually, but that is the topic for another blog post.

Now back to the question. He is asking why do we “tokenize” the connection strings in web.config. To clarify take a look at my web.config file below.

<configuration>
  <appSettings>
    <add key="setting01" value="value01"/>
  </appSettings>
  
  <connectionStrings>
    <add name="ApplicationServices"
         connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
  
</configuration>

After I perform a package this will get changed. Take a look @ the web.config file which resides in the package (you can get to the file at obj\{CofigurationName}\Package\PackageTmp\web.config). You will see what is shown below.

<configuration>
  <appSettings>
    <add key="setting01" value="value01"/>
  </appSettings>
  <connectionStrings>
    <add name="ApplicationServices"
         connectionString="$(ReplacableToken_ApplicationServices-Web.config Connection String_0)"
         providerName="System.Data.SqlClient" />
  </connectionStrings>

</configuration>

So his question is why is the connection string replaced with $(ReplacableToken_ApplicationServices-Web.config Connection String_0) and nothing else is? We do this because we do not want you to accidently copy your web to a location and have it executing SQL statements against a SQL server which you did not intend. The idea is that you will create a package that you can deploy to many different environments. So the value that was in your web.config (or web.debug.config/web.release.config if you are using a web.config transformation) will not be placed inside the web.config in the package. Instead those values will be used as defaults in the package itself. We also create a SetParameters.xml file for you so that you can tweak the values. For my app see the MvcApplication1.SetParameters.xml file below.

<?xml version="1.0" encoding="utf-8"?>
<parameters>
  <setParameter name="IIS Web Application Name" 
                value="Default Web Site/MvcApplication1_deploy" />
  <setParameter name="ApplicationServices-Web.config Connection String" 
                value="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" />
</parameters>

The idea is that you can deploy your package in 2 ways. Through the IIS Manager which will prompt you for the parameters or you can deploy using msdeploy.exe with the –setParamFile switch to specify the path to the SetParameters.xml file. In this case I could create a QA01.SetParameters.xml file along with a QA02.SetParameters.xml file to deploy my web to my two QA servers. How do we do this?

How connection strings are tokenized

You might be wondering how the connection strings are tokenized to begin with. With Visual Studio 2010 we released web.config transformations, which all you to write terse web.config transformations inside of files like web.debug.config/web.release.config. When you package/publish your web these transform files are used to transform your web.config based on what you expressed in the appropriate transform file. We have an MSBuild task TransformXml which performs the transformation. We use that same task to tokenize the connection strings. If you are interested in the details take a look at %ProgramFiles32%\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets in the AutoParameterizationWebConfigConnectionStringsCore target.

Now what if you do not want the connection string tokenized?

Prevent tokenizing connection strings

If you want to prevent your web.config connection strings from being tokenized it’s pretty easy. All we need to do is the add a property to the build/package/publish process. We can do that in 2 ways. Edit the project file itself or create a file with the name {ProjectName}.wpp.targets where {ProjectName} is the name of your project. The second approach is easier so I use that. In my case it would be MvcApplication1.wpp.targets. The contents of the file are shown below.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <PropertyGroup>
    <AutoParameterizationWebConfigConnectionStrings>false</AutoParameterizationWebConfigConnectionStrings>
  </PropertyGroup>
  
</Project>

Note: You may need to reload the project in Visual Studio for this to take effect.

Inside of this file I have declared the property, AutoParameterizationWebConfigConnectionStrings, to be false. This is telling the Web Publishing Pipeline (WPP) that it should not replace replace the connection strings with tokens, instead leave them as they are.

Questions/Comments???

Other Resources

asp.net | Deployment | msbuild | MSBuild 4.0 | MSDeploy Thursday, November 11, 2010 5:41:09 AM (GMT Standard Time, UTC+00:00)  #     | 
Friday, October 22, 2010

MSBuild: Extending the solution build

One of the most commonly asked questions that I get is “How can I extend the build process for my solution?” In fact I was asked this soo much that 4 years ago (wow I’ve been dealing with MSBuild for far too long:) ) I wrote a blog post on Use MSBuild to build Solution files (no longer recommended technique) and that post to this day is one of the most frequently visited ones. Well in the 4+ years that have passed I’m glad to say that things have changed. The problem is that almost no one knows about it. Hopefully this blog post will be the start to that changing.

What’s under the covers

In all versions of Visual Studio, the solution file is not an MSBuild file. Which sucks for many of us. Essentially the solution build was like a black box.

Blackbox

Even in VS2010 it still kinda is, but there is a hook for us to extend it built in. Let me explain what happens when you build a solution using msbuild.exe. When you build a solution using msbuild.exe the solution file is converted to an MSBuild file in memory then that MSBuild file is used to build the solution itself. When you build from the command line here is the rough outline of the MSBuild project that gets built.



  
  
  

  
  
  
  
  
                    
  

OK here I have left out the targets and properties because they are irrelevant to this discussion. Take a look at these import statements. There are two pairs of import statements ones that are imported before the build is defined and the imports which are after the build definition. Instead of looking at the first import statement lets look at the second one, because I think its more compelling and its easier to demo.

This statement is pretty simple, if a file with the name of before.ExtendSolutionBuild.sln.targets exists in the same directory as the ExtendSolutionBuild.sln file then it will be imported in at the top of the build file. Also there is a corresponding entry for after.ExtendSolutionBuild.sln.targets. In stead of getting into all the nitty gritty of why two exists let me just boil it down. You should place properties/items inside of the before targets file, and inside of the after .targets file you should place targets but its OK to put properties/items here as well. The most important thing to keep a note of is that you cannot place anything inside of the before file which relies on any properties/items defined in the build because they will not be available. Because of this most times you will just need an after targets file. So in my case I will create a file named after.ExtendSolutionBuild.sln.targets, this follows the pattern after.{SolutionFile}.sln.targets where {SolutionFile} is the name of the solution file mine happens to be ExtendSolutionBuild. Every solution has 4 targets that will be defined.

4 Targets on Every Solution file

So let’s say that I want to extend the build file and inject two targets; GenerateCode and RunCodeAnalysis. Obviously I want the GenerateCode target to run before anything is built and the RunCodeAnalysis to be run after everything is built. So in my after.ExtendSolutionBuild.sln.targets file I place the following content.



  
  
    
  

  
    
  
  

You can see here that I am using BeforeTargets=”Build” to make sure that the GenerateCode target is run before the Bulid target and also the RunCodeAnalysis target uses AfterTargets=”Build”. So now let’s see what happens when I build my solution from the command line using msbuild.exe.

ExtendSolution01

From the figure above you can see that the GenerateCode target was executed before the build process started and then the RunCodeAnalysis target was executed after the build. With MSBuild 3.5 and earlier this was simply not possible, and you would have had to write another build file to do these kinds of things for you. If you are building solution files from the command then you might be interested in the technique. I would be interested to hear you feedback and to see how you guys would use this feature.

ImportBefore/ImportAfter

Now let’s talk briefly about the two that we skipped. If you want an MSBuild file to be imported for every solution that is build using msbuild.exe for a given machine then you can place that file in one of the two folders below.

So if you place any file in either of those folders (which don’t exist by default) then they will automatically be imported for every solution file which builds on that machine using msbuild.exe. Typically you do not run into many times where you want to do this, but the times that you do then it is critical and really hard to work around without something like this. This is useful if you have a build machine and you want to perform a set of actions for every build.

Resources

Sayed Ibrahim Hashimi | @SayedIHashimi

msbuild | MSBuild 4.0 | Visual Studio 2010 Friday, October 22, 2010 7:14:22 AM (GMT Daylight Time, UTC+01:00)  #     | 
Thursday, October 21, 2010

MSBuild: Filter list take 2

Last night I saw this tweet.

@MikeFourie MSBuild Q: given I have @(ProjectOutputs) how can I get a collection of all *.Unit.dll files?

About a year ago I blogged about inline tasks including a FilterList task. So for his case the FilterList would work, but then I was thinking that he should be able to do this with some of the goodness that was released wtih MSBuild 4.0. Specifically with Property Function and Item Functions. So I thought about it and whipped up a quick example.

Take a look at the file below.



  
    
    
  
  
  
        
    
      
    
    
  

In this file I have created a dummy item named TargetOutputs and populated it with a bunch of fake assembly names. Then inside the Demo target, I print out the items in that list and construct another item list, UnitTestAssemblies. In this declaration I am including %(TargetOutputs.Identity) with the condition '@(TargetOutputs->Contains("unit"))'=='True'. Let me break this down for you a bit. With Item Functions there is a specific set of functions that you can call like DirectoryName/Metadata/Distinct/etc (see below for the reference link for full list), but you can also execute any string instance method. What you need to understand is that it will execute the method for all items in the list then return a new list with the invocation result. Essentially these are a new form of Item Transformations. I didn’t want to handle these in bulk, I need to apply the filter to each individual item value. In order to do that I use the %(TargetOutputs.Identity) which kicks batching in. Since I can see that the values in the include value are unique I know that the Identity metadata is unique. So what that means is When I do something like the message task will be invoked once per item value in TargetOutputs. The same goes for the item declaration. So in the UnitTestAssemblies item group declaration I can use a condition of Condition='@(TargetOutputs->Contains("unit"))'=='True' , because we are know that @(TargetOutputs) will only have 1 value per invocation. With that condition I only added files that have unit in the name. When you execute the Demo target the result is shown below.

image

Pretty cool huh? The best part, I managed to fit my reply in 140 characters in this tweet.

@HowardvRooijen

Anywayz, if you are not following me on twitter you can find me at @sayedihashimi.

Resources

Sayed Ibrahim Hashimi

msbuild | MSBuild 4.0 Thursday, October 21, 2010 7:11:11 AM (GMT Daylight Time, UTC+01:00)  #     | 
Wednesday, August 04, 2010

MSBuild: Empty Metadata versus no metadata, inline task to solve

Recently there was a question on an internal mailing list asking the question can you tell the difference between

<ItemGroup>
  <Content Include="Sample-DefinedEmpty.sdf">
    <SubPath></SubPath>
  </Content>
</ItemGroup>

and

<ItemGroup>
  <Content Include="Sample-NotDefined.sdf">
  </Content>
</ItemGroup>

You cannot detect this out of the box, but you can by creating a custom task. Better would be to create an inline task so that you don’t have to deal with the headache of maintaining a .dll for something like this, unless you are using it on many different project files. Basically the task that we will create will need two parameters; the Item itself (single value) and MetadataName (metadata name to check for). It will have one output parameter, MetadataDefined, which we can check to see if the metadata value was defined or not.

This is a pretty easy task to create because we just look at the MetadataNames property on the ITaskItem interface. The task as well as a sample target is shown below.

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  
  <UsingTask TaskFactory="CodeTaskFactory"
             TaskName="MetadataExists"
             AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <MetadataName Required="true"/>
      <Item ParameterType="Microsoft.Build.Framework.ITaskItem"/>
      <MetadataDefined ParameterType="System.Boolean" Output="true" />      
    </ParameterGroup>
    <Task>
      <Code>
        <![CDATA[
            this.MetadataDefined = false;
            if (this.Item != null)
            {
                foreach (string name in this.Item.MetadataNames)
                {
                    if (string.Compare(this.MetadataName, name, StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        this.MetadataDefined = true;
                        break;
                    }
                }
            }          
        ]]>
      </Code>
    </Task>
  </UsingTask>

  <Target Name="CheckForMetadata">

<ItemGroup>
  <Content Include="Sample-DefinedEmpty.sdf">
    <SubPath></SubPath>
  </Content>
  <Content Include="Sample-NotDefined.sdf">
  </Content>
</ItemGroup>

    <Message Text="Starting - Content"/>

    <!-- Create an Item which has exactly 1 value to pass to the task -->
    <ItemGroup>
      <_Content Remove="@(_Content)"/>
      <_Content Include="@(Content)" Condition=" '%(Content.Identity)' == 'Sample-DefinedEmpty.sdf' "/>
    </ItemGroup>
    <MetadataExists MetadataName="SubPath" Item="@(_Content)">
      <Output PropertyName="existsResult" TaskParameter="MetadataDefined"/>
    </MetadataExists>

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


    <Message Text="Starting - Content2"/>
    <!-- Create an Item which has exactly 1 value to pass to the task -->
    <ItemGroup>
      <_Content Remove="@(_Content)"/>
      <_Content Include="@(Content)" Condition=" '%(Content.Identity)' == 'Sample-NotDefined.sdf' "/>
    </ItemGroup>
    <MetadataExists MetadataName="SubPath" Item="@(_Content)">
      <Output PropertyName="existsResult" TaskParameter="MetadataDefined"/>
    </MetadataExists>

    <Message Text="existsResult: $(existsResult)" />
  </Target>
</Project>

Here you can take a look at the MetadataExists task and its usage. The only thing that really needs to be pointed out here is that since this task accepts a single item value we will have to take the item group Content and pick from it a specific value which is passed to the task. That is what I am doing when I create the temp item _Content. If you execute the CheckForMetadata target with the command msbuild CheckMetadata01.proj /t:CheckForMetadata the result will be what is shown below.

CheckMetadata

So from the output you can see that we were able to tell the difference!

BTW, if you were wondering if you can do the same with properties, the answer is no.

Sayed Ibrahim Hashimi

inline task | msbuild | MSBuild 4.0 Wednesday, August 04, 2010 7:17:20 AM (GMT Daylight Time, UTC+01:00)  #     | 
Saturday, July 31, 2010

MSBuild 4 : Inline Tasks Part 3, tasks in JavaScript

A while back I talked about the new feature available in MSBuild 4 Inline Tasks in.

In the previous examples I have use C# as my language of choice. C# is not your only choice you can also use VB.Net but you can also use JavaScript! When MSBuild creates the class for the inline task it uses CodeDom to do so, and JavaScript is one of its supported languages. Perhaps its actually called JScript. In any case take a look at the project file below which shows this in action.

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

  <UsingTask
    TaskName="Jsex01"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <Files ParameterType="Microsoft.Build.Framework.ITaskItem[]"/>
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="javascript">
        <![CDATA[
          function printMessageFor(item : Microsoft.Build.Framework.ITaskItem) {
            Log.LogMessage(item.ItemSpec + "fullpath: " + item.GetMetadata("FullPath"));
          }
          
          for(var i = 0; i<Files.length; i++) {
            printMessageFor(Files[0]);
          }
          
        ]]>
      </Code>
    </Task>
  </UsingTask>

  <ItemGroup>
    <Source Include="one.cs"/>
    <Source Include="two.cs"/>
    <Source Include="three.cs"/>
    <Source Include="four.cs"/>
  </ItemGroup>
  
  <Target Name="PrintValues">
    <Jsex01 Files="@(Source)" />
  </Target>

</Project>

Here you can see that I created a new inline task called, Jsex01 and its written in Javascript. Then inside of the PrintValues target this task is called. If you execute the PrintValues target the result will be what you see below.

image

So if you prefer JavaScript to C# or VB.Net then you should try this out!

Sayed Ibrahim Hashimi

inline task | msbuild | MSBuild 4.0 Saturday, July 31, 2010 5:22:28 AM (GMT Daylight Time, UTC+01:00)  #     | 
Saturday, May 01, 2010

Web Deployment Tool (MSDeploy) : Build Package including extra files or excluding specific files

If you are using Visual Studio 2010 then you may already be aware that Web Deployment Tool (aka MSDeploy) is integrated into Visual Studio. I’ve posted a few blog entries already about this tool. Two of the common questions that I get discussing this with people are

  1. How do I exclude files from being placed in the package?
  2. How do I add other files to the created package?

I will address these two questions here, first we look at the easier one, how to exclude files but we will go over a bit of background first.

Web Publishing Pipeline

With Visual Studio 2010 a new concept has been created which is known as the Web Publishing Pipeline. In a nutshell this is a process which will take your web application, build it and eventually create a package that you can use to deploy your application. This process is fully captured in MSBuild. With VS 2010 many targets and many tasks are shipped to support this process. Since its captured in MSBuild format, you can customize and extend to your hearts desire. So what we need to do is hook into this process to perform the customizations that we need. This process is captured in the following files.

%program files%\MSBuild\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets
%program files%\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets

The Microsoft.WebApplication.targets file is imported by the web applications projects file, then that file imports the Microsoft.Web.Publishing.targets file.

Excluding files from being packaged

If you open the project file of a web application created with VS 2010 towards the bottom of it you will find a line with.

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

BTW you can open the project file inside of VS. Right click on the project pick Unload Project. Then right click on the unloaded project and select Edit Project.

This statement will include all the targets and tasks that we need. Most of our customizations should be after that import, if you are not sure put if after! So if you have files to exclude there is an item name, ExcludeFromPackageFiles, that can be used to do so. For example let’s say that you have file named Sample.Debug.js which included in your web application but you want that file to be excluded from the created packages. You can place the snippet below after that import statement.

<ItemGroup>
  <ExcludeFromPackageFiles Include="Sample.Debug.xml">
    <FromTarget>Project</FromTarget>
  </ExcludeFromPackageFiles>
</ItemGroup>

By declaring populating this item the files will automatically be excluded. Note the usage of the FromTarget metadata here. I will not get into that here, but you should know to always specify that.

Including extra files into the package

Including extra files into the package is a bit harder but still no bigee if you are comfortable with MSBuild, and if you are not then read this.  In order to do this we need to hook into the part of the process that collects the files for packaging. The target we need to extend is called CopyAllFilesToSingleFolder. This target has a dependency property, PipelinePreDeployCopyAllFilesToOneFolderDependsOn, that we can tap into and inject our own target. So we will create a target named CustomCollectFiles and inject that into the process. We achieve this with the following (remember after the import statement).

<PropertyGroup>
  <CopyAllFilesToSingleFolderForPackageDependsOn>
    CustomCollectFiles;
    $(CopyAllFilesToSingleFolderForPackageDependsOn);
  </CopyAllFilesToSingleFolderForPackageDependsOn>
</PropertyGroup>

This will add our target to the process, now we need to define the target itself. Let’s assume that you have a folder named Extra Files that sits 1 level above your web project. You want to include all of those files. Here is the CustomCollectFiles target and we discuss after that.

<Target Name="CustomCollectFiles">
  <ItemGroup>
    <_CustomFiles Include="..\Extra Files\**\*" />

    <FilesForPackagingFromProject  Include="%(_CustomFiles.Identity)">
      <DestinationRelativePath>Extra Files\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
    </FilesForPackagingFromProject>
  </ItemGroup>
</Target>

Here what I did was create the item _CustomFiles and in the Include attribute told it to pick up all the files in that folder and any folder underneath it. Then I use this item to populate the FilesForPackagingFromProject item. This is the item that MSDeploy actually uses to add extra files. Also notice that I declared the metadata DestinationRelativePath value. This will determine the relative path that it will be placed in the package. I used the statement Extra Files%(RecursiveDir)%(Filename)%(Extension) here. What that is saying is to place it in the same relative location in the package as it is under the Extra Files folder.

Admittedly this could be easier, but its not too bad, and its pretty flexible.

Sayed Ibrahim Hashimi

msbuild | MSBuild 4.0 | MSDeploy | Visual Studio | Visual Studio 2010 | Web Deployment Tool | Web Publishing Pipeline Saturday, May 01, 2010 4:09:16 AM (GMT Daylight Time, UTC+01:00)  #     | 
Friday, April 30, 2010

MSBuild 4.0: New command line switches

If you are using MSBuild 4.0 then you may be interested in knowing that there are a couple new switches that you can pass to msbuild.exe when you kick off a build. The new switches, /preprocess(/pp) and /detailedsummary(/ds), are more convenient then necessary.

/preprocess (/pp)

Since you can import other MSBuild files using the Import Element sometimes locating where a target, property or item is being defined can lead to a search that takes you through several files. It can be even more confusing if more than 1 file defines the property or target that you are interested in, because you may have thought that you found the right target but you may one that was overridden by another file. Now with MSBuild 4.0 you don’t have to search through all of those files. You can use the switch /preprocess switch. Here is the snippet from msbuild.exe /? describing it.

/preprocess[:file] 
 Creates a single, aggregated project file by inlining all the files that would be imported during a build, with their boundaries marked. This can be useful for figuring out what files are being imported and from where, and what they will contribute to the build. By default the output is written to the console window. If the path to an output file is provided that will be used instead.
 (Short form: /pp)
 Example:
   /pp:out.txt

When you use this the full logical project file is dumped to the console, or optionally to a file, and it includes references to where the elements are defined. For example I created the following very simple project files.

import-01.proj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="TargetOne">
        <Message Text="From import-02.proj - TargetOne"/>
    </Target>
</Project>

import-02.proj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="TargetTwo">
        <Message Text="From import-02.proj - TargetTwo"/>
    </Target>
</Project>

master.proj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="TargetOne;TargetTwo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

    <Target Name="TargetOne">
        <Message Text="From master.proj - TargetOne"/>
    </Target>

    <Target Name="TargetTwo">
        <Message Text="From master.proj - TargetTwo"/>
    </Target>

    <Import Project="import-01.proj"/>
    <Import Project="import-02.proj"/>
</Project>

After executing the command msbuild.exe master.proj /pp:out.xml the following was written to the out.xml file.

<?xml version="1.0" encoding="utf-8"?>
<!--
============================================================================================================================================
C:\temp\MSBuild\import\master.proj
============================================================================================================================================
-->
<Project ToolsVersion="4.0" DefaultTargets="TargetOne;TargetTwo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="TargetOne">
    <Message Text="From master.proj - TargetOne" />
  </Target>
  <Target Name="TargetTwo">
    <Message Text="From master.proj - TargetTwo" />
  </Target>
  <!--
============================================================================================================================================
  <Import Project="import-01.proj">

C:\temp\MSBuild\import\import-01.proj
============================================================================================================================================
-->
  <Target Name="TargetOne">
    <Message Text="From import-02.proj - TargetOne" />
  </Target>
  <!--
============================================================================================================================================
  </Import>

C:\temp\MSBuild\import\master.proj
============================================================================================================================================
-->
  <!--
============================================================================================================================================
  <Import Project="import-02.proj">

C:\temp\MSBuild\import\import-02.proj
============================================================================================================================================
-->
  <Target Name="TargetTwo">
    <Message Text="From import-02.proj - TargetTwo" />
  </Target>
  <!--
============================================================================================================================================
  </Import>

C:\temp\MSBuild\import\master.proj
============================================================================================================================================
-->
</Project>

As you can see with /pp it is very easy to see exactly what is defined where and at what location.

/detailedsummary (/ds)

Another new feature with MSBuild 4.0 is the /detailedsummary (/ds) command line switch. When you use this switch you will be shown a detailed summary (haha) of build execution. This summary includes the amount of time spent build each project file as well as the node utilization. I just preformed a build with the command msbuild RuleStack.Engine.sln /m /ds and the summary is shown below.

============================== Build Hierarchy (IDs represent configurations) =====================================================
 Id                  : Exclusive Time   Total Time   Path (Targets)
 -----------------------------------------------------------------------------------------------------------------------------------
 0                   : 0.020s           1.211s       C:\...\RuleStack.Engine.sln ()
 | 1                 : 0.667s           0.667s       C:\...\RuleStack.Engine.Common\RuleStack.Engine.Common.csproj ()
 | 3                 : 0.255s           0.718s       C:\...\Unittest\RuleStack.Engine.Tests\RuleStack.Engine.Tests.csproj ()
 | | 6               : 0.000s           0.000s       C:\...\ObjectBinder\RuleStack.ObjectBinder\RuleStack.ObjectBinder .csproj ()
 | | 5               : 0.000s           0.000s       C:\...\RuleStack.Data\RuleStack.Data.csproj ()
 | | 1               : 0.000s           0.000s       C:\...\RuleStack.Engine.Common\RuleStack.Engine.Common.csproj ()
 | | 2               : 0.000s           0.000s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( )
 | | 8               : 0.292s           0.460s       C:\...\RuleStack.Engine.Admin.Web\RuleStack.Engine.Admin.Web.csproj ()
 | | | 24            : 0.000s           0.000s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( GetNativeManifest)
 | | | 5             : 0.000s           0.000s       C:\...\RuleStack.Data\RuleStack.Data.csproj ()
 | | . 2             : 0.000s           0.000s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( )
 | . 36              : 0.003s           0.003s       C:\...\RuleStack.Engine.Admin.Web\RuleStack.Engine.Admin.Web.csproj (GetNativeManifest)
 | 2                 : 0.319s           0.390s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( )
 | | 6               : 0.000s           0.000s       C:\...\ObjectBinder\RuleStack.ObjectBinder\RuleStack.ObjectBinder.csproj ()
 | | 5               : 0.000s           0.000s       C:\...\RuleStack.Data\RuleStack.Data.csproj ()
 | | 17              : 0.002s           0.002s       C:\...\RuleStack.Data\RuleStack.Data.csproj (GetNativeManifest)
 | . 21              : 0.001s           0.001s       C:\...\RuleStack.Data\RuleStack.Data.csproj (GetCopyToOutputDirectoryItems)
 | 4                 : 0.382s           0.567s       C:\...\RuleStack.Services\RuleStack.Services.csproj ()
 | | 5               : 0.000s           0.000s       C:\...\RuleStack.Data\RuleStack.Data.csproj ()
 | | 2               : 0.000s           0.000s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( )
 | | 24              : 0.002s           0.002s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( GetNativeManifest)
 | . 29              : 0.001s           0.001s       C:\...\RuleStack.Engine.Backend\RuleStack.Engine.Backend.csproj ( GetCopyToOutputDirectoryItems)
 | 7                 : 0.333s           0.337s       C:\...\ObjectBinder\Test_RuleStack.ObjectBinder\Test_RuleStack.ObjectBinder.csproj ()
 | | 6               : 0.000s           0.000s       C:\...\ObjectBinder\RuleStack.ObjectBinder\RuleStack.ObjectBinder.csproj ()
 | | 13              : 0.001s           0.001s       C:\...\ObjectBinder\RuleStack.ObjectBinder\RuleStack.ObjectBinder.csproj (GetNativeManifest)
 | . 19              : 0.001s           0.001s       C:\...\ObjectBinder\RuleStack.ObjectBinder\RuleStack.ObjectBinder.csproj (GetCopyToOutputDirectoryItems)
 | 6                 : 0.210s           0.210s       C:\...\ObjectBinder\RuleStack.ObjectBinder\RuleStack.ObjectBinder.csproj ()
 | 5                 : 0.277s           0.277s       C:\...\RuleStack.Data\RuleStack.Data.csproj ()
 | . 12              : 0.000s           0.000s       C:\...\RuleStack.Engine.Common\RuleStack.Engine.Common.csproj (GetNativeManifest)
 . 43                : 0.002s           0.002s       C:\...\RuleStack.Engine.Admin.Web\RuleStack.Engine.Admin.Web.csproj.metaproj ()

 ============================== Node Utilization (IDs represent configurations) ====================================================
 Timestamp:            1       2       3       4       5       6       7       8        Duration   Cumulative
 -----------------------------------------------------------------------------------------------------------------------------------
 634081842447519669:   0       x       x       x       x       x       x       x        0.018s     0.018s
 634081842447699679:   1       x       x       x       x       x       x       x        0.461s     0.479s #########
 634081842452309943:   |       6       7       5       3       4       2       x        0.130s     0.609s ##
 634081842453610018:   |       |       |       |       8       |       |       x        0.086s     0.695s #
 634081842454470067:   |       |       |       |       |       |       |       x        0.001s     0.696s
 634081842454480067:   x       |       |       |       |       |       |       x        0.001s     0.697s
 634081842454490068:   x       |       x       |       |       x       |       x        0.001s     0.698s
 634081842454500068:   x       |       x       |       |       x       x       x        0.001s     0.699s
 634081842454510069:   x       x       7       |       |       x       x       x        0.002s     0.701s
 634081842454530070:   12      x       |       |       |       x       x       x        0.002s     0.703s
 634081842454550071:   |       13      x       |       |       x       x       x        0.001s     0.704s
 634081842454560072:   |       x       7       |       |       x       x       x        0.008s     0.712s
 634081842454640076:   |       x       |       |       x       x       x       x        0.054s     0.766s #
 634081842455180107:   |       x       |       x       x       x       2       x        0.003s     0.769s
 634081842455210109:   |       x       |       17      x       x       x       x        0.002s     0.771s
 634081842455230110:   |       x       |       x       x       x       2       x        0.036s     0.807s
 634081842455590131:   |       19      x       x       x       x       |       x        0.001s     0.808s
 634081842455600131:   |       x       7       x       x       x       |       x        0.018s     0.826s
 634081842455780142:   |       x       x       x       x       x       |       x        0.036s     0.862s
 634081842456140162:   |       x       x       21      x       x       x       x        0.001s     0.863s
 634081842456150163:   |       x       x       x       x       x       2       x        0.016s     0.879s
 634081842456310172:   |       x       x       x       8       4       x       x        0.003s     0.882s
 634081842456340174:   |       x       x       x       |       x       24      x        0.001s     0.883s
 634081842456350174:   |       x       x       x       x       x       |       x        0.001s     0.884s
 634081842456360175:   |       x       x       x       8       4       x       x        0.148s     1.032s ##
 634081842457840259:   |       x       x       x       |       x       29      x        0.001s     1.033s
 634081842457850260:   |       x       x       x       |       4       x       x        0.023s     1.056s
 634081842458080273:   |       x       x       x       |       x       x       x        0.013s     1.069s
 634081842458210281:   |       x       x       x       3       x       x       x        0.004s     1.073s
 634081842458250283:   |       x       x       x       36      x       x       x        0.003s     1.076s
 634081842458280285:   |       x       x       x       3       x       x       x        0.131s     1.207s ##
 634081842459590360:   0       x       x       x       x       x       x       x        0.001s     1.208s
 634081842459600360:   43      x       x       x       x       x       x       x        0.002s     1.210s
 634081842459620361:   0       x       x       x       x       x       x       x        0.001s     1.211s
 -----------------------------------------------------------------------------------------------------------------------------------
 Utilization:          57.8    30.3    46.9    39.6    76.5    53.6    45.4    .0       Average Utilization: 43.8

In the snippet above you should know that I replace the path to the files with to reduce the width of the output. Also the machine that I’m currently using has 8 cores so it shows 8 nodes, on your machine you may have a different number of columns for the node utilization table.

Sayed Ibrahim Hashimi

msbuild | MSBuild 4.0 Friday, April 30, 2010 1:38:03 AM (GMT Daylight Time, UTC+01:00)  #     | 
Monday, April 26, 2010

Config transformations outside of web app builds

If you are using Visual Studio 2010 then you may already be familiar with the Web.config transformations that are now available. What you might not know is that you can use that same technology to transform config files outside of the build process. You will need Visual Studio 2010 installed on the machine where you perform these transformations. It is very easy to perform these transformation as well. Let’s say that we start with the app.config file shown below.

<configuration>
    <connectionStrings>
        <clear/>
        <add name="Default" connectionString="Data Source=localhost;Initial Catalog=Sample01;Integrated Security=True;" />
    </connectionStrings>
    
    <appSettings>
        <add key="contactEmail" value="contact@demo.example.com"/>
        <add key="siteUrl" value="http://demo.example.com"/>
    </appSettings>
    
</configuration>

Then we create another file, transform.xml, which contains our transformations. That file is shown below.

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <connectionStrings>
        <clear/>
        <add name="Default" connectionString="Data Source=NOT-localhost;Initial Catalog=Sample01;Integrated Security=True;" 
             xdt:Locator="Match(name)" xdt:Transform="Replace"/>
    </connectionStrings>

    <appSettings>
        <add key="contactEmail" value="contact@example.com" xdt:Locator="Match(key)" xdt:Transform="Replace"/>
        <add key="siteUrl" value="http://example.com" xdt:Locator="Match(key)" xdt:Transform="Replace"/>
    </appSettings>

</configuration>

Then we can easily execute the transformations by using MSBuild. So I created a file named trans.proj and it is shown below.

<Project ToolsVersion="4.0" DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <UsingTask TaskName="TransformXml"
             AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>

    <Target Name="Demo">
        <TransformXml Source="app.config"
                      Transform="Transform.xml"
                      Destination="app.prod.config"/>
    </Target>
</Project>

This MSBuild file uses the TransformXml task which is shipped with Visual Studio 2010. We specify the source file, transform file and the destination. Pretty straight forward.

In order to execute this I open a Visual Studio 2010 command prompt, browse to the directory containing both files, and enter the following command

msbuild trans.proj /t:Demo

Once you do this then you will find the file app.prod.config with the following contents.

<configuration>
    <connectionStrings>
        <clear/>
        <add name="Default" connectionString="Data Source=NOT-localhost;Initial Catalog=Sample01;Integrated Security=True;"/>
    </connectionStrings>
    
    <appSettings>
        <add key="contactEmail" value="contact@example.com"/>
        <add key="siteUrl" value="http://example.com"/>
    </appSettings>
    
</configuration>

Sayed Ibrahim Hashimi

Config-Transformation | msbuild | MSBuild 4.0 | MSDeploy | Visual Studio | Visual Studio 2010 Monday, April 26, 2010 5:22:06 AM (GMT Daylight Time, UTC+01:00)  #     | 
Thursday, March 11, 2010

MSBuild 4.0 Reserved Properties

A while back I wrote about Reserved Properties in MSBuild 3.5, now its time to update that post to include reserved properties for MSBuild 4.0. There are a number of new properties here is the list:

•    MSBuild
•    MSBuildBinPath
•    MSBuildExtensionsPath
•    MSBuildExtensionsPath32
•    MSBuildExtensionsPath64
•    MSBuildLastTaskResult
•    MSBuildNodeCount
•    MSBuildOverrideTasksPath
•    MSBuildProgramFiles32
•    MSBuildProjectDefaultTargets
•    MSBuildProjectDirectory
•    MSBuildProjectDirectoryNoRoot
•    MSBuildProjectExtension
•    MSBuildProjectFile
•    MSBuildProjectFullPath
•    MSBuildProjectName
•    MSBuildStartupDirectory
•    MSBuildThisFile
•    MSBuildThisFileDirectory
•    MSBuildThisFileDirectoryNoRoot
•    MSBuildThisFileExtension
•    MSBuildThisFileFullPath
•    MSBuildThisFileName
•    MSBuildToolsPath
•    MSBuildToolsVersion

If you want to see what the values are you can execute this simple proj file that I created.

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

  <Target Name="PrintValues">
    <Message Text="MSBuild: $(MSBuild)"/>
    <Message Text="MSBuildBinPath: $(MSBuildBinPath)"/>
    <Message Text="MSBuildExtensionsPath: $(MSBuildExtensionsPath)"/>
    <Message Text="MSBuildExtensionsPath32: $(MSBuildExtensionsPath32)"/>
    <Message Text="MSBuildExtensionsPath64: $(MSBuildExtensionsPath64)"/>
    <Message Text="MSBuildLastTaskResult: $(MSBuildLastTaskResult)"/>
    <Message Text="MSBuildNodeCount: $(MSBuildNodeCount)"/>
    <Message Text="MSBuildOverrideTasksPath: $(MSBuildOverrideTasksPath)"/>
    <Message Text="MSBuildProgramFiles32: $(MSBuildProgramFiles32)"/>
    <Message Text="MSBuildProjectDefaultTargets: $(MSBuildProjectDefaultTargets)"/>
    <Message Text="MSBuildProjectDirectory: $(MSBuildProjectDirectory)"/>
    <Message Text="MSBuildProjectDirectoryNoRoot: $(MSBuildProjectDirectoryNoRoot)"/>
    <Message Text="MSBuildProjectExtension: $(MSBuildProjectExtension)"/>
    <Message Text="MSBuildProjectFile: $(MSBuildProjectFile)"/>
    <Message Text="MSBuildProjectFullPath: $(MSBuildProjectFullPath)"/>
    <Message Text="MSBuildProjectName: $(MSBuildProjectName)"/>
    <Message Text="MSBuildStartupDirectory: $(MSBuildStartupDirectory)"/>
    <Message Text="MSBuildThisFile: $(MSBuildThisFile)"/>
    <Message Text="MSBuildThisFileDirectory: $(MSBuildThisFileDirectory)"/>
    <Message Text="MSBuildThisFileDirectoryNoRoot: $(MSBuildThisFileDirectoryNoRoot)"/>
    <Message Text="MSBuildThisFileExtension: $(MSBuildThisFileExtension)"/>
    <Message Text="MSBuildThisFileFullPath: $(MSBuildThisFileFullPath)"/>
    <Message Text="MSBuildThisFileName: $(MSBuildThisFileName)"/>
    <Message Text="MSBuildToolsPath: $(MSBuildToolsPath)"/>
    <Message Text="MSBuildToolsVersion: $(MSBuildToolsVersion)"/>
  </Target>

</Project>

For me the results are:

C:\Data\Development\My Code\Community\MSBuild>msbuild ReservedProps02.proj /m /nologo
Build started 3/18/2010 12:43:49 AM.
     1>Project "C:\Data\Development\My Code\Community\MSBuild\ReservedProps02.proj" on node 1 (default targets).
     1>PrintValues:
         MSBuild:
         MSBuildBinPath: C:\Windows\Microsoft.NET\Framework\v4.0.30128
         MSBuildExtensionsPath: C:\Program Files (x86)\MSBuild
         MSBuildExtensionsPath32: C:\Program Files (x86)\MSBuild
         MSBuildExtensionsPath64: C:\Program Files\MSBuild
         MSBuildLastTaskResult: true
         MSBuildNodeCount: 8
         MSBuildOverrideTasksPath:
         MSBuildProgramFiles32: C:\Program Files (x86)
         MSBuildProjectDefaultTargets: PrintValues
         MSBuildProjectDirectory: C:\Data\Development\My Code\Community\MSBuild
         MSBuildProjectDirectoryNoRoot: Data\Development\My Code\Community\MSBuild
         MSBuildProjectExtension: .proj
         MSBuildProjectFile: ReservedProps02.proj
         MSBuildProjectFullPath: C:\Data\Development\My Code\Community\MSBuild\ReservedProps02.proj
         MSBuildProjectName: ReservedProps02
         MSBuildStartupDirectory: C:\Data\Development\My Code\Community\MSBuild
         MSBuildThisFile: ReservedProps02.proj
         MSBuildThisFileDirectory: C:\Data\Development\My Code\Community\MSBuild\
         MSBuildThisFileDirectoryNoRoot: Data\Development\My Code\Community\MSBuild\
         MSBuildThisFileExtension: .proj
         MSBuildThisFileFullPath: C:\Data\Development\My Code\Community\MSBuild\ReservedProps02.proj
         MSBuildThisFileName: ReservedProps02
         MSBuildToolsPath: C:\Windows\Microsoft.NET\Framework\v4.0.30128
         MSBuildToolsVersion: 4.0
     1>Done Building Project "C:\Data\Development\My Code\Community\MSBuild\ReservedProps02.proj" (default targets
       ).

If you want to see the correct valus for MSBuildNodeCount make sure to use the /m switch when you invoke msbuild.exe.

I won’t go over these properties in detail here, because they are mostly obvious but I would like to point out a couple really useful properties, those include.

MSBuildThisFile
MSBuildThisFileDirectory
MSBuildThisFileDirectoryNoRoot

These properties can be used to locate the path to the file that you are currently in. So if you have a shared .targets file and it will execute an .exe in the same folder you can use the MSBuildThisFileDirectory property to resolve the full path to that tool reliably. This has historically been difficult. See a recent question on Stackoverflow.com about it at How can I get the path of the current msbuild file? If you are not using MSBuild 4.0 and need to resolve the location to a .targets file then see that post for what you will need to do.

msbuild | MSBuild 4.0 | Visual Studio 2010 Thursday, March 11, 2010 12:53:51 AM (GMT Standard Time, UTC+00:00)  #     | 
Sunday, March 07, 2010

MSBuild 4.0: Property Functions Part 1

I have previously blogged about some new features in MSBuild 4.0 at:

Besides inline tasks there are a set of other new features including Property Functions. In this post we will discuss property functions and how you might use them in your build scripts. With property functions you can call an instance method of the string object on properties now.

The syntax will be in the format $(PropertyName.MethodName([Parameters])) when you want to invoke a string method. Parameters in the previous expression is optional. For instance if you need to call the Trim method then you do not need to supply any arguments. Take a look at the snippet below to get a better feel for how to use these new features.

<Project ToolsVersion="4.0" 
DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <SampleString>This is a sample string</SampleString> <SampleString2> This is a sample string </SampleString2> <!-- Here we now can call instance methods on the String class --> <Sub04>$(SampleString.Substring(0,4))</Sub04> <Contains01>$(SampleString.Contains("This"))</Contains01> <Contains02>$(SampleString.Contains("this"))</Contains02> <CompareTo01>$(SampleString.CompareTo($(SampleString)))</CompareTo01> <EndsWith01>$(SampleString.CompareTo("string"))</EndsWith01> <Insert01>$(SampleString.Insert(2,"INSERTED"))</Insert01> <Trim01>$(SampleString2.Trim())</Trim01> </PropertyGroup> <Target Name="Demo"> <Message Text="SampleString: $(SampleString)" Importance="high"/> <Message Text="Sub04: $(Sub04)" Importance="high"/> <Message Text="Contains01: $(Contains01)" Importance="high"/> <Message Text="Contains02: $(Contains02)" Importance="high"/> <Message Text="CompareTo01: $(CompareTo01)" Importance="high"/> <Message Text="EndsWith01: $(EndsWith01)" Importance="high"/> <Message Text="Insert01: $(Insert01)" Importance="high"/> <Message Text="Trim01: $(Trim01)" Importance="high"/> </Target> </Project>

In this snippet I am calling various string methods on the properties defined. The results of executing this are shown in the fragment below.

Demo:
  SampleString: This is a sample string
  Sub04: This
  Contains01: True
  Contains02: False
  CompareTo01: 0
  EndsWith01: 1
  Insert01: ThINSERTEDis is a sample string
  Trim01: This is a sample string

This is just one way that you can use property functions. We can also call a static methods (and properties) on a known set of classes. See http://msdn.microsoft.com/en-us/library/dd633440%28VS.100%29.aspx for the list of complete classes that you can call static methods on. The syntax is as follows $([Full-Class-Name]::Method(Parameters)) or if you are calling a property you just leave off the (Parameters). To demonstrate this I have created the following file.

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

  <PropertyGroup>
    <!-- 
      Here we Now01 can call static methods/properties on many types 
      See http://msdn.microsoft.com/en-us/library/dd633440%28VS.100%29.aspx
      for the list of classes that we can call static methods/properties on.
    -->
    <Now01>$([System.DateTime]::Now)</Now01>
    <Pow01>$([System.Math]::Pow(2,3))</Pow01>
    <TempFile01>$([System.IO.Path]::GetTempFileName())</TempFile01>
  </PropertyGroup>
  
  <Target Name="Demo">
    <Message Text="Now01: $(Now01)" Importance="high"/>
    <Message Text="Pow01: $(Pow01)" Importance="high"/>
    <Message Text="TempFile01: $(TempFile01)" Importance="high"/>
  </Target>

</Project>

And the result is:

Demo:
  Now01: 3/6/2010 7:31:23 PM
  Pow01: 8
  TempFile01: C:\Users\Ibrahim\AppData\Local\Temp\tmp4C1.tmp

Similar to this you can call a handful of MSBuild methods. Those methods are documented at http://msdn.microsoft.com/en-us/library/dd633440%28VS.100%29.aspx. You would use a similar syntax to access those which is $([MSBuild]::Method(Parameters)). To show you those take a look at the follwing sample file.

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

  <PropertyGroup>
    <Add>$([MSBuild]::Add(5,9))</Add>
    <Subtract01>$([MSBuild]::Subtract(90,768))</Subtract01>
    <Mult01>$([MSBuild]::Multiply(4,9))</Mult01>
    <Div01>$([MSBuild]::Divide(100,5.2))</Div01>
  </PropertyGroup>
  
  <Target Name="Demo">
    <Message Text="Add: $(Add)" Importance="high"/>
    <Message Text="Subtract01: $(Subtract01)" Importance="high"/>
    <Message Text="Mult01: $(Mult01)" Importance="high"/>
    <Message Text="Div01: $(Div01)" Importance="high"/>
  </Target>

</Project>

I will leave it up to you to execute that file to see the result, but you can probably figure it out :)

Sayed Ibrahim Hashimi

msbuild | MSBuild 4.0 Sunday, March 07, 2010 12:35:29 AM (GMT Standard Time, UTC+00:00)  #     | 
Friday, January 22, 2010

MSBuild 4.0: Inline Tasks Part 2

This post contains based on .NET 4.0 Beta 2 and Visual Studio 2010 Beta 2 which may change.

The other day I wrote my first post on Inline Tasks in MSBuild 4.0, this post will add onto that topic. In the previous post we covered some basics, but there were a lot that was skipped. Last time we demonstrated how to pass parameters but we never declared what type those parameters were. If you declare a parameter and leave off the type, then it will be declared as a string. If you need to declare parameters of other types then you need to use the ParameterType attribute on the parameter. You have to pass in the full name of the type to be used. For example if you need to declare an int you must use System.Int32, not int and not Int32 but System.Int32. It would be good if they supported aliases like int, string, long, etc but right now they don't. Below you'll find a new inline task which can be used to perform a substring. I've placed this in a file named IT-Substring-01.proj.

<!--

Sample Demonstrates Inline Tasks

© 2010 Sayed Ibrahim Hashimi

-->

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

 

  <UsingTask

    TaskName="Substring"

    TaskFactory="CodeTaskFactory"

    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >

    <ParameterGroup>

      <Input Required="true" />

      <StartIndex Required="true" ParameterType="System.Int32" />

      <Length ParameterType="System.Int32" />

      <Result Output="true" />

    </ParameterGroup>

    <Task>

      <Code Type="Fragment" Language="cs">

        <![CDATA[

        if (Length > 0)

        {

            Result = Input.Substring(StartIndex, Length);

        }

        else

        {

            Result = Input.Substring(StartIndex);

        }

        ]]>

      </Code>

    </Task>

  </UsingTask>

 

  <Target Name="Demo">

    <Substring StartIndex="8" Input="Demo of Inline Tasks">

      <Output PropertyName="taskValue" TaskParameter="Result"/>

    </Substring>

    <Message Text="Value from task: $(taskValue)" />

   

    <Substring StartIndex="0" Length="4" Input="Demo of Inline Tasks">

      <Output PropertyName="taskValue" TaskParameter="Result"/>

    </Substring>

    <Message Text="Value from task: $(taskValue)" />

   

  </Target>

</Project>

In the task above I have declared 4 parameters, from those two have the type specified to be int. If we execute the Demo target here is the result.

From the above image you can see that the task performs the actions requested. One thing to make a mental note of above is my usage of a CDATA tag to wrap the definition of the task. I would suggest that you do so for all inline tasks that you write.

The types supported for parameters on inline types are the same for normal types. For a detailed account of that see my book, but as a general guideline it accepts string, primitive types, ITaskItem, and arrays of all three.

Let's see how we can use an array in an inline task. I created a simple task which will create a list of Guids and place them into an array and return the result back to the calling MSBuild script, the file I placed this is in named IT-CreateGuid-02.proj. The contents of that file are shown in the snippet below.

<!--

Sample Demonstrates Inline Tasks

© 2010 Sayed Ibrahim Hashimi

-->

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

 

  <UsingTask

    TaskName="CreateGuid02"

    TaskFactory="CodeTaskFactory"

    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >

    <ParameterGroup>

      <NumToCreate ParameterType="System.Int32" Required="true" />

      <Guids ParameterType="System.String[]" Output="true" />

    </ParameterGroup>

    <Task>

      <Code Type="Fragment" Language="cs">

        <![CDATA[

            List<string> guids = new List<string>();

            for (int i = 0; i < NumToCreate; i++)

            {

                guids.Add(Guid.NewGuid().ToString());

            }

            Guids = guids.ToArray();

        ]]>

      </Code>

    </Task>

  </UsingTask>

 

  <Target Name="Demo">

    <CreateGuid02 NumToCreate="1">

      <Output ItemName="Id01" TaskParameter="Guids" />

    </CreateGuid02>

    <Message Text="Id01: @(Id01)" Importance="high" />

 

    <CreateGuid02 NumToCreate="4">

      <Output ItemName="Id02" TaskParameter="Guids" />

    </CreateGuid02>

    <Message Text=" " Importance="high" />

    <Message Text="Id02: @(Id02)" Importance="high" />

  </Target>

 

</Project>

Above you can see that I declared the parameter Guids as System.String[]. I typically prefer to use generic lists instead of arrays so in my task definitions I will use a list and then just call ToArray when I assign the output parameter. Here is a nifty, but very simplistic task. Given an item list, it will filter them based on a Regular Expression passed in.

<!--

Sample Demonstrates Inline Tasks

© 2010 Sayed Ibrahim Hashimi

-->

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

 

  <UsingTask

    TaskName="FilterList"

    TaskFactory="CodeTaskFactory"

    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >

    <ParameterGroup>

      <ListToFilter ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />

      <Filter Required="true" />

      <FilteredList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />

    </ParameterGroup>

    <Task>

      <Using Namespace="System.Text.RegularExpressions" />

      <Code Type="Fragment" Language="cs">

        <![CDATA[

            var results = (from l in ListToFilter

                           where Regex.IsMatch(l.ItemSpec, Filter)

                           select l).ToList();

 

            FilteredList = results.ToArray();

        ]]>

      </Code>

    </Task>

  </UsingTask>

 

  <ItemGroup>

    <Source Include="src\01.cs" />

    <Source Include="src\02.cs" />

    <Source Include="test\test01.cs" />

    <Source Include="test\sub\test02.cs" />

    <Source Include="test\sub\test03.cs" />

    <Source Include="test\sub\sub2\test04.cs" />

  </ItemGroup>

 

  <Target Name="Demo">

    <FilterList ListToFilter="@(Source)" Filter="test">

      <Output ItemName="_filteredList" TaskParameter="FilteredList" />

    </FilterList>

    <Message Text="Filter: test. Results: @(_filteredList)" />

   

    <!-- Clear the list before calling again -->

    <ItemGroup>

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

    </ItemGroup>

 

    <Message Text="======" />

    <FilterList ListToFilter="@(Source)" Filter="sub\\">

      <Output ItemName="_filteredList" TaskParameter="FilteredList" />

    </FilterList>

    <Message Text="Filter: .\sub. Results: @(_filteredList)" />

  </Target>

</Project>

Here you see that you can you can perform LINQ queries inside of inline tasks without any additional setup. Here is the result of executing the Demo target on this script.

Another thing to notice is the Using element under the Task element. This injects a Using statement into the generated class for the given namespace. If you need to reference another assembly you can do this with the Reference element under the Task element.

 

Sayed Ibrahim Hashimi

inline task | msbuild | MSBuild 4.0 | Visual Studio 2010 Friday, January 22, 2010 4:01:46 AM (GMT Standard Time, UTC+00:00)  #     | 
Wednesday, January 20, 2010

MSBuild 4.0: Inline Tasks

This post contains based on .NET 4.0 Beta 2 and Visual Studio 2010 Beta 2 which may change.

If you didn't already know, MSBuild will have a new version shipped with .NET 4.0 (Visual Studio 2010 will use this new version). I will cover many of those features here. This is the first in a series of posts that I will make regarding MSBuild 4.0. One of the big additions to MSBuild 4.0 is Inline Tasks. I'm pretty excited about this new addition. The story before was if you wanted to perform any action it was always through a Task. Which worked pretty well, but the major drawback is that the tasks needed to be written in code and then compiled into an assembly in order for it to be used. What this meant was if you wanted to perform an action (however simple) that wasn't covered out of the box you would have to look for 3rd party tasks or write one yourself. This is time consuming and can be tricky from a "deployment" perspective. Now with Inline Tasks you can declare the behavior of the task right inside of the MSBuild file itself.

Inline Task

The way that Inline Tasks are supported is by using the UsingTask XML element. This element was around before but it has some new options. If you read my book, you probably saw a few different Hello World tasks that I created. In order to demonstrate Inline Tasks I have taken a similar set of examples. Take a look at the project file (IT-HelloWorld-01.proj) below.

<!--

Sample Demonstrates Inline Tasks

© 2010 Sayed Ibrahim Hashimi

-->

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

 

  <UsingTask

    TaskName="HelloWorld"

    TaskFactory="CodeTaskFactory"

    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >

    <Task>

      <Code Type="Fragment" Language="cs">

        Log.LogMessage("Hello World!");

      </Code>

    </Task>

  </UsingTask>

 

  <Target Name="Demo">

    <HelloWorld />

  </Target>

 

</Project>

For this basic inline task here are the key things to note, the name of the task is specified in the TaskName attribute. You will use this like you would any other task inside of targets. The definition of the task will be contained in the Code XML element. In order to call that task, I use the syntax <HelloWorld /> inside of the Demo target. The same exact way "classic" tasks are called. Here is the result.

The result shown above is as expected.

Inline Task with Parameters

You can also make inline tasks with parameters, both required and optional. Here is an example (IT-HelloWorld-02.proj) with a lone required parameter.

<!--

Sample Demonstrates Inline Tasks

© 2010 Sayed Ibrahim Hashimi

-->

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

 

  <UsingTask

    TaskName="HelloWorld"

    TaskFactory="CodeTaskFactory"

    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >

    <ParameterGroup>

      <Name Required="true"/>

    </ParameterGroup>

    <Task>

      <Code Type="Fragment" Language="cs">

        Log.LogMessage(string.Format("Hello {0}",Name));

      </Code>

    </Task>

  </UsingTask>

 

  <PropertyGroup>

    <YourName Condition=" '$(YourName)'=='' ">Sayed</YourName>

  </PropertyGroup>

 

  <Target Name="Demo">

    <HelloWorld Name="$(YourName)" />

  </Target>

 

  <Target Name="DemoWithNoName">

    <!-- This shows the result if you don't pass in a required param -->

    <HelloWorld />

  </Target>

 

</Project>

If you take a look at the UsingTask declaration above you will see that I included a ParameterGroup element. All parameters must be included under that element. In this case we just have one. If we execute the Demo target the result will be as follows.

We can see that the message was sent to the console as we expected. You can execute the DemoWithNoName if you are interested in verifying that MSBuild will ensure that required parameters are set.

Inline Tasks with Output Parameter

You can also create your own inline tasks which have one or more output parameters. You will define those output parameters under the ParameterGroup element and use the Output="true" attribute. Take a look at IT-HelloWorld-03.proj below.

<!--

Sample Demonstrates Inline Tasks

© 2010 Sayed Ibrahim Hashimi

-->

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

 

  <UsingTask

    TaskName="HelloWorld"

    TaskFactory="CodeTaskFactory"

    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >

    <ParameterGroup>

      <Name Required="true"/>

      <TaskMessage Output="true"/>

    </ParameterGroup>

    <Task>

      <Code Type="Fragment" Language="cs">

        TaskMessage = string.Format("Hello {0}",Name);

        Log.LogMessage(TaskMessage);

      </Code>

    </Task>

  </UsingTask>

 

  <PropertyGroup>

    <YourName Condition=" '$(YourName)'=='' ">Sayed</YourName>

  </PropertyGroup>

 

  <Target Name="Demo">

    <HelloWorld Name="$(YourName)">

      <Output PropertyName="MsgFromTask" TaskParameter="TaskMessage"/>

    </HelloWorld>

    <Message Text="Message from task: $(MsgFromTask)" Importance="high" />

  </Target>

 

</Project>

In this example I created a HelloWorld task to output the entire message back to the calling MSBuild task invocation inside the target. This output parameter is the TaskMessage parameter. Inside the Demo target I call the task and then print the value that was passed back from the task. Here is the result of that.

From here we can see that the value was correctly sent back to the script.

 

There is a lot more to inline tasks and I plan on covering more features very soon here, but this should get you started.

 

Sayed Ibrahim Hashimi

inline task | msbuild | MSBuild 4.0 | Visual Studio 2010 Wednesday, January 20, 2010 10:31:12 PM (GMT Standard Time, UTC+00:00)  #     |