MSBuild, how can I get the second parent - msbuild

MSBuild (targets file), how can I get the second parent of an address like this?
<PropertyGroup>
<LibFolder>$([System.IO.Directory]::GetParent($(MSBuildProjectDirectory)))</LibFolder>
</PropertyGroup>
This is for one level parent, how can I get the 2nd level? (Parent of the parent)

Simply call the function again, on the previous result:
<PropertyGroup>
<LibFolder>$([System.IO.Directory]::GetParent($(MSBuildProjectDirectory)))</LibFolder>
<LibFolder>$([System.IO.Directory]::GetParent($(LibFolder)))</LibFolder>
</PropertyGroup>

Related

MSBuild 4.0 property functions cannot access properties inside of them

Is it a limitation of MSBuild 4.0 property functions that I cannot access a property from inside of one?
Here is an example that works just fine:
<PropertyGroup>
<PartialConnection>$(TargetConnectionString.Substring( 0 + 12))</PartialConnection>
</PropertyGroup>
Here is another example that doe snot work. (I replace the 0 with another property)
<PropertyGroup>
<LocationOfDataSource>$(TargetConnectionString.IndexOf("Data Source="))</LocationOfDataSource>
</PropertyGroup>
<Message Importance="high" Text="Location is = $(LocationOfDataSource)"/>
<PropertyGroup>
<PartialConnection>$(TargetConnectionString.Substring( $(LocationOfDataSource) + 12))</PartialConnection>
</PropertyGroup>
this outputs
Location is = 0
Error MSB4184: The expression ""Data Source=MySQLServer;Integrated Security=True;Pooling=False".Substring(0 + 12)" cannot be evaluated. Input string was not in a correct format.
I took the output and plugged into a console app and it works just fine. I have tried several variations and I they always fail when I put a property inside a property function. (I even tried access the same property twice in a my property function and that failed too.)
Do property functions not support accessing properties?
I think my issue was assuming that math came for free.
I needed to do this kind of thing:
<PropertyGroup>
<LocationOfDataSource>$(TargetConnectionString.IndexOf("Data Source="))</LocationOfDataSource>
<LenthOfDataSourceString>12</LenthOfDataSourceString>
<LocationOfEndOfDataSourceString>$([MSBuild]::Add($(LocationOfDataSource), $(LenthOfDataSourceString)))</LocationOfEndOfDataSourceString>
<PartialConnectionString>$(TargetConnectionString.Substring($(LocationOfEndOfDataSourceString)))</PartialConnectionString>
</PropertyGroup>
Note that I am adding using Add($(Property), $(Property)) in this version. Add is one of the built-in MSBuild Property Functions (since MSBuild 4).
It seems to be working now.

how is MS Build properties hierarchy maintained?

Can anyone tell me how does the MsBuild picks up the value of the property..??
eg.
<TempProperty>Property Value</TempProperty>
now I can use $(TempProperty) anywhere to get the value of it.
now the scenario is I have made custom task that has configuration like this..
<PropertyGroup>
<ItemList>
<ConfigChange>
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>$(TempProperty)</Value>
<IsList>False</IsList>
</ConfigChange>
</ItemList>
</PropertyGroup>
now instead of getting "Property Value" in the tag I am getting $(TempProperty)... can anyone tell me how to get actual value in tag ???
thanks in advance.
Hey, Guys I have got the actual Problem and solution too... I think I was not able to describe my problem here.. .the problem was.. In my custom task I was passing the file path of the file containing the above ItemList tag..now when I tried to parse the XMLNode "ItemList" it was getting "$(TempProperty)" as value in the Item.. and I think thats correct because thats what is present in the passed XML.
So to overcome the issue , I did two things..
I created ItemGroup instead of propertyGroup and passed that ItemGroup to my custom task instead of file path.. thus now at my code I am getting the desired values.
thanks for your replies.
There's not enough there to really diagnose your problem. It looks correct at first glance, but where is the PropertyGroup for TempProperty declared?
For an illustrative example, here's a snippet from a C# project file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
...
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
...
</PropertyGroup>
The Configuration Property is being set to Debug (if it is blank at at that point). In the following group, it keys off of the Confuration and Platform properties.
The only gotcha tidbit is that for a property value to show up properly, it must be declared before it is used.
Please try to provide a little more context, that may help with understanding the issue.
I just tried this:
<Project
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="4.0"
DefaultTargets="Demo" >
<PropertyGroup>
<TempProperty>property value</TempProperty>
<ItemList>
<ConfigChange>
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>$(TempProperty)</Value>
<IsList>False</IsList>
</ConfigChange>
</ItemList>
</PropertyGroup>
<Target Name="Demo">
<Message Text="TempProperty: $(TempProperty)"/>
<Message Text="ItemList: $(ItemList)"/>
</Target>
</Project>
And my results where:
Task "Message"
TempProperty: property value
Done executing task "Message".
Task "Message"
ItemList:
<ConfigChange xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>property value</Value>
<IsList>False</IsList>
</ConfigChange>
Done executing task "Message".
Are you seeing something else?
on Ritch's suggestion I am adding my solution.
Initially I had
<TempProperty>Property Value</TempProperty>
<PropertyGroup>
<ItemList>
<ConfigChange>
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>$(TempProperty)</Value>
<IsList>False</IsList>
</ConfigChange>
</ItemList>
</PropertyGroup>
now my problem was I was providing my properties file part to my custom task
like
<UpdatePegasusConfigXML
Environment="$(Environment)"
Instance="$(Instance)"
BuildSourceRoot="$(BuildSourceRoot)"
></UpdatePegasusConfigXML>
now since I was providing file path itself so it took the value which was put in "Value" tag and was not picking up the property value, thus at code level I was getting "$(TempProperty)" instead of "Property Value"
now what I did was instead of creating property Group I created ItemGroup like this
<ItemGroup>
<PlaceHolders Include="#MACHINE_NAME#">
<Value>$(TempProperty)</Value>
<IsList>True</IsList>
</PlaceHolders>
</ItemGroup>
now I updated my custom task to take IteamGroup as one of the inputs, thus calling changed to
<UpdatePegasusConfigXML
Environment="$(Environment)"
Instance="$(Instance)"
BuildSourceRoot="$(BuildSourceRoot)"
PlaceHolders="#(PlaceHolders)"
></UpdatePegasusConfigXML>
now at code level I am able to get the value in "Value" tag of the ItemGroup.
I hope I had explained my solution that it was understandable.

Can I get the success/failure of a task into a property if ContinueOnError="true" is used?

I'm executing a task in msbuild:
<MyTask ContinueOnError="true" OtherParam="Cheese"/>
<PropertyGroup>
<MyTaskResult>????how do I set this????</MyTaskResult>
<PropertyGroup>
I want to get the result of MyTask into a property (it's Execute method returns a bool) so I can test it and conditionally do stuff. Is this possible?
Thanks.
You could modify your task to put its results in an output parameter.

How can task parameters be defaulted in MSBuild

In mytask.targets, I have something like:
<UsingTask TaskName="DoStuff" AssemblyFile="....etc....."/>
<PropertyGroup>
<RequiredParamDefault>hello</RequiredParamDefault>
</PropertyGroup>
This task currently has a required parameter (which could be changed from required if necessary).
When the task is used:
<DoStuff RequiredParam="$(RequiredParamDefault)" OtherParam="wobble"/>
Currently, RequiredParam has to be specified everytime. Is there anyway that when UsingTask is defined, the default can be set up so it doesn't have to be specified on every use of DoStuff?
I know the default could be hardcoded in the assembly, but I'd like to be able to define different defaults with different UsingTask statements.
Thanks.
You can't do this at the UsingTask or Task but instead you can using properties that you pass into the task. For example.
<Target>
<PropertyGroup>
<ReqParam Condition=" '$(ReqParam)'=='' ">Param-Default-Value</ReqParam>
</PropertyGroup>
<DoStuff RequiredParam="$(ReqParam)" OtherParam="wobble"/>
</Target>
In this case I define the property ReqParam to be Param-Default-Value only if the property doesn't already have a value. This is not exactly what you are looking for, but it may be your best option unless you can change the task itself.

Find MSBuildProjectDirectory Parent Directory

MSBuild 3.5
I have the following project structure:
trunk/MainSolution.sln
trunk/Build/MyBuild.Proj
trunk/Library/...
trunk/etc...
So far, I've been using the following property to find out the project root folder:
<RootFolder>$(MSBuildProjectDirectory)\..\</RootFolder>
Everything was working great, until I tried using a copy task that relied on this path. It is not resolving correctly. I basically end up getting something like this which is not valid:
C:\Projects\MyProject\Trunk\Build\..\CodeAnalysis\myfile.xml
So basically, I need to get the full path for (MSBuildProjectDirectory)'s Parent.
Item metadata is your friend!
<Target Name="GetMSBuildProjectParentDirectory">
<!-- First you create the MSBuildProject Parent directory Item -->
<CreateItem Include="$(MSBuildProjectDirectory)\..\">
<Output ItemName="MSBuildProjectParentDirectory" TaskParameter="Include"/>
</CreateItem>
<!-- You can now retrieve its fullpath using Fullpath metadata -->
<Message Text="%(MSBuildProjectParentDirectory.Fullpath)"/>
<!-- Create a property based on parent fullpath-->
<CreateProperty Value="%(MSBuildProjectParentDirectory.Fullpath)">
<Output PropertyName="CodeFolder" TaskParameter="Value"/>
</CreateProperty>
</Target>
Nowadays with MSBuild 4.0 and above you don't want to use CreateItem or CreateProperty tasks anymore. What you are asking for can be solved easily with msbuild property functions:
<!-- Prints the parent directory's full path. -->
$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))
If you just want to read the parent directory's folder name you can combine the above statement with the GetFileName property function:
$([System.IO.Path]::GetFileName('$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))'))
A bit verbose but much better than the other answer as this works outside of targets and can be assigned to a property.
In case someone like me is still interested in this, here is how I did it in 2022 ^_^
<PropertyGroup>
<ParentFolderPath>$([System.IO.Directory]::GetParent($(MSBuildProjectDirectory)))</ParentFolderPath>
<ParentFolder>$([System.IO.Path]::GetFileName($(ParentFolderPath)))</ParentFolder>
...
</PropertyGroup>
I'm using this technique to auto-name the assemblies and default namespaces in the complex solutions.
<AssemblyName>$(ParentFolder).$(MSBuildProjectName)</AssemblyName>
<RootNamespace>$(ParentFolder).$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>