Specifying Tf.exe and MsBuild version in CC.Net - tfs

The problem
I am porting a build to a newer version of CC.Net (from 1.4.4 to a very recent version of 1.6). Previously the build mixed <msbuild /> tags with <exec /> tags that call batch files with tf.exe commands in them.
The build must have relied on the source control version being the same between the msbuild and tf.exe commands, because now I get an error on the tf.exe command:
Unable to determine the workspace.
When I launch a command prompt, with runas as the same user the service is using, and call the same command the batch file used (from the same directory), I get the same error.
However if I launch a Visual Studio 2010 command prompt, with runas, and call that command (from the same directory), I don't get the error.
This also makes sense, because the %UserProfile%\Local Settings\Application Data\Microsoft\Team Foundation\3.0\Cache\VersionControl.config file has the workspace mapping, but the ...\1.0\Cache\VersionControl.config file does not.
The solution I'd like to try
I can't simply change the PATH for the batch file because it actually needs to build against an older version of Visual Studio, due to licensing.
I think I could solve this by specifying the TFS/msbuild version used with the <msbuild /> tag. But I'm not sure how to go about this. I think it would be some sort of server configuration, but I haven't found anything about this in the docs.
My question is: How do I specify the version of TFS that Cruise Control uses for <msbuild /> tags? Or is there another way to solve this?

As far as I know, there is no way to specify this directly with msbuild. The way it is solved at our place is that we set the executable for msbuild to a bat-file that's basically a copy of the desired vcvars-script (used by Visual Studio Command Prompt to set up the environment).
<tasks>
<msbuild>
<executable>vcvars_VC10_amd64.bat</executable>
<workingDirectory>SomeFolder</workingDirectory>
<environment>
<variable name="foo" value="bar" />
</environment>
<projectFile>msbuild.proj</projectFile>
<buildArgs>/m:2 /p:Configuration=Release /p:Platform=x64 /v:normal</buildArgs>
<targets>Clean;Build;Test</targets>
<timeout>3600</timeout>
</msbuild>
</tasks>
Maybe you could get away with setting up your desired paths using the <environment> property as well.

I won't accept this solution, in case people have other solutions (or can solve the problem I directly asked about), but here's how I ended up solving my problem:
I added a workspace sync task to the top of the build tasks:
<tasks>
<exec>
<description>Sync the workspaces with the TFS server, for the version of TFS used in exec commands</description>
<executable>Tf.exe</executable>
<buildArgs>workspaces /s:$(TfsServer)</buildArgs>
</exec>
This works because in my scenario, I don't care about the versioning as much as I do about the workspaces getting out of sync.

Related

sfproj - outpath Build error

Iam trying to build the sfproj using msbuild on my build machine , This is what Iam doing.
<target ="package">
<foreach item="File" property="sfproj">
<in>
<items refid="servicefabric.files.sfproj" />
</in>
<do>
<exec program="${msbuild14.exe}">
<arg value="${sfproj}" />
<arg value="/p:Configuration=${config}" />
<arg value="/p:Platform=x64" />
<arg value="/target:Package" />
</exec>
</do>
</foreach>
</target>
The error Iam getting on the build machine is
(_CheckForInvalidConfigurationAndPlatform target) ->
10:25:10 [exec] C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets(724,5): error : The OutputPath property is not set for project 'App.sfproj'. Please check to make sure that you have specified a valid combination of Configuration and Platform for this project. Configuration='Debug' Platform='x64'. You may be seeing this message because you are trying to build a project without a solution file, and have specified a non-default Configuration or Platform that doesn't exist for this project.
It appears that the Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.1.0 Nuget package contains an MSBuild target that skips building of the sfproj itself (that or makes certain options present in order for it to succeed).
In my case I had accidentally checked in some Nuget package folders into source control that were incomplete. Visual Studio saw the presence of the folder so it didn't try to redownload it. You need to make sure that the build folder is present and it contains an MSBuild target file. Your best bet is just to delete the packages folder entirely to ensure you're starting off with a fresh start.
https://ijustwrite.software/2016/07/20/ouputpath-property-not-set/
I ran into a problem with the same symptoms. It happened to me after upgrading the Azure SDK, but it seems to me that it could easily happen in a few ways. The trouble is that there are two paths in the sfproj file that lead into the directory where nuget packages are stored, oddly enough on the first and last line of the file. This causes problems because you may have overridden where nuget packages are stored (this is not at all uncommon). Since these are relative paths, it's easy for nuget to get the packages to where you have specified, but for the lines in the project to point to "..\packages" instead, which makes Visual Studio want to restore the packages, but to never think they have been restored as it's looking for them in the wrong place.
To fix it all you need to do is modify the paths in the sfproj file to point to wherever you have your nuget packages set to download (this setting is in the nuget.config file, which can be in any directory above your project directory).
These are the two broken lines in my sfproj file:
<Import Project="..\packages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.props" Condition="Exists('..\packages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.props')" />
<Import Project="..\packages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.targets" Condition="Exists('..\packages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.targets')" />
Since I have the value ThirdPartyLibraries\NuGetPackages in my nuget.config file (which is two directories above my project file), these lines had to be modified to be the following in order to work again :
<Import Project="..\..\ThirdPartyLibraries\NuGetPackages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.props" Condition="Exists('..\..\ThirdPartyLibraries\NuGetPackages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.props')" />
<Import Project="..\..\ThirdPartyLibraries\NuGetPackages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.targets" Condition="Exists('..\..\ThirdPartyLibraries\NuGetPackages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.targets')" />
And that fixes it right up. I was lucky: I had two sfproj files, one with this issue, and one without, so all I had to do was diff to see the trouble.

How to use MSBuild with TFS and SSIS

I'm a complete novice in using TFS Build definition and MSBuild scripts.
I want to automate my SSIS build and deployments and create a build definition which will build and deploy my SSIS project whenever I queue it up.
I found this project: http://sqlsrvintegrationsrv.codeplex.com/releases/view/82369
which allows you to create a DLL which you can place in C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies
Then you can call the SSIS.MSBuild.proj (See end for this) with certain parameters like this in a visual studio command line:
MSBuild SSIS.MSBuild.proj /t:SSISBuild,SSISDeploy /p:SSISProj="MySSISProject",Configuration="DEV",,SSISServer="AB-CDE-FGH-I1\DEV",ProjectName="MySSISProject"
or I can put it in a BAT file like this:
%systemroot%\Microsoft.Net\Framework\v4.0.30319\MSBuild.exe SSIS.MSBuild.proj /t:SSISBuild,SSISDeploy /p:SSISProj="MySSISProject",Configuration="DEV",,SSISServer="AB-CDE-FGH-I1\DEV",ProjectName="MySSISProject"
It works fine when you run the BAT file, it builds and deploys the SSIS project.
Questions:
How can I use this so it is automated, so I can manually kick off a build and deployment from within VS/TFS? Using a build definition.
How can I ensure the correct configurations are selected, and the correct destination server? For example we have SSIS configurations for DEV, SIT, SYS, UAT, PRD. Each with its own server name. Do I need a separate build definition for each environment or is there a way to use one build definition?
Anything useful in using powershell somehow?
Here is SSIS.MSBuild.proj:
<?xml version="1.0" encoding="Windows-1252"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="SSISBuild;SSISDeploy">
<!--Requires a property called $(SSISProj) to be defined when this script is called-->
<UsingTask TaskName="DeploymentFileCompilerTask" AssemblyFile="C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\Microsoft.SqlServer.IntegrationServices.Build.dll" />
<Target Name="SSISBuild" Condition="'$(SSISProj)' != ''">
<PropertyGroup>
<SSISProjPath>$(SSISProj)\$(SSISProj).dtproj</SSISProjPath>
</PropertyGroup>
<Message Text="**************Building SSIS project: $(SSISProjPath) for configuration: $(CONFIGURATION)**************" />
<DeploymentFileCompilerTask
InputProject="$(SSISProjPath)"
Configuration="$(CONFIGURATION)"
ProtectionLevel="DontSaveSensitive">
</DeploymentFileCompilerTask>
</Target>
<UsingTask TaskName="DeployProjectToCatalogTask" AssemblyFile="C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\Microsoft.SqlServer.IntegrationServices.Build.dll" />
<Target Name="SSISDeploy" Condition="'$(SSISProj)' != ''">
<Message Text="**************Publishing SSIS project: $(SSISProj) to: $(SSISServer) to folder: $(PROJECTNAME)**************" />
<PropertyGroup>
<ISPac>$(SSISProj)\bin\$(CONFIGURATION)\$(SSISProj).ispac</ISPac>
</PropertyGroup>
<DeployProjectToCatalogTask
DeploymentFile="$(ISPac)"
Instance="$(SSISServer)"
Folder="$(PROJECTNAME)"
CreateFolder="true"/>
</Target>
</Project>
EDIT I tried adding some MSBuild Arguments to the TFS Build Definition. I tried various combinations of arguments, some with quotes, some without. I couldn't get it to work.
"C:\Users\me\Desktop\Buildssis\SSIS.MSBuild.proj" /t:SSISBuild,SSISDeploy /p:SSISProj="MySSISProject",Configuration="SIT",SSISServer="AB-CDE-FGH-I1\DEV",ProjectName="MySSISProject"
But I always get this error:
MSBUILD : error MSB1008: Only one project can be specified.
Switch: C:\Users\me\Desktop\Buildssis\SSIS.MSBuild.proj
For switch syntax, type "MSBuild /help"
I think you are prety close about the solution. you can call your package in console application and set the variables there. And for different DEV, SIT, SYS, UAT, PRD. you can have configuration file for the the console application. and then you can set the package variables in the console app. I hope it solve the preoblem. As much i could understand.
Please let me know if this is not related to your problem then explain your question a bit more.
To answer your question, the best way would be to use an UpgradeBuildTemplate for your team build.
Modify the build script to calls the tasks that you have created in the "AfterCompile" target of the build. See below
http://msdn.microsoft.com/en-us/library/aa337604(v=vs.100).aspx
You can pass build parameters in your team build definition. If you edit your build definition and edit Process, you will see option to pass MSBuild Arguments.

Executing Visual Studio from Team Foundation Server

I am working on a project to convert a large application (Database, SSIS packages, some class libraries, PowerShell scripts) from building using a batch file to TFS build and possibly deploy. Currently, how the process works is that a Batch file is ran, and some inputs are taken in, which get stored as variables (environment which helps determine which build to kick off), than MSBuild is called, like so
MSBUILD BuildAll.proj /t:Clean,Build,Package /p:Configuration=%CONFIGURATION% /fl /flp:Summary;LogFile=%LOGFILE% /fl1 /flp1:ErrorsOnly;LogFile=%ERRORLOGFILE% /tv:4.0
The BuildAll.proj is a custom project file that sets some variables, but most importantly (and the part that is failing) executes a command to call Visual Studio to build the solution
<Target Name="Build">
<Exec Condition=" '%(Application.Solution)' != '' " Command='"$(DevEnv10InstallDir)Devenv.com" %(Application.Solution) /Rebuild $(Configuration)' />
</Target>
Here is the log that gets built out
Target "Build" in project "C:\Source\MBFinancial\Development\Trunk\Source\BuildAll.proj" (entry point):
Using "Exec" task from assembly "Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "Exec"
"C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\Devenv.com" Application.sln /Rebuild LocalDevelopment
When I look at the Build Events, it just hangs there, and the Build Folder on the TFS server is never populated as it should. The build process runs just fine if I run it from the TFS server, running as the TFS Build Account, so I know all the proper installs are on the Build Server, but when I execute through Visual Studio Team Explorer, it hangs. I can provide additional details if needed.
you can add a /out switch to devenv.exe (pretty sure you can to devenv.com too), I think you're only looking at teambuild's output and not devenv's, which hopefully will shed light on what's up.
FWIW here's my exec task:
<Exec WorkingDirectory="$(MSBuildProjectDirectory)\Xxx" Command='"$(VS2010)"\devenv.exe xxx.sln /REBUILD "$(Configuration)|x86" /OUT $(MakeLog) /LOG C:\tmp\ActivityLog.xml' ContinueOnError="false" />
I can't remember why I use .exe and an outfile rather than .com.

Using tf.exe to get code for different workspaces

I'm developing code on my machine using one local workspace to get and check in code from TFS. I'm also building an automated CI app which uses my machine and needs to get the latest code. I use the following to get latest for the CI app:
tf get $/abc/abc /recursive /all
I want to use a different local workspace, a CI workspace, to get latest to avoid conflict with dev code. BUT using tf, I'm not able to specify a workspace when using the get command and the MSDN doc doesn't show anyway to do this. How can I use "tf get" to specify the workspace I want to use?
Prior to the tf get, change directory to one of the directories mapped in the CI workspace. tf get will base the server and workspace off of the details for the mapped drive.
I am also creating an automated build process and as part of this performing a 'clean' get i.e. renaming the current local workfolder and then re-getting the files from tfs to a clean copy of the work folder prior to performing full build and unit testing. I am using a Nant script in a separate folder and so couldn't just change directory (changing the current directory within Nant doesn't work for this problem). My solution was to create and run a batch file. Sample Nant script below:
<!--do a clean get to the solution folder-->
<target name="clean_get">
<!--Create the batch file-->
<property name ="batch.file" value="clean_get.bat" />
<echo file="${batch.file}" append="True">md ${workfolder.path}</echo>
<echo file="${batch.file}" append="True">${environment::newline()}cd ${workfolder.path}</echo>
<echo file="${batch.file}" append="True">${environment::newline()}tf get $/${project.name} /recursive</echo>
<!--Run the batch file-->
<exec program="clean_get.bat" >
</exec>
<!--Delete the batch file - we're done with it-->
<delete file="${batch.file}" failonerror="false" />

Can you do a TFS get without needing a workspace?

I'm trying to change our build scripts from using SourceSafe to TFS without using MsBuild (yet).
One hiccup is that the workspace directory is renamed and archived by the scripts, which makes TFS think it doesn't need to get any files. Even with the /force flag it just gets the directories without getting the source files.
I am currently using
TF.exe get "Product/Main/Project1" /force /recursive /noprompt
To save me managing workspaces in the scripts or using intermediate directories, does anyone know of a command that can get directories and code without needing a workspace?
It's not possible to run a tf get without a workspace. The reason is that the server needs to know the mapping between the server paths and the local paths.
If you are working with a large number of files, it is not a good idea to:
Create & Delete a new workspace every time
Or, Create a new workspace (and then never delete it)
The reason for this is that every time you do a Get, the server keeps track of which files, at which versions were downloaded to which workspace. If you never clean up these workspaces, then the table that stores this information will grow over time.
Additionally, if you are creating & deleting a workspace all the time, the server has to write all these rows, then delete them when you are done. This is unnecessary.
You really should try and reuse the same workspace each time. If you do, the server is very efficient about only sending you files that have changed since you last downloaded them. Even if your build is moving from one branch to another, you can use tf get /remap which is sometimes more efficient if the branches share common files.
Although it doesn't solve your problem, it is possible to list files and download files without a workspace.
To list files:
tf dir $/Product/Main/Project1 /R
To download a file:
tf view $/Product/Main/Project1/file.cs
With a creative batch file, you can string these two together with a FOR command. However I would recommend trying to solve your workspace problem first, since that is the way that TFS was intended to be used.
A workspace is a mapping between the source repository location and the filesystem location, so no you can't get away with not using a workspace. But you can easily set up and tear down a workspace when you need to.
Here is a simple TFS task i use to get my database source files from TFS prior to doing some text substitutions and building them into a database update package. You can easily translate this to whatever syntax your current build scripts require:
<Target Name="GetDatabaseSources">
<!-- clean out the folder before doing the fresh get of the database sources -->
<Folder.CleanFolder Path="$(DatabaseBuildBaseLocation)" Force="true"/>
<!-- create a workspace for the database source of the product -->
<CallTarget Targets="CreateDatabaseSourceWorkspace" />
<!-- get the database sources for the product -->
<Get TeamFoundationServerUrl="$(TeamFoundationServerUrl)" Workspace="$(DatabaseSourceWorkspaceName)" Recursive="true" Version="$(DatabaseSourceVersion)" Force="true" />
<!-- delete the workspace -->
<Exec Command="$(Tf) workspace /delete $(DatabaseSourceWorkspaceName) /server:$(TeamFoundationServerUrl) /noprompt " ContinueOnError="true" />
</Target>
<!-- creates and maps a temporary workspace for the database source of the product -->
<Target Name="CreateDatabaseSourceWorkspace">
<Exec Command="$(Tf) workspace /delete $(DatabaseSourceWorkspaceName) /server:$(TeamFoundationServerUrl) /noprompt " ContinueOnError="true" />
<Exec Command="$(Tf) workspace /new $(DatabaseSourceWorkspaceName) /server:$(TeamFoundationServerUrl) /noprompt" />
<Exec Command="$(Tf) workfold /unmap /workspace:$(DatabaseSourceWorkspaceName) $/" />
<Exec Command="$(Tf) workfold /map /workspace:$(DatabaseSourceWorkspaceName) /server:$(TeamFoundationServerUrl) $(DatabaseSourceLocation) "$(DatabaseBuildBaseLocation)"" />
</Target>
TFS is your source repository, but you didn't explicitly mention what your build scripts were designed for. You really should migrate them to a TFS build script, then you can simplify your build, for example you won't have to worry about mapping workspaces or getting the latest source code because TFS does that for you, all you have to worry about is any custom build steps and possibly archiving your build results.
Neno Loje created a small utility that does exactly what you need. To boot it can also remove any source control bindings from the solution and project file, should you need that.
C# has the VersionControlServer library and you can use VersionControlServer.GetItems call to fetch TFS Item. If the ItemType is File then call DownloadFile to retrieve the file.
I don't know why your buildscripts delete the workspace directories everytime. But to answer your question I don't think you can get source code from TFS without a workspace. Maybe you can try to create a worspace everytime before you to a get. The command is
tf.exe workspace /new
You could do this easily with SourceSafe: get any version to any specific directory you wanted. And it was very often very convenient. There are often occasions why one would want to do this. Shame (if) TFS does not support it, it is a missing functionality, imho.

Resources