Integrate NuGet Package Restore TFS Build 2013 or Older

Integrate NuGet Package Restore TFS Build 2013 or Older

TFS Test API
181 Shares
Restore NuGet Packages TFS Build 2013 or Older

If you write .NET application most probably you use NuGet packages. There are a couple of ways to restore them during the build process. The old ways required a special .nuget folder to be included in every solution with a couple of other files. I will show you how not to pollute your projects and solutions with additional MSBuild targets. However, you will need to integrate the NuGet restoration on a build level. So I will show you how to edit the default TFS build template and add a new step for NuGet packages restore. This will work for TFS 2013 or older builds' templates since the new ones have a built-in capability to restore NuGet packages. However, lots of folks don't have the advantage of using the new TFS.

Old Way of Restoring NuGet Packages

Previously all solutions needed to have included a .nuget folder where nuget.targets and nuget.exe were placed. They needed to be added under source control too. Moreover, you were required to include something like the below MSBuild to each one of the solutions' projects.

<RestorePackages>true</RestorePackages>
...
<Import Project="$(SolutionDir).nugetuget.targets" />
...
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir).nugetNuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir).nugetNuGet.targets'))" />
</Target>

Drawbacks MSBuild-integrated Restore Approach

  • It requires additional files within the solution folder.
  • It requires importing a .targets file into all projects in the solution, which this can introduce issues when projects are shared among multiple solutions.
  • Projects will fail to load if nuget.targets cannot be found.
  • Projects won't build successfully if any of the restored NuGet packages extend MSBuild through a targets file import.
  • Packages are automatically added to Team Foundation Version Control, when in use, unless specifically disabled.

"To avoid all these issues, it's recommended to migrate any project using MSBuild-integrated restore to use the automatic restore capabilities of NuGet 2.7 and above." There is a short tutorial how to migrate your projects and links to PowerShell scripts that can migrate all of your projects. In short, you need to delete the .nuget folder and delete the previously mentioned MSBuild statements. The last step is to integrate the NuGet.exe restore into your builds. This is the main topic of the article.

TFS Build Restore NuGet Packages

Default Template

1. Locate and open the default Windows Workflow Foundation build template

2. Locate the sequence where your projects are built. This is happening in the Run MSBuild for Project activity

Default Build Sequence

We need to include the new activity before the actual build of the projects. 

Default Arguments

I will add a few new parameters (arguments) to the build template. 

3. Click on the Arguments tab and add the new arguments

Default Arguments TFS Build Template

Add New Arguments

Below you can find the new parameters, their type and default values.

New Arguments TFS Build Template

ShouldRestorePackages- You can specify whether the NuGet restore is enabled.

NuGetFeedSources- Specify the feed URLs which nuget.exe will use to restore the packages. You can specify more than one URL using ";" as a delimiter.

NuGetExeServerPath- The default TFS server path to NuGet.exe. The build will download it if the restore of packages is enabled.

TeamCollectionUrl- The URL of the TFS team collection where the NuGet.exe is placed.

Once declared we could use these settings through our WWF activities.

Add New Build Sequence

4. Add a new sequence and place For Each Project in BuildSettings.ProjectsToBuild inside it

Add New Sequence to Build

Add Download NuGet.exe Sequence

5. Add a new If statement, deciding whether to download the NuGet.exe or not, based on the ShouldRestorePackages argument

if ShouldRestorePackages Step

It is a good practice to change the DisplayName of the if statements. This will give you better troubleshooting capabilities during template's debugging. Also, makes the template more readable.

Default If Step Display Name

You can change the DisplayName to If ShouldRestorePackages- Download NuGet.exe.

6. Add a new Download File Activity to download the NuGet.exe from TFS (of course, you need to download it from the official site first and then check-in it)

7. Configure LocalPath (where the file will be downloaded)

It will be saved in a temp folder. However, as you can see from the image below the Path class could not be resolved.

Path Not Found Warning

8. Add an import to System.IO (click on the Imports tab)

Add System.IO Import

9. Configure Version parameter

Version Download File Activity

You need to set the Version to the following statement- TfsTeamProjectCollectionFactory.GetTeamProjectCollection(New Uri(TfsCollectionUrl)).GetService(Of VersionControlServer)().GetLatestChangesetId().ToString()

For this one to work you need to add another import.

10. Add an import to Microsoft.TeamFoundation.Client

11. Configure VersionControlServer parameter

Version Control Server Download File Activity

You need to set the VersionControlServer to the following statement- 

TfsTeamProjectCollectionFactory.GetTeamProjectCollection(New Uri(TfsCollectionUrl)).GetService(Of VersionControlServer)()

Create Restore NuGet Packages Activity

The next step is to add the primary activity that will restore the NuGet packages. It will call the downloaded NuGet.exe with a few parameters.

12. Add a new InvokeProcess activity and wrap it again in a new If statement

Add Invoke Process Activity

13. Set the Arguments parameters

Set Arguments to String.Format("restore {0} -source ""{1}""", localBuildProjectItem, NuGetFeedSources)

This way you will tell the NuGet.exe to restore the packages of the currently built project from the specified feed URLs.

New Arguments Restore Packages

This is how finally your template should look like.

Restore Packages Activity

Create NuGet Restore Packages Section

If you want to add a new settings tab in your build configuration, you need to add new metadata.

14. Locate the Metadata Argument and click the button at the end of the row.

Configure TFS Build Metadata

15. Add a new metadata for each of the new arguments that we previously created. 

First, specify the exact name of the argument, then you can assign a display name. The category should be equal for each of the parameters, this will be the name of the section, for example- Restore Packages. Finally, set some proper description so that the people can orient themselves when using your template. 

Metadata Default View TFS Build Template

This is how your new settings' section will look like if you edit a build definition that uses the new template.

New Custom Metadata Restore Packages

  • Bart

    Great work!! Just what I needed. One suggestion: I struggled for quite a while with just the mechanics of how to edit the template workflow. You do not show how to use the toolbox to drag things onto the workflow so I was looking through menus, etc. Just one extra screenshot showing the Toolbox (or at least some text that mentions the Toolbox) would have really helped me. Thanks!!

    • Thank you for the suggestion, I will write it down to add one when I have time. If you have something specific in mind, you can add it here, and I will update the post immediately. :)

  • David Logan

    Anton, As Bart said I struggled with the template workflow a little bit and I don’t see anywhere that you specify the FileName property for the Restore Packages Process. Did I miss that? I’ve requested the source code and am waiting for the email to get that to look at. Also you’ve got some typo’s on your Version parameter where you use “TFSCollectionUrl” instead of “TeamCollectionUrl” as defined. And “DefaultNuGetExeServerPath” instead of “NuGetExeServerPath” as defined.

    • Hello David. Thank you for the comment. Not sure that there any typos. The editing differs from IDE to IDE. So, my suggestion is to download the code and try to play with it.

      • David Logan

        Anton, there are definitely typo’s in the text of the article. You define under “Add New Arguments” the argument “TeamCollectionUrl” but in the “Add Download NuGet.exe Sequence” sections under items 9 and 10 you use the argument name “TfsCollectionUrl” as:

        TfsTeamProjectCollectionFactory.GetTeamProjectCollection(New Uri(TfsCollectionUrl)).GetService(Of VersionControlServer)().GetLatestChangesetId().ToString()

        and

        TfsTeamProjectCollectionFactory.GetTeamProjectCollection(New Uri(TfsCollectionUrl)).GetService(Of VersionControlServer)()​

  • David Logan

    Anton, You might add quotes around “”{0}”” of the restore in:String.Format(“restore {0} -source “”{1}”””, localBuildProjectItem, NuGetFeedSources) so that Build names with spaces in them work properly. Also I don’t know if you’ve ever noticed but the “Get WorkSpace” happens AFTER the “clean” which means the first time through the source code hasn’t been downloaded and the File.Exists(ProjectName) will return false and the NuGet packages will not be downloaded the first time a build agent is used. The next time through it will work but it will be using potentially out of date project file to do the get with.