Accessing semicolon delimited items in a MSBuild Variable - msbuild

I have a variable that I am working with called $(TargetConnectionString)
It is set to
SomeValue=Things;Data Source=MySQLServer;Integrated Security=True;Pooling=False
Is there a cool MSBuild way to get a reference to just the MySQLServer part of this list?
(I can use a batch file to parse it, but then I have to find a way to read it back in. So I am hoping there is a way to say $(TargetConnectionString."Data Source") (or something similar)
So, how can I get to get the MySQLServer text.

the following works, by calling back into the project file using an MsBuild task with the TargetConnection string as property set. Does not work with spaces though, so I removed them, hopefully that's usefull for you
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetConnectionString>DataSource=MySQLServer;IntegratedSecurity=True;Pooling=False</TargetConnectionString>
</PropertyGroup>
<Target Name="Main">
<MSBuild Projects="$(MSBuildProjectFullPath)" Targets="GetDataSource" Properties="$(TargetConnectionString)"/>
</Target>
<Target Name="GetDataSource">
<Message Text="$(DataSource)"/>
<Message Text="$(IntegratedSecurity)"/>
<Message Text="$(Pooling)"/>
</Target>
</Project>
output:
> msbuild test.proj /t:Main
...
Project "test.proj" on node 0 (Main target(s)).
Project "test.proj" (1) is building "test.proj" (1:2) on node 0 (GetDataSource target(s)).
MySQLServer
True
False

Related

How to pass env-var to MSBuild Task and make it effective for all sub-process? (concrete code)

I have a question about passing environment variables to MSBuild Task. See my code below:
a.proj
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="A1">
<Target Name="A0">
<Message Text="A0 start."/>
<MSBuild Projects="b.proj" Properties="myvar=000" />
<Message Text="A0 end."/>
</Target>
<Target Name="A1" DependsOnTargets="A0">
<Message Text="A1 start."/>
<MSBuild Projects="b.proj" Properties="myvar=111" />
<Message Text="A1 end."/>
</Target>
</Project>
b.proj
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="B">
<Message Text="B start."/>
<Exec command="c.bat" />
</Target>
</Project>
c.bat
echo [C]myvar=%myvar%
I hope that c.bat can see myvar's value passed from a.proj. My code above failed to do that.
Consider(Assume) a running <MSBuild> Task a process on the OS, then, I'd like to pre-set environment variable myvar for that process, so that its subprocesses(no matter how deep) can all see myvar's value.
How can I achieve that? Thank you.
When MSBuild starts, it maps the environment variables in its process to MSBuild Properties. For example the environment variable COMPUTERNAME is available in an MSBuild project as the $(COMPUTERNAME) property.
But Properties are not added to the environment variables.
The Exec task creates a new process. The new process will inherit the environment variables of the 'parent' process and Exec has a parameter for passing additional environment variable definitions that will either add to or override the existing environment variables. The parameter is EnvironmentVariables.
Modify your Exec task as follows:
<Exec command="c.bat" EnvironmentVariables="myvar=$(myvar)" />
Example
The following is a full example of the environment variable passing.
Given a file 'test.proj':
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="TestExecPassVar">
<MSBuild Projects="$(MSBuildThisFile)" Targets="ExecPassVar" Properties="myvar=000" />
<MSBuild Projects="$(MSBuildThisFile)" Targets="ExecPassVar" Properties="myvar=111" />
<MSBuild Projects="$(MSBuildThisFile)" Targets="ExecPassVar" />
</Target>
<Target Name="ExecPassVar">
<Exec Command="test.bat" EnvironmentVariables="myvar=$(myvar)" EchoOff="true" />
</Target>
</Project>
And given a file 'test.bat':
#echo running test.bat
#echo %myvar%
Running the command
set myvar=222
and then
msbuild test.proj
will produce output like the following (which has been edited to remove paths and other noise):
Project "test.proj" on node 1 (TestExecPassVar target(s)).
Project "test.proj" (1) is building "test.proj" (1:2) on node 1 (ExecPassVar target(s)).
ExecPassVar:
running test.bat
000
Done Building Project "test.proj" (ExecPassVar target(s)).
Project "test.proj" (1) is building "test.proj" (1:3) on node 1 (ExecPassVar target(s)).
ExecPassVar:
running test.bat
111
Done Building Project "test.proj" (ExecPassVar target(s)).
Project "test.proj" (1) is building "test.proj" (1:4) on node 1 (ExecPassVar target(s)).
ExecPassVar:
running test.bat
222
Done Building Project "test.proj" (ExecPassVar target(s)).
Done Building Project "test.proj" (TestExecPassVar target(s)).
The test project invokes its own ExecPassVar target three times. The first two times it passes Properties that define a $(myvar) property. The third time no property is passed and the $(myvar) property is undefined.
The Exec task uses the EnvironmentVariables parameter.
When the $(myvar) property is defined, the batch file sees a myvar environment variable with the passed value.
When the $(myvar) property is not defined, the EnvironmentVariables parameter value evaluates to "myvar=". This doesn't override or clear the value of myvar and the value of 222, that was set before the msbuild command, is seen by the batch file.

TeamCity MSBuild refer to build counter

I have a property group which includes a property for the build_number which is being passed in from TeamCity as solely the Build Counter. The build number format being set in TeamCity as simply {0} for the counter.
<PropertyGroup>
<Major>10</Major>
<Minor>1</Minor>
<Build>$(BUILD_NUMBER)</Build>
<Release>0</Release>
...
</PropertyGroup>
The Major, Minor and Release properties are then updated from values in a file in source control.
So that TeamCity logs the build as the full 4 part build reference (not just the counter), I set it thus:
<TeamCitySetBuildNumber BuildNumber="$(Major).$(Minor).$(Build).$(Release)" />
However, now when I reference my $(Build) property, it's now set to the 4 part build reference, and any property I have made which makes reference to $(BUILD_NUMBER) prior to setting using TeamCitySetBuildNumber also gets overwritten with the 4 part reference.
NB I've also changed it with a system message:
<Message Text="##teamcity[buildNumber '$(Major).$(Minor).$(Build).$(Release)']" />
but the overall effect is the same.
How Can I refer to the build counter (only) AFTER I have set the BuildNumber above?
If you're using a project file, you could try calling the TeamCitySetBuildNumber command in the AfterBuild section of the *.vbproj or *.csproj file:
<Target Name="AfterBuild">
<TeamCitySetBuildNumber BuildNumber="$(Major).$(Minor).$(Build).$(Release)" />
</Target>
If you're using a solution file, I'd create a *.proj file that calls your solution file and then after that call the TeamCitySetBuildNumber command (not sure if you can call the TeamCitySetBuildNumber command within the target like this though...):
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="SetBuildNumber">
<PropertyGroup>
<Major>10</Major>
<Minor>1</Minor>
<Build>$(BUILD_NUMBER)</Build>
<Release>0</Release>
</PropertyGroup>
<Target Name="Build">
<Message Text="Build task called... " Importance="high"/>
<MSBuild Projects="$(teamcity_build_checkoutDir)\your_solution.sln" Properties="Configuration=Release"/>
</Target>
<Target Name="SetBuildNumber" DependsOnTargets="Build">
<Message Text="Setting build number back to TeamCity... " Importance="high"/>
<TeamCitySetBuildNumber BuildNumber="$(Major).$(Minor).$(Build).$(Release)" />
</Target>
</Project>

VCBuild task in MSBuild - change outputpath

I'm attempting to write an automated build for one of our products, and I've hit up against a wall for some of our VC++ projects: I need to be able to set the output path to where the assemblies will be copied once its done.
Here is a makeshift msbuild file:
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="3.5">
<Target Name="Build">
<VCBuild Projects="C:\src\SomeProject\SomeProject.vcproj"
ToolPath="C:\Program Files\Microsoft Visual Studio 9.0\VC\vcpackages"
Configuration="Debug" />
</Target>
</Project>
Stijn's Answer:
I thought I'd use this space to clarify how I personally used Stijn's answer to solve this. He has some code in his MSBuild file that writes the vsprops file for him. I decided to take a simpler approach and just write the file manually.
I created this file, called build.vsprops (my output path is V:)
<?xml version="1.0"?>
<VisualStudioPropertySheet ProjectType="Visual C++"
Version="8.00"
Name="Overrides"
OutputDirectory="V:\">
<Tool Name="VCCLCompilerTool"
AdditionalUsingDirectories="V:\" />
</VisualStudioPropertySheet>
Then I edited my MSBuild file to add the Override parameter:
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="3.5">
<Target Name="Build">
<VCBuild Projects="C:\src\SomeProject\SomeProject.vcproj"
ToolPath="C:\Program Files\Microsoft Visual Studio 9.0\VC\vcpackages"
Configuration="Debug"
Override="$(MSBuildProjectDirectory)\build.vsprops" />
</Target>
</Project>
have a look at the Override parameter for the VCBuild task. Basically you specify a property sheet which you can use to override whatever property you want (it has the same effect as adding a property sheet to the top of the list in a project within VS). You could even generate the override file using the WriteLinesToFile task.
Example:
<PropertyGroup>
<VCOverridesFile Condition=" '$(VCOverridesFile)'=='' ">overrides.vsprops</VCOverridesFile>
<VCOverridesOpen>%3C?xml version=%221.0%22?%3E%0D%0A%3CVisualStudioPropertySheet ProjectType=%22Visual C++%22 Version=%228.00%22 Name=%22My Overrides%22%3E</VCOverridesOpen>
<VCOverridesClose>%3C/VisualStudioPropertySheet%3E</VCOverridesClose>
<MyOutPath><Tool Name="VCLinkerTool" OutputFile ="c:\my.exe"/></MyOutPath>
</PropertyGroup>
<Target Name="WriteOverridesFile">
<WriteLinesToFile
File="$(VCOverridesFile)"
Lines="$(VCOverridesOpen);$(AdditionalVCOverrides);$(VCOverridesClose)"
Overwrite="true" />
</Target>
Then pass $(VCOverridesFile) to the Override property and make sure your VCBuild Task DependsOnTarget WriteOverridesFile.
Doing it the dirty way you can pass output directory path through command line arguments of msbuild.
msbuild yourProject /p:OutDir=yourPath
Although I suspect, there should be the better way to accomplish the task. The main idea is to set 'OutDir' property in such a way that it will not be overriden by your SomeProject.vcproj
if you are using Azure DevOps and needs to create a YAML do build a .net framework (vintage[old])
- task: VSBuild#1
inputs:
solution: '**\*.sln'
msbuildArgs: '/p:DeployOnBuild=true /p:SkipInvalidConfigurations=false /p:OutDir="$(System.DefaultWorkingDirectory)\publish_output"'
platform: 'Any CPU'
configuration: 'Release'

MSBuild error when building <project> unrecognized

I have installed VS2010. I have a simple .build file which contains this.
<?xml version="1.0"?>
<project name="system" default="build">
<target name="build">
<exec program="C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\msbuild.exe">
<arg value="C:\System\system.build" />
</exec>
</target>
</project>
When I try to build the project by typing C:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe system.build, I received this error.
C:\System\system.build(2,1): error MSB4068: The element is unrecognized, or not supported in this context.
I'm interested in recommendations or suggestions.
The reason it doesn't work is you've written the msbuild file almost exactly like you'd write ant/nant. Yes the Project is missing the xmlns tag but every other element is also incorrect. In MSbuild The first letter is always in capitals - Project and nor project. The same goes for all the elements.
Like so:
<?xml version="1.0"?>
<Project name="system" default="build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="build">
<MSBuild Project="C:\System\system.build"/>
</Target>
</Project>
Dont forget to read your question after you post (the sample was missing which is why you got no quick answers)
You're missing the xmlns on the project node - see a csproj for the required ns bit.
Also, it normally doesn't make much sense to run msbuild from within msbuild as you've done, but then you knew that! (There's an MsBuild task too, which is generally what you want if you want to nest invocations)

Getting the Content item from a csproj using the MSBuild task

I have an MSBuild file and I am building C# projects like this:
<ItemGroup>
<ProjectsToBuild Include="./source/ProjectA/ProjectA.csproj"/>
<ProjectsToBuild Include="./source/ProjectB/ProjectB.csproj"/>
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="#(ProjectsToBuild)" Targets="Build">
<Output ItemName="ProjectOutputs" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="#ProjectOutputs"/>
</Target>
I successfully get an Item containing all of the .dll files that were built:
Build:
c:\code\bin\ProjectA.dll;c:\code\bin\ProjectB.dll
I would also like to get the Content item from each project without modifying the .csproj files. After digging around in the Microsoft .targets files, I was almost able to get it working with this:
<MSBuild Projects="#(ProjectsToBuild)" Targets="ContentFilesProjectOutputGroup">
<Output ItemName="ContentFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="#(ContentFiles->'%(RelativeDir)')"/>
The problem with this approach is the RelativeDir is not being set correctly. I am getting the full path instead of relative:
Build:
c:\ProjectA\MyFolder\MyControl.ascx;c:\ProjectB\MyOtherFolder\MyCSS.css;
instead of:
Build:
MyFolder\MyControl.ascx;MyOtherFolder\MyCSS.css;
Is there a property I can pass to the MSBuild task that will make RelativeDir behave correctly?
Or, even better, is there an easier way to get the Content item?
You can do this but it is not very intutive. I've discussed this type of technique a few times on my blog ( which is currently down :( ).
So create a new file, I named it GetContentFiles.proj which is shown here.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Projects Include="WindowsFormsApplication1\WindowsFormsApplication1.csproj"/>
</ItemGroup>
<!-- This target will be executed once for each file declared in the Project target -->
<Target Name="PrintFiles" Outputs="%(Projects.Identity)">
<Message Text="PrintFiles" Importance="high"/>
<MSBuild Projects="$(MSBuildProjectFile)"
Targets="GetContentFiles"
Properties="ProjectToGetFiles=%(Projects.Identity)">
<Output ItemName="projContent" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="ProjContent: #(projContent)" Importance="high"/>
<!-- Transform the projContent to have correct path -->
<!--
Get the relative path to the project itself, this serves as the base for
the Content files path
-->
<PropertyGroup>
<_ProjRelativeDir>%(Projects.RelativeDir)</_ProjRelativeDir>
</PropertyGroup>
<!-- This item will contain the item with the corrected path values -->
<ItemGroup>
<ProjContentFixed Include="#(projContent->'$(_ProjRelativeDir)%(RelativeDir)%(Filename)%(Extension)')"/>
</ItemGroup>
<!-- Create a new item with the correct relative dirs-->
<Error Condition="!Exists('%(ProjContentFixed.FullPath)')"
Text="File not found at [%(ProjContentFixed.FullPath)]"/>
</Target>
<Import Project="$(ProjectToGetFiles)" Condition="'$(ProjectToGetFiles)'!=''"/>
<Target Name="GetContentFiles" Condition="'$(ProjectToGetFiles)'!=''" Outputs="#(Content)">
<Message Text="Content : #(Content)" Importance="high"/>
<Message Text="Inside GetContentFiles" Importance="high"/>
</Target>
</Project>
I will try and explain this, but it may be tough to follow. Let me know if you need me to expand on it. This file has two targets PrintFiles and GetContentFiles. The entry point into this file is the PrintFiles target, in the sense that this is the target that you are going to call. So you call the PrintFiles target which it then uses the MSBuild task to call the GetContentFiles target on itself, also it passes a value for the ProjectToGetFiles property. Because of that the Import elemnent will be executed. So what you are really doing is taking the project defined in the ProjectToGetFiles property and extending it to include the target GetContentFiles (and whatever other content is inside the GetContentFiles.proj file). So we are effectively extending that file. I'm calling this technique "MSBuild Inheritance" because. So inside the GetContentFiles target we can access all properties and items that are declared inthe ProjectToGetFiles property. So I take advantage of that by simply putting the content of the Content item into the outputs for the target, which can be accessed by the original file using the TargetOutputs from the MSBuild task.
You mentioned in your post that you wanted to correct the path values to be the right ones. The problem here is that in the .csproj file all items are declared relative to the original project file. So if you "extend" the project file in this way from a file in a different directory you must correct the file path values manually. I've done this inside the PrintFiles target, check it out.
If you execute the command msbuild GetContentFile.proj /fl /t:PrintFiles the result would be:
Build started 7/3/2009 12:56:35 AM.
Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" on node 0 (PrintFiles target(s)).
PrintFiles
Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (1) is building "C:\Data\Development\My Co
de\Community\MSBuild\FileWrites\GetContentFile.proj" (1:2) on node 0 (GetContentFiles target(s)).
Content : Configs\Config1.xml;Configs\Config2.xml
Inside GetContentFiles
Done Building Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (GetContentFiles target(s)).
PrintFiles:
ProjContent: Configs\Config1.xml;Configs\Config2.xml
Done Building Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (PrintFiles target(s)).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.03
Sayed Ibrahim Hashimi
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build
In case this helps someone else - use TargetPath instead of RelativeDir:
<MSBuild Projects="#(ProjectsToBuild)" Targets="ContentFilesProjectOutputGroup">
<Output ItemName="ContentFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="#(ContentFiles->'%(TargetPath)')"/>
This will give you the relative path for each item.