1

I'm trying to override the OutputPath parameter globally in a csproj by using an external task to get information regarding where I would like to store the final output.

I created a task:

<Target Name="SetSolutionTarget">
    <SetSolutionConfiguration SolutionPath="$(SolutionPath)">
      <Output PropertyName="SolutionConfig" TaskParameter="SolutionConfiguration"/>
    </SetSolutionConfiguration>
    <Message Text="SolutionConfiguration is: $(SolutionConfig)" Importance="high" />
</Target>

The task works good, and the message outputs the correct value where I want to output my files.

The problem is when I try to integrate it with the OutputPath. Trying to do:

<OutputPath>$(SolutionConfig)</OutputPath>

Does not work - it throws an error: "The OutputPath property is not set ...." - Which means that either the variable does not pass between tasks or the variable for OutputPath must be set before executing build.

I have also tried other things like setting an environment variable in my task instead of outputting the result (but still, no luck).

Roy Reznik
  • 2,040
  • 4
  • 22
  • 28
  • Are you sure your task is getting executed before Microsoft targets file is being imported? – skolima Nov 02 '12 at 13:28
  • Actually, I'm not sure. My project definition is as follows: `)` - so the set is executed (or should be) before the build. – Roy Reznik Nov 02 '12 at 13:33
  • I don't think this will work. Try overriding BeforeBuild target instead (but it still might be too late). – skolima Nov 02 '12 at 13:37

1 Answers1

0

I realize that this is an old question, but figuring out the solution was tiring tedious. Therefore posting my findings here to (hopefully) save others spending to much time.


Solution 1 (recommended)

Now that MSBuild is opened sourced on GitHub, I asked MSFT team member (Jeff Kluge) on his MSBuild samples repo a similar question (exception being, I was trying with an inline task), and received a solution: https://github.com/jeffkl/MSBuild-NetCore/issues/2

@jeffkl said:

Here is my example: https://github.com/jeffkl/MSBuild-NetCore/blob/master/src/OutputPathViaInlineTask/build.targets

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <UsingTask TaskName="GetOutputPath" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
    <ParameterGroup>
      <OutputPath ParameterType="System.String" Output="true" />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="cs">
        <![CDATA[
          OutputPath = Path.Combine("bin", Guid.NewGuid().ToString("N")) + System.IO.Path.DirectorySeparatorChar;
        ]]>
      </Code>
    </Task>
  </UsingTask>
  
  <Target Name="SetOutputPath"
          BeforeTargets="_CheckForInvalidConfigurationAndPlatform">

    <Message Text="Before:" />
    <Message Text=" OutputPath: '$(OutputPath)'" />
    <Message Text=" OutDir: '$(OutDir)'" />
    <Message Text=" PublishDir: '$(PublishDir)'" />
    <Message Text=" _InvalidConfigurationError: '$(_InvalidConfigurationError)'" />
    <Message Text=" _InvalidConfigurationWarning: '$(_InvalidConfigurationWarning)'" />

    <GetOutputPath>
      <Output TaskParameter="OutputPath" PropertyName="OutputPath"/>
      <!--
        OutDir is set in Microsoft.Common.CurrentVersion.targets as a copy of $(OutputPath). Since
        we've reset $(OutputPath), we need to reset $(OutDir) as well for backwards compatibility.
      -->
      <Output TaskParameter="OutputPath" PropertyName="OutDir" />
    </GetOutputPath>


    <PropertyGroup>
      <!--
        PublishDir is set in Microsoft.Common.CurrentVersion.targets and starts with $(OutputPath).
        Since we've reset $(OutputPath), we need to reset $(PublishDir).  We have to do this in a
        <PropertyGroup /> because the <Output /> from <GetOutputPath /> can't append text.
      -->
      <PublishDir>$(OutputPath)app.publish\</PublishDir>
      <!--
        If <OutputPath /> is not set in a top-level <PropertyGroup /> then $(_InvalidConfigurationError)
        is set to 'true' which will cause a build error.  However, we've set $(OutputPath) in a task 
        just before _CheckForInvalidConfigurationAndPlatform ran so we can turn off the error now that
        we know everything is set properly. 
      -->
      <_InvalidConfigurationError>false</_InvalidConfigurationError>
      <_InvalidConfigurationWarning>false</_InvalidConfigurationWarning>
    </PropertyGroup>

    <Message Text="After:" />
    <Message Text=" OutputPath: '$(OutputPath)'" />
    <Message Text=" OutDir: '$(OutDir)'" />
    <Message Text=" PublishDir: '$(PublishDir)'" />
    <Message Text=" _InvalidConfigurationError: '$(_InvalidConfigurationError)'" />
    <Message Text=" _InvalidConfigurationWarning: '$(_InvalidConfigurationWarning)'" />

  </Target>
</Project>

@jeffkl said:

Which is imported here: https://github.com/jeffkl/MSBuild-NetCore/blob/master/src/OutputPathViaInlineTask/OutputPathViaInlineTask.csproj#L24

<?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')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{C76AA0AA-3B50-48D6-BE46-0F2D8DC56C02}</ProjectGuid>
    <OutputType>Exe</OutputType>
    <RootNamespace>OutputPathViaInlineTask</RootNamespace>
    <AssemblyName>OutputPathViaInlineTask</AssemblyName>
    <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "/>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "/>
  
  <ItemGroup>
    <Reference Include="System" />
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <None Include="App.config" />
  </ItemGroup>
  
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Import Project="$(MSBuildThisFileDirectory)build.targets" />
  
</Project>

Note that this solution is not limited to .NET Core (MSBuild 15.0+). I have tested with MSBuild 14.0 (VS2015) and it worked. :)


Solution 2

We can also use MSBuild Property Functions. It provides the limited / white-listed set of BCL APIs methods and requires an external environment variable to load custom assembly. Note that we cannot use System.Enviornment.SetEnvironmentVariable within our target/proj file, as it is not in the white-list (only System.Enviornment.GetEnvironmentVariable is). Hence it requires an external change in the environment prior to building the solution (or even opening the solution in VS). From command line it would be bit easier:

# cmd
set MSBUILDENABLEALLPROPERTYFUNCTIONS=1 && msbuild my.sln

# PowerShell
$env:MSBUILDENABLEALLPROPERTYFUNCTIONS=1 ; msbuild my.sln


# UnixShell
MSBUILDENABLEALLPROPERTYFUNCTIONS=1 dotnet build my.sln

See https://blogs.msdn.microsoft.com/visualstudio/2010/04/02/msbuild-property-functions/ for further details on usage, limitations and their workaround.

Community
  • 1
  • 1
vulcan raven
  • 32,612
  • 11
  • 57
  • 93
  • Solution 1 doesn't appear to work with Visual Studio 2017. Everything correctly compiles to the changed directory location. Unfortunately Visual Studio refuses to acknowledge the change and tries to launch the .exe from the previous location. As a result it can't find the .exe and fails to launch debugging or display xaml in DesignMode. – h0st1le Aug 10 '18 at 08:53