Auto-update your NuGet packages at build time

By | November 28, 2014

NuGet is great. So great, in fact, that I’ve set up my own NuGet server in Azure to use during my personal development to share Common projects, etc. It’s insanely nice as now I can do my builds using packages from other VSO projects without any hassle.

But for my own personal development, and likely any private NuGet dev that my readers might be doing, it makes some sense to not only deploy libs as NuGet projects, but also configure the applications that use them to pull the latest version of the libs at build time. But NuGet doesn’t do this. By default, anyway… ;)

When you configure NuGet package restore for your project, what happens at build time is simply a download of the version specified in packages.config to the build location to be used to resolve references. It doesn’t grab any version other than that specified in that file. If you want to get a new version, you have to run the equivalent of ‘nuget update <package id>’ before doing the build.

But it’s not as simple as putting ‘nuget update <package id>’ in the pre-build steps. In fact that’s actually too late.

So how can we make this happen? Sure you can add a new build step to your process – if you’re doing a big fat build server build configuration with TFS, etc. But what about on my local machine? I want the update to take place on its own without me having to think about it, and also to find out if anything breaks when I get this new version – at a time when I’m able to fix it, not on the build server.

Let’s have a look at a few snippets from the NuGet.Targets file that’s put in place when you configure package restore:

<!-- Commands -->
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)</RestoreCommand>
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols</BuildCommand>

<!-- We need to ensure packages are restored prior to assembly resolve -->
<BuildDependsOn Condition="$(RestorePackages) == 'true'">
RestorePackages;
$(BuildDependsOn);
</BuildDependsOn>

<!-- Make the build depend on restore packages -->
<BuildDependsOn Condition="$(BuildPackage) == 'true'">
$(BuildDependsOn);
BuildPackage;
</BuildDependsOn>

<Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">        
<Exec Command="$(RestoreCommand)"
Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />

<Exec Command="$(RestoreCommand)"
LogStandardErrorAsError="true"
Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
</Target>

<Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
<Exec Command="$(BuildCommand)"
Condition=" '$(OS)' != 'Windows_NT' " />

<Exec Command="$(BuildCommand)"
LogStandardErrorAsError="true"
Condition=" '$(OS)' == 'Windows_NT' " />
</Target>

Seeing how this works? We’ve got a set of ‘Commands’ we’re going to run. We define what they actually do, define the order of execution, then wrap that up in a ‘<Target>’ tag to be used by the MSBuild engine.

So have a look at these apples:

<!-- Commands -->
<UpdateCommand>$(NuGetCommand) update "$(PackagesConfig)" -source "$(PackageSources)" -id AutoUpdater $(NonInteractiveSwitch)</UpdateCommand>
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)</RestoreCommand>
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols</BuildCommand>

<!-- We need to ensure packages are restored prior to assembly resolve -->
<BuildDependsOn Condition="$(RestorePackages) == 'true'">
RestorePackages;
UpdatePackages;
$(BuildDependsOn);
</BuildDependsOn>

<Target Name="UpdatePackages" DependsOnTargets="CheckPrerequisites">
<Exec Command="$(UpdateCommand)"
Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />

<Exec Command="$(UpdateCommand)"
LogStandardErrorAsError="true"
Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
</Target>

Define an UpdateCommand and wire it in to the MSBuild processing and voila. What you’ll see happen is the originally-specified package (eg: v1.0) will be “restored” at build time, then the update takes place (to v1.1) and that’s what gets referenced by the parent Project file. On your physical disk you’ll see both packages show up in SolutionDir\packages, but the end result is what you want. I tried ordering UpdatePackages before RestorePackages to avoid this quirk but I was met with errors, unfortunately.

Best part is this works locally in your VS instance and on the build server when the build gets run.

Enjoy!

  • mwojtyczka

    This is what I’m currently looking for but what’s the AutoUpdater?

    • That’s the ID of the package I want to update automatically at build time.

  • Joseph

    It did not work on my solution, could you please help me? Thank you

    • I’d need a bit of information to start helping…

  • Martin David

    Is there a way to auto update all the packages or do you have to specify each package id separately?

    • take out the `-id AutoUpdater` part of the command I put in. It *should* read your `packages.config` and update everything in it that has an update available.

      • Martin David

        Thanks for getting back to me! I am getting an error on build: Unable to find [package]. Make sure they are specified in the packages.config.
        I think it is related to the -source “$(PackageSources)” part of the UpdateCommand because its blank when the command actually runs. I dont seem to be getting this issue with the RestoreCommand though. Any ideas?

  • Jürgen Gehrig

    Will not work very well with tfvc. If there are new versions of the packages, NuGet can’t write to packages.config to update the file. So you have to manually checkout all packages.config, which is not very convenient.

  • yogesh patel

    I am writing id of package to update as below, but it gives errors like “The expression “””.AspNet” cannot be evaluated. Method ‘System.String.AspNet’ not found.”
    $(NuGetCommand) update “$(PackagesConfig)” -source “$(PackageSources)” -id “$(Microsoft.AspNet.Cors)” $(NonInteractiveSwitch)

    What is the correct syntax to define id of nuGet package?

  • cblin

    Old post but still usefull.

    The problem is that is was working until I uninstall VS2010.

    The problem I have is with the nuget command ath the end of cmy comment.

    Do you know why nuget is falling back to v10.0 ?

    c:xeroxworkspaceFMKEXAMPLES.nuget>nuget update “C:xeroxworkspaceFMKEXAMPLESExample.Apipackages.config” -source “NuGet Xerox package source” -NonInteractive -Verbosity detailed

    Microsoft.Build.Exceptions.InvalidProjectFileException: Le projet importé “C:Program Files (x86)MSBuildMicrosoftVisualStudiov10.0WebApplicationsMicrosoft.WebApplication.targets” est introuvable. Vérifiez que le chemin d’accès dans la déclaration est correct et que le fichier existe sur le disque. C:xeroxworkspaceFMKEXAMPLESExample.ApiEXAMPLE.API.csproj

    à Microsoft.Build.Shared.ProjectErrorUtilities.ThrowInvalidProject(String errorSubCategoryResourceName, IElementLocation elementLocation, String resourceName, Object[] args)
    à Microsoft.Build.Shared.ProjectErrorUtilities.ThrowInvalidProject(IElementLocation elementLocation, String resourceName, Object arg0)
    à Microsoft.Build.Evaluation.Evaluator`4.ExpandAndLoadImports(String directoryOfImportingFile, String importExpressionEscaped, ProjectImportElement importElement)
    à Microsoft.Build.Evaluation.Evaluator`4.EvaluateImportElement(String directoryOfImportingFile, ProjectImportElement importElement)
    à Microsoft.Build.Evaluation.Evaluator`4.PerformDepthFirstPass(ProjectRootElement currentProjectOrImport)
    à Microsoft.Build.Evaluation.Evaluator`4.Evaluate()
    à Microsoft.Build.Evaluation.Evaluator`4.Evaluate(IEvaluatorData`4 data, ProjectRootElement root, ProjectLoadSettings loadSettings, Int32 maxNodeCount, PropertyDictionary`1 environmentProperties, ILoggingService loggingService, IItemFactory`2 itemFactory, IToolsetProvider toolsetProvider, ProjectRootElementCache projectRootElementCache, BuildEventContext buildEventContext, ProjectInstance projectInstanceIfAnyForDebuggerOnly)
    à Microsoft.Build.Evaluation.Project.ReevaluateIfNecessary(ILoggingService loggingServiceForEvaluation)
    à Microsoft.Build.Evaluation.Project.Initialize(IDictionary`2 globalProperties, String toolsVersion, String subToolsetVersion, ProjectLoadSettings loadSettings)
    à Microsoft.Build.Evaluation.Project..ctor(String projectFile, IDictionary`2 globalProperties, String toolsVersion, String subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings)
    à NuGet.Common.MSBuildProjectSystem.GetProject(String projectFile)
    à NuGet.Commands.UpdateCommand.GetMSBuildProject(String packageReferenceFilePath)
    à NuGet.Commands.UpdateCommand.ExecuteCommand()
    à NuGet.Commands.Command.Execute()
    à NuGet.Program.Main(String[] args)

    • cblin

      Note that you could work around it via: mklink /D “C:Program Files (x86)MSBuildMicrosoftVisualStudiov10.0WebApplications” “C:Program Files (x86)MSBuildMicrosoftVisualStudiov12.0WebApplications”

      But this is ugly and must be done on every machine without vs2010…

    • If you look in your csproj files, I believe you’ll see this is an artifact of when they were created.

      You’ll see something like:

      10.0

  • Alexander Panfilenok

    Here is more easy way, in Pre Build Event text area type something like this:

    “$(SolutionDir).nugetNuGet.exe” update “$(ProjectDir)packages.config” -source “{YOUR NUGET FEED}” -id {YOUR NUGET DEPENDENCY ID} -NonInteractive