Word-Automatisierung in einem Scheduled Task des Windows-Servers

Der Dotnet-Doktor  –  0 Kommentare

So löst man die Probleme beim Start der Word-Automatisierungsobjekte in einem Hintergrundprozess.

Im Rahmen unseres technischen Supports für Softwareentwickler hatte ich diese Woche die Anfrage eines Kunden, der eine .NET-Anwendung mit Windows Forms in einem Scheduled Task auf einem Windows-Server periodisch und unbeaufsichtigt laufen lassen wollte. Die Aufgabe der Windows-Forms-Anwendung war die Umwandlung von Microsoft-Word-Dokument (.docx) in PDF-Dokumente. Dafür wurde das Word-Objektmodell eingesetzt. Die Ausgaben erfolgten in einem Formular. Fehler wurden mit MessageBox.Show() ausgegeben. Die Umwandlung wurde bislang manuell angestoßen und sollte nun automatisch laufen.

In einem Scheduled Task in Windows kann man keine grafischen Benutzeroberflächen darstellen. Folglich war der erste Schritt, die Windows-Forms-Anwendung in eine Konsolenanwendung umzubauen, das heißt, alle Verweise auf GUI-Elemente durch Konsolenausgaben zu ersetzen. Nachfolgend sieht man einen zentralen Ausschnitt aus dem Code, der das Word-Objektmodell verwendet:

/// <summary>
/// Convert DOCX to PDF
/// </summary>
/// <param name="docxPath">Path to DOCX</param>
/// <param name="pdfPfath">Path to resulting PDF</param>
public void DOCXtoPDF(string docxPath, string pdfPfath)
{
Word.Application app = null;
Word.Document doc = null;
try
{
Console.WriteLine("Starting Word...");
app = new Word.Application();
// only for Debugging!. Word must be invisible when running in a background job
app.Visible = System.Diagnostics.Debugger.IsAttached;
app.ScreenUpdating = System.Diagnostics.Debugger.IsAttached;
Console.WriteLine("Loading Word Document: " + docxPath);
doc = app.Documents.Open(docxPath);
Console.WriteLine("Save as PDF: " + pdfPfath);
doc.SaveAs(pdfPfath, Word.WdSaveFormat.wdFormatPDF);
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.ToString());
}
finally
{
if (doc != null)
{
Console.WriteLine("Closing Document...");
doc.Close(SaveChanges: false);
doc = null;
}
if (app != null)
{
Console.WriteLine("Closing Word...");
app.Quit();
app = null;
Console.WriteLine("Done!");
}
}
}

Nun stellte sich heraus, dass die Anwendung zwar in folgenden Situationen lief:

  • interaktiv an der Windows-Eingabeaufforderung mit einem Benutzerkonto, das entsprechende Rechte im Dateisystem hatte.
  • mit dem gleichen Benutzerkonto im Task Scheduler, wenn die Option "Run only when user is logged on" gewählt war.

Die Word-Automatisierung brach aber ab, wenn "Run wether user is logged on or not" gewählt wurde.

Die Lösung bestand darin, auf dem Server ein leeres (!) Verzeichnis anzulegen:

  • Für ein 32-Bit-Word: C:\Windows\SysWOW64\config\systemprofile\Desktop
  • Für ein 64-Bit-Word: C:\Windows\System32\config\systemprofile\Desktop

Tatsächlich sind die Pfade hier nicht vertauscht, siehe Erklärung zu WOW64. Der Grund für die Notwendigkeit, dieses Verzeichnis anzulegen, ergibt sich daraus, dass Word eine Desktop-Anwendung ist und eigentlich nicht für die Hintergrundautomatisierung auf einem Server geeignet ist. Man sollte dafür Softwarekomponenten wie Aspose Words oder die TextControl-Server-Komponente einsetzen.

Noch ein Tipp: Um die Konsolenausgaben zur Diagnose verwenden zu können, sollte man sie in dem Scheduled Task in eine Protokolldatei umleiten. Diese folgende Einstellung funktioniert dabei nicht, weil die .exe-Anwendung selbst das Piping mit >> (Anfügen an eine Datei) nicht versteht.

Programm/Script: OfficeAutomatisierung.exe
Add arguments: >>log.txt
Start in: x:\ordnername

Richtig ist das Vorschalten der Windows-Eingabeaufforderung (siehe auch Abbildung):

Programm/Skript: cmd
Add arguments: /c .\OfficeAutomatisierung.exe >>log.txt
Start in: x:\ordnername