How to run MsBuild with specific target when publishing? - c#

I've added a target section to my csproj file like this.
<Target Name="Spa">
<Exec Command="ng --version" WorkingDirectory="../Spa" />
<Exec Command="ng build --prod" WorkingDirectory="../Spa" />
<Exec Command="del .\wwwroot\* /F /Q /S" />
<Exec Command="copy ..\Spa\dist\Spa\* .\wwwroot" />
</Target>
When I'm executing it using the command below, it does precisely what it's supposed to.
dotnet msbuild /t:Spa
However, it'd be nice if the target could be invoked just prior to the execution of publishing within Visual Studio (b+h+Tab+Enter).
I've read somewhere that it's possible and the docs claim that BeforePublish is the correct target name. However, when I change the name Spa to BeforePublish, I'm not getting the effect of my SPA being built and copied over.
What am I missing and how do I automate the process?

How to run MsBuild with specific target when publishing?
I post an answer here to make sure this question more clear.
For this question, The first thing to note is that it is related to the project type. If you are publish a WPF/Winodws Forms project, the <Target Name="BeforePublish "> should be works as expected. That because these project types include the target publish by default, so the target "BeforePublish" will work as expected.
However, the web project not contain this default target Publish, so if you use <Target Name="BeforePublish "> in the web project, it will not executed as expected. To resolve this issue, we could add a BeforeTargets="BeforePublish" to the target, like:
<Target Name="Spa" BeforeTargets="BeforePublish">
...
</Target>
Then Overriding "DependsOn" Properties:
<PropertyGroup>
<BuildDependsOn>
BeforeBuild;
CoreBuild;
AfterBuild;
BeforePublish
</BuildDependsOn>
</PropertyGroup>
Or you can simple add AfterTargets="Build" to the target Spa, it should woks fine:
<Target Name="Spa" AfterTargets="Build">
...
</Target>
The second thing to note is that whether the target section needs to be at the bottom of the csproj file is depends on the style csproj. Just as Martin said, if you are in the old style csproj, those targets BeforeBuild, AfterBuild are actually pre-defined in the Microsoft.Common.targets file that contains the Visual Studio Build process, so we have to set our custom target at the bottom of the csproj to overwrite it in the Microsoft.Common.targets file. If you are in the new style csproj(), it doesn't matter where you set it.
Hope this helps.

Related

MSBuild Target Detecting skipped build

When MS build builds a solution, it will skip projects that are up to date. Is there a way to detect this in the subsequent targets to avoid doing additional unnecessary work?
The case example is one of my projects is creating a NuGet package after the build process targeting an AfterBuild target. If the build is skipped then we can conclude that Nuget package doesn't need to be replaced.
I've tried AfterBuild and CoreCompile
<Target Name="DebugProps" AfterTargets="CoreCompile">
<Message Importance="high" Text="Current Saved Properties are:"/>
Logfile output:
CoreCompile:
Skipping target "CoreCompile" because all output files are up-to-date with respect to the input files.
DebugProps:
Current Saved Properties are:
I found the most effective way to solve this problem, is set assign a special property called TargetsTriggeredByCompilation to a desired target that you want to call after core compile is complete.
Core compile is skipped with incremental builds, with that I've set separate property SkipPostBuildActions to true at the start of the build. This is then further used as condition in any targets which I want to be conditional on core compile.
Once core compile is complete, it will trigger my EnablePostBuild target, this target intern sets SkipPostBuildActions to false. This then enables subsequent build targets.
<!-- Defines Targets that should be run after Compile, but skipped if Compile doesn't take place -->
<PropertyGroup>
<TargetsTriggeredByCompilation>
$(TargetsTriggeredByCompilation);
EnablePostBuild
</TargetsTriggeredByCompilation>
</PropertyGroup>
<Target Name="EnablePostBuild">
<!-- Disable post build actions -->
<PropertyGroup>
<SkipPostBuildActions>false</SkipPostBuildActions>
</PropertyGroup>
</Target>
An example of '$(SkipPostBuildActions)' == 'false' used on a NugetProject target:
<!-- Creating Nuget Packages -->
<Target Name="NugetProject" AfterTargets="PostBuild" Condition="'$(NugetProject)' == 'true' And '$(SkipPostBuildActions)' == 'false'" DependsOnTargets="GetVersion">
<!-- Manages getting the assembly version for output folders -->
<Message Importance="High" Text="Current Assembly Version $(GitVersion_NuGetVersion)"/>
<!-- Note the Exec Task needs to provide a path relative to current Project that is building -->
<Exec Command="$(ToolsPath)NuGet.exe pack -Version $(VersionStamp) -Symbols -SymbolPackageFormat snupkg -IncludeReferencedProjects -OutputDirectory $(LocalMachineNugetPath) -Verbosity detailed -Properties Configuration=$(Configuration)"/>
</Target>
TargetsTriggeredByCompilation is triggered with a call target deep in the internals of the Targets graph that is pulled in with default build.
I think you could use Inputs and outputs on msbuild.
Try this:
<Target Name="test123" BeforeTargets="CoreCompile">
<ItemGroup>
<File Include="$(TargetPath)"></File>
</ItemGroup>
<Copy SourceFiles="#(File)" DestinationFolder="$(SolutionDir)"></Copy>
<Message Importance="high" Text="$(TargetName)"></Message>
</Target>
<Target Name="DebugProps" AfterTargets="Build" Inputs="$(TargetPath)" Outputs="$(SolutionDir)$(TargetFileName)">
<Message Importance="high" Text="Current Saved Properties are:"/>
</Target>

Create folder while publishing with MSBuild

I am Trying to create a Temp folder while doing the publish of my Web Application Project with file system location in Visual Studio 2015.I have tried adding the code below in .csfile but its not Creating folder in the target location of the Publish Directory.If given Static location to create folder its working fine.How can get Publish Directory dynamically?
<Target Name="CustomCollectFiles" AfterTargets="Build">
<MakeDir Directories="$(PublishDirectory)$(OutputDirectoryTemp)" />
</Target>
<PropertyGroup>
<OutputDirectoryTemp>\Temp\</OutputDirectoryTemp>
</PropertyGroup>
Create folder while publishing with MSBuild
You need to declare attributes PublishDirectory by creating an element with the attribute name as a child of the PropertyGroup element, because there is no such MSBuild Macros for this, you can check the Common Macros for Build Commands and Properties.
As test, I set PublishDirectory to the path D:\Publish:
<PropertyGroup>
<PublishDirectory>D:\Publish</PublishDirectory>
</PropertyGroup>
Then I add a output command line <Message Test="xxx" /> in the target to output the content of $(PublishDirectory)$(OutputDirectoryTemp):
<Target Name="CustomCollectFiles" AfterTargets="Build">
<MakeDir Directories="$(PublishDirectory)$(OutputDirectoryTemp)" />
<Message Text="$(PublishDirectory)$(OutputDirectoryTemp)">
</Message>
</Target>
<PropertyGroup>
<OutputDirectoryTemp>\Temp\</OutputDirectoryTemp>
</PropertyGroup>
<PropertyGroup>
<PublishDirectory>D:\Publish</PublishDirectory>
</PropertyGroup>
In the output window, you will see following log:
And the folder Temp would be created:
If you have already defined the variable of PublishDirectory, you can try to use the output command line <Message Test="xxx" /> check if the path is correct.
Update:
I will like to get the target location of the Publish Directory dynamically not static or hard coded
Since you want to get the target location of the Publish Directory dynamically, as we know, the location of the Publish Directory was stored in the .pubxml file, in the node <publishUrl>D:\Publish\Test</publishUrl>, to get this value dynamically, we could use $(publishUrl) to get this value in the target, However, the publishing process is after the build, we could not get this value in the build process, so we need to change the order of this target from AfterTargets="Build" to AfterTargets="PipelineTransformPhase". The target should be:
<Target Name="CustomCollectFiles" AfterTargets="PipelineTransformPhase">
<MakeDir Directories="$(publishUrl)$(OutputDirectoryTemp)" />
<Message Text="$(publishUrl)$(OutputDirectoryTemp)" />
</Target>
<PropertyGroup>
<OutputDirectoryTemp>\Temp\</OutputDirectoryTemp>
</PropertyGroup>
In this case, when you publish your project to the system location, the publish directory will be stored in the publishUrl, we could get it in that target.
Hope this helps.
A little irrelevant at this point in time, but I think it may help someone like me. Whenever I searched how to dynamically add the folders and files within the same project in my publish profile in Azure DevOps, I landed on this question. So, I decided to put my answer here if it can help someone.
MSBuild expects you to make sure that you include certain files into the project file if you want Azure to deploy them during a git deployment. It is very frustrating, especially when you're using an external tool to copy files into a certain folder.
Wouldn’t it be nice if Visual Studio and (therefore) MSBuild just recursively included and deployed a folder of files within the same project without you having to define it in the publish profile?
You have to hand-edit the .csproj/.vbproj file (using Notepad++), but all you need to do is use a wildcard in the Include statement:
MyProject.Web.vbproj
<!-- double wildcards include all files and folders -->
<Content Include="folder\**" />
<!-- You can also filter certain type of files (e.g. .js files) -->
<Content Include="folder\*.min.js" />
It helped me tremendously.
Try specifying your PropertyGroup before your target, to ensure it has been created.
Also check the value of PublishDirectory to make sure it is a valid path - I suspect it may come with a "\" on the end of it, so you're ending up with two slashes

Why doesn't Exec Command work in a .csproj for netstandard2.0?

I'm busy moving my code from .Net Framework libraries to .netstandard2.0 libraries. So far it's going pretty well, but now i'm stuck with the in the .csproj file.
The existing project file has this defined
<Target Name="Rebuild">
<Exec Command="echo Now Rebuilding the package" />
</Target>
the actual command executes an exe that generates a bunch of xml classes based on an xsd.
I cannot get this to work in a .netstandard2.0 project?
I've searched everywhere but i cannot find a reason for this not working...
I suspect that in your specific instance, the Rebuild target will be overwritten by the sdk targets that are implicitly imported after your code. If you want to overwrite SDK-provided tasks, you need to change to explicit SDK imports (instead of <Project Sdk="...">):
<Project>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<!-- other project content -->
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
<Target Name="Build">
<!-- overwrite Build target here -->
</Target>
<Target Name="Rebuild">
<!-- overwrite Rebuild target here -->
</Target>
</Project>
The Exec target is supported though the echo command may or may not work depending on the platform you are running it on (since echo may be just a built-in command of the shell but no executable that can be run).
Make sure that:
The command starts with the path to an executable that is found on the PATH or is specified absolute or relative to the csproj file being built.
The target is actually executed. E.g. some programs could use /t:Clean;Build instead of /t:Rebuild.

Customizations on MSBuild (like version) for a C# solution

I'm thinking that the final result is going to be "it can't be that easily done", but just seems like it should be. I have a personal project I am working on. I'd hate to have to manually (or even in script) change versions, company, copyright, and all that on ALL the assembly.cs files and would like all that to be either in a script or in a file I can change (so the script stays the same mostly) when I want to update the version. But it seems like MSBuild is mostly a "build as is specified in Visual Studio". I'd just hate to have all that history of these files where I change just the version and possibly even make a mistake as this project will continue to get bigger and bigger. I'd like to just be able to add a new project to Visual studio and have whatever command line in my powershell script just say "compile this, but give it this company name and this file version instead of whatever is listed in the code file".
Google has NOT proven fruitful in this. I've even found it difficult to build my files to a specific folder. I've had to so far make sure all my projects are 2 folders deep and was able to say to build them at ....\, but I would like to be able to change that randomly if I like and have them built elsewhere if I so desire.
Is MSBuild perhaps not the way to go? Is there someway else to build visual studio that would be better from command line? Eventually I also want to auto build the install with wix and be able to match its version with the binary versions.
thank you
Since csproj is xml, you can use XmlUpdate "helpers" to modify the values inside the csproj file before you do your build.
For other files, you can use some other Tasks to do the job.
Here is one helpful target:
http://msbuildtasks.tigris.org/ and/or https://github.com/loresoft/msbuildtasks has the ( FileUpdate (and SvnVersion task if that is your Source-Control) ) tasks.
<Target Name="BeforeBuild_VersionTagIt_Target">
<ItemGroup>
<AssemblyInfoFiles Include="$(ProjectDir)\**\*AssemblyInfo.cs" />
</ItemGroup>
<!--
<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="$(SVNToolPath)">
<Output TaskParameter="Revision" PropertyName="MyRevision" />
</SvnVersion>
-->
<PropertyGroup>
<MyRevision>9999</MyRevision>
</PropertyGroup>
<FileUpdate Files="#(AssemblyInfoFiles)"
Regex="AssemblyFileVersion\("(\d+)\.(\d+)\.(\d+)\.(\d+)"
ReplacementText="AssemblyFileVersion("$1.$2.$3.$(MyRevision)" />
</Target>
Below is an example of manipulating the csproj(xml).
How to add a linked file to a csproj file with MSBuild. (3.5 Framework)
But basically, when you build, you can put all the repetative stuff in a msbuild definition file (usually with the extension .proj or .msbuild)...and call msbuild.exe MyFile.proj.
Inside the .proj file, you will reference your .sln file.
For example:
$(WorkingCheckout) would be a variable (not defined here)...that has the directory where you got a copy of hte code from your source-control.
<Target Name="BuildIt" >
<MSBuild Projects="$(WorkingCheckout)\MySolution.sln" Targets="Build" Properties="Configuration=$(Configuration)">
<Output TaskParameter="TargetOutputs" ItemName="TargetOutputsItemName"></Output>
</MSBuild>
<Message Text="BuildItUp completed" />
</Target>
So below is the more complete example.
You would save this as "MyBuild.proj" and then call
"msbuild.exe" "MyBuild.proj".
Start .proj code. (Note, I did not import the libraries for the FileUpdate Task)
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="AllTargetsWrapped">
<PropertyGroup>
<!-- Always declare some kind of "base directory" and then work off of that in the majority of cases -->
<WorkingCheckout>.</WorkingCheckout>
</PropertyGroup>
<Target Name="AllTargetsWrapped">
<CallTarget Targets="BeforeBuild_VersionTagIt_Target" />
<CallTarget Targets="BuildItUp" />
</Target>
<Target Name="BuildItUp" >
<MSBuild Projects="$(WorkingCheckout)\MySolution.sln" Targets="Build" Properties="Configuration=$(Configuration)">
<Output TaskParameter="TargetOutputs" ItemName="TargetOutputsItemName"></Output>
</MSBuild>
<Message Text="BuildItUp completed" />
</Target>
<Target Name="BeforeBuild_VersionTagIt_Target">
<ItemGroup>
<AssemblyInfoFiles Include="$(ProjectDir)\**\*AssemblyInfo.cs" />
</ItemGroup>
<!--
<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="$(SVNToolPath)">
<Output TaskParameter="Revision" PropertyName="MyRevision" />
</SvnVersion>
-->
<PropertyGroup>
<MyRevision>9999</MyRevision>
</PropertyGroup>
<FileUpdate Files="#(AssemblyInfoFiles)"
Regex="AssemblyFileVersion\("(\d+)\.(\d+)\.(\d+)\.(\d+)"
ReplacementText="AssemblyFileVersion("$1.$2.$3.$(MyRevision)" />
</Target>
</Project>
To enhance the above, you would create a new target that would run before "BeforeBuild_VersionTagIt_Target", that would pull your code from source-control and put it in the $(WorkingCheckout) folder.
The basic steps would then be: 1. Checkout code from Source-Control. 2. Run the targets that alter the AssemblyVersion (and whatever else you want to manipulate) and 3. Build the .sln file.
That's the basics of a .proj file. You can do much more. Usually by using helper libraries that already exists.

error MSB4006: There is a circular dependency in the target dependency graph involving target

I have following target on csproj file using VS2012, i want to run "Publish" on the project once the build has finished.
This gives me following error "error MSB4006: There is a circular dependency in the target dependency graph involving target "AfterBuild""
<Target Name="AfterBuild">
<Message Text="Running Publish..." Importance="high"/>
<MSBuild Projects="$(ProjectPath)" Properties="DeployOnBuild=true;PublishProfile=WebDeploy;CreatePackageOnPublish=True;VisualStudioVersion=11.0"/>
</Target>
i have tried replacing the MSBuild step with
<Exec Command="$(MSBuildBinPath)\MSBuild C:\MyProj.csproj /p:DeployOnBuild=true /p:PublishProfile=WebDeploy /p:CreatePackageOnPublish=True /p:VisualStudioVersion=11.0" ContinueOnError="false" />
Doing above results in build/publish being run over and over.
i have tried naming target different, calling it via another target to no avail.
How can i run "Publish" on the project without getting into repeated cycles? can this be done in another way?
If you only build in Visual Studio, then putting something like this into your csproj file (at the end) will work:
<Target Name="Deploy" AfterTargets="Build">
<MSBuild
Projects="$(ProjectPath)"
Targets="WebPublish"
Properties="PublishProfile=LocalDeploy"
/>
</Target>
However, if you build with with MSBuild this will result in a circular dependency. I have not yet figured out a solution that will allow you to publish on build that works in both Visual Studio and MSBuild.
Previously I was building with Visual Studio and TFS Server, which worked fine, but when i moved to GO-CD the following code caused a circular dependency creating packages.
<Target Name="CopyLinkedContentFiles" AfterTargets="Build" DependsOnTargets="PipelinePreDeployCopyAllFilesToOneFolder">
<Copy SourceFiles="%(Content.Identity)"
DestinationFiles="%(Content.Link)"
SkipUnchangedFiles='true'
OverwriteReadOnlyFiles='true'
Condition="'%(Content.Link)' != ''" />

Categories