Excel- und CSV-Arbeiten mit PowerShell automatisieren — Praxisrezepte für Auswertung, Abgleich und Berichtsausgabe

· · PowerShell, Windows, CSV, Excel, Automatisierung, Betriebseffizienz, Skript, Nutzung vorhandener Ressourcen

„Jeden Monat öffnen wir die aus dem Kernsystem heruntergeladene CSV-Datei in Excel, werten sie mit einer Pivot-Tabelle aus, richten das Layout her und verschicken sie per E-Mail.“ „Wir gleichen die von zwei Systemen erzeugten Namenslisten gegeneinander ab und suchen die Unterschiede mit bloßem Auge.“ — Diese Art von Routinearbeit wird mir oft geschildert. Selbst wenn ein Durchgang nur 30 Minuten dauert, löst sich, sobald es jede Woche, jeden Monat und über mehrere Personen hinweg passiert, im Laufe eines Jahres eine erhebliche Zeitmenge auf — und manuelles Kopieren und Einfügen mischt immer wieder Fehler hinein.

PowerShell passt gut zu diesem Bereich. Es ist standardmäßig in Windows enthalten (Windows PowerShell 5.1), liest CSV als „Tabelle von Objekten“ und erlaubt es, Auswertung, Abgleich und Ausgabe in einer Pipeline aneinanderzureihen. Darüber hinaus lassen sich mit dem von der Community erstellten Modul ImportExcel xlsx-Berichte auch ganz ohne Excel selbst erzeugen.

Gleichzeitig hat dieser Bereich Stolperfallen, die speziell für japanischsprachige Umgebungen gelten. Die Standard-Zeichenkodierungen unterscheiden sich zwischen Windows PowerShell 5.1 und PowerShell 7 vollständig, weshalb „es hat auf meinem PC funktioniert, ist aber woanders zerhackt herausgekommen“ so häufig vorkommt. Dieser Artikel ordnet die Praxisrezepte zur Automatisierung von CSV- und Excel-Arbeiten mit PowerShell — für IT-Mitarbeiter und Fachanwender in kleinen und mittleren Unternehmen — beginnend mit den Kodierungsfallen.

1. Das Wichtigste zuerst

  • Geben Sie beim Lesen und Schreiben von CSV stets -Encoding explizit an. Windows PowerShell 5.1 hat je Cmdlet unterschiedliche Standardkodierungen, während PowerShell 7 einheitlich UTF-8 ohne BOM verwendet — die Standardwerte der beiden unterscheiden sich also vollständig.1
  • Der Standard von Export-Csv in 5.1 ist ASCII. Vergessen Sie -Encoding, geht japanischer Text bereits beim Speichern verloren. Zudem interpretiert Import-Csv in 5.1 eine Datei ohne BOM als UTF-8, sodass das direkte Lesen einer Shift_JIS-CSV Datensalat erzeugt.1
  • Die Grundform der Auswertung ist die Kombination aus Group-Object (Gruppierung) und Measure-Object (Summe, Durchschnitt, Maximum/Minimum). Vieles, was Sie sonst jedes Mal mit einer Excel-Pivot-Tabelle erledigen, lässt sich durch diese beiden ersetzen.23
  • Um zwei CSV-Dateien abzugleichen, verwenden Sie Compare-Object, wenn Sie nur wissen möchten, ob es Unterschiede gibt, und eine Hashtabelle, wenn Sie Spalten nachschlagen müssen (Join). Compare-Object zeigt Ihnen über den SideIndicator, auf welcher Seite eine Zeile existiert.4
  • Für das Lesen und Schreiben von xlsx ist das (von der Community erstellte) Modul ImportExcel der erste Kandidat. Eine Installation von Excel selbst ist nicht erforderlich, und es kann Tabellen, Formatierungen und Pivot-Tabellen erzeugen.5
  • Die Steuerung von Excel selbst über COM ist ein letztes Mittel. Werden Referenzen (RCWs) nicht freigegeben, bleibt EXCEL.EXE leicht bestehen,67 und Microsoft empfiehlt Office-Automatisierung in unbeaufsichtigten Umgebungen (Dienste, zeitgesteuerte Ausführung) ohnehin weder, noch unterstützt es sie.8
  • Bevor Sie ein Skript in die Aufgabenplanung einstellen, legen Sie drei Dinge explizit fest: Kodierung, Pfade und die Ausführungsumgebung. Diese drei Punkte machen den Großteil der Fälle aus, in denen ein interaktiv funktionierendes Skript bei zeitgesteuerter Ausführung bricht.18

2. Grundlagen von Import-Csv/Export-Csv — Die größte Stolperfalle ist die Zeichenkodierung

Import-Csv liest eine CSV als Tabelle, bei der „eine Zeile = ein Objekt, eine Spalte = eine Eigenschaft“ gilt. Die Kopfzeile wird zu den Spaltennamen, und alles Nachfolgende lässt sich über Eigenschaftsnamen schreiben.9 Export-Csv macht das Umgekehrte und schreibt die Spalten eines Objekts in eine CSV.10 Bis hierhin ist alles einfach. Das Problem ist die Zeichenkodierung.

Wie die offizielle Dokumentation ausdrücklich feststellt, sind die Standardkodierungen in Windows PowerShell 5.1 nicht über alle Cmdlets hinweg konsistent.1 Hier eine Tabelle des Bereichs, der in der Praxis für japanischsprachige Umgebungen relevant ist.

Vorgang Standard in Windows PowerShell 5.1 Standard in PowerShell 7
Export-Csv ASCII (japanischer Text geht verloren)1 UTF-8 ohne BOM10
Import-Csv (Datei ohne BOM) Als UTF-8 interpretiert1 UTF-8 ohne BOM
Get-Content (Datei ohne BOM) ANSI = in einer japanischsprachigen Umgebung Shift_JIS1 UTF-8 ohne BOM
Out-File und Umleitung (>) UTF-16LE (mit BOM)1 UTF-8 ohne BOM

Mit anderen Worten: In 5.1 treten sowohl „mit Get-Content ließ es sich problemlos als Shift_JIS lesen, mit Import-Csv wird es aber zerhackt“ als auch „nach Export-Csv wurde das gesamte Japanisch zu ?“ genau spezifikationsgemäß auf. PowerShell 7 ist durchgängig konsistent mit UTF-8 ohne BOM,1 doch jetzt gilt: Eine vom Kernsystem gelieferte Shift_JIS-CSV wird zerhackt, wenn Sie sie mit den Standardwerten lesen. Es gibt nur eine Schlussfolgerung: Geben Sie -Encoding sowohl beim Lesen als auch beim Schreiben explizit an.

# Die vom Kernsystem ausgegebene Shift_JIS-CSV lesen
# Windows PowerShell 5.1: Default = die ANSI-Codepage des Systems (in einer japanischsprachigen Umgebung Shift_JIS)
$orders = Import-Csv -LiteralPath 'C:\data\orders.csv' -Encoding Default

# In PowerShell 7 lässt sich dies über die Codepage-Nummer angeben (932 = Shift_JIS)
# $orders = Import-Csv -LiteralPath 'C:\data\orders.csv' -Encoding 932

# Eine Ausgabe als UTF-8 mit BOM verringert das Risiko, dass es beim Öffnen per Doppelklick in Excel zerhackt wird
# PowerShell 7: UTF8 bedeutet ohne BOM; geben Sie utf8BOM explizit an, wenn Sie ein BOM möchten
$orders | Export-Csv -LiteralPath 'C:\data\orders_out.csv' -NoTypeInformation -Encoding utf8BOM
# Windows PowerShell 5.1: Den Wert utf8BOM gibt es nicht. Die Angabe von UTF8 liefert ein BOM
# $orders | Export-Csv -LiteralPath 'C:\data\orders_out.csv' -NoTypeInformation -Encoding UTF8

Ab PowerShell 6.2 akzeptiert -Encoding außerdem Codepage-Nummern (932) und registrierte Namen, und ab 7.4 steht auch der Wert ansi zur Verfügung.1 Beachten Sie, dass -NoTypeInformation dazu dient, die von 5.1 an den Anfang gesetzte #TYPE-Zeile zu unterdrücken; ab PowerShell 6 wird sie standardmäßig nicht mehr ausgegeben, sodass der Parameter überflüssig ist (seine Angabe verursacht jedoch keinen Fehler).10 Für Skripte, die sowohl unter 5.1 als auch unter 7 laufen müssen, ist es am sichersten, ihn trotzdem anzugeben.

Die Fallstricke des CSV-Formats selbst (verschwindende führende Nullen beim Öffnen in Excel, Werte mit Kommas oder Zeilenumbrüchen, Schutz vor Injection) werden ausführlich in CSV ist nicht „nur Text“ behandelt. Die Grundlagen zu Zeichenkodierungen und Zeilenumbrüchen finden Sie unter Windows-Zeichenkodierungen und Zeilenumbrüche.

3. Auswertung — Mit Group-Object und Measure-Object das Pivot-Tabellen-Äquivalent bauen

Für Auswertungen wie „Anzahl und Summe pro Abteilung“ ist die Grundform, mit Group-Object zu gruppieren und jede Gruppe mit Measure-Object zu summieren.23

# Wir lesen eine Shift_JIS-CSV in PowerShell 7, daher Codepage 932 explizit angeben
# (-Encoding Default bedeutet in 7 UTF-8 und führt zu Zerhacktem. Bei 5.1 Default angeben)
$orders = Import-Csv -LiteralPath 'C:\data\orders.csv' -Encoding 932

# Anzahl und Summe pro Abteilung ermitteln.
# Alle Werte aus Import-Csv sind Zeichenketten; entscheidend ist daher die Umwandlung in [decimal] vor der Summierung
# (die Sum von Measure-Object -Sum ist ein Double, daher Beträge als decimal belassen und selbst aufsummieren,
#  um bei großen Summen oder Nachkommastellen keine Genauigkeit zu verlieren)
$summary = $orders | Group-Object -Property Dept | ForEach-Object {
    $total = [decimal]0
    foreach ($row in $_.Group) { $total += [decimal]$row.Amount }
    [pscustomobject]@{
        Dept  = $_.Name    # Wert des Gruppierungsschlüssels
        Count = $_.Count   # Anzahl der Zeilen
        Total = $total     # Summe
    }
}

$summary | Sort-Object -Property Total -Descending |
    Export-Csv -LiteralPath 'C:\data\summary.csv' -NoTypeInformation -Encoding UTF8

Leicht stolpert man darüber, dass jeder Wert in einer CSV eine Zeichenkette ist. Was Import-Csv zurückgibt, ist eine Sammlung von String-Eigenschaften,9 daher müssen Sie explizit nach [decimal] konvertieren, bevor Sie etwas, das Sie für eine Zahl halten, an Measure-Object -Sum übergeben. Vergessen Sie die Umwandlung, kann es in 5.1 trotzdem scheinbar funktionieren, was zu genau der Art von Zwischenfall neigt, den man erst bemerkt, nachdem sich die Stellen eines Betrags verschoben haben.

Neben -Sum kann Measure-Object gleichzeitig auch -Average, -Maximum und -Minimum liefern.3 Und Group-Object -AsHashTable liefert direkt eine Hashtabelle „Schlüssel → Array der Zeilen dieser Gruppe“, die sich auch auf den weiter unten beschriebenen Abgleich anwenden lässt.2 Wo sich diese Einzelbefehle bewähren, ist in Praktische PowerShell-Befehlsrezepte zusammengefasst.

4. Abgleich — Die Wahl zwischen Compare-Object und einem Hashtabellen-Join

4.1. Compare-Object, wenn Sie nur wissen müssen, ob es Unterschiede gibt

Der klassische Abgleich „wer ist hinzugekommen, wer ist verschwunden“ zwischen der Namenslisten-CSV von gestern und heute geht mit Compare-Object am schnellsten. Geben Sie mit -Property die Schlüsselspalte an, vergleicht es ausschließlich anhand der Werte dieser Spalte; der resultierende SideIndicator zeigt Ihnen, ob eine Zeile => (nur auf der Differenzseite vorhanden) oder <= (nur auf der Referenzseite vorhanden) ist.4

$yesterday = Import-Csv -LiteralPath '.\users_0716.csv' -Encoding UTF8
$today     = Import-Csv -LiteralPath '.\users_0717.csv' -Encoding UTF8

# Nur anhand der Personalnummer vergleichen. => ist eine Zeile nur in heute (hinzugekommen), <= ist eine Zeile nur in gestern (entfernt)
Compare-Object -ReferenceObject $yesterday -DifferenceObject $today -Property EmpNo |
    Sort-Object -Property EmpNo |
    Format-Table -Property EmpNo, SideIndicator

Zwei Dinge sind zu beachten. Erstens bleiben bei Angabe von -Property im Ergebnis nur diese Spalte und der SideIndicator übrig; möchten Sie auch andere Spalten wie den Namen der Person sehen, müssen Sie die Originaldaten anhand des Schlüssels im Ergebnis erneut nachschlagen. Zweitens bricht es mit einem Fehler ab, wenn entweder die Referenz- oder die Differenzseite $null ist (nicht null Zeilen, sondern null selbst).4 In Abläufen, bei denen eine leere Datei möglich ist, ist es der sichere Weg, das Ladeergebnis mit @() zu einem Array zu erzwingen.

4.2. Eine Hashtabelle, wenn Sie auch Spalten nachschlagen müssen (Join)

Das SQL-JOIN-Äquivalent — „zur Personalnummer in der Detail-CSV Namen und Abteilung aus der Master-CSV nachschlagen“ — wird üblicherweise so gelöst, dass man die Master-Seite in eine Hashtabelle von Schlüssel → Zeile umwandelt und dann Zeile für Zeile nachschlägt. Eine doppelte Schleife (jede Detailzeile gegen jede Masterzeile) wird bei einigen Tausend mal einigen Tausend spürbar langsam, während eine Hashtabelle auch bei zehntausenden Datensätzen in praxistauglicher Geschwindigkeit läuft.

# Die Master-Seite in eine Hashtabelle "Personalnummer -> Zeile" umwandeln
# Doppelte Schlüssel werden von der späteren Zeile überschrieben; bei möglichen Duplikaten vorab prüfen
$master = @{}
foreach ($row in (Import-Csv -LiteralPath '.\master.csv' -Encoding UTF8)) {
    $master[$row.EmpNo] = $row
}

# Jede Detailzeile der Reihe nach nachschlagen. Nicht gefundene Zeilen nicht verschlucken - in eine separate Datei auslagern
$unmatched = New-Object System.Collections.Generic.List[object]
$joined = foreach ($row in (Import-Csv -LiteralPath '.\details.csv' -Encoding UTF8)) {
    $hit = $master[$row.EmpNo]
    if ($null -eq $hit) {
        $unmatched.Add($row)
        continue
    }
    [pscustomobject]@{
        EmpNo  = $row.EmpNo
        Name   = $hit.Name
        Dept   = $hit.Dept
        Amount = $row.Amount
    }
}

$joined    | Export-Csv -LiteralPath '.\joined.csv'    -NoTypeInformation -Encoding UTF8
$unmatched | Export-Csv -LiteralPath '.\unmatched.csv' -NoTypeInformation -Encoding UTF8

Das Herzstück dieses Rezepts sind die letzten beiden Zeilen. Verwerfen Sie Zeilen, deren Schlüssel sich nicht auflösen ließ, nicht stillschweigend. Der Wert von Abgleicharbeit liegt darin, dass „ein Mensch die nicht übereinstimmenden Dinge prüfen kann“, geben Sie daher stets die nicht zugeordnete Menge aus und schreiben Sie die Anzahl ins Protokoll oder auf die Standardausgabe.

4.3. Abweichende Spaltennamen und der Umgang mit Kopfzeilen

CSV-Dateien aus der Praxis neigen dazu, bei ihren Spaltennamen zu variieren — 社員番号, 社員No, emp_no. Die Abhilfe läuft darauf hinaus, unmittelbar nach dem Laden auf interne Namen zu normalisieren. Wird alles Nachfolgende ausschließlich über interne Namen geschrieben, muss bei einer Formatänderung nur an einer einzigen Stelle — der Normalisierung — etwas angepasst werden.

# Für eine CSV ohne Kopfzeile Spaltennamen mit -Header angeben (Zeile 1 wird dann als Daten gelesen)
# Die Kodierung folgt derselben Überlegung wie in Abschnitt 2: bei Shift_JIS in 7 932 angeben, in 5.1 Default
$rows = Import-Csv -LiteralPath '.\no_header.csv' -Header 'EmpNo', 'Name', 'Dept' -Encoding 932

# Bei einer CSV mit japanischen Kopfzeilen unmittelbar nach dem Laden auf ASCII-interne Namen normalisieren
$normalized = Import-Csv -LiteralPath '.\jinji.csv' -Encoding 932 |
    Select-Object -Property @{ Name = 'EmpNo'; Expression = { $_.'社員番号' } },
                            @{ Name = 'Name';  Expression = { $_.'氏名' } },
                            @{ Name = 'Dept';  Expression = { $_.'所属部署' } }

-Header ist für Dateien ohne Kopfzeile gedacht; beachten Sie, dass bei seiner Angabe auch Zeile 1 als Daten gelesen wird.9 Ist eine Kopfzeilenzelle zudem leer, vergibt PowerShell automatisch einen vorläufigen Spaltennamen wie H1,9 weshalb Sie zuerst die Kopfzeile in Verdacht haben sollten, wenn sich eine Eigenschaft unter dem erwarteten Spaltennamen nicht abrufen lässt.

5. Mit xlsx arbeiten — ImportExcel ist der erste Kandidat, COM das letzte Mittel

5.1. Das ImportExcel-Modul — xlsx ohne Excel selbst lesen und schreiben

So wie japanische Arbeitsplätze nun einmal sind, werden zusammengefasste Ergebnisse „als Excel-Datei statt als CSV, mit eingefärbter Tabelle“ verlangt. Der erste Kandidat hierfür ist das auf der PowerShell Gallery veröffentlichte, von der Community erstellte Modul ImportExcel. Es liest und schreibt xlsx, ohne dass eine Installation von Excel selbst erforderlich ist, und kann Tabellen erzeugen, Spaltenbreiten anpassen und sogar Pivot-Tabellen erstellen.5

# Nur beim ersten Mal. Aus der PowerShell Gallery für den aktuellen Benutzer installieren
Install-Module -Name ImportExcel -Scope CurrentUser

# Eine xlsx lesen (fühlt sich genau wie Import-Csv an - die Tabelle des Blatts wird zu einem Array von Objekten)
$budget = Import-Excel -Path 'C:\data\budget.xlsx' -WorksheetName 'Budget'

# Die Auswertung aus Abschnitt 3 als xlsx mit Tabelle, automatisch angepassten Spalten und Pivot-Tabelle ausgeben
$summary | Export-Excel -Path 'C:\data\monthly-report.xlsx' `
    -WorksheetName 'Summary' -TableName 'Summary' -AutoSize `
    -IncludePivotTable -PivotRows Dept -PivotData @{ Total = 'Sum' }

Da es von der Community erstellt wird, prüfen Sie bei der Einführung die Regeln Ihrer Organisation zur Softwareeinführung (das Modul wird aus der PowerShell Gallery bezogen5). Dennoch ist es im Vergleich zu einer Konstruktion „Excel auf dem Server installieren und über COM steuern“ in jeder Hinsicht die solidere Wahl: Lizenzierung, Stabilität und Wartung. Ein Vergleich der Ansätze zur Berichtserstellung (COM/Open XML/Vorlagen) findet sich in Wie man Excel-Berichtsausgaben erstellt.

5.2. Excel-COM-Automatisierung — Wenn Sie sie nutzen, schreiben Sie die Freigabe bis zum Ende durch, und führen Sie sie nie unbeaufsichtigt aus

Sie möchten ein Makro in einer bestehenden xls-Datei anstoßen; Sie benötigen die Funktionen von Excel selbst (Neuberechnung, Drucken, das Auflösen definierter Namen). Nur in solchen Fällen steuern Sie Excel selbst über COM. Aus PowerShell heraus lässt es sich mit New-Object -ComObject starten, doch das bekannte Problem sind zurückbleibende EXCEL.EXE-Prozesse. Werden Referenzen auf COM-Objekte (RCWs) nicht freigegeben, bleibt der Prozess auch nach dem Aufruf von Quit() bestehen.6 Die Abhilfe auf .NET-Seite ist die explizite Freigabe mit Marshal.ReleaseComObject.7

# COM ist ein letztes Mittel. "Immer schließen, was geöffnet wurde; Referenzen immer freigeben" in finally garantieren
# Alle COM-Aufrufe nach dem Start von Excel innerhalb des try ausführen (damit die Bereinigung auch läuft, wenn schon die erste Zeile eine Ausnahme auslöst)
$excel = New-Object -ComObject Excel.Application
$book = $null
try {
    $excel.Visible = $false
    $excel.DisplayAlerts = $false   # verhindert, dass Bestätigungsdialoge uns blockieren
    $book  = $excel.Workbooks.Open('C:\data\template.xlsx')
    $sheet = $book.Worksheets.Item(1)
    $sheet.Range('B2').Value2 = 12345
    $book.SaveAs('C:\data\output.xlsx')
    $book.Close($false)
}
finally {
    # Auch wenn Quit fehlschlägt (etwa weil Excel nicht reagiert), Freigabe und GC immer ausführen
    try {
        $excel.Quit()
    }
    finally {
        # Berührte COM-Objekte lassen EXCEL.EXE leicht zurück, wenn ihre RCWs nicht explizit freigegeben werden
        if ($sheet) { [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($sheet) }
        if ($book)  { [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($book) }
        [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel)
        [GC]::Collect()
        [GC]::WaitForPendingFinalizers()
    }
}

Das Tückische ist, dass allein das Aneinanderreihen von Punkten wie in $excel.Workbooks.Open(...) eine Referenz auf ein Zwischenobjekt erzeugt (in diesem Beispiel die Workbooks-Sammlung) — und auch diese muss freigegeben werden. Streng genommen hält der obige Code weiterhin eine Referenz auf Workbooks; um wirklich sicherzugehen, müssten Sie auch Zwischenobjekte in Variablen entgegennehmen und freigeben. Genau diese „Schwierigkeit, die Referenzverwaltung bis zum Ende durchzuschreiben“ ist der Grund, COM als letztes Mittel einzustufen. Der Mechanismus im Detail und die Entscheidung über einen Ersatz werden in Warum EXCEL.EXE-Prozesse nach C#-Excel-COM-Automatisierung bestehen bleiben behandelt, und COM-/.NET-Aufrufe aus PowerShell im Allgemeinen werden im gleichzeitig veröffentlichten COM und .NET aus PowerShell aufrufen behandelt.

Und es gibt noch eine weitere, entscheidende Einschränkung. Microsoft empfiehlt die Automatisierung von Office von unbeaufsichtigten, nicht interaktiven Clients aus (Dienste, zeitgesteuerte Ausführung usw.) nicht, und unterstützt sie auch nicht. Office ist unter der Annahme entworfen, dass ein interaktiver Benutzer vorhanden ist, und die Dokumentation stellt ausdrücklich fest, dass es sich in unbeaufsichtigten Umgebungen instabil verhalten oder blockieren kann.8 Auch die SSIS-Dokumentation empfiehlt, Excel-Verbindungen in unbeaufsichtigten Ausführungsumgebungen durch CSV- oder Open-XML-basierte Ansätze zu ersetzen.11 Eine Konstruktion wie „jeden Abend über die Aufgabenplanung Excel öffnen und den Bericht erstellen“ ist ein Kartenhaus, selbst wenn es funktionierend erscheint. Verlagern Sie alles, was zeitgesteuert laufen soll, auf ImportExcel oder einen Open-XML-basierten Ansatz.

6. Checkliste vor dem Einstellen in die Aufgabenplanung

Stellen Sie ein Skript, das auf Ihrem Rechner funktioniert hat, direkt in die Aufgabenplanung ein, bricht es meist auf eine der folgenden Arten.

  • Zeichenkodierung: Das Verhalten unterscheidet sich nicht zwischen interaktiver und zeitgesteuerter Ausführung, aber ein häufiger Unfall ist ein Skript, das „auf dem eigenen Rechner zufällig unter 7 funktioniert hat“ und zerhackt wird, weil der Startbefehl der Aufgabe powershell.exe (= 5.1) war und sich dadurch die Standardkodierung geändert hat. Geben Sie -Encoding wie in Abschnitt 2 sowohl beim Lesen als auch beim Schreiben explizit an, ist das Ergebnis gleich, egal wer es startet.1
  • Pfade: Verwenden Sie keine relativen Pfade, die vom aktuellen Verzeichnis abhängen; schreiben Sie absolute Pfade oder Pfade relativ zu $PSScriptRoot. Netzlaufwerke (Z: und Ähnliches) sind in einer zeitgesteuerten Sitzung nicht sichtbar, verwenden Sie daher UNC-Pfade.
  • Ausführungsumgebung: Geben Sie im Startbefehl explizit an, welches PowerShell Sie ausführen (powershell.exe oder pwsh.exe). Zu den Unterschieden zwischen 5.1 und 7 und der Wahl zwischen beiden siehe das gleichzeitig veröffentlichte Die Unterschiede zwischen Windows PowerShell 5.1 und PowerShell 7.
  • Stellen Sie nichts, was Excel-COM betrifft, hinein: Wie im vorherigen Abschnitt ist unbeaufsichtigte Ausführung nicht unterstützt.8

Wie sich Protokolle und Nachweise bei der Ausführung über die Aufgabenplanung führen lassen, ist ausführlich in PowerShell-Skripte in der Praxis ── Protokollauswertung, Archivierung und Reporting sicher automatisieren beschrieben.

7. Praktische Faustregeln (Entscheidungstabelle)

Fragestellung Optionen Entscheidungshilfe
Zeichenkodierung der CSV Dem Standard überlassen / -Encoding angeben Immer angeben. Da sich die Standardwerte zwischen 5.1 und 7 unterscheiden, wird „es zerhackt beim Umgebungswechsel“ strukturell verhindert1
Auswertung Handarbeit in Excel / Group-Object + Measure-Object Bei wöchentlicher oder monatlicher Wiederholung skripten. Der Ablauf bleibt als Code erhalten und wird reproduzierbar23
Abgleich zweier CSVs Compare-Object / Hashtabellen-Join Compare-Object, wenn Sie nur wissen müssen, ob es Unterschiede gibt. Eine Hashtabelle, wenn Sie andere Spalten nachschlagen müssen. Nicht übereinstimmende Zeilen immer separat ausgeben4
xlsx-Ausgabe Sich mit CSV begnügen / ImportExcel / COM Bei Bedarf an Formatierung oder Pivots ImportExcel (Excel selbst nicht erforderlich). COM nur, wenn wirklich die Funktionen von Excel selbst benötigt werden, etwa das Ausführen eines Makros5
Wie Excel-COM ausgeführt wird Unbeaufsichtigt über die Aufgabenplanung / nur manuell, interaktiv Unbeaufsichtigte Office-Automatisierung wird nicht unterstützt. Alles, was unbeaufsichtigt laufen soll, auf einen Ansatz umstellen, der Excel selbst nicht benötigt8
Zeitgesteuerte Ausführung Jedes Mal von Hand ausführen / Aufgabenplanung -Encoding, absolute Pfade und welches PowerShell startet, festlegen, bevor daraus eine Aufgabe wird

8. Zusammenfassung

  • Die größte Stolperfalle bei der CSV-Automatisierung ist die Zeichenkodierung. In 5.1 variieren die Standardwerte je Cmdlet (Export-Csv ist ASCII); in 7 ist es einheitlich UTF-8 ohne BOM. Geben Sie -Encoding sowohl beim Lesen als auch beim Schreiben explizit an, verschwinden die Umgebungsunterschiede.
  • Die Grundform der Auswertung ist Group-Object + Measure-Object. Da jeder CSV-Wert eine Zeichenkette ist, vor der Auswertung in eine Zahl umwandeln.
  • Verwenden Sie für den Abgleich Compare-Object, wenn Sie nur wissen müssen, ob es Unterschiede gibt, und eine Hashtabelle, wenn Sie einen Join benötigen. Schreiben Sie nicht auflösbare Zeilen immer in eine separate Datei, damit ein Mensch sie prüfen kann.
  • Fangen Sie Abweichungen bei Spaltennamen durch Normalisierung unmittelbar nach dem Laden ab und schreiben Sie alles Nachfolgende ausschließlich über interne Namen.
  • xlsx lässt sich mit dem ImportExcel-Modul ohne Excel selbst lesen und schreiben. COM ist ein letztes Mittel für Fälle, in denen Sie den Freigabecode bis zum Ende durchschreiben können, und ist nicht für unbeaufsichtigte Ausführung gedacht.
  • Legen Sie, bevor Sie etwas in die Aufgabenplanung einstellen, die Kodierung, die Pfade und welches PowerShell startet, explizit fest.

Verwandte Artikel

Verwandte Beratungsbereiche

Die KomuraSoft LLC übernimmt das Schreiben von Automatisierungsskripten für Routinearbeiten rund um CSV und Excel, die Abkehr von Berichtsverarbeitung, die von Excel COM abhängt (Ersatz durch ImportExcel/Open XML), sowie die Untersuchung von Störungen wie „es zerhackt nur in einer bestimmten Umgebung“ oder „EXCEL.EXE bleibt bestehen“.

  1. Microsoft Learn, about_Character_Encoding. Dazu, dass die Standardkodierungen in Windows PowerShell 5.1 über die Cmdlets hinweg uneinheitlich sind (Export-Csv ist ASCII, Set-Content/Get-Content verwenden das ANSI-Default, Out-File und Umleitung verwenden UTF-16LE, und Import-Csv interpretiert eine Datei ohne BOM als UTF-8), dazu, dass ab PowerShell 6 einheitlich UTF-8 ohne BOM als Standard gilt, dass -Encoding ab 6.2 Codepage-Nummern und registrierte Namen akzeptiert, sowie zum Wert ansi in 7.4.  2 3 4 5 6 7 8 9 10 11 12

  2. Microsoft Learn, Group-Object. Dazu, dass Group-Object Objekte nach dem Wert einer angegebenen Eigenschaft gruppiert und Anzahl und Mitglieder jeder Gruppe zurückgibt, sowie dazu, dass -AsHashTable eine Hashtabelle von Schlüssel zu Gruppe liefert.  2 3 4

  3. Microsoft Learn, Measure-Object. Dazu, dass Measure-Object neben der Anzahl auch Sum, Average, Maximum und Minimum (sowie die Standardabweichung) berechnen kann.  2 3 4

  4. Microsoft Learn, Compare-Object. Dazu, dass Compare-Object zwei Objektmengen vergleicht und mit dem SideIndicator (<= / => / ==) anzeigt, auf welcher Seite ein Element ausschließlich existiert, dass -Property den Vergleich auf die angegebenen Spalten beschränkt, sowie dazu, dass ein abschließender Fehler auftritt, wenn die Referenz- oder die Differenzseite null ist.  2 3 4

  5. Microsoft Learn, An active Excel process continues to run after using a VBA macro to programmatically quit Excel. Dazu, dass der EXCEL.EXE-Prozess bestehen bleibt, wenn eine Referenz auf Excel oder dessen Member global gehalten wird, selbst nachdem die Arbeitsmappe geschlossen, Quit aufgerufen und Referenzen gelöscht wurden.  2

  6. Microsoft Learn, Marshal.ReleaseComObject(Object) Method. Dazu, dass ReleaseComObject den Referenzzähler des mit einem COM-Objekt verbundenen RCW (Runtime Callable Wrapper) verringert, zu seiner Verwendung bei der expliziten Steuerung der Lebensdauer eines COM-Objekts, sowie dazu, dass Vorsicht geboten ist, weil der Zugriff auf ein bereits freigegebenes Objekt eine Ausnahme auslöst.  2

  7. Microsoft Learn, Considerations for unattended automation of Office in the Microsoft 365 for unattended RPA environment. Dazu, dass Microsoft die Automatisierung von Office-Anwendungen von unbeaufsichtigten, nicht interaktiven Clients aus (ASP, DCOM, NT-Dienste und Ähnliches) weder empfiehlt noch unterstützt, dazu, dass Office sich in unbeaufsichtigten Umgebungen instabil verhalten oder blockieren kann, sowie dazu, dass Alternativen wie Open XML empfohlen werden.  2 3 4 5

  8. Microsoft Learn, Import-Csv. Dazu, dass Import-Csv aus einer CSV tabellenförmige benutzerdefinierte Objekte erzeugt, dass die erste Zeile als Kopfzeile interpretiert wird, dass -Header bei einer Datei ohne Kopfzeile Spaltennamen liefert, dass leere Kopfzeilenzellen vorläufige, mit H beginnende Spaltennamen erhalten, sowie dazu, dass Werte als Zeichenketten gelesen werden.  2 3 4

  9. Microsoft Learn, Export-Csv. Dazu, dass Export-Csv jede Eigenschaft eines Objekts als CSV-Spalte schreibt, dass die Standardkodierung in PowerShell 7 UTF8NoBOM ist, sowie dazu, dass ab PowerShell 6.0 die #TYPE-Zeile standardmäßig nicht mehr ausgegeben wird, wodurch NoTypeInformation implizit gilt.  2 3

  10. Microsoft Learn, Import data from Excel or export data to Excel with SQL Server Integration Services (SSIS). Dazu, dass die Verwendung der Excel-Komponenten in unbeaufsichtigten, nicht interaktiven Umgebungen nicht unterstützt wird, sowie dazu, dass für die automatisierte Verarbeitung im Produktivbetrieb Flatfiles (CSV) oder Open-XML-basierte Ansätze empfohlen werden. 

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Warum kommt es beim Lesen und Schreiben von CSV-Dateien mit PowerShell zu Zeichenkodierungsfehlern?
Weil Windows PowerShell 5.1 und PowerShell 7 unterschiedliche Standard-Zeichenkodierungen verwenden. In 5.1 unterscheiden sich die Standardwerte von Cmdlet zu Cmdlet: Export-Csv verwendet ASCII (wodurch japanischer Text zerstört wird), Import-Csv interpretiert eine Datei ohne BOM als UTF-8, und Get-Content verwendet ANSI (in einer japanischsprachigen Umgebung Shift_JIS). In 7 ist der Standard einheitlich UTF-8 ohne BOM. Die einzig sichere Maßnahme ist, -Encoding sowohl beim Lesen als auch beim Schreiben explizit anzugeben.
Wie gleiche ich zwei CSV-Dateien ab (Diff)?
Wenn Sie nur wissen möchten, ob es Unterschiede gibt, ist Compare-Object mit -Property zur Angabe der Schlüsselspalte der schnellste Weg. Der SideIndicator zeigt Ihnen, welche Zeilen hinzugekommen (=>) und welche entfernt worden sind (<=). Wenn Sie Spalten wie Name oder Abteilung aus der anderen CSV nachschlagen und verknüpfen müssen, besteht der Standardansatz darin, die Master-Seite in eine Hashtabelle von Schlüssel zu Zeile umzuwandeln und dann Zeile für Zeile nachzuschlagen; das verarbeitet auch zehntausende Datensätze zügig. Verschlucken Sie keine Zeilen, deren Schlüssel nicht gefunden wird — schreiben Sie diese in eine separate Datei, damit ein Mensch sie prüfen kann.
Brauche ich Excel selbst, um aus PowerShell heraus eine Excel-Datei (xlsx) zu erzeugen?
Nein. Mit dem von der Community erstellten Modul ImportExcel können Sie xlsx lesen und schreiben, Tabellen erzeugen, Formatierungen anwenden und sogar Pivot-Tabellen erstellen — auf einem Rechner ohne installiertes Excel. Sie installieren es aus der PowerShell Gallery mit Install-Module. Am sichersten ist es, die Steuerung von Excel selbst über COM als letztes Mittel zu behandeln, für Fälle, in denen Sie wirklich „die Funktionen von Excel selbst“ benötigen, etwa das Ausführen von Makros.
Warum bleibt EXCEL.EXE bestehen, nachdem ich Excel über COM aus PowerShell automatisiert habe?
Weil der Excel-Prozess nicht beendet wird, solange Referenzen auf COM-Objekte (RCWs) nicht freigegeben sind — selbst wenn Sie Quit aufrufen. Bei jeder Berührung einer Arbeitsmappe oder eines Zellbereichs sammeln sich Referenzen auf Zwischenobjekte an; geben Sie diese daher nach Abschluss explizit mit Marshal.ReleaseComObject frei und regen Sie die Bereinigung mit GC.Collect an. Zudem empfiehlt Microsoft die Office-Automatisierung in unbeaufsichtigten Umgebungen wie der Aufgabenplanung weder, noch unterstützt es sie; alles, was zeitgesteuert laufen soll, sollte daher auf einen Ansatz umgestellt werden, der Excel selbst nicht benötigt, etwa ImportExcel.
Sollte ich CSV-Dateien mit einer Excel-Pivot-Tabelle oder mit PowerShell auswerten?
Für eine einmalige Analyse reicht Excel aus. Wiederholen Sie dieselben Schritte jede Woche oder jeden Monat, lohnt es sich, dies mit Group-Object und Measure-Object zu skripten. Der Ablauf existiert dann als Code statt als Dokument, sodass sich dieselben Ergebnisse reproduzieren lassen, auch wenn die zuständige Person wechselt, und er lässt sich an eine zeitgesteuerte Ausführung über die Aufgabenplanung anbinden. Geben Sie die zusammengefassten Ergebnisse mit ImportExcel als xlsx aus, kann der Empfänger sie weiterhin wie eine gewohnte Excel-Datei behandeln.

Autorenprofil

Profilseite des Artikelautors.

Go Komura

Geschäftsführer von KomuraSoft LLC

Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.

Zurück zum Blog