Microsoft stellt mit den InterOp Assemblies für Office Applikationen Klassenbibliotheken zur Automation von Excel Prozessen zur Verfügung. Diese werden in der Regel dazu benutzt Excel Dateien zu lesen oder zu erzeugen.
Da es sich bei Excel jedoch nicht um eine Server Applikation für Hintergrundanwendungen handelt sind bei dem Einsatz dort einige Fallstricke zu umschiffen.
In Folge werden wir einen kleinen Windows Service entwickeln, der in der Lage ist en Verzeichnis zu überwachen, Excel Dateien dort zu lesen, zu modifizieren und sie in ein Ausgabe Verzeichnis zu kopieren. Hierzu müssen Visual Studio, die Microsoft Interop Assemblies als auch Microsoft Excel auf dem Entwicklungssystem installiert sein.
Erstellung des Windows Service Projektes
Wir benutzen dafür die entsprechende Projektvorlage unter Visual C# / Windows
Zusätzlich benötigen wir die Referenz auf die entsprechende Excel Interop Assembly: Microsoft.Office.Interop.Excel (14.0.0.0).
Nachdem Sie die automatisch generierte Service Komponente umbenannt haben, benötigen wir Routinen zur Installation des Service. Diese können am einfachsten über das Kontext Menü im Komponenten Designer erstellt werden.
Der letzte Teil der Vorbereitungen ist die Erstellung eines Setup Projektes als Teil der Solution. Wir benutzen auch hier wieder die entsprechende Vorlage, und fügen die Primäre Ausgabe unseres Projektes hinzu. Und zum Abschuss fügen wir die Primäre Ausgabe auch noch einmal zu den Benutzerdefinierten Aktionen hinzu, um die weiter oben bereits generierten Installations Routinen für den Service auch während des Setups ausführen zu lassen.
Für die Deinstallation muss diese Aktion natürlich auch ausgeführt werden.
Implementierung der Logik
Nachdem wir nun alle Vorbereitungen getroffen haben können wir nun zu dem interessanterem Teil der Entwicklung kommen.
Da der Service als Hintergrund Applikation laufen soll erstellen wir einen Service Loop mittels eines Threads den wir starten und stoppen können. Wir wechseln hierzu in die Code Ansicht unserer Service Komponente, und implementieren den Rumpf dieser Schleife.
public partial class BlogExcelService : ServiceBase
{
private bool ServiceShouldRun { get; set; }
private Thread ServiceRunner { get; set; }
public BlogExcelService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
ServiceRunner = new Thread(new ThreadStart(ServiceLoop));
ServiceShouldRun = true;
ServiceRunner.Start();
}
protected override void OnStop()
{
if (ServiceRunner != null && ServiceShouldRun && ServiceRunner.IsAlive)
{
ServiceShouldRun = false;
ServiceRunner.Join(10000);
}
}
protected void ServiceLoop()
{
while (ServiceShouldRun)
{
Thread.Sleep(3000);
}
}
}
Die eigentliche Implementierung unserer wiederkehrenden Aufgaben können wir nun innerhalb der Methode ServiceLoop implementieren. D.h. als erstes selektieren wir alle Excel Dateien im Eingangsverzeichnis, und bewegen sie in ein Bearbeitungsverzeichnis. Dort werden diese Dateien geöffnet, ein neues Blatt eingefügt und nach dem Speichern in das Ausgabeverzeichnis verschoben.
protected void HandleNewFiles()
{
var inputDirectory = new DirectoryInfo(Settings.Default.InputPath);
foreach (var file in inputDirectory.GetFiles(„*.xlsx“))
{
var tempFilename = Settings.Default.TempPath + @“\“ + file.Name;
var outputFilename = Settings.Default.OutputPath + @“\“ + file.Name;
file.MoveTo(tempFilename);
var tempFile = new FileInfo(tempFilename);
EditFile(tempFile);
tempFile.MoveTo(outputFilename);
}
}
protected void EditFile (FileInfo f)
{
var excelApp = new Application() {DisplayAlerts = false, Visible = false};
var workbook = excelApp.Workbooks.Open(f.FullName);
var sheet = workbook.Sheets.Add() as Worksheet;
var cell = sheet.Range[„A1“];
sheet.Name = „ExcelService“;
cell.Value2 = „Hello World“;
workbook.Save();
workbook.Close();
excelApp.Quit();
Marshal.FinalReleaseComObject(cell);
Marshal.FinalReleaseComObject(sheet);
Marshal.FinalReleaseComObject(workbook);
Marshal.FinalReleaseComObject(excelApp);
}
Da wir uns in einem lang laufendem Prozess befinden, ist es notwendig die über Interop verbunden COM Objekte wieder frei zu geben, da diese sonst Memory Leaks oder verwaiste Excel Prozesse hinterlassen können. Dieses erreichen wir über die Aufrufe am Ende der Edit File Methode.
Zum Debuggen verfügt Visual Studio leider über keinen direkten Start Typen für unser Projekt, d.h. wir werden uns mit einem kleinem Umweg dorthin begeben müssen. Wir installieren als erstes den Service über das Kontextmenü des Setup Projektes.
Dabei müssen während der Installation auch die Zugangsdaten des Ausführenden Benutzers übergeben werden. Während der Entwicklung benutzt man entweder schon das Zielkonto oder das eigene. Die Berechtigungen dieses Benutzers werden benötigt um Verzeichnisse zugreifbar zu machen oder Datenbankverbindungen zu autorisieren.
Nachdem wir den Service über die Service Console gestartet haben verbinden wir uns mit dem Dienstprozess aus Visual Studio heraus.
Jetzt sind wir in der Lage unseren Service zu debuggen, und stoßen auf einen Fallstrick:
Sobald wir eine Excel Arbeitsmappe im Input Verzeichnis hinterlegen kommt es zu folgender Fehlermeldung:
Der Service wird zwar mit den Berechtigungen des eingegebenen Benutzerkontos ausgeführt, jedoch wird das System Profil als Umgebung geladen. Da Excel ein Benutzerapplikation ist setzt sie auch ein Desktopverzeichnis im Profil voraus, jedoch besitzen weder Windows 2008 und Windows 7 diese standardmäßig im Profil.
Erzeugen Sie je nach Plattform folgende Verzeichnisse:
32-bit |
%WinDir%\SysWOW64\config\systemprofile\Desktop |
64-bit |
%WinDir%\System32\config\systemprofile\Desktop |
Wenn wir jetzt den Service ausführen können wir fehlerfrei unsere Excel Dateien modifizieren:
Mögliche weitere Fehlerquellen können sein, dass kein Standarddrucker eingerichtet ist, oder dass der Service Benutzer keine Berechtigungen auf die verwendeten Verzeichnisse hat.
Letze Worte
.NET und Excel bieten vielseitige Möglichkeiten. Man sollte aber immer daran denken, dass Excel kein Serverprodukt ist, und somit immer damit gerechnet werden muss, das Benutzerinteraktion erforderlich sein kann. Jedoch sind die allermeisten Eigenheiten programmatisch umschiffbar.
be a good ninja .. use C#
Andreas Schwarzer
Fachbereichsleiter SharePoint und Anwendungsentwicklung
ixto GmbH
Danke! Habe damit endlich die Lösung für die Interop-Fehlermeldung nach Portieren der Applikation von 2003 auf 2008R2 gefunden!
Desktopverzeichnis im Profil etc.
Mahlzeit,
wenn der Account der vom Service benutzt werden soll – z.B. „ExcelService“ – sich einmal am entsprechenden Server interaktiv eingeloggt hat sollten die entsprechenden Verzeichnisse und die Angaben zum Environment eigentlich erzeugt worden sein.
Ist es dann immer noch so, dass der Service in einem System Profile läuft? Immerhin war der Account dann ja schon mal eingeloggt und die Verzeichnisse und das Environment sollten dann vorhanden sein.
Vielleicht wird ein default System Profile ja nur dann benutzt wenn für den Account die Verzeichnisse und das Environment noch nie erzeugt wurden.
Falls trotz einmaliger interaktiver Anmeldung und erzeugten Verzeichnissen und Environment dennoch ein System Profile benutzt wird ist das natürlich ziemlich doof. Dann müsste man ggf. die nötigen Verzeichnisse durch das Setup erzeugen lassen.
bohni
Die Anleitung hat mir nach langer Fehlersuche geholfen, vielen Dank!! Allerdings muß noch die Transferleistung erbracht werden, die Pfade bei 32 und 64 zu tauschen. Richtig ist:
32-bit
%WinDir%System32configsystemprofileDesktop
64-bit
%WinDir%SysWOW64 configsystemprofileDesktop