Can you do a TFS get without needing a workspace? - tfs

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.

Related

TFSBuild.proj import common targets after getting from source control

I am currently rewriting/cleaning up our TFS Builds, and I have noticed that we have a lot of duplication. I was wanting to create a "Common.targets" file which all our tfs builds (about 30) would then import.
I have tried a few things along these lines of:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
<Import Project="$(MSBuildExtensionsPath)\Microsoft.Sdc.Common.Tasks"/>
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<Import Project="$(BuildDefinition).proj"/>
<PropertyGroup>
<ProjectName>Felix.LincsPublishingHandlers</ProjectName>
</PropertyGroup>
<Import Project="$(BuildDirectory)/src/BuildScripts/Common/CommonTargets.targets"/>
</Project>
Where:
$(BuildDefinition).proj -> contains project/environment specific properties (eg db connection strings, IP addresses)
CommonTargets.targets -> used across all the various projects. Defined in the Build Definition > Source Settings
However when I try to run on the TFS Build agent (version 2010 if anyone needs to know), it tries to run the statements before it has copied down the source files. So I get an error saying that the CommonTargets.targets file doesn't exist.
I've had a look around the web, and the solutions I have come across so far have suggestions that I would prefer not to use:
Copy the Common.targets file under into the same folder that has the TFSBuild.proj file
I have 20+ builds in different folders
I would like them all to use a single common.targets file (not 20 copies of the same file)
Copy the Common.targets file to a location on the build server(s) that the TFS Build Agents can access
I want to have it in source control with the rest of the build scripts and code.
As we are doing a sort of migration project, the Common.targets will change a bit over time, so best to have in source control
Has come across any similar issue of trying to import a Common.targets file that is kept in TFS source control?
Thanks for any help
First, you should consider upgrading TFS as mainstream support for TFS 2010 ends in a few months.
http://nakedalm.com/its-that-time-again-get-ready-to-upgrade-to-tfs-2015/
Second, you should consider updating your build system to use a modern method. Use the TFS 2013 template and PowerShell.

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" />

Overriding the PostBuildEvent in TFSBuild.proj

I'm using currently VS 2010 and TFS 2008. In one of our solutions we have a .csproj file with the following:
<Target Name="BeforeBuild">
<TransformXml Source="..\..\..\ProjectX\ProjectXWebSite\ProjectXWebSite\Web.config" Transform="..\..\..\ProjectX\ProjectXWebSite\ProjectXWebSite\Web.$(Configuration).config" Destination="..\..\..\ProjectX\ProjectXWebSite\ProjectXWebSite\Web.$(Configuration).config.transformed" />
</Target>
<PropertyGroup>
<PostBuildEvent>xcopy "C:\Source\Projects\ProjectX\ProjectXWebSite\ProjectXWebSite\Web.$(Configuration).config.transformed" "C:\Source\Projects\ProjectX\ProjectXWebSite\ProjectXWebSite\Web.config" /R /Y</PostBuildEvent>
</PropertyGroup>
This works fine when building locally, but TFS is failing when it reaches <PostBuildEvent>, because this path is not available on the build machine. How can I get TFS to either skip the entire "BeforeBuild" or just set the <PostBuildEvent> to an empty string, so that the build will work successfully when built locally in VS2010 and via TFS 2008? I've tried within TFSBuild.proj to set <Target Name="BeforeBuild" />, but this doesn't work.
I'd recommend against using absolute pathnames in your projects - relative paths will make them relocatable and headaches like this will disappear.
i.e. In the post-build xcopy, just change C:\Source\Projects\ProjectX to ..\..\..\ProjectX
(You can't rely on always being able to put your code on C: - I've had to move my code from C: to D: or E: due to lack of disk space or installation of a new drive on several occasions, and using relative paths has made this process very easy. It's also very handy at times to be able to have multiple copies (different versions or branches) of your source code on your PC at once and still be able to build them all)

Is there any task to copy files from TFS to a folder?

Scenario: I need as part of my deploy scripts I have a task that copy files from a source to a origin.
Now I have to change the source. Instead of being a normal folder it has to be a location in my TFS.
Is there any task to do it? I can't find any.
I am trying to get the files manually from TFS using something similar to:
<PropertyGroup>
<TF>"C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\tf.exe"</TF>
<TFSourceLocation>$/TFSDIR</TFSourceLocation>
<SolutionRoot>.</SolutionRoot>
<RemoteWebRoot>$(DestinationRoot)\DIR</RemoteWebRoot>
<Copy>xcopy /E /I /R /Y</Copy>
</PropertyGroup>
<Exec Command="$(TF) get $(TFSourceLocation) /force /recursive /version:T /noprompt" ContinueOnError="true" />
I don't have to compile anything. I only need to copy some files that now are stored in TFS to a folder.
Question: Is this the best approach? or exists a task that allows me to copy from TFS to a folder?
I don't understand your question completely, but if you want to download some files from TFS to a folder on the build server, the command you are using is the best option.
Only be aware that you need a workspace configured so TFS knows where to download the files to.
Another option is to use the TF VIEW command:
tf view $/myitem /console /collection:http://mytfsserver:8080/tfs/defaultcollection >c:\localfile
This will show the contents of the item to the console and prints it to c:\localfile. You then get around the workspace issue.

How do I put files in the TFS Build drop location

I'm new to using TFS build. I've got a build defined that runs as a continuous integration. It creates a drop folder, but there's nothing in it.
What's the best practice for moving stuff in the drop folder? I've seen a Binaries folder, do I need to copy things into there, or do I alter the TFSbuild.proj in some way to copy the files I want to the drop folder?
It sounds like you want to copy miscellaneous files from your workspace (or elsewhere) into the drop location?
The target above gives you an example of how to create a target to copy files, but you're probably wondering how to hook it up in your TFSBuild.proj.
A simple way to do this is using one of the pre-defined skeleton targets for this such as AfterDropBuild. If you had a target like the one mentioned above for copying your files you would put this in TFSBuild.proj:
<CreateItem Include="$(SolutionRoot)\Source\RandomFilesInWorkspaceFolder\**\*.*">
<Output TaskParameter="Include" ItemName="RandomFiles" />
</CreateItem>
<Copy SourceFiles="#(RandomFiles)" DestinationFiles="#(RandomFiles->'$(DropLocation)\RandomDestinationSubFolder\%(RecursiveDir)%(Filename)%(Extension)')" />
I seemed to get it working by adding this near the end of my TFSBuild.proj
<Target Name="PackageBinaries">
<ItemGroup>
<FilesToDrop Include="$(SolutionRoot)\MyProduct\Installer\Bin\**\*.msi"/>
</ItemGroup>
<Message Text="FilesToDrop=#(FilesToDrop)"/>
<Copy SourceFiles="#(FilesToDrop)"
DestinationFiles="#(FilesToDrop ->'$(BinariesRoot)\%(RecursiveDir)%(Filename)%(Extension)')"/>
</Target>
It copies wanted msi files into the Binaries folder which the normal tfs build system then copies to the drop location. I noticed the Binaries folder gets deleted everytime a build is started, so you don’t have to worry about cleaning up.
The PackageBinaries target seems to be the standard target name that you can override for doing this kind of thing.
Update Newer versions of TFS probably have better ways!

Resources