Setting file properties with NuGet using .targets file

16,846

I am not seeing the FrameworkTests.feature file get copied to the project when the package is installed. What do I need to change?

This is the default behavior of the nuget folder conventions. If you set the file in the content folder, nuget will copy those contents to the project root, then you will see them in the solution explorer. If you set files in the build folder, nuget will automatically inserted them into the project file or project.lock.json, like following script in the project file:

<Import Project="..\packages\MyProject.1.0.0\build\MyProject.targets" Condition="Exists('..\packages\MyProject.1.0.0\build\MyProject.targets')" />

So, this is the reason why you could not see the file FrameworkTests.feature in the solution explorer unless you change the folder to content.

You can refer to the document Creating the .nuspec file for more details:

NuGet folder conventions

Besides, if you change the folder to content, the .targets would not work. Because when you change the folder to content, in the .targetsfile, you need to change the path from:

<None Include="$(MSBuildThisFileDirectory)FrameworkTests\FrameworkTests.feature">

To:

<None Include="$(MSBuildThisFileDirectory)..\content\FrameworkTests\FrameworkTests.feature">

But MSBuild could not parse the path ..\content. To resolve this issue, we need to change the .targets file to:

<None Include="$(ProjectDir)FrameworkTests.feature">

Alternatively, you can change property of files with Install.ps1 file, set it into the nuget package.

See this thread for more details.

Update:

I have also applied the changes you've described, but still cannot get the CopyToOutputDirectory file property to be updated.

Found it. There are two points you need update and one point should to know.

First point:

I found following script in your .nuspec:

  <files>
    <file src="build\MyProject.targets" target="build\MyProject.targets" />
    <file src="build\FrameworkTests\FrameworkTests.feature" target="build\Framework\FrameworkTests.feature" />
  </files>

Note: You set your target folder for build\Framework but in your .targets file, you including it with FrameworkTests;

<None Include="$(MSBuildThisFileDirectory)FrameworkTests\FrameworkTests.feature">

So, when change it to content folder, your .nuspec file should be:

  <files>
    <file src="build\MyProject.targets" target="build\MyProject.targets" />
    <file src="content\FrameworkTests\FrameworkTests.feature" target="content\FrameworkTests\FrameworkTests.feature" />
  </files>

And the .targets file should be:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <None Include="$(ProjectDir)FrameworkTests\FrameworkTests.feature">
      <Link>FrameworkTests.feature</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CustomToolNamespace></CustomToolNamespace>
    </None>
  </ItemGroup>
</Project>

Second point need update:

The value of CopyToOutputDirectory in the .targets file should be PreserveNewest Not Copy if newer.

The point you need to know:

When you use this method to change the property of the FrameworkTests.feature, the property of this file would not changed in the solution explorer, however, MSBuild will apply this change when you build the project. That because <Import Project="....\MyProject.targets" Condition="Exists('...')" /> will be parsed when you compile the project.

So, you can check the output for CopyToOutputDirectory file property after build the project(After build your project, the file FrameworkTests.feature would be copied to the output folder.)

Update2:

A lot of comments circulating that PowerShell scripts can't be run on build servers because the install command doesn't run them; Only VisualStudio will

Not sure of all the reasons why PowerShell scripts can't be run on build servers, but the vast majority are because PowerShell's default security level.

Try typing this in the PowerShell:

set-executionpolicy remotesigned

In the Visual Studio 2015, you need to install the extension PowerShell Tools for Visual Studio 2015 and the Windows Management Framework 4.0. After install them, the install.ps1 would works fine. Following is my .nuspec file and install.ps1 script.

.nuspec file:

  <files>
    <file src="content\FrameworkTests\FrameworkTests.feature" target="content\FrameworkTests\FrameworkTests.feature" />
    <file src="scripts\Install.ps1" target="tools\Install.ps1" />
  </files>

Note: Do not forget remove the MyProject.targets in the .nuspec file.

install.ps`1 script:

param($installPath, $toolsPath, $package, $project)

function MarkDirectoryAsCopyToOutputRecursive($item)
{
    $item.ProjectItems | ForEach-Object { MarkFileASCopyToOutputDirectory($_) }
}

function MarkFileASCopyToOutputDirectory($item)
{
    Try
    {
        Write-Host Try set $item.Name
        $item.Properties.Item("CopyToOutputDirectory").Value = 2
    }
    Catch
    {
        Write-Host RecurseOn $item.Name
        MarkDirectoryAsCopyToOutputRecursive($item)
    }
}

#Now mark everything in the a directory as "Copy to newer"
MarkDirectoryAsCopyToOutputRecursive($project.ProjectItems.Item("FrameworkTests"))

Then, after install the nuget package, the property of the file FrameworkTests.feature would be changed to copy if newer:

enter image description here

Hope this helps.

Share:
16,846
Matt W
Author by

Matt W

I write code for fun and profit. Sometimes I blog about it. Not enough time for either. At work I break websites. At home I break physics based mobile games. "No code more than 8 lines ever worked as intended first time." - My Dad.

Updated on June 26, 2022

Comments

  • Matt W
    Matt W almost 2 years

    I am building a project to be installed as a NuGet package and I want to set the properties of a SpecFlow feature file (because it is latest SpecFlow and should not produce code-behind files for the features.)

    To achieve the effect of selecting a file, opening it's Properties pane and setting a couple of values, I have set my project structure like this:

    \MyProject
      \build
        MyProject.targets
        \Framework <the folder containing the file I want to affect>
          FrameworkTests.feature <the file I want to affect>
      \Framework
        FrameworkTests.feature <the original location of the file I want to affect>
    

    My .nuspec like this:

    <?xml version="1.0"?>
    <package >
      <metadata minClientVersion="2.5">
        <id>$id$</id>
        <version>$version$</version>
        ...
      </metadata>
      <files>
        <file src="build\MyProject.targets" target="build\MyProject.targets" />
        <file src="build\FrameworkTests\FrameworkTests.feature" target="build\Framework\FrameworkTests.feature" />
      </files>
    </package>
    

    My .targets file like this:

    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <ItemGroup>
        <None Include="$(MSBuildThisFileDirectory)FrameworkTests\FrameworkTests.feature">
          <Link>FrameworkTests.feature</Link>
          <CopyToOutputDirectory>Copy if newer</CopyToOutputDirectory>
          <CustomToolNamespace></CustomToolNamespace>
        </None>
      </ItemGroup>
    </Project>
    

    I am not seeing the FrameworkTests.feature file get copied to the project when the package is installed. What do I need to change?

  • Matt W
    Matt W about 6 years
    I have already tried adding a content folder and placing files into it but they did not get copied into the target project when the nuget package was installed. I am packing by providing the name of the .csproj - does that imply a difference? I have also applied the changes you've described, but still cannot get the CopyToOutputDirectory file property to be updated.
  • Leo Liu
    Leo Liu about 6 years
    @MattW, there should be have something I did not explain clearly, I will update my answer later, and will let you know it.
  • Leo Liu
    Leo Liu about 6 years
    @MattW, after create a sample with your .nuspec and .targets files, I found the reason why it not work for you, there are two points you need update and one point should to know. Please check my Update answer for details. And I have test it, it works fine on my side, if it still not work for you, please let me know.
  • Matt W
    Matt W about 6 years
    Thanks - at a quick glance I see PreserveNewest is a change I've already made. Taking a further look...
  • Matt W
    Matt W about 6 years
    Ok, so I've applied everything you've said. Thank you for the help. One problem remains... You'll notice the files I'm trying to apply the property changes to are .feature files - these are used with SpecFlow/xUnit and when changing those properties in Visual Studio, the SpecFlow plugin is removing the code-behind .cs files which back the .feature files. As the NuGet is currently, this is not happening and when I build the project (after installing the NuGet package) and try to run the SpecFlow/xUnit tests the build complains because it thinks the auto-generated .cs files are there.
  • Matt W
    Matt W about 6 years
    Another gotcha I'm seeing is that the .feature files are being copied into the target project's bin\Debug\FrameworkTests` folder, which means the Test Explorer shows 3 copies of each .feature` file, rather than the one in the project folder. How can I prevent the bin\Debug item being created when the package installs?
  • Leo Liu
    Leo Liu about 6 years
    @MattW, Since you are limited by the SpecFlow/xUnit, you could use the another way which I mentioned in the answer. Using Install.ps1 file, in this case, when you complete the installation nuget package, nuget would change the property.stackoverflow.com/questions/8474253/…
  • Matt W
    Matt W about 6 years
    A lot of comments circulating that PowerShell scripts can't be run on build servers because the install command doesn't run them; Only VisualStudio will. :(
  • Leo Liu
    Leo Liu about 6 years
    @MattW, Not sure of all the reasons why PowerShell scripts can't be run on build servers, but the vast majority are because PowerShell's default security level. You could check my update2 answer for details, it works fine when I install that nuget package in VS2015.
  • Matt W
    Matt W about 6 years
    Thank you! You are amazing! No words! Truly exceptional - thank you so much!
  • bruestle2
    bruestle2 about 3 years
    Hey, I just want to say this helped me a ton. Thank you!