msbuild refuses to copy unsigned dll if it is of lower version than demanded by one of the project dependencies - is it by design? - msbuild

I have the following situation:
All the involved dlls are unsigned
All the projects in the solution depend on version 1.0.21221.1 of Shared.dll
Some NuGet dependencies of some projects in the solution depend on version 1.0.21237.1 of the same dll.
When the web application is built (let us name it Api) it is expected to copy Shared.dll from $(OutDir) to the $(OutDir)_PublishedWebsites\Api\bin folder. The Shared.dll found in $(OutDir) has the version 1.0.21221.1.
The Shared.dll is NOT copied and the web application fails to run.
Here is the evidence from the binary log:
Exhibit A - The conflict of versions:
Exhibit B - ResolveAssemblyReference instructs NOT to copy Shared.dll:
I understand that msbuild does not like the idea of conflicting versions, but NOT copying the dll produces a downright bug, because the application fails to start.
I understand one can resolve it by adding an assembly binding redirect. But I thought it was unnecessary for unsigned assemblies. Am I understanding wrong or am I missing something?
EDIT 1
Here are my answers to the questions posted in the comments:
(Unfortunately I was asked to obfuscate some keywords, I do not know why)
How exactly does the Api project reference Shared.dll?
As we can see in the exhibit B Shared.dll is a transitive dependency of Api. Indeed, Api depends on Xyz.BusinessApi like this:
<Reference Include="Xyz.BusinessAPI" />
Now that DLL depends on Shared.dll through the respective NuGet dependency, here is a snippet from the project.assets.json file of Xyz.BusinessAPI:
What other projects reference Shared.dll and how?
There are a lot of projects referencing it as a NuGet package at version 1.0.21221.1. The problem is that some projects also reference two other NuGet packages which in turn depend on the version 1.0.21237.1 of the same NuGet package. This is indicated in the RAR output - see the exhibit A.
I would like to emphasize - no project references Shared.dll as a raw dll, only either as NuGet package or indirectly through other NuGets or projects or project dlls. Project dll is a dll of a project from a previously built solution - we do not allow project references to other solutions, so if a project is built in a previous solution, then it would be referenced as DLL in subsequent solutions.
What is the mechanism used to copy from OutDir to _PublishedWebsites\api\bin?
This is the standard web application publishing target _CopyFilesMarkedCopyLocal from C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\VisualStudio\v16.0\WebApplications\Microsoft.WebApplication.targets:
<!-- copy any referenced assemblies to _PublishedWebsites\app\bin folder -->
<Copy SourceFiles="#(ReferenceCopyLocalPaths)"
DestinationFiles="#(ReferenceCopyLocalPaths->'$(WebProjectOutputDir)\bin\%(DestinationSubDirectory)%(Filename)%(Extension)')"
SkipUnchangedFiles="true"
Retries="$(CopyRetryCount)"
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"/>
How does Shared.dll end up in OutDir?
All of our code is built into the shared bin directory - we set OutDir to the same value for all the projects. Thus all the project binaries and their dependencies, including Shared.dll first end up there.
Are there any double-writes in the binlog?
Yes, but I do not think they are relevant:

The short answer is that your projects indirectly depend on two versions of Shared.dll: 1.0.21221.1 and 1.0.21237.1.
RAR (the MSBuild ResolveAssemblyReference task) inspected all the references of all .dlls and found these two versions. It reported a conflict:
Pay attention how it reported the found file path in square brackets for Shared.dll 21221 and reported [] (meaning no file of such version was found) for 21237.
It's useful to search for these using There was a conflict under($rar) or $warning under($rar).
Now, the OutDir only contains the Shared.dll 21221, so the Shared.dll with version 21237 couldn't be found anywhere.
The trick is to search for Shared.dll under($rar project(api.csproj)) You will find the relevant messages from RAR:
Considered "C:\Xyz\61\bin.link\Shared.dll",
but its name "Shared, Version=1.0.21221.1, Culture=neutral, PublicKeyToken=null"
didn't match the expected name "Shared, Version=1.0.21237.1, Culture=neutral, PublicKeyToken=null".
So, it saw a conflict, decided to unify on a later version (21237), but didn't find the file of that version. So the reference of 21221 was not CopyLocal because it didn't unify on that version, and the reference to 21237 was not resolved because a file of that version was not found.
To resolve this, I recommend adding an explicit reference to Shared.dll of the version 21237 (either via NuGet or via GeneratePathProperty metadata on the package reference: https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#generatepathproperty). If you use GeneratePathProperty you can then reference the .dll directly using $(PkgFoo_Bar)\lib\net472\Shared.dll or similar. Also add a binding redirect from 21221 to 21237 to resolve the conflict. Once the correct version (21237) will be in your OutDir, it will get copied to output correctly.
Hope this clarifies.

Related

Create a nuget package with GeneratePackageOnBuild and version patching

What I've been strugling with for some time:
I have a "traditional" (i.e. non-SDK) csproj file. I want to create a nuget package. That's easy but devil hides in the details and I end up in the following catch-22
I can manually call nuget pack .\Foo.csproj, with or without a nuspec file, but then I get the dreaded NU5128 error. The chosen answer suggests to simply ignore the error. I would happily do that, but after publishing the package locally, I noticed that the dependencies are not included. Though there is one dependency to JSON.net that I added for the sake of testing. The same error appears in AppVeyor if I configure my yml to create the package.
I can specify all the packaging related properties in the .csproj file by setting GeneratePackageOnBuild to true. It works and takes the dependencies right but then I can't patch the version like I could do with the replacement tokens of nuspec. The version defaults to 1.0.0.0 even if I change it in the AssemblyInfo.cs to 1.2.3.4.
Notes
This answer suggests that nuspec and csproj configuration can coexist and they are merged. The question is from 2013 so I guess it's not referring to SDK projects. Unfortunately that's not the case for me.
I tried adding a <NuspecFile> in the csproj but then it seems that replacement tokens don't work and I get the error described here. Probably because this instructed nuget to run pack on the nuspec instead of running it on csproj and merge the nuspec. I can't know for sure though as setting the build verbosity to Detailed didn't help to understand what nuget pack tried to do.
Getting the AssemblyInformationalVersion in msbuild and use it straight to the <Version> property isn't as straightforward as one might think and it also defeats the purpose as $version$ is doing exactly that according to the documentation:
AssemblyInformationalVersion if present, otherwise AssemblyVersion
Is there a way to really "merge" .csproj and .nuspec with $version$ patching and get the dependencies in the nuspec right?
So far the best course of action was to convert the project to SDK format. It has several advantages. Less configuration, no need for .nuspec file, no need for AssemblyInfo.cs or VersionInfo.cs files, .nupkg dependencies are correct and AppVeyor can patch directly the .csproj file if it's an SDK project file.
Not a solution per se if one wants to keep the legacy format, but it gets you going with the CI/CD instead of having you fighting with tools.

How does msbuild resolve assembly from a multi-framework nuget package?

I have a solution with a console app ConsoleApp-net461 that references a library ClassLibrary-net452.
Both projects reference a multi-framework nuget package NuGetPackage-net452-net461.
When I build, I end up with the nuget package's net461 dll in the output folder. At runtime, I am getting errors like:
System.TypeLoadException: Inheritance security rules violated by type: 'ClassA'.
Derived types must either match the security accessibility of the base type or be less accessible.
I am suspecting this is because my ClassLibrary-net452 ends up having a dependency on the nuget package dll with the net461 framework.
Is this normal behavior? What are the best ways to ensure the net452 dll is resolved from the nuget package? BindingRedirects don't seem to let you choose a target framework.
Not 100% sure this will work, but might be worth trying:
(Relies on using SDK csproj format, this link might work for packages.config, but I know less about the area.)
It looks like what you want is something like this. However the dotnet/SDK team haven't got round to implementing it yet.
The suggested work around is:
- Grab the dll path from your package reference
- Tell the package reference to not copy compile assets (dlls).
- Use the dll path to generate a similar path for a non-package reference that redirects to the Net452 version of the package.
Explanatory links here and here.
Summary is that your csproj should contain something like this:
<ItemGroup>
<PackageReference Include="*MyPackageName*" ExcludeAssets="Compile" GeneratePathProperty="true">
<Version>x.y.z</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Reference Include="*NameYouWantToGiveThisReference(suggest dll name)*">
<HintPath>$(Pkg*MyPackageName*)lib\net452\*DLLname*.dll</HintPath>
</Reference>
Everything surrounded by ** needs to be named according to whatever your projects are actually called.
The two MyPackageNames must match up (with the caveat that all "."s in the first one should be replaced with "_" in the second one).

VSTS build project with reference to other solution

I have a repository that contains two solutions. One solution (in this case solution A) for a web project that has a reference to a project in the second solution (in this case solution B) (in the same repository).
When I build the web project in VSTS I pull the repository, build solution B, and then build solution B.
Build solution B work, but, the build of solution A is failed cause the reference dll of the project in solution B didn't found
You have a few options:
1) Use project references. You don't need to depend on an assembly.
2) Use NuGet packages -- the shared piece is built via a CI process, turned into a NuGet package, and then published to a Packages feed. The dependent projects can reference the NuGet package and restore an appropriate version on build.
Which approach you should take depends on a lot of factors. If you're not worried about versioning, just use project references.
As Daniel said that it’s better to use NuGet packages.
Regarding reference the assembly file directly, refer to these steps:
Open your web project file through Notepad
Find the related reference and check Hintpath value, should be relative path.
Add Copy files task to your build definition (Before build solution A task) to copy corresponding assembly files to corresponding folder (per to that relative path)

Why Msbuild does not copy Newtonsoft.Json.dll referenced by Microsoft.AspNet.WebApi.Client?

My web projectA references my projectB that references Microsoft.AspNet.WebApi.Client that references Newtonsoft.Json assembly. It is not referenced anywhere else. The problem was that on my deployment server Newtonsoft.Json was not copied to bin directory once run as
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe .\src\path.to.Web.projectA.csproj /t:Rebuild /p:Configuration=Release
And it eventually broke with this exception:
FileNotFoundException: Could not load file or assembly
'Newtonsoft.Json, Version=7.0.0.0, Culture=neutral,
PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The
system cannot find the file specified.
I've resolved it by adding a stub code to projectB:
string json = JsonConvert.SerializeObject(new Something());
Can someone describe why it was not copied before?
Note. These questions do not seem to answer the question or out of date or I miss something?
MSBuild does not copy directly referenced dlls to bin folder
msbuild not copying referenced assembly
MSBuild doesn't copy references (DLL files) if using project dependencies in solution
http://blog.alexyakunin.com/2009/09/making-msbuild-visual-studio-to.html
Copying a DLL's dependencies in Visual Studio
I made a comment under Alex's blog post,
It does not work for me when the indirect dependency comes from NuGet packages (Microsoft.AspNet.WebApi.Client and Newtonsoft.Json). But the tip described in http://www.paraesthesia.com/archive/2014/05/09/recursively-copying-indirect-project-dependencies-in-msbuild.aspx/ solves the issue. I think it uses a more suitable indirect dependency detection approach.
If you follow that solution, then there is no need to use the stub at all.
About why the dll is not copied over, it has been narrowed down to an MSBuild issue (or more as there are too many reports related), which Microsoft claims won't fix on Connect,
https://connect.microsoft.com/VisualStudio/feedback/details/797034/msbuild-handles-direct-and-transitive-references-differently-for-unused-assembly-references

Targetting different Frameworks using MSBuild gives problems with dependencies

I have a little project, and I want to have 2 compiled versions of that project:
one that is targetting the .NET 2.0 framework
one that is targetting the .NET 3.5 framework
All is going well; I've put my project under continuous integration (using CC.NET), and I've created 2 CC.NET 'projects'. One project for each target-framework.
I won't go too much in (irrelevant) details, but my solution is set up to target the .NET 3.5 framework in VS.NET.
I have 2 msbuild-tasks:
one task that builds the solution for
.NET 3.5 (simple and easy)
one task that builds the solution for
.NET 2.0
In this Task, I call MSBuild, and I specify that the TargetFrameworkVersion should be v2.0. I also define some additional build-conditions (so that .NET3.5 specific code is not built in the assembly targetting .NET2.0).
So far, so good. Everything works fine.
Now, the problem however is this:
My solution has a few dependencies (references to 3rd party assemblies). In VS.NET, I've set 'copy local' to true for these dependencies.
When CC.NET builds my .NET3.5 version of the assembly, the 3rd party dependencies are indeed copied to my output-directory.
However, when CC.NET builds my .NET2.0 version of the assembly, the dependencies are not copied to my output-directory. (Then, this causes my unit-tests to fail).
My question now is:
How can I say to msbuild that certain of the 3rd party references have to be copied local when building my .NET2.0 version of my project ?
Or, is there any other way to achieve this, since, I wouldn't like to specify every dependency once again in my build-script. This would quickly become a maintenance nightmare, I guess.
I've been revisiting this problem again, since I do not like to have to manually change the csproj file.
(When I change my reference, I must not forget to adapt the csproj file again, to set the Private node to true again).
So, I've been digging into MSDN, and I stumbled upon this:
ResolveAssemblyReference.TargetFrameworkDirectories
Property
Remarks This property is required to determine the CopyLocal status for
resulting items.
If this property is not specified, no
resulting items will be have a
CopyLocal value of true unless they
explicitly have a Private metadata
value of true on their source item.
So, this means that there is yet another possibility, and that is to set the TargetFrameworkDirectories of the ResolveAssemblyReference task.
However, is there anybody out there who knows how to do this ?
I've been trying different things, but nothing seems to be working ...
I've tried this:
<ItemGroup>
<TargetFrameworkDir Include="$(SystemRoot)\Microsoft.NET\Framework\v2.0.50727" />
</ItemGroup>
<PropertyGroup>
<TargetDirsToUse>#(TargetFrameworkDir)</TargetDirsToUse>
</PropertyGroup>
<ResolveAssemblyReference TargetFrameworkDirectories="$(TargetDirsToUse)" />
But to no avail ...
Maybe someone else knows how to do this, or has a golden tip. (I've been spending way to much time on this f*cking issue).
I've been able to solve this problem by making sure that I do not reference assemblies from the GAC.
Instead, I've created a 'lib' directory in my project that contains the 3rd party assemblies.
In my solution, I reference the 3rd party assemblies from there, and set copy local==True.
Next to that, you must also make sure that in your csproj file, the referenced assemblies have a Private tag whose value is set to true.
Like this:
<Reference Include="...">
<SpecificVersion>False</SpecificVersion>
<HintPath>...</HintPath>
<Private>True</Private>
</Reference>
One question: what happens if you try to compile your 2.0 solution/projects inside VisualStudio? Are 3rd party references auto-copied or not?
It's odd that this would work for 3.5 and not for 2.0. I haven't done any parallel-building myself, but when I converted my projects from 2.0 to 3.5 all the 3rd party references were copied regardless of the .NET version.
BTW: I never reference 3rd party libraries from GAC (only those from Microsoft, and even not all of them). I always copy them to my lib directory structure, add them to the source control and reference them from there. Using assemblies from GAC is bad practice as far as I am concerned, since it represents an unnecessary dependency on a development machine setup.
Example of such a directory: http://code.google.com/p/projectpilot/source/browse/#svn/trunk/lib