Set content files to "copy local : always" in a nuget package - visual-studio

I generate a nuget package from a project with this command in the post-build event. the variable %conf% is set to the right configuration (debug or release) and %1 is the project name (e.g. "MyCompany.MyProject").
nuget pack -Prop Configuration=%conf% "%1.csproj" -exclude *.sql -IncludeReferencedProjects
This package is for our own usage only, it will never be published on nuget. It ends in our private repository.
In the project, there is a file that is set to generate action : content and copy local : always. (My Visual Studio is in French, so I'm not 100% sure of the translation). Let's name it importantfile.xml.
In the generated package, I end up with this structure :
- content
- importantfile.xml
- lib
-net45 (.NetFramework,Version=v4.5)
-MyCompany.MyProject.dll
Which is fine, I want importantfile.xml to be deployed in the package, because, well, this file is important!
When I install the package in another project, importantfile.xml is deployed at the root of the project. That's OK. But it is not set to copy local : always.
I need importantfile.xml to be copy local : always in this project where I install my package.
How can I achieve that?
Notes :
I can set copy local : always on the file just after installing the package, that's no big deal. I would live with it if later updates of the package would let this property as-is, which is not the case. When updating the package, copy local is reset to never (as stated here).
There's a nuspec file in the project's folder, here it is :
<?xml version="1.0"?>
<package >
<metadata>
<id>$id$</id>
<version>$version$</version>
<title>$title$</title>
<authors>$author$</authors>
<owners>$author$</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>$description$</description>
<copyright>Copyright 2014</copyright>
<tags>some random tags</tags>
</metadata>
</package>

Instead of using a PowerShell script another approach is to use an MSBuild targets or props file with the same name as the package id:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)importantfile.xml">
<Link>importantfile.xml</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
In the nuspec file then, instead of adding the required files to the Content directory, add them to the Build directory along with the targets file.
Build
importantfile.xml
MyPackage.targets
lib
net45
MyAssembly.dll
If you require different content for different architectures then you can add architecture folders under Build also each with their own targets file.
Benefits to using a targets file over the PowerShell script with NuGet Content directory:
required content files aren't shown in the project in Visual Studio
content files are linked to rather than copied into the directory of each project which references the NuGet package (preventing there being multiple copies and keeping behaviour the same as for assemblies / libraries from NuGet packages)
PowerShell scripts only work in Visual Studio and aren't run when NuGet is run from the commandline (build servers, other IDEs and other OS), this approach will work everywhere
PowerShell install scripts are not supported in NuGet 3.x project.json system.

I know you guys got a working solution to this but it didn't work for me so I'm going to share what I pulled out of the NLog.config NuGet package install.ps1 (github source here).
NOTE: this is not my code, this is the content of the install.ps1 from
the NLog.config nuget package just sharing the knowledge.
It seems a little more straight forward to me and just hoping to help others that will likely stumble upon this.
You can find the accepted int values for BuildAction here and the accepted values for CopyToOutputDirectory here.
if the link breaks again
Fields
prjBuildActionCompile 1
The file is compiled.
prjBuildActionContent 2
The file is included in the Content project output group (see Deploying Applications, Services, and Components)
prjBuildActionEmbeddedResource 3
The file is included in the main generated assembly or in a satellite assembly as a resource.
prjBuildActionNone 0
No action is taken.
param($installPath, $toolsPath, $package, $project)
$configItem = $project.ProjectItems.Item("NLog.config")
# set 'Copy To Output Directory' to 'Copy if newer'
$copyToOutput = $configItem.Properties.Item("CopyToOutputDirectory")
# Copy Always Always copyToOutput.Value = 1
# Copy if Newer copyToOutput.Value = 2
$copyToOutput.Value = 2
# set 'Build Action' to 'Content'
$buildAction = $configItem.Properties.Item("BuildAction")
$buildAction.Value = 2

I have made this which copies files from my build folder to the output folder (bin/debug or bin/release). Works like a charm for me.
Nuspec file:
<package>
<files>
<file src="\bin\Release\*.dll" target="lib" />
<file src="\bin\Release\x64\*.dll" target="build\x64" />
<file src="\bin\Release\x86\*.dll" target="build\x86" />
<file src="MyProject.targets" target="build\" />
</files>
</package>
MyProject.targets
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
<None Include="#(NativeLibs)">
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

You can use PowerShell and the Install.ps1 hook provided by NuGet.
See the documentation.
Via PowerShell you have to 'search' for the content element which includes your importantfile.xml in an attribute. When the script found it, it has to add <CopyToOutputDirectory>Always</CopyToOutputDirectory> as a child element.
<Content Include="importantfile.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
You can find some PowerShell snippets here. Just take a look at the .ps1 files.
You could try the following (not tested). The file has to be named Install.ps1 and copied into the tools folder:
param($installPath, $toolsPath, $package, $project)
# Load project XML.
$doc = New-Object System.Xml.XmlDocument
$doc.Load($project.FullName)
$namespace = 'http://schemas.microsoft.com/developer/msbuild/2003'
# Find the node containing the file. The tag "Content" may be replace by "None" depending of the case, check your .csproj file.
$xmlNode = Select-Xml "//msb:Project/msb:ItemGroup/msb:Content[#Include='importantfile.xml']" $doc -Namespace #{msb = $namespace}
#check if the node exists.
if($xmlNode -ne $null)
{
$nodeName = "CopyToOutputDirectory"
#Check if the property already exists, just in case.
$property = $xmlNode.Node.SelectSingleNode($nodeName)
if($property -eq $null)
{
$property = $doc.CreateElement($nodeName, $namespace)
$property.AppendChild($doc.CreateTextNode("Always"))
$xmlNode.Node.AppendChild($property)
# Save changes.
$doc.Save($project.FullName)
}
}
You should also check if everything is removed completely when uninstalling the package.
Note by Jonhhy5
When updating the package via update-package, Visual Studio warns that the project is modified "outside the environnment". That's caused by $doc.Save($project.FullName). If I click reload before the command is fully terminated, it sometimes causes errors. The trick is to leave the dialog there until the process finishes, and then reload the projects.

I have written a little tool called NuGetLib to automatically add files to the nuget package after build.
create a tools folder with your Install.ps1 script
build your nugetPackage
add the tools folder to the built nugetPackage
https://stackoverflow.com/a/47134733/6229375

Related

Restore nuget package in different folder

We have some projects (Plugins) which use in several projects, the output of these projects will copy to the specific folder in the target projects (Plugins folder).
We pack project with Visual studio 2019 Pack option and after that, we push npkg files to our local NuGet server for further use.
The problem is when we want to get these packages, Package Manager should put lib files in the Plugins folder, but unfortunately, the package manager extracts these in the root folder (bin).
My question is: How can I config nuspec file to force package manager to extract to the right folder, and can I do it with visual studio or I have to create nuspec file manually.
You should use a <packages_id>.props file to realize it.
create a file called <packages_id>.props under the build folder of your lib project.
You should note that if your nupkg file is called Plugins.1.0.0.nupkg, you should name this file as Plugins.props so that it will work.
add these on Plugins.props file:
<Project>
<Target Name="CopyFiles" BeforeTargets="Build">
<ItemGroup>
<File Include="$(MSBuildThisFileDirectory)..\Plugins\*.*"></File>
</ItemGroup>
<!--It will copy the plugins output files into the Plugins folder of the goal project-->
<Copy SourceFiles="#(File)" DestinationFolder="$(ProjectDir)Plugins"></Copy>
</Target>
</Project>
add these on Plugins.csproj file:
<ItemGroup>
<None Include="bin\Debug\netstandard2.0\Plugins.dll" Pack="true" PackagePath="Plugins"></None>
//add any output files from Plugins project which you want them to be packed
<None Include="build\Plugins.props" Pack="true" PackagePath="build"></None>
</ItemGroup>
use Pack Button to create the new release version of your nuget pakckage.
Also, when you install this new version of nuget package, please clean your nuget caches or delete all files under C:\Users\xxx(current user)\.nuget\packages.
When you finish the installing process, please click Build button and the files will generated under Plugins folder.
There is also a similar issue about this.

include the XML documentation from a NuGet package

I have a NuGet package with an xml documentation file (doc.xml).
I have installed the NuGet package in my project.
I know want to add the NuGet documentation file doc.xml to my solution.
I am running .net core 3.1 but I have no idea how this can be achieved.
Thank you!
If your nuget project is net standard or net core, please check these steps:
1) create a file called <package_id>.props file under a folder called build on your project.
Note that, you should make sure that the your package_id of your nuget project is the same as the .props file, otherwise, it will not work. See this link's description.
In my side, my nuget package called test.1.0.0.nupkg, so I should rename the file as test.props file.
2) please add these content in test.props file.
<Project>
<Target Name="CopyFilesToProject" BeforeTargets="Build">
<ItemGroup>
<SourceScripts Include="$(MSBuildThisFileDirectory)..\File\*.*"/>
</ItemGroup>
<Copy
SourceFiles="#(SourceScripts)"
DestinationFolder="$(ProjectDir)"
/>
</Target>
</Project>
The propose of this target is to copy the xml file from the File folder of the nupkg into the target project's folder when you install this nuget into the main project.
3) add these under xxx.csproj file:
<ItemGroup>
<None Include="bin\Debug\netcoreapp3.1\test.xml(the path of the xml file in your nuget project)" Pack="true" PackagePath="File"></None>
<None Include="build\test.props(the path of the test.props file in your nuget project)" Pack="true" PackagePath="build"></None>
</ItemGroup>
4) Then, when you pack your project, the structure should be this:
Before you install this new version of nuget package, you should clean nuget caches first or just delete all cache files under C:\Users\xxx(current user)\.nuget\packages to remove the old ones in case you still install the old one.
After that, rebuild your main project to execute the target and you will see the xml document file exist under the main project folder.
In addition, there is a similar issue you can refer to and if you use the net framework project, the link also provide the method.
===========================
Update 1
If you want to copy the file into bin\Release or bin\Debug, you should modify step 2, change to use this in the .props file:
<Copy
SourceFiles="#(SourceScripts)"
DestinationFolder="$(ProjectDir)$(OutputPath)"
/>
or just
<Copy
SourceFiles="#(SourceScripts)"
DestinationFolder="$(OutDir)"
/>
as you want.
Before you install this new version, you should first delete nuget caches under C:\Users\xxx(current user)\.nuget\packages.

Prevent duplicating files in NuGet content and contentFiles folders

My NuGet package needs to deliver some rather large files to build output directory.
In an old NuGet model, such files have to be stored in content folder of the .nupkg. While in a new model introduced in NuGet 3.3, such files have to be stored in contentFiles folder.
To maintain a compatibility with older versions of NuGet and mainly with Package.config package management format, I need to duplicate the files into both folders. That unfortunately almost doubles a size of the package.
Is there a way to prevent that? Can I somehow link contentFiles to content folder?
Found updated documentation describing this in detail at
MSBuild targets for NuGet.
By default, everything gets added to the root of the content and contentFiles\any\<target_framework> folder within a package and preserves the relative folder structure, unless you specify a package path:
<Content Include="..\win7-x64\libuv.txt">
<Pack>true</Pack>
<PackagePath>content\myfiles\</PackagePath>
</Content>
If you want to copy all your content to only a specific root folder(s) (instead of content and contentFiles both), you can use the MSBuild property ContentTargetFolders, which defaults to "content;contentFiles" but can be set to any other folder names. Note that just specifying "contentFiles" in ContentTargetFolders puts files under contentFiles\any\<target_framework> or contentFiles\<language>\<target_framework> based on buildAction.
If you only want to output the file to the build output (content only copies the file to the output directory but does cause it to be set as copy to output directory item), you can use a completely different approach by creating an msbuild file that will be included in the project.
You can do this by putting both the file - say test.jpg into the tools folder (you could also use build) and add a Your.Package.Id.targets file to the build folder (the name being the package id of your package with .targets as extension) with the following content:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Content Include="$(MSBuildThisFileDirectory)..\tools\test.jpg">
<Link>test.jpg</Link>
<Visible>false</Visible>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
</Project>
This target will be automatically imported into the project files regardless of which "style" of NuGet reference is used (packages.config, PackageReference) and should be backwards compatible to older versions of VS as long as they support NuGet and ToolsVersion 4.0.
The Link metadata denotes where in the output / publish directories the file will end up. You could set it to e.g. defaultContent\images\foo.jpg to create a nested structure and rename the file. (you could even use MSBulid variables to use some of the referencing project's configuration). The Visible metadata prevents the solution explorer from showing the full relative path to the file, which could end up in lots of nested .. nodes. The CopyToPublishDirectory applies to .NET Core / ASP.NET Core apps or SDK-based projects using the Publish target for publishing.
Note that you can set the Inclue-path to anything depending on where in your package the file is. You can also use wildcards (but then set Link to %(Filename)%(Extension))

Specify PackageFlatten for content files in Nuget package using Visual Studio 2017

I'm trying to get content files in a C#.Net project that is being packed into a Nuget package to flatten the containing folders. Based on this page it seems that this is possible with the flatten attribute.
But I want to use the new MSBuild Nuget packaging built into VS2017. Based on this page, I should be able to achieve the same thing with the <PackageFlatten> tag, but I cannot get it to work.
Here is a basic project file which demonstrates the problem. I'm expecting the single content file under the folder hierarchy .\RemoveMe\ContentFile.txt to end up in the Nuget package under .\content\ContentFile.txt - i.e. without the folder RemoveMe. But I still get this subfolder.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<Content Include="RemoveMe\ContentFile.txt">
<PackageFlatten>true</PackageFlatten>
</Content>
</ItemGroup>
</Project>
Specify PackageFlatten for content files in Nuget package using Visual Studio 2017
It should be a issue about argument flatten="true" for me. According to the document NuGet ContentFiles Demystified:
flatten - Should the files be all delivered to the root folder of the project or should they keep the folder structure that they were packaged in. The default value is false, which indicates they should create (if necessary) and keep the same structure they have after the TFM folder in the pacakge.
So if we set this argument flatten="true" in the .nuspec file, then pack this .nuspec, install the generate nuget package to the project, the file should be delivered to the root folder of the project. But after install this package to project, still got the that file in the sub-folder RemoveMe not in the root folder.
I also test other arguments buildAction="content" copyToOutput="false", both of them works fine.
So it should be a issue for the argument flatten="true", I submit this issue on GitHub, you can track this issue.

Add post build event without overwriting existing events

I have a Powershell script (run by my NuGet package) that adds a post build event to a user's Visual Studio project.
$project.Properties | where { $_.Name -eq "PostBuildEvent" } | foreach { $_.Value = "xcopy `"`$(ProjectDir)MyFolder`" `"`$(OutDir)`"" }
Unfortunately it currently overwrites the existing post build event.
How can I modify the Powershell script to append my build event if it does not already exist?
I've never used Powershell before but I tried simply appending it inside the foreach, but this didn't work:
$_.Value = "$_.Value`nxcopy `"`$(ProjectDir)MyFolder`" `"`$(OutDir)`""
just gives:
System.__ComObject.Value xcopy "$(ProjectDir)MyFolder" "$(OutDir)"
I think editing the PostBuildEvent property is the wrong way to go about adding a post build action to a user's project. I believe the recommended way is to put your custom action into a target that is then imported into the project file. As of NuGet 2.5 if you include a 'build' folder in your package (at the same level as content and tools) and it contains a {packageid}.targets file or {packageid}.props file, NuGet will automatically add an Import to the project file when you install the package.
For example you have a package called MyNuGet. You create a file build\MyNuGet.targets containing:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<MySourceFiles Include="$(ProjectDir)MyFolder\**" />
</ItemGroup>
<Target Name="MyNuGetCustomTarget" AfterTargets="Build">
<Copy SourceFiles="#(MySourceFiles)" DestinationFolder="$(OutDir)" />
</Target>
</Project>
This creates a custom target that is configured to run after the standard Build target. It will copy some files to the output directory.
You do not need to do anything else in install.ps1. When the user installs your package, NuGet will automatically add an Import to the user's proj files and your target will run after the Build target is run.
When inside quotes, and referencing variables with properties, you have to enclose them in a $()
so this:
$_.Value = "$_.Value`nxcopy `"`$(ProjectDir)MyFolder`" `"`$(OutDir)`""
should be this:
$_.Value = "$($_.Value)`nxcopy `"`$(ProjectDir)MyFolder`" `"`$(OutDir)`""
or the simpler method is to use a +=
i.e:
$_.Value += "`nxcopy `"`$(ProjectDir)MyFolder`" `"`$(OutDir)`""

Resources