Capture msbuild tf.exe changeset /latest - tfs

Within an MSBuild file I would like to extract the changeset number from the output of the following command into a $(parameter) value;
<Exec Command="c:\path\tf.exe changeset /latest /i" />
In my build script I can see the Changeset number in the first line of the result:
Changeset: 7539
User: John Doe
Date: 23 September 2015 17:03:19
Comment:
some check-in comment
Items:
[[List of items here]]
Work Items:
[[List of linked work items here]]
I feel like I am very close and want the simplest solution. I can use MSBuild.ExtensionPack or the MSBuild.Community tasks if there is a better option.

To only get the changeset number, please try the following steps: (code is quoted from this link):
Download and install MSBuildExtensionPack on the machine
Add the following code to the .csproj file (assume you're working with a C# project). Note that: you need to change the folder path where MSBuild.ExtensionPack.TaskFactory.PowerShell.dll is installed on your machine for the AssemblyFile property.
<UsingTask TaskFactory="PowershellTaskFactory" TaskName="Changeset" AssemblyFile="C:\Program Files\MSBuild\ExtensionPack\4.0\MSBuild.ExtensionPack.TaskFactory.PowerShell.dll">
<ParameterGroup>
<changeset Output="true" />
</ParameterGroup>
<Task>
<![CDATA[
$tf = & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\tf.exe" history . /r /noprompt /stopafter:1 /version:W
$changeset=$tf[2].Split(" ")[0]
]]>
</Task>
</UsingTask>
<Target Name="TestBuild">
<Changeset>
<Output TaskParameter="changeset" PropertyName="changeset" />
</Changeset>
<Message Importance="High" Text="Changeset:++++++ ::::: $(changeset)" />
</Target>

You can use some new standard MsBuild features to achieve this.
<PropertyGroup>
<TF>"$(VS140COMNTOOLS)..\IDE\tf.exe"</TF>
</PropertyGroup>
<Target Name="GetVersion">
<Exec Command="$(TF) changeset /latest /i" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="TfOutput" />
</Exec>
<PropertyGroup>
<Changeset>$(TfOutput.Split(';')[0].Substring(11))</Changeset>
</PropertyGroup>
<Message Importance="high" Text="ChangeSet is $(Changeset)" />
</Target>

Related

Microsoft Tfs build variable gets lost

I am running build with next targets:
<Target Name="BeforeCompile">
<Message Text="Build no: $(BuildNumber)" />
</Target>
Then in between I run build (using nmake). After that I want to deploy firmware:
<Target Name="AfterCompile">
<MSBuild Projects="$(MSBuildProjectFile)" Targets="CustomDeployMagicFirmware" Properties="Id=1" />
</Target>
<Target Name="CustomDeployMagicFirmware">
<Message Text="...Deploying bootloader files to build machine - folder: $(BuildNumber)" />
<Exec Command='xcopy "$(SolutionRoot)\Repository\bootloader\*.axf" \\machine\bootloader\$(BuildNumber) /y /q'/>
<Exec Command='xcopy "$(SolutionRoot)\Repository\bootloader\*.hex" \\machine\bootloader\$(BuildNumber) /y /q'/ -->
</Target>
The problem is that in target "BeforeCompile" I get properly message output for $(BuildNumber). But later I have Message Test output like:
...Deploying bootloader files to build machine - folder:
There looks like variable $(BuildNumber) is not set anymore. Also command xcopy copies files to folder bootloader and not to folder bootloader\$(BuildNumber).
What do I do wrong? Which things can influence variable contents?
The error is in this line
<MSBuild Projects="$(MSBuildProjectFile)" Targets="CustomDeployMagicFirmware" Properties="Id=1" />
The MSBuild task spawns a new MSBuild.exe process and you are not passing the property $(BuildNumber) to the new process. Change it like below and this would work
<MSBuild Projects="$(MSBuildProjectFile)" Targets="CustomDeployMagicFirmware" Properties="Id=1;BuildNumber=$(BuildNumber)" />
Use <CallTarget> instead of <MSBuild> to invoke your CustomDeployMagicFirmware target.

Msbuild ignores continueonerror?

I am trying to invoke MSbuild for a couple of projects twice. First time without any clean, but if this fails I will invoke a clean followed by a new build. (The reasoning is that I want my build to be fast but if that fails fall back to clean, restore nuget packages build etcetera). This works but the build will still fail if the first call failed (it has continueonerror set to true so I dont want it to fail...). Here are the relevant parts of the build file:
<ItemGroup>
<ProjectsToPublish Include="X.sln" />
</ItemGroup>
<Target Name="RestoreAllPackages">
<Message Text="#(ProjectsToPublish)" />
<Exec Command='"$(MSBuildProjectDirectory)\.nuget\nuget.exe" restore "$(MSBuildProjectDirectory)\%(ProjectsToPublish.Identity)"'
/>
</Target>
<Target Name="Build" >
<MSBuild Projects="#(ProjectsToPublish)" Properties="SkipRestore=True;RunCodeAnalysis=False;Retries=10;RetryDelayMilliseconds=50"
BuildInParallel="true" ContinueOnError="WarnAndContinue" />
<!-- MSBuildLastTaskResult outcome of previous task-->
<PropertyGroup>
<FastBuildFailed>false</FastBuildFailed>
<FastBuildFailed Condition="'$(MSBuildLastTaskResult)' == 'false'" >true</FastBuildFailed>
</PropertyGroup>
<Message Importance="high" Text="Initial build failed? $(FastBuildFailed)" />
<Message Importance="high" Text="Initial build failed will retry" Condition="'$(FastBuildFailed)'" />
<CallTarget Targets="FullBuild" Condition="'$(FastBuildFailed)'" />
</Target>
<Target Name="FullBuild" DependsOnTargets="RestoreAllPackages">
<!--Fake property below to reexecute build exact same properties prevents the build lform re-->
<MSBuild Projects="#(ProjectsToPublish)" Properties="SkipRestore=True;RunCodeAnalysis=False;FakeProperty=one" BuildInParallel="true" />
</Target>
<Target Name="RestoreAllPackages">
<Message Text="#(ProjectsToPublish)" />
<Exec Command='"$(MSBuildProjectDirectory)\.nuget\nuget.exe" restore "$(MSBuildProjectDirectory)\%(ProjectsToPublish.Identity)"'
/>
</Target>
For anyone with this rather exotic error. It seems that when a build is run inside of a TFS build server, the build server parses log output and will fail a build even with a ContinueOnError setting. My workaround ended up being <Exec Command="$(MSBuildBinPath)\msbuild.exe #(ProjectsToPublish) /noconlog " ContinueOnError="true" />
Basically spawning a new MSBuild using Exec and making sure that it did not output anything using /noconlog
Rather than using <OnError..., you can assign ContinueOnError the value 'WarnAndContinue', and then use a condition to check the $(MSBuildLastTaskResult) property.
Generic example:
<Error ContinueOnError="WarnAndContinue" />
<Message Importance="High" Text ="$(MSBuildLastTaskResult)" />
<!-- (Returns false) -->
(I believe both "WarnAndContinue" and $(MSBuildLastTaskResult) were introduced in MSBuild 4.0; they should be available on your TFS 2012 build server.)

MSBuild conditional Exec?

I am building various projects using the <MSBuild Projects="... markup. I am then executing some command line tools after the project is built.
E.g
<Target Name="Name">
<MSBuild Projects="" />
<Exec Command="" />
</Target>
I notice that the project is only built as required and get the following output when the build script is run: "Skipping target "CoreCompile" because all output files are up-to-date". This is great but how do I make my <Exec... commands use the same condition so that they are only run when necessary as well?
Update: I've implemented gregmac's suggestion but its still executing the command regardless. This is what I've got now:
<Target Name="Name">
<MSBuild Projects="">
<Output TaskParameter="TargetOutputs" ItemName="AssembliesBuiltByChildProjects" />
</MSBuild>
<Exec Command="" Condition="'#(AssembliesBuiltByChildProjects)'!=''" />
Any further help is much appreciated. This is a bit of a sticking point for me.
Thanks for any tips.
Alan
You should be able to use the TargetOutputs parameter:
<MSBuild Projects="" >
<Output TaskParameter="TargetOutputs" ItemName="AssembliesBuiltByChildProjects" />
</MSBuild>
<Message Text="Assemblies built: #(AssembliesBuiltByChildProjects)" /> <!-- just for debug -->
<Exec Command="" Condition="'#(AssembliesBuiltByChildProjects)'!=''" />
If you can add the following to each of your projects:
<Target Name="DoStuffWithNewlyCompiledAssembly">
<Exec Command="" />
</Target>
... then you only need to add a property:
<Target Name="Name">
<MSBuild Projects="" Properties="TargetsTriggeredByCompilation=DoStuffWithNewlyCompiledAssembly" />
</Target>
This works because someone smart at Microsoft added the following line at the end of the CoreCompile target in Microsoft.[CSharp|VisualBasic][.Core].targets (the file name depends on the language and MSBuild/Visual Studio version).
<CallTarget Targets="$(TargetsTriggeredByCompilation)" Condition="'$(TargetsTriggeredByCompilation)' != ''"/>
So if you specify a target name in the TargetsTriggeredByCompilation property, your target will run if CoreCompile runs-- and your target will not run if CoreCompile is skipped (e.g. because the output assembly is already up-to-date with respect to the code).
You are asking wrong question.
Exec does not have any condition, But you can have condition on Target element which can be used like this.
<Target Name="Name" Condition="#(AssembliesBuiltByChildProjects)'!=''">
<MSBuild Projects="">
<Output TaskParameter="TargetOutputs" ItemName="AssembliesBuiltByChildProjects" />
</MSBuild>
<Exec Command=""/>
</Target>
<Target Name="Name" Condition="#(AssembliesBuiltByChildProjects)'==''">
...
</Target>
I did manage to find a solution to fit my needs although it may not be the optimal solution.
See my answer to my other question here: MSBuild Post-Build
Thanks,
Alan

Msbuild and SLN unbindig

I want to unbind my sln file from TFS server and publish it on SVN is there any "easy" option to do this. It's easy to open sln and chose unbind option in Visual Studio, but does any one ever tried to automate this process? There is a solution to edit sln file using xmlpoke and deleting binding information, but is it safe?
I have some samples published on the MSDN Code Gallery for the TFS 2010 SDK that illustrate how to do this with MSBuild and the MSBuild Community Tasks. Here's a snippet of MSBuild script from the WorkItemObjectModel sample's WorkItemType.csproj file:
<Import Project="$(MSBuildExtensionsPath32)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<ItemGroup>
<SourceFiles
Include="$(SolutionDir)**/*.*"
Exclude="$(SolutionDir)Package/**/*.*;$(SolutionDir)**/bin/**/*.*;$(SolutionDir)**/obj/**/*.*;$(SolutionDir)**/internal.proj;$(SolutionDir)**/*.*scc;$(SolutionDir)$(SolutionName).zip">
<Visible>False</Visible>
</SourceFiles>
</ItemGroup>
<Target Name="AfterBuild" Condition="'$(Configuration)'=='Release'"
Inputs="#(SourceFiles)" Outputs="$(SolutionDir)$(SolutionName).zip">
<Delete
Files="$(SolutionDir)$(SolutionName).zip"
Condition="Exists('$(SolutionDir)$(SolutionName).zip')" />
<PropertyGroup>
<PackageDir>$(SolutionDir)Package\</PackageDir>
</PropertyGroup>
<MakeDir
Directories="$(PackageDir)" />
<Copy
SourceFiles="#(SourceFiles)"
DestinationFiles="$(PackageDir)%(RecursiveDir)%(Filename)%(Extension)" />
<Delete
Files="$(PackageDir)**/bin/**/*.*;$(PackageDir)**/obj/**/*.*" />
<RemoveDir
Directories="$(PackageDir)**/bin;$(PackageDir)**/obj" />
<Attrib
Files="#(PackageFiles)"
ReadOnly="false" />
<FileUpdate
Files="$(PackageDir)$(SolutionFileName)"
IgnoreCase="true"
Regex="^\s+GlobalSection\(TeamFoundationVersionControl\).+\n(\s*Scc.*\n)+\s+EndGlobalSection"
ReplacementText=" "
Multiline="true"
Singleline="false" />
<ItemGroup>
<ProjectFiles Include="$(PackageDir)**/*.*proj" />
</ItemGroup>
<FileUpdate
Files="#(ProjectFiles)"
Regex="<Scc[A-z]+>.+</Scc[A-z]+>"
ReplacementText=" " />
<ItemGroup>
<PackageFiles Include="$(PackageDir)**\*.*" />
</ItemGroup>
<Zip
Files="#(PackageFiles)"
WorkingDirectory="$(PackageDir)"
ZipFileName="$(SolutionDir)$(SolutionName).zip" />
<Delete
Files="#(PackageFiles)" />
<RemoveDir
Directories="$(PackageDir)" />
</Target>
In a nutshell, this script copies the source files to a temporary directory, removes the source control bindings from the solution and project files, then zips up the sources and finally deletes the temporary directory.

TFS2008 recursively copying files not always works (compiling vs2003) (AfterCompile target)

I'm having some strange problems copying files in a custom script in TFS2008 without SP1, I have to run the build several times to get the files copied (most of the times its in the second build that i get the files), let me give you the details:
This is happening with ASP sites and VS2003 Web solutions, (vs2008 solutions are OK)
In ASP I have a dummy 2008 solution, the build compiles this dummy, I override AfterCompile and in there I copy all the files to the drop location
In VS 2003 i have also a dummy 2008 solution, the build first compiles the dummy, I override AfterCompile, use "Exec" and "Command" to compile the 2003 solution and then copy the files to the drop location.
As you can see both approaches are similar, I'm not having problems with the builds per se, my problem is reproducible in two ways (and yes, i do check out, update, check in and then test the build):
Create a new build, configure the script, run the build the first time, some DLL's in the bin folder are not copied, run the build for the second time and i get all the files.
Build already configured and running OK, add some file to the project (this mostly happens with the ASP sites), run the build, don't get this new file, run the build again and i get this new file.
Here is my build script for a VS2003 Web solution as an example
<PropertyGroup>
<TasksPath>D:\BuildTools\</TasksPath>
<VS2003Devenv>D:\Archivos de programa\Microsoft Visual Studio .NET 2003\Common7\IDE\devenv.com</VS2003Devenv>
<VS2003VirtualFolder>CnbvPifWeb</VS2003VirtualFolder>
<VS2003Suba>Cnbv.Pif.Web</VS2003Suba>
<VS2003Project>Cnbv.Pif.Web</VS2003Project>
<VS2003WebSiteName>Sitio Web predeterminado</VS2003WebSiteName>
<VS2003Configuration>Release</VS2003Configuration>
<VS2003Branch>Desarrollo</VS2003Branch>
<VS2003RelativePath>$(SolutionRoot)\$(VS2003Branch)\$(VS2003Suba)\</VS2003RelativePath>
<VS2003SolutionPath>$(VS2003RelativePath)Cnbv.Pif.Web.sln</VS2003SolutionPath>
<VS2003LocalFolder>$(VS2003RelativePath)Sources\$(VS2003Project)\</VS2003LocalFolder>
<VS2003Output>$(BinariesRoot)\$(VS2003Project)\</VS2003Output>
<VS2003CachePath>C:\Documents and Settings\srvfoundation\VSWebCache\230-2555-CPU015\</VS2003CachePath>
<VS2003ProjectExtension>vbproj</VS2003ProjectExtension>
<VS2003CacheFile>$(VS2003CachePath)$(VS2003VirtualFolder)\_vti_pvt\$(VS2003Project).$(VS2003ProjectExtension).cache</VS2003CacheFile>
</PropertyGroup>
<Import Project="$(TasksPath)Microsoft.Sdc.Common.tasks"/>
<UsingTask TaskName="Microsoft.Sdc.Tasks.Web.WebSite.CreateVirtualDirectory" AssemblyFile="Microsoft.Sdc.Tasks.dll" />
<UsingTask TaskName="Microsoft.Sdc.Tasks.Web.WebSite.DeleteVirtualDirectory" AssemblyFile="Microsoft.Sdc.Tasks.dll" />
<ItemGroup>
<!--list of ouput files, excluding .DLL outside bin and some other files-->
<VS2003OutputFiles
Include="$(VS2003LocalFolder)**\*.*"
Exclude="$(VS2003LocalFolder)**\*.vb;$(VS2003LocalFolder)**\*.cs;$(VS2003LocalFolder)**\*.resx;$(VS2003LocalFolder)**\*.vspscc;$(VS2003LocalFolder)**\*.csproj;$(VS2003LocalFolder)**\*.vbproj;$(VS2003LocalFolder)**\*.scc;$(VS2003LocalFolder)**\*.webinfo;$(VS2003LocalFolder)**\*.snk;$(VS2003LocalFolder)**\*.dll;$(VS2003LocalFolder)**\*.exe;" />
<!-- copy dll to bin folder -->
<VS2003OutputBinFiles
Include="$(VS2003LocalFolder)bin\*.dll"/>
</ItemGroup>
<Target Name="AfterCompile">
<Message Text="Deleting cache file" />
<Microsoft.Build.Tasks.Delete
Condition="Exists('$(VS2003CacheFile)')"
Files="$(VS2003CacheFile)" />
<Message Text="Creating virtual folder $(VS2003VirtualFolder) in IIS in local path $(VS2003LocalFolder)" />
<Web.WebSite.CreateVirtualDirectory
VirtualDirectoryName="$(VS2003VirtualFolder)"
Path="$(VS2003LocalFolder)"
WebSiteName="$(VS2003WebSiteName)" />
<Message Text="Compiling $(VS2003Project) in $(VS2003Branch)" />
<Exec
Command=""$(VS2003Devenv)" "$(VS2003SolutionPath)" /build $(VS2003Configuration) /out "$(VS2003LocalFolder)$(VS2003Project).log" "/>
<Message Text="Eliminando la carpeta virtual $(VS2003VirtualFolder) en IIS" />
<Web.WebSite.DeleteVirtualDirectory
WebSiteName="$(VS2003WebSiteName)"
VirtualDirectoryName="$(VS2003VirtualFolder)" />
<MakeDir Condition="!Exists('$(VS2003Output)')" Directories="$(VS2003Output)" />
<Message Text="Copying output files #(VS2003OutputFiles)" />
<Copy
SourceFiles="#(VS2003OutputFiles)"
DestinationFiles="#(VS2003OutputFiles->'$(VS2003Output)%(RecursiveDir)%(Filename)%(Extension)')"
/>
<MakeDir Condition="!Exists('$(VS2003Output)bin\')" Directories="$(VS2003Output)bin\" />
<Message Text="Copying DLL to bin folder #(VS2003OutputBinFiles)" />
<Copy
SourceFiles="#(VS2003OutputBinFiles)"
DestinationFiles="#(VS2003OutputBinFiles->'$(VS2003Output)bin\%(Filename)%(Extension)')"
/>
<OnError ExecuteTargets="VS2003Fail" />
</Target>
<Target Name="VS2003Fail">
<Message Text="Copying log file $(VS2003RelativePath)$(VS2003Project).log" />
<Copy Condition="Exists('$(VS2003RelativePath)$(VS2003Project).log')" SourceFiles="$(VS2003RelativePath)$(VS2003Project).log" DestinationFolder="$(DropLocation)\$(BuildNumber)" />
<CallTarget ContinueOnError ="true" Targets ="CreateWorkItemWhenPartialSucceed" />
</Target>
<Target
Name="CreateWorkItemWhenPartialSucceed"
Condition=" '$(SkipWorkItemCreation)'!='true' and '$(IsDesktopBuild)'!='true' ">
<Message Text="ejecutando work" />
<PropertyGroup>
<WorkItemTitle>$(WorkItemTitle) $(BuildNumber)</WorkItemTitle>
<BuildLogText>$(BuildlogText) <ahref='file:///$(DropLocation)\$(BuildNumber)\BuildLog.txt'>$(DropLocation)\$(BuildNumber)\BuildLog.txt</a >.</BuildLogText>
<ErrorWarningLogText Condition="!Exists('$(MSBuildProjectDirectory)\ErrorsWarningsLog.txt')"></ErrorWarningLogText>
<ErrorWarningLogText Condition="Exists('$(MSBuildProjectDirectory)\ErrorsWarningsLog.txt')">$(ErrorWarningLogText) <a href='file:///$(DropLocation)\$(BuildNumber)\ErrorsWarningsLog.txt'>$(DropLocation)\$(BuildNumber)\ErrorsWarningsLog.txt</a >.</ErrorWarningLogText>
<WorkItemDescription>$(DescriptionText) %3CBR%2F%3E $(BuildlogText) %3CBR%2F%3E $(ErrorWarningLogText)</WorkItemDescription>
</PropertyGroup>
<CreateNewWorkItem
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
BuildNumber="$(BuildNumber)"
Description="$(WorkItemDescription)"
TeamProject="$(TeamProject)"
Title="$(WorkItemTitle)"
WorkItemFieldValues="$(WorkItemFieldValues)"
WorkItemType="$(WorkItemType)"
ContinueOnError="true" />
</Target>
When I see the ouput of this message in the log
<Message Text="Copying DLL to bin folder #(VS2003OutputBinFiles)" />
the first time i see just the name of one file, the second time it prints all the correct files, and the same happens with the ASP sites, if I add a file i see the file in the output in the second build.
I hope you can help me out figuring this out, thanks a lot.
Juan Zamudio
this was the answer in the tfs forum by OsirisJakob
The problem is that you define your item groups at the root level. This means that they are evaluated immediately when the project file loaded. What you want is for them to be evaluated when the AfterCompile target is executed.
Since you are running TFS 2008, you can solve this problem by moving the item groups into the AfterCompile target (a.k.a. Dynamic item groups). This will cause the item group to be evaluated by the time the AfterCompile target is executed, and will give you the correct result.

Resources