Why is my Target not executed? - msbuild

I have the following Targets file that is imported into my .csproj file, One of the targets (AfterAddPostAction) never fires. Why not?
(Sorry it's so verbose but MSBuild is shit at abstraction and CallTask doesn't see Property values set inside the Target containing the CallTask element.)
<?xml version="1.0" encoding="Windows-1252"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="EstablishLog">
<MakeDir Condition="!Exists('$(MSBuildProjectDirectory)\Logs')" Directories=".\Logs"/>
<PropertyGroup>
<PowerShellExe Condition=" '$(PowerShellExe)'=='' ">%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe</PowerShellExe>
<ScriptPath Condition=" '$(ScriptPath)'=='' ">C:\Users\Admin\Documents\GitHub\powershell-scripts\</ScriptPath>
<LogState>$(ScriptPath)ProjectSnapShot.ps1</LogState>
<DoPostAction>$(ScriptPath)postAction-BeforePublish.ps1</DoPostAction>
<Switches>-NonInteractive -executionpolicy Unrestricted</Switches>
<Arguments>"& { &&apos;$(ScriptPath)&apos; } "</Arguments>
</PropertyGroup>
</Target>
<Target Name="AfterClean" DependsOnTargets="EstablishLog">
<PropertyGroup>
<LogFile >AfterClean$(ApplicationVersion).log</LogFile>
<LogFile Condition="Exists('$(MSBuildProjectDirectory)\Logs')">.\Logs\$(LogFile)</LogFile>
<Arguments>"& { &&apos;$(LogState)&apos; } "</Arguments>
</PropertyGroup>
<Exec Command="$(PowerShellExe) $(Switches) -command $(Arguments) > $(LogFile)" />
</Target>
<Target Name="BeforeBuild" DependsOnTargets="EstablishLog">
<PropertyGroup>
<LogFile >BeforeBuild$(ApplicationVersion).log</LogFile>
<LogFile Condition="Exists('$(MSBuildProjectDirectory)\Logs')">.\Logs\$(LogFile)</LogFile>
<Arguments>"& { &&apos;$(LogState)&apos; } "</Arguments>
</PropertyGroup>
<Exec Command="$(PowerShellExe) $(Switches) -command $(Arguments) > $(LogFile)" />
</Target>
<Target Name="AfterBuild" DependsOnTargets="EstablishLog">
<PropertyGroup>
<LogFile >AfterBuild$(ApplicationVersion).log</LogFile>
<LogFile Condition="Exists('$(MSBuildProjectDirectory)\Logs')">.\Logs\$(LogFile)</LogFile>
<Arguments>"& { &&apos;$(LogState)&apos; } "</Arguments>
</PropertyGroup>
<Exec Command="$(PowerShellExe) $(Switches) -command $(Arguments) > $(LogFile)" />
</Target>
<Target Name="BeforePublish" DependsOnTargets="EstablishLog">
<PropertyGroup>
<LogFile >BeforePublish$(ApplicationVersion).log</LogFile>
<LogFile Condition="Exists('$(MSBuildProjectDirectory)\Logs')">.\Logs\$(LogFile)</LogFile>
<Arguments>"& { &&apos;$(LogState)&apos; } "</Arguments>
</PropertyGroup>
<Exec Command="$(PowerShellExe) $(Switches) -command $(Arguments) > $(LogFile)" />
</Target>
<Target Name="AddPostAction" AfterTargets="BeforePublish" DependsOnTargets="EstablishLog">
<PropertyGroup>
<PostAction>FileCopyPDA.FileCopyPDA</PostAction>
<Arguments>"& { &&apos;$(DoPostAction)&apos; &apos;$(PostAction)&apos; $(Configuration)} "</Arguments>
<LogFile >AddPostAction$(ApplicationVersion).log</LogFile>
<LogFile Condition="Exists('$(MSBuildProjectDirectory)\Logs')">.\Logs\$(LogFile)</LogFile>
</PropertyGroup>
<Exec Command="$(PowerShellExe) $(Switches) -command $(Arguments) > $(LogFile)" />
</Target>
<!--This one is never called-->
<Target Name="AfterAddPostAction" DependsOnTargets="EstablishLog;AddPostAction">
<PropertyGroup>
<LogFile >AfterAddPostAction$(ApplicationVersion).log</LogFile>
<LogFile Condition="Exists('$(MSBuildProjectDirectory)\Logs')">.\Logs\$(LogFile)</LogFile>
<Arguments>"& { &&apos;$(LogState)&apos; } "</Arguments>
</PropertyGroup>
<Exec Command="$(PowerShellExe) $(Switches) -command $(Arguments) > $(LogFile)" />
</Target>
<Target Name="AfterPublish" DependsOnTargets="EstablishLog">
<PropertyGroup>
<LogFile >AfterPublish$(ApplicationVersion).log</LogFile>
<LogFile Condition="Exists('$(MSBuildProjectDirectory)\Logs')">.\Logs\$(LogFile)</LogFile>
<Arguments>"& { &&apos;$(LogState)&apos; } "</Arguments>
</PropertyGroup>
<Exec Command="$(PowerShellExe) $(Switches) -command $(Arguments) > $(LogFile)" />
</Target>
</Project>

DependsOnTargets is the primary way to chain tasks into the sequence. But if you have sequence A->B->C implemented via DependsOnTargets (B depends on A) and call target A, then B and C will not be executed. But if you call C, then both A and B are executed.
On the contrary the target that has target A mentioned in AfterTargets attribute will be executed after A is executed.
That's why in your case if you want to use DependsOnTargets it is important which target you execute.

Related

WiX Toolset v4: Dynamically set Target/Ouput Name of installer

After upgrading a WiX installer project (.wixproj) file to v4, the MSBuild step in the project to set the TargetName of the installer file output is no longer working.
I would like to dynamically append the assembly version of the application to be installed to the TargetName of the msi file name i.e. MyApplication.X.X.X.X.msi.
Below is a snippet of the .wixproj file in its current state (I have removed some code for the sake of brevity):
<Project InitialTargets="SetVersion;BeforeBuild;CopyLinkedContentFiles">
<Import Project="Sdk.props" Sdk="WixToolset.Sdk" Version="4.0.0-rc.1" />
[...]
<Target Name="SetVersion">
<GetAssemblyIdentity AssemblyFiles="$(SolutionDir)\bin\$(Platform)\$(Configuration)\MyApp.exe">
<Output TaskParameter="Assemblies" ItemName="Assembly" />
</GetAssemblyIdentity>
<CreateProperty Value="$(SolutionName).%(Assembly.Version)">
<Output TaskParameter="Value" PropertyName="TargetName" />
</CreateProperty>
<PropertyGroup>
<DefineConstants>BuildVersion=%(Assembly.Version)</DefineConstants>
</PropertyGroup>
</Target>
<Target Name="CopyLinkedContentFiles" BeforeTargets="Build">
<Copy SourceFiles="%(Content.Identity)" DestinationFiles="%(Content.Link)" SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" Condition="'%(Content.Link)' != ''" />
</Target>
<Import Project="Sdk.targets" Sdk="WixToolset.Sdk" Version="4.0.0-rc.1" />
[...]
</Project>
The .wixproj previous had the following code to adjust the TargetName:
<Target Name="SetVersion">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x64</Platform>
<SccProjectName>SAK</SccProjectName>
<SccProvider>SAK</SccProvider>
<SccAuxPath>SAK</SccAuxPath>
<SccLocalPath>SAK</SccLocalPath>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\OCS.Service\</SolutionDir>
</PropertyGroup>
<GetAssemblyIdentity AssemblyFiles="$(SolutionDir)\bin\$(Platform)\$(Configuration)\MyApp.exe">
<Output TaskParameter="Assemblies" ItemName="AssemblyVersion" />
</GetAssemblyIdentity>
<PropertyGroup>
<DefineConstants>BuildVersion=%(AssemblyVersion.Version)</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<TargetName>$(SolutionName).%(AssemblyVersion.Version)</TargetName>
</PropertyGroup>
</Target>
The latter solution worked as expected prior to upgrading to v4, however, upon building the project and inspecting the output of the build, the change to the TargetName have not been applied.

Nuget Catch 22 after migrating VB project

I am migrating a VB project from VS 2010 to VS 2017 and I am getting the following errors:
Error occurred while restoring NuGet packages: System.ArgumentException: '$(NETStandardImplicitPackageVersion)' is not a valid version string.
**StackTrace**
and
'C:\Users\PathToProject\obj\project.assets.json' not found. Run a NuGet package restore to generate this file.
I can get around the first error by turning off nuget restore as mentioned here but then i can't get past the second error, which requires nuget restore, or some other way to generate project.assets.json
Here is my vbproj file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" Sdk="Microsoft.NET.Sdk.Web" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{E3DC9794-BE3D-43DC-8198-8843B1A65546}</ProjectGuid>
<OutputType>WinExe</OutputType>
<StartupObject>_GLENAIR_EEPROM_CONFIG_GUI.My.MyApplication</StartupObject>
<RootNamespace>_GLENAIR_EEPROM_CONFIG_GUI</RootNamespace>
<AssemblyName>Glenair EEPROM Config Interface</AssemblyName>
<FileAlignment>512</FileAlignment>
<MyType>WindowsForms</MyType>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
<IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>true</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ProductName>Glenair EEPROM Config GUI</ProductName>
<PublisherName>Glenair</PublisherName>
<SuiteName>Glenair EEPROM</SuiteName>
<ApplicationRevision>4</ApplicationRevision>
<ApplicationVersion>2.4.0.4</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<CreateDesktopShortcut>true</CreateDesktopShortcut>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<OutputPath>bin\Debug\</OutputPath>
<DocumentationFile>Glenair EEPROM Config Interface.xml</DocumentationFile>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
<RemoveIntegerChecks>true</RemoveIntegerChecks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<DefineDebug>false</DefineDebug>
<DefineTrace>true</DefineTrace>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DocumentationFile>Glenair EEPROM Config Interface.xml</DocumentationFile>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
<RemoveIntegerChecks>true</RemoveIntegerChecks>
</PropertyGroup>
<PropertyGroup>
<OptionExplicit>On</OptionExplicit>
</PropertyGroup>
<PropertyGroup>
<OptionCompare>Binary</OptionCompare>
</PropertyGroup>
<PropertyGroup>
<OptionStrict>Off</OptionStrict>
</PropertyGroup>
<PropertyGroup>
<OptionInfer>On</OptionInfer>
</PropertyGroup>
<PropertyGroup>
<ManifestCertificateThumbprint>AD3083285EA99B2F1F52BD898477B62BC6D3A802</ManifestCertificateThumbprint>
</PropertyGroup>
<PropertyGroup>
<ManifestKeyFile>990-05010-X_EEPROM_LOADER_TemporaryKey.pfx</ManifestKeyFile>
</PropertyGroup>
<PropertyGroup>
<GenerateManifests>true</GenerateManifests>
</PropertyGroup>
<PropertyGroup>
<SignManifests>false</SignManifests>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>My Project\app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>GLENAIR_BERT_GUI.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<TargetZone>LocalIntranet</TargetZone>
</PropertyGroup>
<PropertyGroup>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Management" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Windows.Forms.DataVisualization" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
</ItemGroup>
<ItemGroup>
<Import Include="Microsoft.VisualBasic" />
<Import Include="System" />
<Import Include="System.Collections" />
<Import Include="System.Collections.Generic" />
<Import Include="System.Data" />
<Import Include="System.Drawing" />
<Import Include="System.Diagnostics" />
<Import Include="System.Windows.Forms" />
<Import Include="System.Linq" />
<Import Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="ApplicationEvents.vb" />
<Compile Include="Form1.vb">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.vb">
<DependentUpon>Form1.vb</DependentUpon>
<SubType>Form</SubType>
</Compile>
<Compile Include="My Project\AssemblyInfo.vb" />
<Compile Include="My Project\Application.Designer.vb">
<AutoGen>True</AutoGen>
<DependentUpon>Application.myapp</DependentUpon>
</Compile>
<Compile Include="My Project\Resources.Designer.vb">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="My Project\Settings.Designer.vb">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<Compile Include="Settings.vb" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.vb</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="My Project\Resources.resx">
<Generator>VbMyResourcesResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.vb</LastGenOutput>
<CustomToolNamespace>My.Resources</CustomToolNamespace>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="My Project\app.manifest">
<SubType>Designer</SubType>
</None>
<None Include="My Project\Application.myapp">
<Generator>MyApplicationCodeGenerator</Generator>
<LastGenOutput>Application.Designer.vb</LastGenOutput>
</None>
<None Include="My Project\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<CustomToolNamespace>My</CustomToolNamespace>
<LastGenOutput>Settings.Designer.vb</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4 Client Profile %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
<Visible>False</Visible>
<ProductName>Windows Installer 3.1</ProductName>
<Install>true</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Content Include="GLENAIR_BERT_GUI.ico" />
<Content Include="GLENAIR_BERT_GUI2.ico" />
<None Include="Resources\9_inch_300_dpi_cmyk_logo.jpg" />
</ItemGroup>
<ItemGroup>
<PublishFile Include="Microsoft.VisualBasic.PowerPacks.Vs">
<Visible>False</Visible>
<Group>
</Group>
<TargetPath>
</TargetPath>
<PublishState>Include</PublishState>
<IncludeHash>True</IncludeHash>
<FileType>Assembly</FileType>
</PublishFile>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
Detailed Build Log: Here (Too long to post here)
Remove the csproj from the solution and add it again. Make sure you have a recent version of VS 2017 (at the time of writing 15.7.2).
This can happen if you migrate to an sdk-style project (<Project Sdk="Microsoft.NET.Sdk">) which is loaded using the classic csproj project system instead of the new project system specifically designed for this type of project.
The GUIDs in the solution as well as the ocurrance of a <TargetFramework> property determine which project system is used to load the csproj.

Include attribute unknown in .props file

I'm preparing a project solution because i plan on publishing a couple nuget projects. In order to avoid the future hassle i've considered to share build properties which are equal in all csproj files.
Oddly enough i get a message saying that the include attribute within the content element is unknown.
Am i doing something wrong/unusual here?
project.v400.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="..\..\CommonBuildTargets.props"/>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{AA7888A1-E7B4-477F-924E-BF97964B17FA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>project</RootNamespace>
<AssemblyName>project</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>$(CommonBuildPathDebug)</OutputPath>
<DefineConstants>TRACE;DEBUG;NET400</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>$(CommonBuildPathRelease)</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
CommonBuildTargets.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CommonBuildPathDebug>..\Build\$(AssemblyName)\$(Configuration)\$(TargetFrameworkVersion)\</CommonBuildPathDebug>
<CommonBuildPathRelease>..\Build\$(AssemblyName)\$(Configuration)\$(TargetFrameworkVersion)\</CommonBuildPathRelease>
<CommonRecursionRoot>.\src\project.v460\**\*.cs</CommonRecursionRoot>
<CommonContentExclusion>.\src\project.v460\obj\**</CommonContentExclusion>
</PropertyGroup>
<PropertyGroup>
<Content Include="$(CommonRecursionRoot)" Exclude="$(CommonContentExclusion)">
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
</Content>
</PropertyGroup>
</Project>
I've tested the the recursive content inclusion individually, which worked fine. Once i extract it to a shared props file (which loads fine too, tested that as well) it does not work anymore.
Well i'd usually delete this question but since i didn't stumble on other questions like this:
The reason it didn't work was because i put my Content element in a propertygroup instead of an itemgroup

How to execute MSBuild target recursively?

I attempt to execute the following target but ends up with circular dependency error. I do have a stop condition $(Value) > 0 in target Recursive:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<Target Name="Recursive" Condition="$(Value) > 0">
<PropertyGroup>
<Value>$([MSBuild]::Subtract($(Value), 1))</Value>
</PropertyGroup>
<MSBuild Projects="$(MSBuildProjectFile)" Targets="Display" />
</Target>
<Target Name="Display">
<Message Text="Value: $(Value)" />
<MSBuild Projects="$(MSBuildProjectFile)" Targets="Recursive" />
</Target>
<PropertyGroup>
<Value>10</Value>
</PropertyGroup>
<Target Name="Build">
<MSBuild Projects="$(MSBuildProjectFile)" Targets="Display" />
</Target>
</Project>
Your stop condition is fine, but you forgot to pass the recalculated Value property into the next recursion. Change your Recursive target like this:
<Target Name="Recursive" Condition="$(Value) > 0">
<PropertyGroup>
<Value>$([MSBuild]::Subtract($(Value), 1))</Value>
</PropertyGroup>
<MSBuild Projects="$(MSBuildProjectFile)" Targets="Display"
Properties="Value=$(Value)" />
</Target>

How to simplify MSBuild-targets?

<Target Name="ProtobufCompile"
Inputs="#(ProtocCompile)"
Outputs="$(IntermediateOutputPath)$([System.Text.RegularExpressions.Regex]::Replace('%(ProtocCompile.RelativeDir)','\.\.[/\\]',''))%(ProtocCompile.Filename).cs">
<PropertyGroup>
<protooutdir>$(IntermediateOutputPath)$([System.Text.RegularExpressions.Regex]::Replace('%(ProtocCompile.RelativeDir)','\.\.[/\\]',''))</protooutdir>
</PropertyGroup>
<Message Text="%(ProtocCompile.Filename)%(ProtocCompile.Extension)" Importance="high" />
<MakeDir Directories="$(protooutdir)" />
<Exec Command="$(ProtobufCompiler) --protoc_dir=${PROTOBUF_PROTOC_EXECUTABLE}/.. --proto_path=%(ProtocCompile.RootDir)%(ProtocCompile.Directory) -output_directory=$(protooutdir) %(ProtocCompile.FullPath)" />
</Target>
<!-- set Intputs and Outputs -->
<Target Name="ProtobufCSharpCompile"
DependsOnTargets="ProtobufCompile">
<CreateItem Include="$(IntermediateOutputPath)$([System.Text.RegularExpressions.Regex]::Replace('%(ProtocCompile.RelativeDir)','\.\.[/\\]',''))%(ProtocCompile.Filename).cs">
<Output TaskParameter="Include" ItemName="Compile"/>
</CreateItem>
</Target>
<Target Name="ProtobufClean"
BeforeTargets="Clean">
<Delete Files="$(IntermediateOutputPath)$([System.Text.RegularExpressions.Regex]::Replace('%(ProtocCompile.RelativeDir)','\.\.[/\\]',''))%(ProtocCompile.Filename).cs" />
</Target>
This is the piece of target-file. How to simplify this code? How to reduce duplicating of string below?
$(IntermediateOutputPath)$([System.Text.RegularExpressions.Regex]::Replace('%(ProtocCompile.RelativeDir)','\.\.[/\\]',''))
Since you already specified a property for that value like so:
<PropertyGroup>
<protooutdir>$(IntermediateOutputPath)$([System.Text.RegularExpressions.Regex]::Replace('%(ProtocCompile.RelativeDir)','\.\.[/\\]',''))</protooutdir>
</PropertyGroup>
You can just replace references to that string with the property $(protooutdir) like so:
<CreateItem Include="$(protooutdir)%(ProtocCompile.Filename).cs">
and
<Delete Files="$(protooutdir)%(ProtocCompile.Filename).cs" />