Visual Studio - Post Build Event - Throwing Errors Visual Studio - Post Build Event - Throwing Errors powershell powershell

Visual Studio - Post Build Event - Throwing Errors


To create an error, warning, or message that will appear in the Error List window, simply log a message to stdout or stderr in the following format from a script initiated by a pre- or post-build event. Please comment or edit if there is an official spec; this is only what I was able to deduce by trial-and-error and by watching the output of MSBuild. Square brackets denote "optional":

FilePath[(LineNumber[,ColumnNumber])]: MessageType[ MessageCode]: Description

As an example:

E:\Projects\SampleProject\Program.cs(18,5): error CS1519: Invalid token '}' in class, struct, or interface member declaration

For more examples, see the Output window for any error/warning/message that may occur when you run a build in Visual Studio.


Here are two approaches for interrupting the build when a PowerShell script errors.

Use exit() to terminate the PowerShell process

To return a status code from the script which, if non-zero, will show up in the error list, use the following:

exit(45) # or any other non-zero number, for that matter.

This doesn't precisely get the error text onto your error list, but it does terminate the script and get a little something in your error list to indicate which pre- or post-build command failed.

Use a custom MSBuild task to execute the PowerShell script

I spent a little time working out the details of executing a PowerShell script within an MSBuild task. I have a full article with sample code on my blog. Do check it out, as it includes more discussion, and some explanation of how to handle the sample projects. It's probably not a complete or perfect solution, but I got it Working on My MachineTM with a very simple script.

This approach provides line and column precision when reporting PowerShell errors, and even supports the double-click-takes-me-to-the-file behavior we're accustomed to in Visual Studio. If it's lacking, I'm sure you'll be able to extend it to meet your needs. Also, depending on your version of Visual Studio, you may need to massage details like assembly reference versions.

First off, build a custom MSBuild Task in a class library project. The library should reference the following assemblies for MSBuild and PowerShell integration. (Note that this example requires PowerShell 2.0.)

  • Microsoft.Build.Framework (GAC)
  • Microsoft.Build.Utilities.v3.5 (GAC)
  • System.Management.Automation (from C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0)

Build a task class, and expose a property to specify the path to the PowerShell script, like so:

using System.IO;using System.Management.Automation;using System.Management.Automation.Runspaces;using Microsoft.Build.Framework;using Microsoft.Build.Utilities;public class PsBuildTask : Task{    [Required]    public string ScriptPath { get; set; }    public override bool Execute()    {        // ...    }}

Within the Execute() method, start the PowerShell run time, execute the script, and collect errors. Use the Log property to log the errors. When finished, close the runspace and return true if the script logged no errors.

// create Powershell runspaceRunspace runspace = RunspaceFactory.CreateRunspace();runspace.Open();// create a pipeline and feed it the script textPipeline pipeline = runspace.CreatePipeline();pipeline.Commands.AddScript(". " + ScriptPath);// execute the script and extract errorspipeline.Invoke();var errors = pipeline.Error;// log an MSBuild error for each error.foreach (PSObject error in errors.Read(errors.Count)){    var invocationInfo = ((ErrorRecord)(error.BaseObject)).InvocationInfo;    Log.LogError(        "Script",        string.Empty,        string.Empty,        new FileInfo(ScriptPath).FullName,        invocationInfo.ScriptLineNumber,        invocationInfo.OffsetInLine,        0,        0,        error.ToString());}// close the runspacerunspace.Close();return !Log.HasLoggedErrors;

And that's it. With this assembly in hand, we can configure another project to consume the MSBuild task.

Consider, for example, a C#-based class library project (.csproj). Integrating the task in a post build event requires just a few things.

First, register the task just inside the <Project> node of the .csproj file like so:

<UsingTask TaskName="PsBuildTask"           AssemblyFile="..\Noc.PsBuild\bin\Debug\Noc.PsBuild.dll" />

TaskName should be the name of the task class, though it would seem the namespace is not required. AssemblyFile is an absolute path to the custom MSBuild task assembly, or relative path with respect to the .csproj file. For assemblies in the GAC, you can use the AssemblyName attribute instead.

Once registered, the task can be used within pre- and post-build events. Configure a build event within the <Project> element of the .csproj file like so:

<Target Name="AfterBuild">    <PsBuildTask ScriptPath=".\script.ps1" /></Target>

And that's it. When Visual Studio compiles the project, it loads the custom assembly and task object and executes the task. Errors raised by the pipeline are retrieved and reported.

Screenshot