22

I am using the MSBuild runner in TeamCity to build an ASP.net web api and running unit tests. Everything was working, until I upgraded to "Microsoft Build Tools 2017 15.7.2".

Suddenly msbuild was copying an older version of Newtonsoft.Json.dll (version 6.0.4.17603) from either "C:\Program Files (x86)\ISS\Microsoft Web Deploy V3" or "C:\Program Files\ISS\Microsoft Web Deploy V3" to the output folder when building the solution. All the projects are referencing the 9.0.1 version using NuGet.

Monitoring the output folder as the build was running, I could see the .dll switching back and forth between 6.0.4 and 9.0.1 until the build ended, and the 6.0.4 version remained.

I found this question and when I renamed the Newtonsoft.Json.dll files in the Web deploy folders to Newtonsoft.Json_old.dll", msbuild did not replace my 9.0.1 version and everything was working fine.

I have checked that all the projects referencing to Newtonsoft.Json are referencing the 9.0.1 version and using the correct Hint-Path in .csproj files.

Does anyone have any idea how to solve the problem? My solution seems more like a workaround and I would like to know why msbuild was copying this file in the first place.

Eirik Fauske
  • 445
  • 5
  • 16
  • I would decompose your SLN file (presuming that's what your using) and diagnose your CSPROJ's discretely. It smells very much like one of them is depending on a package which in turn depends on that old version of Newtonsoft – Malachi Jun 01 '18 at 08:09
  • 2
    I suggest to configure msbuild to output binary log (switch: `/bl:out.binlog`) and then use the [Binary and Structured Log Viewer](http://msbuildlog.com/) to have a look. It makes it easier to find all involved .targets/.props files. – BatteryBackupUnit Jun 01 '18 at 08:40
  • One of the projects is dependant on the "Microsoft.AspNet.WebApi.Client.5.2.3" nuget package, which in turn is dependant on version 6.0.4 of Newtonsoft.Json. Is there some changes in how msbuild resolve these dependencies? I have a bindingRedirect for Newtonsoft.Json in Web.config: – Eirik Fauske Jun 01 '18 at 08:43
  • 1
    .Using the "Find in Files" tab on the left-hand side you can search all involved msbuild files for references to Newtonsoft.Json. – BatteryBackupUnit Jun 01 '18 at 08:43
  • Possible duplicate of [Visual Studio keeps overwriting NewtonSoft.Json.DLL with an older version](https://stackoverflow.com/questions/22490967/visual-studio-keeps-overwriting-newtonsoft-json-dll-with-an-older-version) – Scott Baldwin May 20 '19 at 21:51

4 Answers4

22

Summary

When MSBuild is resolving assemblies, it will search in some pretty weird directories, including that Web Deploy folder, depending on what you have installed. Based on the MSBuild reference, I believe that this is legacy behavior. You can stop it from doing that with an MSBuild property defined in your project file.

In the affected project file, find the following line:

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

And add this below it:

<PropertyGroup>
    <AssemblySearchPaths>$(AssemblySearchPaths.Replace('{AssemblyFolders}', '').Split(';'))</AssemblySearchPaths>
</PropertyGroup>

This will cause MSBuild to no longer look in the problematic folders when resolving assemblies.


Full Story

My team ran into a similar problem when we moved to Visual Studio 2019. Some of our projects are still targeting .NET Framework 4.0, and after installing Visual Studio 2019 on our build agents, we started getting a mysterious error with projects that referenced some of our core libraries:

The primary reference "OurCoreLibrary, Version=3.4.2.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxx, processorArchitecture=MSIL" could not be resolved because it has an indirect dependency on the assembly "Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed" which was built against the ".NETFramework,Version=v4.5" framework. This is a higher version than the currently targeted framework ".NETFramework,Version=v4.0".

The problem went away upon switching the project to target 4.5, but for reasons I won't get into here, we couldn't do that for every affected project, so I decided to dig in a bit deeper.

As it turns out, your question offered some insight into what was going on. The version of Newtonsoft.Json that we were referencing matched the version in "C:\Program Files (x86)\IIS\Microsoft Web Deploy V3", and when I removed the file, the build succeeded.

Our specific problem was that the copy of Newtonsoft.Json in the Web Deploy folder was the same version (9.0.0.0) but the wrong framework (4.5 instead of 4.0), and for whatever reason the resolution logic doesn't check the target framework, causing a mismatch at build time. Updating to VS2019 involved updating Web Deploy, which also updated that copy of Newtonsoft.Json to 9.0.0.0, causing our collision.

To see why that assembly was even being looked at to begin with, I set the MSBuild project build output verbosity to Diagnostic and took a look at what was happening. Searching for the offending path showed that in the ResolveAssemblyReferences task, MSBuild was going through some unexpected places to find matches:

1>          For SearchPath "{AssemblyFolders}". (TaskId:9)
1>          Considered "C:\Program Files (x86)\Microsoft.NET\ADOMD.NET\140\OurCoreLibrary.winmd", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Microsoft.NET\ADOMD.NET\140\OurCoreLibrary.dll", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Microsoft.NET\ADOMD.NET\140\OurCoreLibrary.exe", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.0\OurCoreLibrary.winmd", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.0\OurCoreLibrary.dll", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.0\OurCoreLibrary.exe", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.5\OurCoreLibrary.winmd", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.5\OurCoreLibrary.dll", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.5\OurCoreLibrary.exe", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files\IIS\Microsoft Web Deploy V3\OurCoreLibrary.winmd", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files\IIS\Microsoft Web Deploy V3\OurCoreLibrary.dll", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files\IIS\Microsoft Web Deploy V3\OurCoreLibrary.exe", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Microsoft SQL Server\140\SDK\Assemblies\OurCoreLibrary.winmd", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Microsoft SQL Server\140\SDK\Assemblies\OurCoreLibrary.dll", but it didn't exist. (TaskId:9)
1>          Considered "C:\Program Files (x86)\Microsoft SQL Server\140\SDK\Assemblies\OurCoreLibrary.exe", but it didn't exist. (TaskId:9)

Further digging shows that the paths searched are passed in as AssemblySearchPaths, which is defined in Microsoft.Common.CurrentVersion.targets:

<AssemblySearchPaths Condition=" '$(AssemblySearchPaths)' == ''">
  {CandidateAssemblyFiles};
  $(ReferencePath);
  {HintPathFromItem};
  {TargetFrameworkDirectory};
  $(AssemblyFoldersConfigFileSearchPath)
  {Registry:$(FrameworkRegistryBase),$(TargetFrameworkVersion),$(AssemblyFoldersSuffix)$(AssemblyFoldersExConditions)};
  {AssemblyFolders};
  {GAC};
  {RawFileName};
  $(OutDir)
</AssemblySearchPaths>

According to the MSBuild Task Reference for the ResolveAssemblyReferences task, SearchPaths parameter is defined as:

Specifies the directories or special locations that are searched to find the files on disk that represent the assemblies. The order in which the search paths are listed is important. For each assembly, the list of paths is searched from left to right. When a file that represents the assembly is found, that search stops and the search for the next assembly starts.

...and it defines a few special constants, including our friend {AssemblyFolders}:

  • {AssemblyFolders}: Specifies the task will use the Visual Studio.NET 2003 finding-assemblies-from-registry scheme.

Because the directories are checked in order, you might expect {HintPathFromItem} to take precedence, and in most cases it does. However, if you have a dependency with a dependency on an older version of Newtonsoft.Json, there won't be a HintPath for that version and so it will continue on until it resolves.

Later on in Microsoft.Common.CurrentVersion.targets we can see that there are cases where this constant is explicitly removed, which is where the answer above comes from:

<PropertyGroup Condition="'$(_TargetFrameworkDirectories)' == '' and '$(AssemblySearchPaths)' != '' and '$(RemoveAssemblyFoldersIfNoTargetFramework)' == 'true'">
  <AssemblySearchPaths>$(AssemblySearchPaths.Replace('{AssemblyFolders}', '').Split(';'))</AssemblySearchPaths>
</PropertyGroup>

Removing this constant removes the offending folders from consideration, and to be honest I cannot think of a situation where I would want an assembly to implicitly resolve to whatever version of say, Newtonsoft.Json, was hanging out in the Web Deploy or SQL Server SDK folder. That being said, I am sure there's a case out there where turning this off will cause somebody issues, so keep that in mind.

Scott Baldwin
  • 422
  • 3
  • 9
  • 2
    Although it's been a while since I had this problem, and I can't remember how I fixed it, this answer explains a lot how this happened and has been very informative. Thank you for taking the time to provide such an detailed answer! – Eirik Fauske May 16 '19 at 05:11
  • No problem! I needed to write it up for my team anyway :). It's a frustrating problem that just seems to be nonsense and there isn't a lot of information out there about it, so hopefully this will help someone out in the future. – Scott Baldwin May 16 '19 at 14:17
  • Thanks Scott! This was exactely the problem on my side. Thanks for the detailed answer :-) – Patrick Leijser Sep 17 '20 at 13:34
  • Life saver. Other than "ISS" being "IIS", this is an awesome answer – Francesco B. Feb 08 '21 at 16:41
  • Thank you so much for this solution Scott! Really made my day! ❤ – M463 Jun 16 '21 at 07:51
1

It may be worth to trigger a custom build and ticking 'clean all files in the checkout directory before the build' - you may have conflicting build tools lingering.

  • This setting is enabled by default, so this is not the case. I have searched for all Newtonsoft.Json.dll files on the drive, and the only occurrances of this file in the 6.0.4 version is in the "Microsoft Web Deploy V3" folders stated in the question. – Eirik Fauske Jun 01 '18 at 08:39
  • I would double check the .csproj file for any reference to 'Newtonsoft.Json' - I currently use NuGet to fetch this reference and have not had any issues, so this could quite be a problem with the 2017 build tools as mentioned in that link you provided in the original post. – Connor Jarvis Jun 01 '18 at 08:46
  • I have searched through all .csproj files and all which have references to Newtonsoft are targeting 9.0.1 version. – Eirik Fauske Jun 01 '18 at 08:55
1

I actually found myself facing the same problem, only that it was not only Newtonsoft.Json.dll causing problems, but also other dlls like System.Threading.Tasks.Extensions.dll and System.Memory.dll.

If you have a look at Microsoft.Common.CurrentVersion.targets, you can see that there is a better mechanism to disable parts of the AssemblySearchPaths, than a string replacement as suggest by Scott Baldwin.

You can use the Directory.Build.props file and then simply specify the parts of the assembly search path you want to disable. I would suggest to put disable the following paths from the Directory.Build.props:

<PropertyGroup>
<!-- https://stackoverflow.com/questions/50638711/msbuild-is-replacing-newtonsoft-json-dll-with-an-older-version -->
<AssemblySearchPath_UseAssemblyFoldersConfigFileSearchPath>false</AssemblySearchPath_UseAssemblyFoldersConfigFileSearchPath>
<AssemblySearchPath_UseRegistry>false</AssemblySearchPath_UseRegistry>
<AssemblySearchPath_UseAssemblyFolders>false</AssemblySearchPath_UseAssemblyFolders>
<AssemblySearchPath_UseGAC>false</AssemblySearchPath_UseGAC>
</PropertyGroup>
0

I found another simple solution. I manually moved

<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
      <HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
    </Reference>

to the last ItemGroup section with Reference.

user2393156
  • 171
  • 1
  • 3