How get exec task output with msbuild - msbuild

I'm trying to get simple output by exec task with msbuild:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Test">
<Exec Command="echo test output">
<Output TaskParameter="Outputs" ItemName="Test1" />
</Exec>
<Exec Command="echo test output">
<Output TaskParameter="Outputs" PropertyName="Test2" />
</Exec>
<Message Text="----------------------------------------"/>
<Message Text="#(Test1)"/>
<Message Text="----------------------------------------"/>
<Message Text="$(Test2)"/>
<Message Text="----------------------------------------"/>
</Target>
</Project>
But get next output:
echo test output
test output
echo test output
test output
----------------------------------------
----------------------------------------
----------------------------------------
How can I get output by my script?

Good news everyone! You can now capture output from <Exec> as of .NET 4.5.
Like this:
<Exec ... ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="OutputOfExec" />
</Exec>
Simply:
Add ConsoleToMsBuild="true" to your <Exec> tag
Capture the output using the ConsoleOutput parameter in an <Output> tag
Finally!
Documentation here

If you want to capture output to an array-like structure and not to a plain string where the output lines are separated by a semicolon, use ItemName instead of PropertyName:
<Exec ... ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" ItemName="OutputOfExec" />
</Exec>

I've gotten to the point where I'm so frustrated with the limitations of MSBuild, and the stuff that's supposed to work but doesn't (at least not in every context), that pretty much anytime I need to do anything with MSBuild, I create a custom build task in C#.
If none of the other suggestions are working, then you could certainly do it that way.

You can pipe the output to a file so to speak, and read it back.
echo test output > somefile.txt

Related

I want to run a task for al files and exclude those who did not change

I have two buildtargets to check my code quality.
I run the following buildtargets every time i compile. This takes up too much time and i would like them to only check the files that did change.
In other words i want to filter files that did not change from the ItemGroup CppCheckFiles / LinterFiles.
<Target Name="CppCheck">
<ItemGroup>
<CppCheckFiles Include="*main.c" />
<CppCheckFiles Include="Source/*/*.c" />
</ItemGroup>
<Message Text="$(Configuration) starting." Importance="High" />
<Exec Command="C:\Cppcheck\cppcheck.exe %(CppCheckFiles.FullPath) --enable=style --template="{file}({line}): error:{severity}-{id}: {message}"" />
</Target>
<Target Name="SPLint">
<ItemGroup>
<LinterFiles Include="*main.c" />
<LinterFiles Include="Source/*/*.c" />
<LinterFiles Include="Source/*/*.h" />
</ItemGroup>
<Message Text="$(Configuration) starting." Importance="High" />
<Exec Command="splintCaller %(LinterFiles.FullPath)" />
</Target>
I know that the regular build process does this and i wonder if i have to go so fas as to write my own task.
hmm.. this sounds interesting. I can't help you. But it would be nice if the cppcheck wiki or manual had some small example project that did this.
Some people use cppcheck in commit hooks. I've tried it with GIT myself (I added a linux shell script). And there is a TortoiseSVN plugin you can try (http://sourceforge.net/apps/phpbb/cppcheck/viewtopic.php?f=3&t=443).
The solution is incremental Build. Where MSBuild compares Timestamps to exclude complete Buildtargets if nothing changed.
The following target creates a timesstamp for each file and skippes those files that did not change.
cppcheck.exe returns -1 if an error was detected and the timestamp is not written.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="CppCheck" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<CppCheckFiles Include="*main.c" />
<CppCheckFiles Include="Source/*/*.c" />
</ItemGroup>
<Target Name="CppCheck"
Inputs="#(CppCheckFiles)"
Outputs="CCPCheck\%(CppCheckFiles.Filename)%(CppCheckFiles.Extension).stamp">
<Exec Command="C:\Cppcheck\cppcheck.exe %(CppCheckFiles.FullPath) --enable=style --template="{file}({line}): error:{severity}-{id}: {message}"" />
<MakeDir Directories="CCPCheck"/>
<Touch Files="CCPCheck\%(CppCheckFiles.Filename)%(CppCheckFiles.Extension).stamp" AlwaysCreate = "true" />
</Target>
</Project>

MSBuild script dependsOnTargets order of execution

I have this line in my build script
<Target Name="Deploy" DependsOnTargets="ServicesInstall;SitesTransfer" >
What I want to know is, in this example, what order will the targets get executed. Also, if ServiceInstall has dependencies do they get executed before or after SiteTransfer. In other words is the execution done in a depth first or breadth first manner?
Thanks,
Sachin
As an experiment I tried this:
<Target Name="Deploy" DependsOnTargets="ServicesInstall;SitesTransfer" />
<Target Name="ServicesInstall" DependsOnTargets="ServicesInstallDependency">
<Message Text="ServicesInstall" />
</Target>
<Target Name="ServicesInstallDependency">
<Message Text="ServicesInstallDependency" />
</Target>
<Target Name="SitesTransfer">
<Message Text="SitesTransfer" />
</Target>
and this was the output:
...
1>ServicesInstallDependency:
1> ServicesInstallDependency
1>ServicesInstall:
1> ServicesInstall
1>SitesTransfer:
1> SitesTransfer
...
However, I suspect the exact sequence is undefined. It is not documented on msdn.
In other words, msbuild will only guarantee that the constraints you specify are satisfied. If you need to guarantee SitesTransfer and its dependencies are executed before ServicesInstall, you should make ServicesInstall depend on SitesTransfer.

In MSBuild, can I use the String.Replace function on a MetaData item?

In MSBuild v4 one can use functions (like string.replace) on Properties. But how can I use functions on Metadata?
I'd like to use the string.replace function as below:
<Target Name="Build">
<Message Text="#(Files->'%(Filename).Replace(".config","")')" />
</Target>
Unfortunately this outputs as (not quite what I was going for):
log4net.Replace(".config","");ajaxPro.Replace(".config","");appSettings.Replace(".config","");cachingConfiguration20.Replace(".config","");cmsSiteConfiguration.Replace(".config","");dataProductsGraphConfiguration.Replace(".config","");ajaxPro.Replace(".config","");appSettings.Replace(".config","");cachingConfiguration20.Replace(".config","");cmsSiteConfiguration
Any thoughts?
You can do this with a little bit of trickery:
$([System.String]::Copy('%(Filename)').Replace('config',''))
Basically, we call the static method 'Copy' to create a new string (for some reason it doesn't like it if you just try $('%(Filename)'.Replace('.config',''))), then call the replace function on the string.
The full text should look like this:
<Target Name="Build">
<Message Text="#(Files->'$([System.String]::Copy("%(Filename)").Replace(".config",""))')" />
</Target>
Edit: MSBuild 12.0 seems to have broken the above method. As an alternative, we can add a new metadata entry to all existing Files items. We perform the replace while defining the metadata item, then we can access the modified value like any other metadata item.
e.g.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Files Include="Alice.jpg"/>
<Files Include="Bob.not-config.gif"/>
<Files Include="Charlie.config.txt"/>
</ItemGroup>
<Target Name="Build">
<ItemGroup>
<!--
Modify all existing 'Files' items so that they contain an entry where we have done our replace.
Note: This needs to be done WITHIN the '<Target>' (it's a requirment for modifying existing items like this
-->
<Files>
<FilenameWithoutConfig>$([System.String]::Copy('%(Filename)').Replace('.config', ''))</FilenameWithoutConfig>
</Files>
</ItemGroup>
<Message Text="#(Files->'%(FilenameWithoutConfig)')" Importance="high" />
</Target>
</Project>
Result:
D:\temp>"c:\Program Files (x86)\MSBuild\12.0\Bin\MSBuild.exe" /nologo test.xml
Build started 2015/02/11 11:19:10 AM.
Project "D:\temp\test.xml" on node 1 (default targets).
Build:
Alice;Bob.not-config;Charlie
Done Building Project "D:\temp\test.xml" (default targets).
I needed to do something similar, the following worked for me.
<Target Name="Build">
<Message Text="#(Files->'%(Filename)'->Replace('.config', ''))" />
</Target>
Those functions works in properties only (as I know). So create target which will perform operation throw batching:
<Target Name="Build"
DependsOnTargets="ProcessFile" />
<Target Name="ProcessFile"
Outputs="%(Files.Identity)">
<PropertyGroup>
<OriginalFileName>%(Files.Filename)</OriginalFileName>
<ModifiedFileName>$(OriginalFileName.Replace(".config",""))</ModifiedFileName>
</PropertyGroup>
<Message Text="$(ModifiedFileName)" Importance="High"/>
</Target>
Do you really need in your example such kind of task? I mean there exists MSBuild Well-known Item Metadata
EDIT: I should specify that this task processes all items in #(Files).
i dont think you can use functions directly with itemgroups and metadata (that would be easy)
However you can use batching:
Taking the ideas from this post:
array-iteration
I was trying to trim an itemgroup to send to a commandline tool (i needed to lose .server off the filename)
<Target Name="ProcessFile" DependsOnTargets="FullPaths">
<ItemGroup>
<Environments Include="$(TemplateFolder)\$(Branch)\*.server.xml"/>
</ItemGroup>
<MSBuild Projects=".\Configure.msbuild"
Properties="CurrentXmlFile=%(Environments.Filename)"
Targets="Configure"/>
</Target>
<Target Name="Configure" DependsOnTargets="FullPaths">
<PropertyGroup>
<Trimmed>$(CurrentXmlFile.Replace('.server',''))</Trimmed>
</PropertyGroup>
<Message Text="Trimmed: $(Trimmed)"/>
<Exec Command="ConfigCmd $(Trimmed)"/>
</Target>
For MSBuild 12.0, here's an alternative.
<Target Name="Build">
<Message Text="$([System.String]::Copy("%(Files.Filename)").Replace(".config",""))" />
</Target>
Got the same problem (except with MakeRelative), so I passed with another solution : Using good old CreateItem that take a string and transform to Item :)
<ItemGroup>
<_ToUploadFTP Include="$(PublishDir)**\*.*"></_ToUploadFTP>
</ItemGroup>
<CreateItem Include="$([MSBuild]::MakeRelative('c:\$(PublishDir)','c:\%(relativedir)%(filename)%(_ToUploadFTP.extension)'))">
<Output ItemName="_ToUploadFTPRelative" TaskParameter="Include"/>
</CreateItem>
<FtpUpload Username="$(UserName)"
Password="$(UserPassword)"
RemoteUri="$(FtpHost)"
LocalFiles="#(_ToUploadFTP)"
RemoteFiles="#(_ToUploadFTPRelative->'$(FtpSitePath)/%(relativedir)%(filename)%(extension)')"
UsePassive="$(FtpPassiveMode)" ></FtpUpload>

How do you update an environment variable from inside an Exec task?

I am trying to pipe the output from a command to an Environment variable thus:
<Exec Command="for /f "tokens=*" %%i in ('svn info') do SET SVNINFO=%%i" />
and then use SVNINFO as a property in MSBuild.
While the command line counterpart:
for /f "tokens=*" %i in ('svn info') do SET SVNINFO=%i
works, the change in the value of the Environment variable when called from the Exec does not persist. (I am not able to obtain its value as a property.) Am I missing something here? Is there any better way to achieve this?
Starting with .NET 4.5 you can capture output of the Exec task using its ConsoleOutput parameter by setting ConsoleToMsBuild="true" (documentation). For example, the following target captures %TIME% value into the Time MSBuild property:
<Project>
<Target Name="Build">
<Exec Command="echo %TIME%" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="Time" />
</Exec>
<Message Text="Current time is $(Time)" />
</Target>
</Project>
Maybe using the Exec Task Output is a better way:
<Project DefaultTargets="DefaultTarget" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Exe">
<Exec Command="echo %PATH%">
<Output TaskParameter="Outputs" PropertyName="ExecOutput" />
</Exec>
</Target>
<Target Name="DefaultTarget" DependsOnTargets="Exe">
<Message Text="Result from Exec is $(ExecOutput)" />
</Target>
</Project>

Gathering outputs from an MSBuild exec task

I have a batch script that I want to call from an MSBuild project, and the documentation says I can't use output from the batch (either console / environment variables) in the MSBuild project.
Is there a workaround?
You can redirect the output of the command to a file using "> output.txt" and read that into a variable.
<PropertyGroup>
<OutputFile>$(DropLocation)\$(BuildNumber)\Output.txt</OutputFile>
</PropertyGroup>
<Exec Command="dir > "$(OutputFile)"" />
<ReadLinesFromFile File="$(OutputFile)">
<Output TaskParameter="Lines" ItemName="OutputLines"/>
</ReadLinesFromFile>
<Message Text="#(OutputLines->'%(Identity)', '%0a%0d')" />