I have a problem with the Xml.ModifyFile task which I do not understand. Can you guys help?
My goal is simply to manipulate an attribute in an xml document.
Im fairly new to the world of xml and especially msbuild hence I how a hard time interpreting the error message i am receiving. It seems to me that my build file is valid so I guess something is wrong in sdc.tasks dll file.
As it can be seen from the build file I have added a target called "ping" for the sake of testing. That target works with the sdc.task Ping without any problems
Can you guys suggest a fix or an alternative solution to the challenge of modifying xml files with msbuild.
An additional question - how do one declare multiple namespaces as argument to the Xml.ModifyFile sdc.task? The explanation of the namespace attribute is as follows:
An array of TaskItems specifiying "Prefix" and "Uri" attributes for use with the specified xPath. I have tried to find an explanation or example of the usage of taskitems but unfortunately without any luck.
thanks / derdres
I will list the following below:
build file
the xml file that I try to modify
the error message
1) build file
<Target Name="Go">
<CallTarget Targets="modify"></CallTarget>
<!--<CallTarget Targets="ping"></CallTarget>-->
</Target>
<Target Name="modify">
<Xml.ModifyFile
Path="C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\bookstore_adv.xml"
AttributeName="age"
Force="true"
XPath="/bookstore/book[#id=2]/#age"
NewValue="200"
ShowMatches="Yes"
>
</Xml.ModifyFile>
<Message Text="After modification"></Message>
</Target>
<!--<Target Name="ping">
<Ping
Machine="localhost"
Count="2"
Interval="1000"
Timeout="3000"
BufferSize="1024"
AllowFragmentation="false"
TimeToLive="128"
StopOnSuccess="true"
LogSuccess="true">
<Output TaskParameter="FailureCount" PropertyName="FailedPingCount" />
<Output TaskParameter="RoundTripTime" PropertyName="RoundTripDuration" />
</Ping>
<Message Text="FailedPingcount: $(FailedPingCount)"></Message>
<Message Text="RoundTripDuration: $(RoundTripDuration)"></Message>
</Target>-->
2) xml file
<?xml version="1.0" encoding="utf-8"?>
<!--<bookstore xmlns:hat="www.google.dk/hat" xmlns:briller="www.google.dk/briller">-->
<!--<bookstore xmlns:hat="www.google.dk/hat">-->
<bookstore>
<book id="1">
<title>Harry Potter</title>
<author>Rowling</author>
</book>
<book id="2" age="100">
<title>Lykke Per</title>
<author>Pontoppidan</author>
</book>
3) Build Error Message
Build FAILED.
"C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj" (default target) (1) ->
(modify target) ->
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : A task error has occured.\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : Message = Object reference not set to
an instance of an object.\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : Action = Replace\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : Path = C:\Users\Andreas\Desktop\MS
Build\Test_05_april\Test01\bookstore_advanced.xml\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : Namespace = <null>\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : XPath = /bookstore/book[#id=2]/#age
\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : RegularExpression = <String.Empty>\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : NewValue = 200\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : AttributeName = age\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : Force = True\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : TreatNewValueAsXml = False\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : ShowMatches = Yes\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : \r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : at Microsoft.Sdc.Tasks.Xml.ModifyFile.Interna
lExecute() in c:\projects\codeplex\sdctasks\Solutions\Main\Tasks\Xml\ModifyFile.cs:line 346\r
C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\Deploy.proj(11,9): error : at Microsoft.Sdc.Tasks.TaskBase.Execute() in
c:\projects\codeplex\sdctasks\Solutions\Main\Tasks\TaskBase.cs:line 66
0 Warning(s)
1 Error(s)
Time Elapsed 00:00:00.20
In your XPath you are searching for the attribute age /bookstore/book[#id=2]/#age but in your task you set the AttributeName to "age". So it is like you want the attribute age of the attribute age.
You just have to change your XPath to /bookstore/book[#id=2] to make it work.
<Target Name="modify">
<Xml.ModifyFile
Path="C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\bookstore_adv.xml"
AttributeName="age"
Force="true"
XPath="/bookstore/book[#id=2]"
NewValue="200"
ShowMatches="Yes">
</Xml.ModifyFile>
<Message Text="After modification"/>
</Target>
How do one declare multiple namespaces as argument to the Xml.ModifyFile sdc.task?
<ItemGroup>
<Namespace Include="www.google.dk/briller">
<Prefix>briller</Prefix>
<Uri>www.google.dk/briller</Uri>
</Namespace>
<Namespace Include="www.google.dk/hat">
<Prefix>hat</Prefix>
<Uri>www.google.dk/hat</Uri>
</Namespace>
</ItemGroup>
<Target Name="modify">
<Xml.ModifyFile
Path="C:\Users\Andreas\Desktop\MSBuild\Test_05_april\Test01\bookstore_adv.xml"
AttributeName="age"
Force="true"
XPath="/bookstore/book[#id=2]"
NewValue="200"
ShowMatches="Yes"
Namespace="#(Namespace)">
</Xml.ModifyFile>
<Message Text="After modification"/>
</Target>
Your XML File Is Invalid.
I Tried It On My Machine , And It Worked Fine.
Just Close The bookstore Tag.
Related
I have a custom MSBuild target, partial snippet as follows ..
<Target Name="PublishHtm">
<PropertyGroup>
<PublishHtmTemplateContents>$([System.IO.File]::ReadAllText($(PublishHtmTemplatePath)))</PublishHtmTemplateContents>
<PublishHtm>$(PublishHtmTemplateContents)</PublishHtm>
</PropertyGroup>
<WriteLinesToFile Lines="$(PublishHtm)" File="$(PublishDir)\publish.htm" Overwrite="true"/>
</Target>
This is a rework attempt for this solution in that I'm trying to isolate this template to an external file. The template contains MSBuild property references such as $(ApplicationName). When doing this exactly as described in the linked solution, it works fine, but when loading the template in as a string, none of these property expressions are evaluated by the time it gets to the file.
<SPAN CLASS="BannerTextApplication">$(ApplicationName)</SPAN>
Is there an MSBuild expression/function I can use to get the string to be reevaluated given the context that the Target is being invoked?
BTW I'd rather not work around the problem using find/replace or regex replace, and stick with the MSBuild expression engine.
Using Visual Studio 2012 & .NET Framework 4.5.
Sorry for not getting back to this question for awhile.
Initially I thought that to solve this problem we'll need to bend MSBuild in very unusual way (plan for today was to write complex inline task which will do regex-replace in external file using Msbuild properties as tokens ). But I think this can be solved easier, using CDATA section, which is valid inside property definition:
Here is main script:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<PropertyGroup>
<MyOtherProperty>$([System.DateTime]::Now)</MyOtherProperty>
<Version>1.0.1b</Version>
<ProjectName>MSBuild Rox</ProjectName>
<Author>Alexey Shcherbak</Author>
</PropertyGroup>
<Target Name="Build">
<ItemGroup>
<PropsToPass Include="MyOtherProperty=$(MyOtherProperty)" />
<PropsToPass Include="Version=$(Version)" />
<PropsToPass Include="ProjectName=$(ProjectName)" />
<PropsToPass Include="Author=$(Author)" />
</ItemGroup>
<MSBuild Projects="TransformHTML.Template.proj" Properties="#(PropsToPass)" />
</Target>
</Project>
And here is your template. It's not pure html, it's still msbuild file, but at least without ugly encoding for html tags in xml. It's just a block in CDATA
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Transform">
<PropertyGroup>
<HtmlProperty><![CDATA[
<body>
<div>$(MyOtherProperty)</div>
<div>$(Version)</div>
<div>$(ProjectName)</div>
<div>$(Author)</div>
</body>
]]></HtmlProperty>
</PropertyGroup>
<Target Name="Transform">
<Message Text="HtmlProperty: $(HtmlProperty)" Importance="High" />
</Target>
</Project>
Maybe it's not very elegant ( I personally don't like the section with #PropsToPass) but it'll do the job. You can put everything inline into single file and then you don't need to pass properties to MSBuild task. I don't like massive html-encoding from proposed "this solution" but I'd rather prefer to keep HTML template in the same script where it'll be transformed, just in nice html format, without encoding.
Single file example:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<PropertyGroup>
<MyOtherProperty>$([System.DateTime]::Now)</MyOtherProperty>
<Version>1.0.1b</Version>
<ProjectName>MSBuild Rox</ProjectName>
<Author>Alexey Shcherbak</Author>
</PropertyGroup>
<Target Name="Build">
<PropertyGroup>
<HtmlProperty><![CDATA[
<body>
<div>$(MyOtherProperty)</div>
<div>$(Version)</div>
<div>$(ProjectName)</div>
<div>$(Author)</div>
</body>
]]></HtmlProperty>
</PropertyGroup>
<Message Text="HtmlProperty: $(HtmlProperty)" Importance="High" />
</Target>
</Project>
You can also download these files here
You can do it using Eval task
<Target Name="PublishHtm">
<PropertyGroup>
<PublishHtmTemplateContents>$([System.IO.File]::ReadAllText($(PublishHtmTemplatePath)))</PublishHtmTemplateContents>
<Eval Values="$(PublishHtmTemplateContents)">
<Output TaskParameter="Result" ItemName="EvalItemTemp"/>
</Eval>
<PublishHtm>%(EvalItemTemp.Identity)</PublishHtm>
</PropertyGroup>
<WriteLinesToFile Lines="$(PublishHtm)" File="$(PublishDir)\publish.htm" Overwrite="true"/>
</Target>
Actually the task does nothing except returning exactly the same value it received, however when you pass the returned value %(EvalItemTemp.Identity) to anywhere, msbuild does evaluation!
Eval task source:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Choose>
<When Condition="'$(MSBuildToolsVersion)' == 'Current' OR $(MSBuildToolsVersion.Split('.')[0]) >= 14">
<PropertyGroup>
<TasksAssemblyName>Microsoft.Build.Tasks.Core.dll</TasksAssemblyName>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<TasksAssemblyName>Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll</TasksAssemblyName>
</PropertyGroup>
</Otherwise>
</Choose>
<UsingTask TaskName="Eval" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\$(TasksAssemblyName)">
<ParameterGroup>
<Values ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="True" Output="False" />
<Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="False" Output="True" />
</ParameterGroup>
<Task>
<Code Type="Class" Language="cs" Source="$(MSBuildThisFileDirectory)TaskSource\EvalTask.cs"/>
</Task>
</UsingTask>
</Project>
Where TaskSource\EvalTask.cs is
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Diagnostics;
using System.Threading;
namespace Varonis.MSBuild.Tasks
{
public class Eval : Task
{
[Required]
public ITaskItem[] Values { get; set; }
[Output]
public ITaskItem[] Result { get; set; }
public override bool Execute()
{
Result = new TaskItem[Values.Length];
for (int i = 0; i < Values.Length; i++)
{
Result[i] = new TaskItem(Values[i].ItemSpec);
}
return true;
}
}
}
This is problematic because any Properties being passed in are lost.
Further Explanation: I pass in a property to the project file. This property is a path to a .props file. It contains tokens and replacement values for the detokenise class. The task apparently reloads the project and the path is not maintained. This doesn't seem to be the case for other task, for example the guid tasks.
In the example I am using a example proj entitled guids.proj
Invoked Using :
<MSBuild.ExtensionPack.FileSystem.Detokenise TaskAction="Detokenise" TargetFiles="#(FileCollectionToBeDetokenized )"/>
Some command line out put follows :
Task "MSBuild.ExtensionPack.FileSystem.Detokenise" (TaskId:11)
Detokenise Task Execution Started [13:04:35] (TaskId:11)
Loading Project: C:\Users\bstrausser\Desktop\guids.proj (TaskId:11)
Detokenising Collection: 1 files (TaskId:11)
C:\Users\*****\Desktop\guids.proj(37,9): error : Property not found: Asset
Directory
Full project file :
Project ToolsVersion="4.0" DefaultTargets="Default" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(ParentMSBuildProjectDirectory)\Bin\MSBuild\ExtensionPack\MSBuild.ExtensionPack.tasks" Condition="Exists('$(ParentMSBuildProjectDirectory)\Bin\MSBuild\ExtensionPack\MSBuild.ExtensionPack.tasks')"/>
<Import Project="C:\Program Files (x86)\MSBuild\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks" Condition="!Exists('$(ParentMSBuildProjectDirectory)\Bin\MSBuild\ExtensionPack\MSBuild.ExtensionPack.tasks') AND Exists('C:\Program Files (x86)\MSBuild\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks')"/>
<PropertyGroup>
<TPath>$(MSBuildProjectDirectory)\..\MSBuild.ExtensionPack.tasks</TPath>
<TPath Condition="Exists('$(MSBuildProjectDirectory)\..\..\Common\MSBuild.ExtensionPack.tasks')">$(MSBuildProjectDirectory)\..\..\Common\MSBuild.ExtensionPack.tasks</TPath>
<PROPS>$(DACP)</PROPS>
</PropertyGroup>
<Import Project="$(PROPS)" Condition="'$(DACP)' != ''" />
<Target Name="Default">
<Message text = "$(DACP)" />
<!-- Create a new Guid and get the formatted and unformatted values -->
<MSBuild.ExtensionPack.Framework.Guid TaskAction="Create">
<Output TaskParameter="FormattedGuidString" PropertyName="FormattedGuidString1" />
<Output TaskParameter="GuidString" PropertyName="GuidStringItem" />
</MSBuild.ExtensionPack.Framework.Guid>
<Message Text="GuidStringItem: $(GuidStringItem)"/>
<Message Text="FormattedGuidString: $(FormattedGuidString1)"/>
<!-- Create a new cryptographically strong Guid and get the formatted and unformatted values -->
<MSBuild.ExtensionPack.Framework.Guid TaskAction="CreateCrypto">
<Output TaskParameter="FormattedGuidString" PropertyName="FormattedGuidString1" />
<Output TaskParameter="GuidString" PropertyName="GuidStringItem" />
</MSBuild.ExtensionPack.Framework.Guid>
<Message Text="GuidStringItem Crypto: $(GuidStringItem)"/>
<Message Text="FormattedGuidString Crypto: $(FormattedGuidString1)"/>
<ItemGroup>
<FileCollectionToBeDetokenized Include="C:\Code\MSBuildGit\Configuration\TaskExecutorConfigTransforms\App.GREEN.SCRATCH.config"/>
</ItemGroup>
<Message text = "BaseUrl : $(BaseUrl)" />
<Message text = "DetokenizedTransformFile : #(FileCollectionToBeDetokenized)" />
<MSBuild.ExtensionPack.FileSystem.Detokenise TaskAction="Detokenise" TargetFiles="#(FileCollectionToBeDetokenized )"/>
</Target>
This msbuild below task can take into account one namespace, but in the case where I'm updating an mxml (flex) that has a mix of namespaces, can I use this task or another msbuild task to do the update?
<XmlUpdate
Prefix="fx"
Namespace="http://ns.adobe.com/mxml/2009"
XmlFileName="myFlexApp.mxml"
Xpath="//mx:Application/fx:Declarations/fx:String[#id='stringId']"
Value="xxxxx">
Here is the flex xml I'm trying to update:
<mx:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Declarations>
<fx:String id="stringId">UPDATE_ME</fx:String>
</fx:Declarations></mx:Application>
I was able to successfully update the source for XmlUpdate so that it takes multiple namespaces:
if (!string.IsNullOrEmpty(_prefix) && !string.IsNullOrEmpty(_namespace))
{
string[] prefixes = _prefix.Split(';');
string[] namespaces = _namespace.Split(';');
if (prefixes.Length != namespaces.Length)
throw new Exception("The number of prefixes is different from the number of namespaces");
for (int prefixIndex = 0; prefixIndex < prefixes.Length; prefixIndex++)
{
manager.AddNamespace(prefixes[prefixIndex], namespaces[prefixIndex]);
}
}
This works with the example of
<XmlUpdate
Prefix="fx;mx"
Namespace="http://ns.adobe.com/mxml/2009;library://ns.adobe.com/flex/mx"
XmlFileName="myFlexApp.mxml"
Xpath="//mx:Application/fx:Declarations/fx:String[#id='stringId']"
Value="xxxxx">
You'll have to use XmlMassUpdate task. (This task is from MSBuild Community Tasks)
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- The replacement value is here -->
<!-- ProjectExtensions keep MSBuild to try to evaluate the content -->
<ProjectExtensions>
<ReplacementNode>
<String id="stringId">CHANGE</String>
</ReplacementNode>
</ProjectExtensions>
<Target Name="XmlUpdate">
<XmlMassUpdate
ContentFile="myFlexApp.mxml"
NamespaceDefinitions="msb=http://schemas.microsoft.com/developer/msbuild/2003;
fx=http://ns.adobe.com/mxml/2009;
mx=library://ns.adobe.com/flex/mx"
ContentRoot="//mx:Application/fx:Declarations/fx:String[#id='stringId']"
SubstitutionsFile="$(MSBuildProjectFullPath)"
SubstitutionsRoot="msb:Project/msb:ProjectExtensions/msb:ReplacementNode/msb:String"/>
</Target>
</Project>
Changing the value during execution
The tricky part is that you can't define a value on fly using XmlMassUpdate only, you'll need to use XmlUpdate to update the value in your replacement node first.
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- The replacement value is here -->
<!-- ProjectExtensions keep MSBuild to try to evaluate the content -->
<ProjectExtensions>
<ReplacementNode>
<String id="stringId">CHANGE</String>
</ReplacementNode>
</ProjectExtensions>
<Target Name="XmlUpdate" DependsOnTargets="ChangeXmlValue">
<XmlMassUpdate
ContentFile="myFlexApp.mxml"
NamespaceDefinitions="msb=http://schemas.microsoft.com/developer/msbuild/2003;
fx=http://ns.adobe.com/mxml/2009;
mx=library://ns.adobe.com/flex/mx"
ContentRoot="//mx:Application/fx:Declarations/fx:String[#id='stringId']"
SubstitutionsFile="$(MSBuildProjectFullPath)"
SubstitutionsRoot="msb:Project/msb:ProjectExtensions/msb:ReplacementNode/msb:String"/>
</Target>
<Target Name="ChangeXmlValue">
<XmlUpdate Prefix="n"
Namespace="http://schemas.microsoft.com/developer/msbuild/2003"
XPath="n:Project/n:ProjectExtensions/n:ReplaceNode/n:String/text()"
XmlFileName="$(MSBuildProjectFullPath)"
Value="$(NewValue)" />
</Target>
</Project>
I have the following build script, which I run with MSBuild:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Compile" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion ="3.5">
<PropertyGroup>
<BuildDir Condition=" '$(BuildDir)'==' ' ">$(BaseDir)/build</BuildDir>
<ProdDir >$(BuildDir)/prod</ProdDir>
<TestDir>$(BuildDir)/test</TestDir>
<MMC2SourceDir>SteuerungsZugriffTest/mmc2</MMC2SourceDir>
<UserSourceDir>SteuerungsZugriffTest/user</UserSourceDir>
<TestXMLDir>$(BuildDir)/test-results</TestXMLDir>
<SolutionFile Condition=" '$(SolutionFile)'==' ' ">HMI2.0.sln</SolutionFile>"
<NUnitTest>nunit-console.exe</NUnitTest>
</PropertyGroup>
<Target Name="Prepare">
<Message Text="Prepare everything" />
<MakeDir Directories="$(BuildDir)" />
<MakeDir Directories="$(ProdDir)" />
</Target>
...
When I now start script on the command line:
D:\MyDir>msbuild /property:BaseDir=D:\MyDir MyScript.build
I got the following error on the commandline output:
D:\MyDir>MyScript.build(11,78): error MSB4067: Das <#text>-Element unterhalb des <PropertyGroup>-Elements ist unbekannt.
Which basically means: The element<#text> is an unknown child.
Does anybody have an idea?
Edit: Sorry, I completed now the script
You have an extra " at the end of this line
<SolutionFile Condition=" '$(SolutionFile)'==' ' ">HMI2.0.sln</SolutionFile>"
It is outside a tag, so it is considered as a text element...
How can I get MSBuild to evaluate and print in a <Message /> task an absolute path given a relative path?
Property Group
<Source_Dir>..\..\..\Public\Server\</Source_Dir>
<Program_Dir>c:\Program Files (x86)\Program\</Program_Dir>
Task
<Message Importance="low" Text="Copying '$(Source_Dir.FullPath)' to '$(Program_Dir)'" />
Output
Copying '' to 'c:\Program Files (x86)\Program\'
In MSBuild 4.0, the easiest way is the following:
$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)\your\path'))
This method works even if the script is <Import>ed into another script; the path is relative to the file containing the above code.
(consolidated from Aaron's answer as well as the last part of Sayed's answer)
In MSBuild 3.5, you can use the ConvertToAbsolutePath task:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="Test"
ToolsVersion="3.5">
<PropertyGroup>
<Source_Dir>..\..\..\Public\Server\</Source_Dir>
<Program_Dir>c:\Program Files (x86)\Program\</Program_Dir>
</PropertyGroup>
<Target Name="Test">
<ConvertToAbsolutePath Paths="$(Source_Dir)">
<Output TaskParameter="AbsolutePaths" PropertyName="Source_Dir_Abs"/>
</ConvertToAbsolutePath>
<Message Text='Copying "$(Source_Dir_Abs)" to "$(Program_Dir)".' />
</Target>
</Project>
Relevant output:
Project "P:\software\perforce1\main\XxxxxxXxxx\Xxxxx.proj" on node 0 (default targets).
Copying "P:\software\Public\Server\" to "c:\Program Files (x86)\Program\".
A little long-winded if you ask me, but it works. This will be relative to the "original" project file, so if placed inside a file that gets <Import>ed, this won't be relative to that file.
In MSBuild 2.0, there is an approach which doesn't resolve "..". It does however behave just like an absolute path:
<PropertyGroup>
<Source_Dir_Abs>$(MSBuildProjectDirectory)\$(Source_Dir)</Source_Dir_Abs>
</PropertyGroup>
The $(MSBuildProjectDirectory) reserved property is always the directory of the script that contains this reference.
This will also be relative to the "original" project file, so if placed inside a file that gets <Import>ed, this won't be relative to that file.
MSBuild 4.0 added Property Functions which allow you to call into static functions in some of the .net system dlls. A really nice thing about Property Functions is that they will evaluate out side of a target.
To evaluate a full path you can use System.IO.Path.GetFullPath when defining a property like so:
<PropertyGroup>
<Source_Dir>$([System.IO.Path]::GetFullPath('..\..\..\Public\Server\'))</Source_Dir>
</PropertyGroup>
The syntax is a little ugly but very powerful.
Wayne is correct that well-known metadata does not apply to properties - only to items. Using properties such as "MSBuildProjectDirectory" will work, but I'm not aware of a built in way to resolve the full path.
Another option is to write a simple, custom task that will take a relative path and spit out the fully-resolved path. It would look something like this:
public class ResolveRelativePath : Task
{
[Required]
public string RelativePath { get; set; }
[Output]
public string FullPath { get; private set; }
public override bool Execute()
{
try
{
DirectoryInfo dirInfo = new DirectoryInfo(RelativePath);
FullPath = dirInfo.FullName;
}
catch (Exception ex)
{
Log.LogErrorFromException(ex);
}
return !Log.HasLoggedErrors;
}
}
And your MSBuild lines would look something like:
<PropertyGroup>
<TaskAssembly>D:\BuildTasks\Build.Tasks.dll</TaskAssembly>
<Source_Dir>..\..\..\Public\Server\</Source_Dir>
<Program_Dir>c:\Program Files (x86)\Program\</Program_Dir>
</PropertyGroup>
<UsingTask AssemblyFile="$(TaskAssembly)" TaskName="ResolveRelativePath" />
<Target Name="Default">
<ResolveRelativePath RelativePath="$(Source_Dir)">
<Output TaskParameter="FullPath" PropertyName="_FullPath" />
</ResolveRelativePath>
<Message Importance="low" Text="Copying '$(_FullPath)' to '$(Program_Dir)'" />
</Target>
You are trying to access an item metadata property through a property, which isn't possible. What you want to do is something like this:
<PropertyGroup>
<Program_Dir>c:\Program Files (x86)\Program\</Program_Dir>
</PropertyGroup>
<ItemGroup>
<Source_Dir Include="..\Desktop"/>
</ItemGroup>
<Target Name="BuildAll">
<Message Text="Copying '%(Source_Dir.FullPath)' to '$(Program_Dir)'" />
</Target>
Which will generate output as:
Copying 'C:\Users\sdorman\Desktop' to 'c:\Program Files (x86)\Program\'
(The script was run from my Documents folder, so ..\Desktop is the correct relative path to get to my desktop.)
In your case, replace the "..\Desktop" with "......\Public\Server" in the Source_Dir item and you should be all set.
If you need to convert Properties to Items you have two options. With msbuild 2, you can use the CreateItem task
<Target Name='Build'>
<CreateItem Include='$(Source_Dir)'>
<Output ItemName='SRCDIR' TaskParameter='Include' />
</CreateItem>
and with MSBuild 3.5 you can have ItemGroups inside of a Task
<Target Name='Build'>
<ItemGroup>
<SRCDIR2 Include='$(Source_Dir)' />
</ItemGroup>
<Message Text="%(SRCDIR2.FullPath)" />
<Message Text="%(SRCDIR.FullPath)" />
</Target>