Home

A logger for MSBuild that sends event data over anonymous or named pipes.

NuGet

MyGet

GitHub

Donations

Buy Me A Coffee


Say what?

As a general purpose build tool, MSBuild is really powerful. However, it can be hard to figure out what's going on under the hood. Thankfully, MSBuild provides a nice logging API that consists of sending a sequence of events to one or more attached loggers. This allows the logger to track (almost) everything that happens during the build.

This project is heavily based on the amazing work done by @KirillOsenkov on MSBuildStructuredLog, which is a custom logger (and log viewer application) that serializes every logging event to disk for post-build viewing, analysis, and playback. While that logger (which is now built into MSBuild itself by the way) serializes logging events to disk, this logger serializes them across either a named or anonymous pipe to a server that receives the serialized logging events, deserializes them, and issues callbacks using the MSBuild logging API for each event received. It uses the same serialization format as MSBuildStructuredLog and lets you write code that responds to MSBuild logging events in much the same way you would if you were writing a custom logger, except your code can respond to events from another process or even another system.

Why would I want to do that?

I wrote this to address a need I had in Buildalyzer to access MSBuild properties from out-of-process MSBuild instances. In that use case, you can use MsBuildPipeLogger with a forked MSBuild instance to send logging events back to your original process where they can be operated on. I'm sure there are other use cases such as remote log aggregation. Drop me a line if you find this logger helpful and let me know what you're up to.

How do I use it?

There are two libraries:

Usage consists of creating a server to receive logging events and then telling MSBuild to use the MsBuildPipeLogger. It's slightly different depending on if you want to use an anonymous pipe or a named pipe.

Anonymous pipe

// Create the server
AnonymousPipeLoggerServer server = new AnonymousPipeLoggerServer();

// Get the pipe handle
string pipeHandle = server.GetClientHandle();

// Register an event handler
server.AnyEventRaised += (s, e) => Console.WriteLine(e.Message);

// Run the MSBuild process, passing it the logger and pipe handle
// Note you can also call MSBuild straight from the CLI, you just need to know the pipe handle to pass it to the logger
Process process = new Process();
process.StartInfo.FileName = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin";
process.StartInfo.Arguments = $"/noconlog /logger:MsBuildPipeLogger,\"C:\Path\To\MsBuildPipeLogger.Logger.dll\";{pipeHandle}";
// ...other process settings like working directory
process.Start();

// Wait for the process to exit
while (!process.HasExited)
{
    // Read a single logger event (which will trigger the handler above)
    server.Read();
}
process.Close();

Named pipe

// Create the server with a pipe name
NamedPipeLoggerServer server = new NamedPipeLoggerServer("Mario");

// Register an event handler
server.AnyEventRaised += (s, e) => Console.WriteLine(e.Message);

// Run the MSBuild process, passing it the logger, pipe name, and optionally the server
// Note you can also call MSBuild straight from the CLI, you just need to know the pipe handle to pass it to the logger
Process process = new Process();
process.StartInfo.FileName = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin";
process.StartInfo.Arguments = $"/noconlog /logger:MsBuildPipeLogger.Logger,\"C:\Path\To\MsBuildPipeLogger.Logger.dll\";name=Mario;server=MyServerName";
// ...other process settings like working directory
process.Start();

// Wait for the process to exit
while (!process.HasExited)
{
    // Read a single logger event (which will trigger the handler above)
    server.Read();
}
process.Close();

Logger parameters

The syntax for specifying a logger to MSBuild is:

[LoggerClass,]LoggerAssembly[;LoggerParameters]

The MsBuildPipeLogger.Logger recognizes these parameters, separated by a ; and distinguished as a name and value by =:

  • A single string is interpreted as an anonymous pipe handle: MsBuildPipeLogger.Logger,MsBuildPipeLogger.Logger.dll;1234
  • handle indicates the anonymous pipe handle to connect to: MsBuildPipeLogger.Logger,MsBuildPipeLogger.Logger.dll;handle=1234
  • name indicates the named pipe name: MsBuildPipeLogger.Logger,MsBuildPipeLogger.Logger.dll;name=Mario
  • server indicates the named pipe server (assumed to be a local pipe if omitted): MsBuildPipeLogger.Logger,MsBuildPipeLogger.Logger.dll;name=Mario;server=MyServerName

A note on concurrency

The AnonymousPipeLoggerServer.Read() and NamedPipeLoggerServer.Read() methods both block while waiting for events. If you need to support concurrency or cancellation, pass a CancellationToken to the server constructors and then cancel it during read operations.