Add dynamically generated file in MSBuild task to output and appx folders - c#

I have created a build task that dynamically generates a file for the project it's used in.
I've managed to get that file to be copied into the $(TargetDir), but not into the AppX folder. The reason I need it there is, that this build task is about unit tests and this file is needed inside the tests.
Sounds a bit confusing so I try to clarify a little:
I generate code in that task and compile that into an assembly which I add as a reference to the main assembly (in this case the test.dll). So what happens is that I do this after the "AfterCompile" target and my assembly gets generated into the obj\Debug folder.
This is my Target:
<UsingTask TaskName="MyBuildTask" AssemblyFile="MyBuildTask.dll"/>
<Target
AfterTargets="AfterCompile"
Name="MyBuildTarget">
<MyBuildTask
SolutionRoot="$(SolutionDir)"
GeneratedAssemblyPath="$(IntermediateOutputPath)$(TargetName).Generated.dll"
TestProjectPath="$(ProjectPath)"
TestAssemblyPath="#(IntermediateAssembly)"
>
<Output TaskParameter="GeneratedFile" ItemName="GeneratedFile"/>
</MyBuildTask>
<ItemGroup>
<Content Include="#(GeneratedFile)">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<DeploymentContent>true</DeploymentContent>
</Content>
</ItemGroup>
</Target>
One this, that I also don't want is, that this file now gets copied to \bin\Debug\obj\Debug.
I could obviously specify this statically, so that it's always copied to the output path root and then to the AppX folder, I only want to do this as my last solution if there is nothing else I can do.
So how do I get my file into this GetCopyToOutputDirectoryItems, which I presume is the list of items that are both for the output directory and the AppX package?

I've actually found after a consecutive 8 hours of further research about the topic and reading a lot through all the target files from both Microsoft.Common.targets and Microsoft.AppxPackage.targets the solution to my problem:
<ItemGroup>
<AppxPackagePayload Include="#(GeneratedAssemblyFullPath)" Condition="#(AppxPackagePayload) != '' Or #(AppxPackagePayload) != '*Undefined*'">
<TargetPath>#(GeneratedFile)</TargetPath>
</AppxPackagePayload>
</ItemGroup>
This adds the file to the AppxPackagePayload and thus it will be copied to the Appx package.
PS: Since I was a little confused myself about this. #(GeneratedFile) and #(GeneratedAssemblyFullPath) are the outputs from my own build task and of type "string", like the names suggest. #(GeneratedFile) is just the file name, so it will be put into the root.

Have you tried removing the $(IntermediateOutputPath) from the GeneratedAssemblyPath, I believe that is what is producing the \bin\Debug\obj\Debug. It is copying that file path into the OutputDirectory (which is generally something like \bin\Debug.

Related

Getting Error using Syncfusion HTML to PDF on azure serverless function : BlinkBinariesLinux not found

I want to use Syncfusion library to convert HTML to PDF but even if I follow this guide: https://www.syncfusion.com/kb/10301/how-to-convert-html-to-pdf-in-azure-functions-4-0
I get this error: Blink files are missing at /home/site/wwwroot/bin/BlinkBinariesLinux
I tried to hardcode BlinkPath in BlinkConverterSettings but it seems to have no impact.
By looking output deployed files I see that BlinkBinariesLinux directory is not inside bin path but in it's parent folder.
So I tried to add this to my csproj :
<ItemGroup>
<FilesToMove Include="$(OutDir)BlinkBinariesLinux\*.*"/>
<LocaleFilesToMove Include="$(OutDir)BlinkBinariesLinux\locales\**\*.*"/>
</ItemGroup>
<Target Name="MoveFiles" AfterTargets="Build">
<MakeDir Directories="$(OutDir)bin\BlinkBinariesLinux;$(OutDir)bin\BlinkBinariesLinux\locales" />
<Move SourceFiles="#(LocaleFilesToMove)" DestinationFolder="$(OutDir)bin\BlinkBinariesLinux\locales"/>
<Move SourceFiles="#(FilesToMove)" DestinationFolder="$(OutDir)bin\BlinkBinariesLinux"/>
</Target>
to move the folder to bin directory but when I look at output file after build, the BlinkBinariesLinux directory created inside bin path is empty and the one in the parent directory still have files.
If I execute build a second time, sometime, files are all correctly moved.
So my question is, why msbuild do that ? and the real question is, what I miss to make it works on azure function ?

Project Reference with ReferenceOutputAssembly tag with false value does not copy its SatelliteDlls

according to this link
https://blogs.msdn.microsoft.com/kirillosenkov/2015/04/04/how-to-have-a-project-reference-without-referencing-the-actual-binary/
I referenced the assembly of ProjectA in the projectB with ReferenceOutputAssembly=false like below :
<ProjectReference Include="..\ProjectA\ProjectA.csproj">
<Project>{b402782f-de0a-41fa-b364-60612a786fb2}</Project>
<Name>ProjectA</Name>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>Content</OutputItemType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</ProjectReference>
This will indicate a dependency between projects to MSBuild, but won’t pass the output assembly of ProjectA as a reference to the compiler when compiling ProjectB.
MSBuild just copies the assembly A dll into the output directory for assembly B ,not the Satellite Dll.
I need both ProjectA.dll and ProjectA.resources.dll in the output directory but just projectA.dll is copied , Satellite Dll of Project A is not copied..
MSBuild just copies the assembly A dll into the output directory for
assembly B ,not the Satellite Dll. I need both ProjectA.dll and
ProjectA.resources.dll in the output directory but just projectA.dll
is copied , Satellite Dll of Project A is not copied..
The issue also appears in my side and I think when you use the function( Methods in the link) above, it will replicate the dependency without referencing project A, but also ignores the DLLs or resources.dlls referenced by project A. This approach focuses on Project dependencies and ignores other third-party DLLs used by the Project.
This behavior is characteristic of that scenario you used.
However, if you still want to use this function to reference Project A, you can try to use build event or copy task to copy ProjectA.resources.dll into the output path of Project B when you build:
1) Right-click on Project B-->Properties-->Build Events-->Post-Build Event Command line and then input this:
xcopy /y "xxxxxxxxxxx\de\ProjectA.resources.dll" "$(ProjectDir)$(OutputPath)de"
xcopy /y path1 path2: path1 means the path of the original file and path2 means the destination address.
Update 1
generally your solution works but it doesnt work for me because I want
to publish with clickonce and Postbuildevent is not called during the
publish .. I added a before publish tag in csproj of the project B to
run the postbuildevent but it doesnt work
First, you do not have to run the custom target after the wrong target Publish,instead, you should use the PublishBuild Target. Then use copy task to copy the file into Puplish folder during you execute the clickonce operation.
After all, you can try my sample which l have tested successfully in the ProjectB.csproj file.
<Target Name="CopyToPublish" AfterTargets="PublishBuild">
<ItemGroup>
<ResourceFiles Include="xxxxxxx\ProjectA.resources.dll" /> // the path of the ProjectA.resources.dll
</ItemGroup>
<Copy SourceFiles="#(ResourceFiles)" DestinationFiles="#(ResourceFiles->'$(PublishUrl)\de\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
Update 2
After l do some research, I found that when VS put files into Clickonce Manifest, it runs the targets(GenerateApplicationManifest, GenerateDeploymentManifest,GenerateManifests and so on) sequentially.These are the targets that act as the concrete execution of the file into the manifest. So when you use use your BeforePublish target to put the extra file as an item, you should run before the target. So please try this:
<Target Name="BeforePublish" BeforeTargets="GenerateApplicationManifest">
<ItemGroup>
<AdditionalPublishFile Include="xxxxxxx\de\ProjectA.resources.dll">
<Visible>False</Visible>
</AdditionalPublishFile>
</ItemGroup>
<Touch Files="#(IntermediateAssembly)" />
<CreateItem Include="#(AdditionalPublishFile)" AdditionalMetadata="TargetPath=de\%(FileName)%(Extension);IsDataFile=false">
<Output TaskParameter="Include" ItemName="_DeploymentManifestFiles" />
</CreateItem>
</Target>
When you publish the project, the ProjectA.resources.dll will display in the ProjectB.manifest file successfully. But the extra file will not display in the Property UI Publish-->Application Files, l think this is a UI display problem that does not affect the output of this file.
Hope it could help you.

How to publish additional files into bin folder

My .net web app project also includes some unmanaged dlls as additional files.
These are a couple of levels deep in subfolders.
When I publish this project I need these files to be copied to the bin folder alongside all the other binaries.
No matter what settings I try, the best I can get is for them to be published into their existing folder structure which is not where I need them to be.
I've created a PostBuild event to copy the files and this works when building locally but not when publishing to a server. I've not been able to get PostPublish events to work in the same way.
Is there another way to achieve this?
Note this is similar but not the same as a previous question:
Publish unmanaged DLL from referenced project
I have a similar setup. 2 projects in my solution, one .NET Core and the other C++. When I am going to publish the dotnetcoreapp2.2 I want to include the precompiled C++ DLL from the other project.
#JuanR's answer is not working for me, though it is already pretty close to my version. It looks like the <ItemGroup> needs to be in the <Target> tag.
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
<ItemGroup>
<DataModelFiles Include="$(ProjectDir)..\MyCppProject\bin\Release\MyCppProject.dll" />
</ItemGroup>
<Copy SourceFiles="#(DataModelFiles)" DestinationFolder="$(PublishDir)" SkipUnchangedFiles="false" />
</Target>
Try using an after-publish task.
You can create an item group for copy:
<ItemGroup>
<binFilesToCopy Include="$(OutDir)\somepath\to\yourexternalDLLFolder\*" />
<!-- Add more folders/files you want to copy here -->
</ItemGroup>
Then add a target for after publishing:
<Target Name="AfterPublish">
<Copy SourceFiles ="#(binFilesToCopy)" DestinationFolder ="$(OutDir)\bin" />
</Target>
I did this mostly from memory so double-check for syntax, but get you the idea.
In the properties of the file you can set Copy to output directoryto Copy always or you can edit the solution file, expand the xml tag of the file needed and add <CopyToOutputDirectory>Always</CopyToOutputDirectory> as sub-tag.

Visual Studio: How to "Copy to Output Directory" without copying the folder structure?

I have a few dll files in \lib folder of my project folder. In the property page of dll, I have selected "Build Action" as "Content" and "Copy to Output Directory" as "Copy always".
After build I am actually getting the dll copied but they are inside \bin\Release\lib and not in \bin\Release.
Is there a way to copy dll files to \bin\Release (and not to \bin\Release\lib) without writing a post-build script or resorting to nant etc?
instead of <Content> use <ContentWithTargetPath> and specify target path, like this:
<ItemGroup>
<ContentWithTargetPath Include="lib\some_file.dat">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>some_file.dat</TargetPath>
</ContentWithTargetPath>
<None Include="lib\some_file.dat" />
</ItemGroup>
Note that this entry may not be visible from Visual Studio (2012, 2015, 2017), but once manually added to the csproj, it will appear in Visual Studio. The target path will not be editable through the UI though.
Adding a <None> entry for the file will ensure that it still shows up in Visual Studio's UI.
Keep them in $(ProjectDir)\Lib, but add those files "As a link" to the root of your .csproj. Now they will get copied to bin\Debug (or whatever other output folder) without being in lib.
EDIT: This answer was written way back when ContentWithTargetPath was not available in the versions of VS/MSBuild I was using. Leaving this answer here for people who might have to use an older version of VS. Please stop commenting on this, we all know there are better ways now.
If your main intent is to include DLLs without cluttering up the project root directory, another solution is to move the DLLs to a separate Shared Project and add this as a reference in the original project.
(Note that this post doesn't directly answer this question as it doesn't preserve the folder and project structure, but I found this approach useful because I was able to restructure my project in my case and because I wanted to avoid some of the downsides of the other approaches here.)
Steps
Right-click your Solution -> Add -> New Project -> Shared Project
Add the DLLs to this project (in the root directory of this project, not in a "lib" sub-folder)
(Check DLL file properties are set correctly, e.g. Build Action: Content and Copy to Output Directory: Copy Always)
Right-click the original project's References -> Add Reference -> Shared Projects
Select the shared project you created earlier
The setup looks like this:
If you need to copy files from the Libs directory to the root folder VS2017:
<ItemGroup Condition="'$(Platform)' == 'x64'">
<None Include="Libs\x64\**" Link="\%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup Condition="'$(Platform)' == 'x86'">
<None Include="Libs\x86\**" Link="\%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
To any other folder, including Libs(RecursiveDir) folder
<ItemGroup Condition="'$(Platform)' == 'x86'">
<None Include="Libs\x86\**" Link="mycustomfolder\%(RecursiveDir)%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
Add the dll-files as a reference to the project, and on the reference set "Copy local" to true.
To add my hat into the ring here, if you want to include a whole directory of content and you don't want to track each individual file in Visual Studio, then you can add this in your project file (for me this is a .vcxproj file of a UWP C++ project):
<ItemGroup>
<Content Include="Content\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
Note that the Content directory must be in the same directory as the project file in order to preserve the directory structure.
It seems in VisualStudio 2015 that if the dlls you are 'adding with a link' are in a subfolder of that same project - they will be automatically put a folder, and the output is also placed in a folder like you saw.
If the dlls are in another project or directory on disk not in a subfolder of the project, you can 'Add with a link', and they will be put in the root directory just fine.
The above solutions did not work reliably for me in Visual Studio 2019 Professional v16.8.2. Sometimes the files would copy, sometimes not. After many attempts, it feels like something may have been broken in the latest updates to VS.
This answer shows how to use a post-build script...something the OP asks not to do! So this answer is intended only for those (who like me) were unable to get more traditional methods to work.
Right-click the project and select Add > Existing Item...
Navigate to the lib folder and select the item(s) to add
To the right of Add, click the down arrow and choose Add As Link
Right-click each file in the new "lib" folder in your project, and set "Copy to Output Directory" to "Do not copy"
Open project properties Build Events and add the following Post-build event
,
rem Copy 3rd party DLL(s) to the output directory on successful build
COPY $(ProjectDir)lib\Something.dll $(TargetDir)
COPY $(ProjectDir)lib\SomethingElse.dll $(TargetDir)
Note that you can use wildcards in the post-build event to copy multiple files.
I used this with VS2022:
<ItemGroup>
<ContentWithTargetPath Include="LibFolder\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>%(Filename)%(Extension)</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
Regarding your question, the following steps worked for me in Visual Studio 2019:
In the Visual Studio editor, for your dll, set the "Build Action" setting as "Content" (This might be optional) and "Copy to Output Directory" setting as "Do not copy".
The following will then be generated within the project csproj file:
<ItemGroup>
<Content Include="lib\IncludedDLL.dll" />
</ItemGroup>
Modify the entry to the following instead:
<ItemGroup>
<Content Include="lib\IncludedDLL.dll" />
<Content Include="lib\IncludedDLL.dll">
<Link>IncludedDLL.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
You can set "CopyToOutputDirectory" option within the project csproj file manually to either "Always" or "PreserveNewest".
In the Visual Studio editor, it will still show the file "Copy to Output Directory" setting as "Do not copy", but the file will be copied to the root output directory upon rebuild.
The above change is not needed if you do not want to copy the file to the root output directory, so if that is the case, you can just manually remove the above change within the project csproj file to revert to the non copying behavior.
An alternate method is just to leave the items as type None. In the solution explorer, click on the ones you want to deploy and set the Content property to True.
Note: I did this in VS2019, and things can change from version to version.
To get this to work, now right-click on your project, and select "Unload Project". Then right-click on the unloaded project and select "Edit project_name.vcxproj".
In the editor, go all the way to the bottom of the file and insert this target right right before the trailing </Project> tag:
<Target Name="CopyContent" AfterTargets="Build">
<Copy SourceFiles="#(None)" Condition="'%(None.DeploymentContent)' == 'true'" DestinationFolder="$(OutputPath)" ContinueOnError="true" />
</Target>
Now right click on the unloaded project and select "Reload Project". Select to save and close if you are prompted.
I also set the OutputDirectory to:
$(SolutionDir)bin\$(Configuration)\$(Platform)\
and the IntermediateDirectory to:
$(SolutionDir)obj\$(Configuration)\$(ProjectName)\$(Platform)\
in the Project Properties General page. This puts the output in a "bin" folder, and the intermediates in an "obj" folder in the root of your solution.
Note: The $(SolutionDir) is not defined when you run MSBuild from the command line. There is a trick you can use to define that to the folder where the .sln file lives using GetDirectoryNameOfFileAbove. (left as an exercise for the reader). Also, it looks like in 2019 they are handling this correctly on the command line anyway. Yeah :) The $(SolutionDir) contains a trailing backslash, hence none after it. The results of each must have a trailing backslash.
Now, if you own Pro or above, please don't do this every time you need to create a project. That would be lame. Instead, once you have your project setup just the way you like it, select Project -> Export Template. You give it a name, and the next time you want to create a project just like that one, just choose that name in the New Project dialog. (In older version, I think this was Files -> Export Teamplate....)
I had the same problem with Visual Studio 2010 / C# Project.
For assemblies (i. e. having the .NET interface) use folder "References" under your project in the Solution Explorer. Right click it, choose "Add existing item" and locate your .dll assembly.
Common .dll files can be placed in a subfolder (as "\lib" was mentioned above) and in the properties select:
Build Action = "HelpFiles"
Copy To OutputDirectory = "If Newer"
This worked for me exactly as desired - during build, the .DLLs are copied to the output directory without the "\lib" subfolder.

Out-of-place builds with C#

I just finished setting up an out-of-place build system for our existing C++ code using inherited property sheets, a feature that seems to be specific to the Visual C++ product. Building out-of-place requires that many of the project settings be changed, and the inherited property sheets allowed me to change all the necessary settings just by attaching a property sheet to the project. I am migrating our team from C++/MFC for UI to C# and WPF, but I need to provide the same out-of-place build functionality, hopefully with the same convenience. I cannot seem to find a way to do this with C# projects - I first looked to see if I could reference an MsBuild targets file, but could not find a way to do this. I know I could just use MsBuild for the whole thing, but that seems more complicated than necessary. Is there a way I can define a macro for a directory and use it in the output path, for example?
I'm not quite sure what an "out-of-place" build system is, but if you just need the ability to copy the compiled files (or other resources) to other directories you can do so by tying into the MSBuild build targets.
In our projects we move the compiled dlls into lib folders and put the files into the proper locations after a build is complete. To do this we've created a custom build .target file that creates the Target's, Property's, and ItemGroup's that we then use to populate our external output folder.
Our custom targets file looks a bit like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectName>TheProject</ProjectName>
<ProjectDepthPath>..\..\</ProjectDepthPath>
<ProjectsLibFolder>..\..\lib\</ProjectsLibFolder>
<LibFolder>$(ProjectsLibFolder)$(ProjectName)\$(Configuration)\</LibFolder>
</PropertyGroup>
<Target Name="DeleteLibFiles">
<Delete Files="#(LibFiles-> '$(ProjectDepthPath)$(LibFolder)%(filename)%(extension)')" TreatErrorsAsWarnings="true" />
</Target>
<Target Name="CopyLibFiles">
<Copy SourceFiles="#(LibFiles)" DestinationFolder="$(ProjectDepthPath)$(LibFolder)" SkipUnchangedFiles="True" />
</Target>
<ItemGroup>
<LibFiles Include=" ">
<Visible>false</Visible>
</LibFiles>
</ItemGroup>
</Project>
The .csproj file in Visual Studio then integrates with this custom target file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" ... >
...
<Import Project="..\..\..\..\build\OurBuildTargets.targets" />
<ItemGroup>
<LibFiles Include="$(OutputPath)$(AssemblyName).dll">
<Visible>false</Visible>
</LibFiles>
</ItemGroup>
<Target Name="BeforeClean" DependsOnTargets="DeleteLibFiles" />
<Target Name="AfterBuild" DependsOnTargets="CopyLibFiles" />
</Project>
In a nutshell, this build script first tells MSBuild to load our custom build script, then adds the compiled file to the LibFiles ItemGroup, and lastly ties our custom build targets, DeleteLibFiles and CopyLibFiles, into the build process. We set this up for each project in our solution so only the files that are updated get deleted/copied and each project is responsible for it's own files (dlls, images, etc).
I hope this helps. I apologize if I misunderstood what you mean by out-of-place build system and this is completely useless to you!
Is there a way I can define a macro for a directory and use it in the output path
Have you looked at the pre-build and post-build events of a project?
Actually, pre-build and post-build events seem to be solely a place to add batch-file type commands. This would not help me to set up standard build directories for our projects, unfortunately. And having these events create batch files seems like a very 1980's approach for a modern language like C#, IMO.
After digging some more, and experimenting, I have found that you can add an <Import> directive into your .csproj file. When you do this, the IDE pops up a warning dialog that there is an unsafe entry point in your project - but you can ignore this, and you can make it not appear at all by editing a registry entry, evidently. So this would give me a way to get the variables containing the directory paths I need into the .csproj file.
Now to get the Output Path to refer to it - unfortunately when you add a string like "$(MySpecialPath)/Debug" to the Output Path field, and save the project, the $ and () chars are converted to hex, and your file get's put in a Debug directory under a directory named "$(MySpecialPath)". Arrgghh. If you edit the .csproj file in a text editor, you can set this correctly however, and it seems to work as long as the <Import> tag appears before the <PropertyGroup> containing the Output Path.
So I think the solution for me will be to create a standard OurTeam.targets MsBuild file in a standard location, add an installer for changing the registry so it doesn't flag warnings, and then create custom project templates that <Import> this file, and also set the Output Path to use the properties defined in the OurTeam.targets file. Sadly, this is more work and a less elegant solution than the property sheet inheritance mechanism in C++.

Categories

Resources