The destination of CopyToOutputDirectory attribute in msbuild is fixed, there are various questions about how to change the destination path:
https://stackoverflow.com/questions/10204370/can-i-specify-the-output-path-for-the-msbuild-content-tag
https://stackoverflow.com/questions/18591107/copytooutputdirectory-override-destination-path
https://stackoverflow.com/questions/5915610/msbuild-project-file-copy-item-to-specific-location-in-output-directory
https://stackoverflow.com/questions/1014207/copy-to-output-directory-copies-folder-structure-but-only-want-to-copy-files/21534669#21534669
The simplest accepted answer requires a custom msbuild task or command running after the build. Please add an additional OutputDirectory attribute.
Project file
<Project>
<ItemGroup>
<None Include="../folder1/**/*.*" CopyToOutputDirectory="PreserveNewest" OutputDirectory="folder2" />
</ItemGroup>
</Project>
Directory contents:
/
- folder1/
- a.cs
- proj/
- proj.csproj
Command line
msbuild proj/proj.csproj
Output directory contents:
bin/Debug/
- folder2/
- a.cs
Output directory contents:
bin/Debug/
- a.cs
This currently works by using the Link metadata on the items:
<ItemGroup>
<None Include="../folder1/**/*" Link="folder2\%(RecursiveDir)%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
SDK-based projects (default for .NET Core and .NET Standard) now support LinkBase metadata as per https://github.com/dotnet/sdk/pull/1246.
If setting the Link metadata automatically, the value will be set to
%(LinkBase)\%(RecursiveDir)%(Filename)%(Extension). If LinkBase or RecursiveDir are not defined, then those pieces will be left out of the value. So<Compile Include="..\Shared\**\*.cs" LinkBase="Shared" />would show the items under the "Shared" folder in solution explorer, instead of the root, and would preserve any heirarchy under the shared folder.
I patched up the code from the SDK (minus a fix when referencing shared projects added later on) to be usable in classic projects in https://stackoverflow.com/questions/44423582/link-additional-files-in-visual-studio/44427519#44427519
The downside of using the Link metadata is that the items will be shown in the solution explorer under the constructed path which may or may not be what the user wants. Maybe the AssignTargetPaths task could be extended to check if there is already a TargetPath metadata on the items it processes. This is useful since in contrast to ContentWithTargetPath, _NoneWithTargetPath is private by convention. This could allow for additional TargetPathBase logic similar to LinkBase but without interfering with logic coming from the SDK.
Personally, I'm fine with the way Link and especially the SDK's LinkBase work, I just wished LinkBase would have been implemented in MSBuild directly.
Example showing usage of both ways:
linktest.zip
Thanks @dasMulli , LinkBase does solve the problem of linking files outside the project dir.
The link element behaves strangely for me in a .net 4.6.2 project.
I have a few files in a "wwwroot/build" folder that, when the project builds, i want to output to the /bin/Release/wwwroot folder. In my .csproj file i added the following:
<PropertyGroup>
<AssignTargetPathsDependsOn>
$(AssignTargetPathsDependsOn);
NpmBuildFiles;
</AssignTargetPathsDependsOn>
</PropertyGroup>
<!-- Gets the files build by 'npm run build' -->
<Target Name="NpmBuildFiles" DependsOnTargets="NpmBuild">
<ItemGroup>
<None Include="wwwroot/build/**/*"
Link="wwwroot\%(RecursiveDir)%(Filename)%(Extension)"
CopyToOutputDirectory="PreserveNewest"/>
</ItemGroup>
</Target>
However, after the build, the bin/Release/wwwroot folder contains a few files, but not all of them and some of them even files that do not exist in the wwwroot/build folder (but that do exist in the original wwwroot folder).
The msbuild output seems to indicate that a bunch of files are mapped onto a single file with the link element:
Added Item(s):
12> None=
12> wwwroot\build\asset-manifest.json
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\favicon.ico
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\index.html
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\manifest.json
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\service-worker.js
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\static\css\main.fc2aea1a.css
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\static\css\main.fc2aea1a.css.map
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\static\js\main.00b5ebca.js
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\static\js\main.00b5ebca.js.map
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\static\media\flags.9c74e172.png
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\static\media\icons.674f50d2.eot
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\static\media\icons.912ec66d.svg
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\static\media\icons.af7ae505.woff2
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\static\media\icons.b06871f2.ttf
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12> wwwroot\build\static\media\icons.fee66e71.woff
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\app.config
12>Added Item(s):
12> None=
12> wwwroot\build\asset-manifest.json
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\favicon.ico
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\index.html
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\manifest.json
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\service-worker.js
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\static\css\main.fc2aea1a.css
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\static\css\main.fc2aea1a.css.map
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\static\js\main.00b5ebca.js
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\static\js\main.00b5ebca.js.map
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\static\media\flags.9c74e172.png
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\static\media\icons.674f50d2.eot
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\static\media\icons.912ec66d.svg
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\static\media\icons.af7ae505.woff2
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\static\media\icons.b06871f2.ttf
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
12> wwwroot\build\static\media\icons.fee66e71.woff
12> CopyToOutputDirectory=PreserveNewest
12> Link=wwwroot\.env
The files listed each time above the "CopyToOutputDirectory" are correct. The files that Link=... indicates obviously are not. However, wwwrootapp.config and wwwroot.env are actual files i have present in my project, but i dont want to copy those to my output, just the contents of the build folder (which need to be put in the root of the wwwroot folder in /bin/Release since the build folder contains the actual files that need to be published).
it's weird, as if the %(Filename)%(Extension) is only being evaluated only once and then being used for every item matching the include.
For what it's worth, if I placed the <ItemGroup><None ... /></ItemGroup> within a target, I'm able to get the output processed without it showing up in my SDK based project. E.g.
<ItemGroup>
<WebPackBuildOutput Include="..\..\WebPackOutput\dist\**\*" />
</ItemGroup>
<Target Name="WebPackOutputContentTarget" BeforeTargets="BeforeBuild">
<Message Text="FileWrites: @(WebPackBuildOutput)" Importance="high"/>
<ItemGroup>
<!-- Manually constructing Link metadata, works in classic projects as well -->
<None Include="@(WebPackBuildOutput)" Link="dist\%(RecursiveDir)%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Target>
I apologize for nudging such an old issue, but I thought I'd try my question here as opposed to opening another new issue.
In a .NET Core format csproj, I'm able to get Link working but not LinkBase. It is unclear to me why it does not work.
With Link (Works):
<ItemGroup>
<None Include="TestInput/*" CopyToOutputDirectory="PreserveNewest" Link="LegacyLogsTesterInput/%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
With LinkBase (Does not work):
<ItemGroup>
<None Include="TestInput/*" CopyToOutputDirectory="PreserveNewest" LinkBase="LegacyLogsTesterInput" />
</ItemGroup>
In the former case, I observe that the directory $(OutDir)LegacyLogsTesterInput does exist. In the latter, it does not exist. On subtle detail I noticed in the linked pull request is this:
The item鈥檚 FullPath is outside of the project directory
Does this mean that I cannot use LinkBase with items that are at or under the directory containing the csproj? Any info on why this isn't working?
For context, I'm trying to "upgrade" my copy logic from the below XML code to use None elements as shown above. The expectation is that the source files, which are located relative to the project, are copied to the defined output directory where the rest of the binaries go.
<Target Name="PostBuild" BeforeTargets="PostBuildEvent">
<Exec Command="xcopy /s /d /y "$(ProjectDir)TestInput\*" "$(OutDir)LegacyLogsTesterInput\"" />
</Target>
I'm using Visual Studio 2019 (latest updates as of this post).
The LinkBase metadata doesn't work on paths that are inside the project directory by design. I suggest opening an issue over at dotnet/sdk (where the source code for it resides) for discussion.
Why not allow user edit in UI?
@Jaans Your method sadly doesn't seem to work transitively across project references.
Most helpful comment
This currently works by using the
Linkmetadata on the items:SDK-based projects (default for .NET Core and .NET Standard) now support
LinkBasemetadata as per https://github.com/dotnet/sdk/pull/1246.https://stackoverflow.com/questions/45800697/new-csproj-format-how-to-specify-entire-directory-as-linked-file-to-a-subdi/45802399#45802399
I patched up the code from the SDK (minus a fix when referencing shared projects added later on) to be usable in classic projects in https://stackoverflow.com/questions/44423582/link-additional-files-in-visual-studio/44427519#44427519
The downside of using the
Linkmetadata is that the items will be shown in the solution explorer under the constructed path which may or may not be what the user wants. Maybe theAssignTargetPathstask could be extended to check if there is already aTargetPathmetadata on the items it processes. This is useful since in contrast toContentWithTargetPath,_NoneWithTargetPathis private by convention. This could allow for additionalTargetPathBaselogic similar toLinkBasebut without interfering with logic coming from the SDK.Personally, I'm fine with the way
Linkand especially the SDK'sLinkBasework, I just wishedLinkBasewould have been implemented in MSBuild directly.Example showing usage of both ways:
linktest.zip