VS-Ausgabe und Fehlerliste lesen

Der Inhalt des Build-Ausgabefensters und die Error-List lassen sich über das DTE2-Interface programmatisch auslesen. Das ist nützlich für automatisierte Build-Berichte.

Voraussetzungen

C#-Beispiel

using EnvDTE;
using EnvDTE80;

DTE2 dte2 = (DTE2)dte;

// Read build output window
OutputWindow outputWindow = dte2.ToolWindows.OutputWindow;
foreach (OutputWindowPane pane in outputWindow.OutputWindowPanes)
{
    if (pane.Name == "Build")
    {
        TextDocument textDoc = pane.TextDocument;
        TextSelection sel = textDoc.Selection;
        sel.StartOfDocument(false);
        sel.EndOfDocument(true);
        string buildOutput = sel.Text;
        Console.WriteLine(buildOutput);
        break;
    }
}

// Read error list
ErrorList errorList = dte2.ToolWindows.ErrorList;
ErrorItems errors = errorList.ErrorItems;
for (int i = 1; i <= errors.Count; i++)
{
    ErrorItem item = errors.Item(i);
    Console.WriteLine($"[{item.ErrorLevel}] {item.Description} ({item.FileName}:{item.Line})");
}

Das Fehlerlisten-Fenster zeigt Statusmeldungen für verschiedene Funktionen der Visual-Studio-Entwicklungsumgebung und damit auch der TwinCAT-Entwicklungsumgebung an. Jedes ErrorItem-Objekt hat folgende Eigenschaften:

Eigenschaft

Beschreibung

Description

Beschreibt die Fehlermeldung.

FileName

Dateiname, in dem der Fehler aufgetreten ist.

Line

Zeile, in der der Fehler aufgetreten ist.

Column

Spalte, in der der Fehler aufgetreten ist.

Project

Projekt, in dem der Fehler aufgetreten ist.

VS-Ausgabe und Fehlerliste lesen 1:

DTE2 (aus EnvDTE80) ist der modernere Typ gegenüber DTE. Der Build-Output-Pane heißt exakt "Build". Index in errors.Item(i) ist 1-basiert. Die letzten vier Eigenschaften werden nicht immer befüllt — eine Fehlermeldung wie "No license found" ist nicht an eine bestimmte Datei oder Zeile gebunden.

Fehlerliste in PowerShell auslesen

Da PowerShell COM-Objekte über Late Binding anspricht, kann der in C# verwendete direkte Cast von DTE zu DTE2 hier nicht übernommen werden. Dabei gehen Typinformationen verloren, was einen zuverlässigen Cast verhindert. Die empfohlene Lösung kompiliert die C#-Logik zur Laufzeit mit Add-Type direkt in PowerShell und führt sie aus. So wird weiterhin Early Binding gegen die Interop-Assemblies genutzt.

Voraussetzungen

PowerShell 5.x oder 7.x und TwinCAT XAE müssen installiert sein.

Problem und Lösung

Der C#-Weg in PowerShell schlägt fehl, weil PowerShell beim DTE2-Late-Binding die ErrorItems-Collection nicht korrekt auflöst:

# FUNKTIONIERT NICHT (Late Binding Fehler):
# $errors = $dte2.ToolWindows.ErrorList.ErrorItems

Lösung PowerShell 5:

# Locate the Visual Studio installation path using vswhere.exe,
# which ships with every VS installation since 2017
# only useable when VisualStudio is installed
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$installPath = & $vswhere -latest -property installationPath

# For TcXaeShell version 4026+
$installPath = 'C:\Program Files\Beckhoff\TcXaeShell'

# Locate the Microsoft.VisualStudio.Interop assembly dynamically,
# since its exact path varies depending on the VS version and edition
$interopDll = Get-ChildItem "$installPath" -Recurse -Filter "Microsoft.VisualStudio.Interop.dll" | Select-Object -First 1

# Load assemblies via Reflection instead of Add-Type -Path,
# because LoadFrom is more tolerant of missing transitive dependencies
$asmEnvDTE   = [System.Reflection.Assembly]::LoadFrom("$installPath\Common7\IDE\PublicAssemblies\EnvDTE.dll")
$asmEnvDTE80 = [System.Reflection.Assembly]::LoadFrom("$installPath\Common7\IDE\PublicAssemblies\EnvDTE80.dll")
$asmInterop  = [System.Reflection.Assembly]::LoadFrom($interopDll.FullName)

# Compile a C# helper class at runtime using the loaded assemblies as references.
# This is necessary because PowerShell uses COM late binding, which loses type information
# and prevents a direct cast from DTE to DTE2. C# uses early binding and resolves
# the cast correctly via the 'as' operator and QueryInterface under the hood.
Add-Type -ReferencedAssemblies @(
    $asmEnvDTE.Location,
    $asmEnvDTE80.Location,
    $asmInterop.Location
) -TypeDefinition @"
using System;
using System.Collections.Generic;
using EnvDTE;
using EnvDTE80;

public class ErrorListReader
{
    public static List<string[]> GetErrors(object dteObject)
    {
        var results = new List<string[]>();

        // Cast the generic COM object to DTE2 using early binding.
        // This is the key step that fails in PowerShell but works correctly in C#.
        DTE2 dte2 = dteObject as DTE2;
        if (dte2 == null) return results;

        // Access the ErrorList via ToolWindows, which is only available on DTE2
        ErrorList errorList = dte2.ToolWindows.ErrorList;
        ErrorItems items = errorList.ErrorItems;

        // Note: COM collections are 1-indexed, not 0-indexed
        for (int i = 1; i <= items.Count; i++)
        {
            ErrorItem item = items.Item(i);
            results.Add(new string[] {
                item.ErrorLevel.ToString(),  // 1 = Error, 2 = Warning, 3 = Message
                item.Description,
                item.FileName,
                item.Line.ToString(),
                item.Project
            });
        }
        return results;
    }
}
"@

# Get a reference to the running TcXaeShell instance via the Running Object Table (ROT)
$dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("TcXaeShell.DTE.17.0")

# Call the C# method, passing the raw COM object
$errors = [ErrorListReader]::GetErrors($dte)

# Format and display the results as a PowerShell table
if ($errors.Count -eq 0) {
    Write-Host "No errors found."
} else {
    $errors | ForEach-Object {
        [PSCustomObject]@{
            Level       = $_[0]
            Description = $_[1]
            File        = $_[2]
            Line        = $_[3]
            Project     = $_[4]
        }
    } | Format-Table -AutoSize
}
VS-Ausgabe und Fehlerliste lesen 2:

Das Late-Binding-Problem betrifft nur das direkte Auslesen der ErrorItems-Collection über PowerShell. In C# funktioniert dte2.ToolWindows.ErrorList.ErrorItems problemlos.

Fehlerliste leeren

Die Fehlerliste kann über den DTE-ExecuteCommand-Mechanismus automatisch geleert werden:
C#:

dte.ExecuteCommand("OtherContextMenus.ErrorList.Clear");

PowerShell:

$dte.ExecuteCommand("OtherContextMenus.ErrorList.Clear")