15

I am running the MSBuild task with ContinueOnError=true:

<MSBuild Projects="@(ComponentToDeploy)"
    Targets="$(DeploymentTargets)"
    Properties="$(CommonProperties);%(AdditionalProperties)"
    ContinueOnError="true" 
    Condition="%(Condition)"/>

So my build always succeeds.

Is there a way to find out if any error occurs?

I could not find any Output of the MSBuild task containing this information. The only way I know is to parse the log file for errors but it looks like a workaround for me.

(I am using MSBuild 4.0)


This is an answer to the last feedback of @Ilya.
I'm using feedback/answer because of the length and formatting restrictions of the comments.

Log is scoped to individual targets or to be more specific tasks...

This was indeed the first question arose when I was reading your comment with the suggestion to use Log.HasLoggedErrors: "Was is the scope of the Log?".
Unfortunately I was not be able to finde a proper documentation. MSND does not help much...
Why did you know it is scoped to the task?
I'm not in doubt about your statement at all! I'm just wondering if there is a proper documentation somewhere.. (I haven't been using MSBuild for years ;-)

In any case, what are you building as project?

My test projects are very simple.
MyTest.project

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

    <UsingTask AssemblyFile="$(MSBuildProjectDirectory)\MyCompany.Tools.MSBuild.Tasks.dll" TaskName="MSBuildWithHasLoggedErrors" />

    <ItemGroup>
        <MyProjects Include="CopyNotExistingFile.proj" />
    </ItemGroup>

    <Target Name="ElenasTarget">
        <MSBuildWithHasLoggedErrors Projects="@(MyProjects)" ContinueOnError="true" >
            <Output TaskParameter="HasLoggedErrors" PropertyName="BuildFailed" />
         </MSBuildWithHasLoggedErrors>

         <Message Text="BuildFailed=$(BuildFailed)" />
  </Target>
</Project>

The CopyNotExistingFile.proj just tries to copy a file that does not exist:

<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Target1" ToolsVersion="4.0">
    <Target Name="Target1"> 
         <Copy SourceFiles="C:\lalala.bum" DestinationFiles="C:\tralala.bam" />
  </Target>
</Project>

And this is my custom task MSBuildWithHasLoggedErrors

namespace MyCompany.Tools.MSBuild.Tasks
{
    public class MSBuildWithHasLoggedErrors : Microsoft.Build.Tasks.MSBuild
    {
        [Output]
        public bool HasLoggedErrors { get; private set; }

        public override bool Execute()
        {
            try
            {
                base.Execute();
                HasLoggedErrors = Log.HasLoggedErrors;
            }
            catch (Exception e)
            {
                Log.LogErrorFromException(e, true);
                return false;
            }

            return true;
        }
    }
}

If I build my MyTest.proj the HasLoggedErrorswill be set to false although an error (MSB3021) was logged(?) to the console logger:

Project "C:\Users\elena\mytest.proj" on node 1 (default targets).
Project "C:\Users\elena\mytest.proj" (1) is building "C:\Users\elena\CopyNotExistingFile.proj" (2) on node 1 (default targets).
Target1:
  Copying file from "C:\lalala.bum" to "C:\tralala.bam".
C:\Users\elena\CopyNotExistingFile.proj(5,4): error MSB3021: Unable to copy file "C:\lalala.bum" to "C:\tralala.bam". Could not find file 'C:\lalala.bum'.
Done Building Project "C:\Users\elena\CopyNotExistingFile.proj" (default targets) -- FAILED.
ElenasTarget:
  BuildFailed=False
Done Building Project "C:\Users\elena\mytest.proj" (default targets).

Build succeeded.

My expectation was HasLoggedErrors would be set to true.



one way is to build self but with different target, for example your DefaultTargets one launches your custom MSBuildWrapper task pointing to itself (ie $(MSBuildProjectFile)) but with a different target that does other builds, copies

I've already tried it (that were my investigations I meant in my post). Unfortunately it doesn't work either :-(
(I am aware you said in theory). My new single project looks like this:

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

    <UsingTask AssemblyFile="$(MSBuildProjectDirectory)\MyCompany.Tools.MSBuild.Tasks.dll" TaskName="MSBuildWithHasLoggedErrors" />

    <Target Name="ElenasTarget">
        <MSBuildWithHasLoggedErrors Projects="$(MSBuildProjectFile)" Targets="CopyNotExistingFile" ContinueOnError="true" >
            <Output TaskParameter="HasLoggedErrors" PropertyName="BuildFailed" />
         </MSBuildWithHasLoggedErrors>

         <Message Text="BuildFailed=$(BuildFailed)" />
  </Target>

  <Target Name="CopyNotExistingFile" >
         <Copy SourceFiles="C:\lalala.bum" DestinationFiles="C:\tralala.bam" />
  </Target>
</Project>

If I build this project HasLoggedErrors will still be set to false.
(Furthermore, my "real" build I'm currently maintaining is much complexer containing several project files with targets... so I can't pack them all in a single project file ).

or writing custom logger and passing it through command line

That was my last hope!
My "real" build has a custom logger passed through the command line (I didn't use it for my test project for the sake of simplicity). That is actually producing the log (a XML file) I'm going to parse to find out if any errors have been logged.
BTW, I thought the console logger is a kind of "global" logger. Am I wrong?

Anyway, the custom logger does not help neither, the Log.HasLoggedErrors is still set to false.
Is there some way I am not aware of to reference a particular logger (e.g. my custom logger) to ask if it has logged any errors?

It really looks like Log is scoped to individual targets.

Hmm... if the reflection on the buildengine instance is the last resort I would still prefer parsing the log.
(Don't blame me! :-) )


My decision
After some investigations I've decided to stick with my initial solution: parse the log to find out if the build failed.

Check my comments to see why I prefer that to the suggestions have been provided so far.

If someone has some other ideas do not hesitate to share :-)

(Otherwise this question can be closed, I suppose...)

Elena
  • 1,928
  • 1
  • 15
  • 23

6 Answers6

22

The MSBuildLastTaskResult reserved property will be set to True if the last task succeeded and False if the last task failed:

<MSBuild Projects="@(ComponentToDeploy)"
         Targets="$(DeploymentTargets)"
         Properties="$(CommonProperties);%(AdditionalProperties)"
         ContinueOnError="true" 
         Condition="%(Condition)" />
<Message Text="MSBuild failed!" Condition="'$(MSBuildLastTaskResult)' == 'False'" />

I believe this was introduced with MSBuild v4.0.

Aaron Jensen
  • 25,861
  • 15
  • 82
  • 91
  • +1 for the hint at a new reserved property that is still missing on the MSDN site! I was not aware of it. This is the solution I was looking for, thank you! – Elena Jun 06 '12 at 08:45
  • If you look at the bottom of the MSDN Reserved Properties page, you'll see that someone from the community kindly documented several missing properties. Puzzling that the official documentation has never been updated. – Aaron Jensen Jun 06 '12 at 12:37
  • Yes, I read this comment just today and I even have the last edition of the Sayed Ibrahim Hashimi's book on my table ;-) but I was not aware of this property, thx again! – Elena Jun 06 '12 at 13:48
  • Testing with MSBuild 17.5, `$(MSBuildLastTaskResult)` is only `False` if the last task failed. This sounds trivial, but it is not regarding task batching. That is, if task batching executes the `MSBuild` task twice and only the first one fails, the whole build will fail, but the property will be set to `True`. – tm1 Mar 30 '23 at 06:25
  • On a related note, if you use `CallTarget` instead of `MSBuild` and use task batching, the whole build will succeed and the `$(MSBuildLastTaskResult)` is set to `True` when the first task fails and the last task succeeds. However, when not using task batching, the `CallTarget` task will stop at the first failing task, set the `$(MSBuildLastTaskResult)` is set to `False` and fail the build. These different behaviors can be seen as inconsistency or as powerful feature, depending on the view point. – tm1 Mar 30 '23 at 06:38
3

I know this thread is a bit old, but another possible solution, as I presume you needed to know that build failed in order to execute some "final task", is to use:

<OnError ExecuteTargets="FinalReportTarget;CleanupTarget" />

That would fail the build in case of error, but execute the "FinalReportTarget" and "CleanupTarget".

ContinueOnError="true" is not needed in this case.

Alex M
  • 2,410
  • 1
  • 24
  • 37
  • 1
    Aleksey, thank you for your suggestion but the point is that I want to use "ContinueOnError="true"". I have several MSBuild tasks in my project and I want to run them all, no matter if some of them fail. But at the end I want be able to check if some of them failed. Using your approach the build will terminate immediately when one of the tasks fails. – Elena Aug 08 '13 at 13:50
  • This might be not the most elegant solution, but you could chain OnError in each separate tag where you expect the error to happen then. Furthermore you could have some common PrintError task, something like , where in PrintError you would check MSBuildLastTaskResult. – Alex M Aug 08 '13 at 15:39
0

You could capture TargetOutputs and check them for error conditions afterwards, but that's still quite hackish.

skolima
  • 31,963
  • 27
  • 115
  • 151
  • Hmm... if there is not better way I will better create a custom task parsing my log file and counting the errors (and warnings). Makes my project file look clearer.. @skolima thank you anyway for the quick answer! – Elena May 29 '12 at 11:16
  • 4
    No need to parse the log, just inherit from Microsoft.Build.Tasks.MSBuild and expose an output that returns Log.HasLoggedErrors – Ilya Kozhevnikov May 29 '12 at 11:50
  • Well... I've implemented a simply example and it doesn't works as expected. If an error has been logged by the `MSBuild` task itself (e.g. if the project file could not be found) the `Log.HasLoggedErrors` returns `true`. But if another task inside some target (e.g. `Copy`task) raised an error the `Log.HasLoggedErrors` returns `false`. What's more MSDN says about `Log`property _"This API supports the .NET Framework infrastructure and is not intended to be used directly from your code."_. That makes me nervous... sounds like it can be removed anytime... – Elena May 30 '12 at 13:19
0

If you only want to check if MSBuild task failed, use Exec task. Set IgnoreExitCode to true and check ExitCode output value. If not zero, something is wrong.

If you need the list of build errors, use /fileloggerparameters command line switch to log errors only to some specific file:

/flp1:logfile=errors.txt;errorsonly

Ludwo
  • 6,043
  • 4
  • 32
  • 48
  • I only want to check if the `MSBuild` task failed but I don't want to use `Exec` task instead of the `MSBuild` task because of the advantages of the last. – Elena May 29 '12 at 12:47
0

But if another task inside some target (e.g. Copytask) raised an error the Log.HasLoggedErrors returns false.

Didn't know comments have length limits...

Log is scoped to individual targets or to be more specific tasks, and (as far as I'm aware) there is no way to get a "global" one, may be through reflection on the buildengine instance, or writing custom logger and passing it through command line. In any case, what are you building as project? HasLoggedErrors works as expected (and has been working unchanged for years), it shows if project being built logged any errors. It doesn't, and shouldn't, have any control over logging of other tasks (that might use other types of loggers). If you want a global one, one way is to build self but with different target, for example your DefaultTargets one launches your custom MSBuildWrapper task pointing to itself (ie $(MSBuildProjectFile)) but with a different target that does other builds, copies, etc, in theory it should simulate a global HasLoggedErrors...

Ilya Kozhevnikov
  • 10,242
  • 4
  • 40
  • 70
  • I admit I should describe my "investigations" in details. I've just done it, see my answer. Anyway, I really appreciate your suggestions and "will" to help! :-) – Elena May 31 '12 at 09:41
0

As of .NET Framework 4.5, you can use ContinueOnError="ErrorAndContinue" to let the overall build fail.

tm1
  • 1,180
  • 12
  • 28