Built a TeamCity makensis.exe output parser

This post follows a previous post where I built a devenv.com output parser based on code from Wolfgang Kleinschmit. I've modified the code now to parse the output from a NSIS (NullSoft Scriptable Installer System) installer script compile so that TeamCity knows whether or not an installer build failed and why. I compile this program and place it in the same directory as makensis.exe. Then from TeamCity, I will use a command line build step that calls the compiled application instead of makensis.exe.

Sample command line build step custom script:

makensisbuildrunner.exe "installer.nsi"

Make sure that the build machine knows the path to makensisbuildrunner.exe (perhaps put the path to the executable in the environment path variable).

Below is the code for NSIS output parser. Just create a VB.Net console app and paste the below code into it.

Imports System.IO
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Threading

Module Program

    Private buildFailed As Boolean

    Sub Main()

        Dim pattern As String = String.Format("^[""]?({0})[""]?\s*", Environment.GetCommandLineArgs()(0).Replace("\", "\\"))
        Dim rgx As New Regex(pattern)
        Dim argumentsOnly As String = rgx.Replace(Environment.CommandLine, String.Empty)
        Dim psi As New ProcessStartInfo("makensis.exe")

        psi.Arguments = argumentsOnly
        psi.CreateNoWindow = True
        psi.ErrorDialog = False
        psi.RedirectStandardError = True
        psi.RedirectStandardOutput = True
        psi.UseShellExecute = False

        Dim P As Process = Process.Start(psi)

        Dim StdOReader As New Thread(New ParameterizedThreadStart(AddressOf ReadStd))
        StdOReader.Start(P.StandardOutput)
        Dim StdEReader As New Thread(New ParameterizedThreadStart(AddressOf ReadStd))
        StdEReader.Start(P.StandardError)

        P.WaitForExit()

        StdOReader.Join()
        StdEReader.Join()
        Console.Out.Flush()
        Environment.ExitCode = P.ExitCode
    End Sub
    Sub ReadStd(obj As Object)
        Dim R As TextReader = DirectCast(obj, TextReader)
        Dim line As String = R.ReadLine
        Do While (line IsNot Nothing)
            ProcessLine(line)
            line = R.ReadLine
        Loop
    End Sub
    Public rxMessage As New Regex("!system: returned 1", RegexOptions.Compiled)
    Public rxMessage2 As New Regex(" -> no files found.", RegexOptions.Compiled)
    Public rxMessage3 As New Regex("aborting creation process", RegexOptions.Compiled)
    Public rxMessage4 As New Regex("Can't open script ", RegexOptions.Compiled)
    Public rxMessage5 As New Regex("Invalid command: ", RegexOptions.Compiled)
    Public rxFirstLine As New Regex("Processing script file:", RegexOptions.Compiled)

    Public flows As New Dictionary(Of Integer, List(Of String))

    Sub ProcessLine(line As String)
        Dim flowID As Integer = 0
        Dim M As Match
        Dim status As String = "NORMAL"

        If String.IsNullOrWhiteSpace(line) Then
            Return
        End If
        M = rxMessage.Match(line)
        If M IsNot Match.Empty Then
            status = "ERROR"
        End If
        M = rxMessage2.Match(line)
        If M IsNot Match.Empty Then
            status = "ERROR"
        End If
        M = rxMessage3.Match(line)
        If M IsNot Match.Empty Then
            status = "FAILURE"
        End If
        M = rxMessage4.Match(line)
        If M IsNot Match.Empty Then
            status = "FAILURE"
        End If

        M = rxMessage5.Match(line)
        If M IsNot Match.Empty Then
            status = "ERROR"
        End If
        M = rxFirstLine.Match(line)
        If M IsNot Match.Empty Then
            Console.Out.WriteLine("##teamcity[progressMessage '{0}']", QuoteLine(line))
        End If

        line = QuoteLine(line)

        If (Not flows.ContainsKey(flowID)) Then
            flows(flowID) = New List(Of String)()
        End If

        Dim fmtLine As String = String.Format("##teamcity[message status='{2}' errorDetails='' text='[{0:000}|] {1}']", flowID, line, status)

        Console.Out.WriteLine(fmtLine)
        Console.Out.Flush()
        flows(flowID).Add(fmtLine)
    End Sub

    Function QuoteLine(line As String) As String
        Dim sb As New StringBuilder

        For Each c As Char In line
            If ("'|]" & Environment.NewLine).IndexOf(c) <> -1 Then
                sb.Append("|"c)
            End If
            sb.Append(c)
        Next

        line = sb.ToString()
        Return line
    End Function

End Module