Wednesday, August 04, 2010

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

Wednesday, August 04, 2010 7:17:20 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Saturday, July 31, 2010

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

Saturday, July 31, 2010 5:22:28 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Saturday, May 01, 2010

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

Saturday, May 01, 2010 4:09:16 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Friday, April 30, 2010
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

Friday, April 30, 2010 1:38:03 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Monday, April 26, 2010

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

Monday, April 26, 2010 5:22:06 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Thursday, March 11, 2010

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.

Thursday, March 11, 2010 12:53:51 AM (GMT Standard Time, UTC+00:00)  #    Comments [2]  | 
Sunday, March 07, 2010

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

Sunday, March 07, 2010 12:35:29 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Friday, January 22, 2010

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

Friday, January 22, 2010 4:01:46 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Wednesday, January 20, 2010

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

Wednesday, January 20, 2010 10:31:12 PM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 

Theme design by Jelle Druyts