Include both Nuget Package References and project reference DLL using “dotnet pack” 📦

Do feel free to provide any comments/feedback to @TheRichCarey on Twitter

Recently I have been trying to generate more Nuget packages for our dotnet core projects, utilizing the dotnet pack command. One issue I have been encountering is that the command was either referencing the required nuget packages, or the project reference DLLs, never both.

The current problem.

If you have Project A which has a project reference to Project B as well as including a nuget package called Package A you would expect the generated package to contain a link to both the required nuget package, and the DLL(s) for Project B, yes? This however is not how the dotnet pack command works.

This issue is widely reported on their repo (I.e. https://github.com/NuGet/Home/issues/3891 ) and unfortunately it seems the developers and the community are in a bit of a disagreement to what is “correct”. The official stance (as I understood it) is that the project references won’t be included as they should be their own packages. This however is not always practical or desired.

The workaround.

Plenty of workarounds have been suggested around Stack Overflow and Github including having a seperate nuspec file, using Powershell to inject things into the generated nupkg and so on…

The solution below worked for me, but of course, YMMV.

In the end I ditched having my own .nuspec file within my project (as per some SO posts) and instead used the CSPROJ (as recommended). Below you can see the required fields for the packaging (version, naming, etc), a reference to a nuget package, and a reference to another project within the solution.

CSProj Snippet of dotnet core project
Snippet of CSPROJ with basic package info filled in.

If you run dotnet pack now, it will generate an appropriately named package which will contain a nuget dependancy on SomeNugetPackage. This can be confirmed by opening the nupkg with an archive tool (7Zip,WinRar, WinZip…) and seeing that the only DLL in the lib folder will be the DLL of the project being packed.

The fix is as follows:

  • Alter the project reference to set the ReferenceOutputAssembly flag to true, and IncludeAssets to the DLL name
<ProjectReference Include="..\ProjectB.csproj">
  <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
  <IncludeAssets>ProjectB.dll</IncludeAssets>
</ProjectReference>  
  • Add the following line into the <PropertyGroup> element
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
  • Add new target between <project> tags
<Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage">
    <ItemGroup>
      <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))"/>
    </ItemGroup>
  </Target>

So now you end up with something that looks like this

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <Version>1.0.9</Version>
    <Product>MyProduct</Product>
    <id>MyProduct</id>
    <PackageId>MyProduct</PackageId>
    <Authors>Your name</Authors>
    <Company>Company Name</Company>
    <Description>My library</Description>
    <Copyright>Copyright © 2019 MyCompany</Copyright>
    <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="SomeNugetPackage" Version="1.2.3"/>  
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\ProjectB.csproj">
      <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
      <IncludeAssets>ProjectB.dll</IncludeAssets>
    </ProjectReference>  
  </ItemGroup>
  <!--Next line is to ensure that dependant DLLS are copied-->
  <Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage">
    <ItemGroup>
      <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))"/>
    </ItemGroup>
  </Target>
</Project>
End result CSPROJ. (Click to enlarge)

Now if you run dotnet pack you should see any project reference DLL under the lib folder of the package, and if you inspect the nuspec file inside the package (or upload it to your package repo) you should see the nuget dependencies.

Hopefully this helps someone, as there is a lot of conflicting info around. Please let me know if this would cause any issues!

Unshelving TFS changes into another branch (VS 2017)

Do feel free to provide any comments/feedback to @TheRichCarey on Twitter

I had some pending changes recently on the wrong branch within TFS in Visual Studio 2017. Rather than clone all my changes in the other branch, I wanted to “migrate” my changes. In GIT this is fairly trivial, in TFS however…

To move changes between 2 branches, you have to ensure:

  • The changes you want to migrate are shelved on the source branch.
  • There are no pending changes in the workspace – This was rather annoying but a limitation of the tooling.
  • You do a “get-latest” on both branches.
  • You have access to Visual Studio Command Prompt.
  • If you are using lower than VS2017, you will also need the TFS Power tools.
  • The source and target branch are in the same workspace. This took me longer than I want to admit to work out as the error is not helpful!

With the above prerequisites met, you need to spin up the VS Command Prompt. This can be found via a start menu search but you can also add it to VS (If not already), following the steps below in VS.

Adding Visual Studio Command Prompt to Visual Studio

Go to “Tools” > “External Tools”, and select “Add”.

Give it an appropriate title – I chose “VS Command Prompt.”. From here we want to specify the following:

  • Command: C:\Windows\System32\cmd.exe
  • Arguments: /k “C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat”
  • Initial Directory: $(SolutionDir)

This means (On saving) that if you go to “Tools” you will now see an option “VS Command Prompt”.

Back to the migration…

With the console open, and using a working directory of a folder under source control (I use the source solution directory), run the command:

tfpt unshelve /migrate /source:"$/Core/MyProduct" /target:"$Core/MyProduct-Branch" "MyShelveset"

In this command, we are saying to use the TFS Power Tool to unshelve a shelveset named “MyShelveset”. The migrate flag indicates that it will be moving between areas, and the source and target are named TFS folders.

If you get an error “An item with the same key has already been added“, ensure you do not have any pending changes in the source or target.

If you get an error “unable to determine the workspace“, make sure you are running the tool within a directory under the source folder.

Providing this command runs successfully, you will then see the “Shelveset Details” panel.

Shelveset details

In this panel you should see the files that make up the shelveset you defined in the command. Pressing “Unshelve” will start the process.

In my case I also saw a “Unshelve/Merge Shelveset” window. You should be able to “auto-merge all”.

Oddly, Auto-Merge took quite a while on my machine (You can see the progress in the cmd window). I am unsure if this is normal, or because I was remote working that day over a VPN.

“Item could not be found in your workspace, or you do not have permission to access it.

"Item could not be found in your workspace, or  you do not have permission to access it.
“Item could not be found in your workspace, or you do not have permission to access it.”

If you get this during the merge, you may do what I did and go down a rabbit hole of getting latest, checking mappings etc. Turns out that this command does not work cross-workspace. When I branch, I map the branch to a completely new workspace as it’s cleaner.

The workaround for this (If like me, you use a new workspace per branch) is to temporarily map the branch into the same workspace.

Wrap up

So overall, it is possible to do but a process that would take a GIT Novice like me minutes to do in GIT took closer to an hour total! Luckily this is still less effort than a manual merge, but if you only had a couple of files I would recommend just doing it manually…

Bonus round: Unshelving another users shelveset into another branch

If the shelveset is a colleagues and not yours, you can simply append “;username” at the end of the command above (Where username is their TFS user), and it will search for that shelveset under that user.