Powershell und CSV-Datei

CSV-Dateien sind ein einfaches handhabbares Format für Listen und flache Datenbanken. Wenn man etwas bezüglich Zeichensatz, Trennzeichen und Feldeinfassungen aufpasst, kann man mit CSV-Datei wunderbar z.B. Kontakte, Mailboxlisten und andere Daten vorhalten. Für gewöhnlich nutze ich Hashtabellen, vor allem wenn es einen primären Schlüssel gibt, nach dem ich regelmäßig dann auf die Daten zugreifen muss wie z.B. Absoluter Dateiname, SMTP-Adresse oder Distinguishedname.

Manchmal möchte ich aber auch Daten speichern, die aus mehreren Spalten bestehen und ich möchte eine Zeile finden, in der bestimmte Spalten einen bestimmten Wert haben. In der Vergangenheit habe ich dann einfach die Spalten aneinander gehängt und als Schlüssel für eine Hashtabelle genutzt. Schön sah das im Code nicht aus und ein Export in eine CSV-Datei war auch nicht einfach.

CSV-Basics

Powershell liefert gleich mehrere Commandlets zum Einlesen, Schreiben und Konvertieren mit, wie ein Get-Help schnell zeigt.

PS C:\> get-help *csv

Name                              Category  Module                    Synopsis
----                              --------  ------                    --------
epcsv                             Alias                               Export-Csv
ipcsv                             Alias                               Import-Csv
Export-Csv                        Cmdlet    Microsoft.PowerShell.U... Export-Csv...
Import-Csv                        Cmdlet    Microsoft.PowerShell.U... Import-Csv...
ConvertTo-Csv                     Cmdlet    Microsoft.PowerShell.U... ConvertTo-Csv...
ConvertFrom-Csv                   Cmdlet    Microsoft.PowerShell.U... ConvertFrom-Csv...

Damit ist schnell erklärt, wie man eine CSV-Datei einliest und wieder schreibt. Schauen Sie sich auf jeden Fall auch die Parameter der Comandlets an, denn einige machen die Funktionen sehr viel leistungsfähiger.

So z.B. die Angabe des Header beim "Import-CVS". Damit ist das Commandlet nicht darauf angewiesen, den Header in der ersten Zeile selbst zu erkennen. Das klappt kaum, wenn sie z.B. die Logfiles eines IIS auswerten und am Anfang die Kommentare sind.

Import-Csv `
   -path .\u_ex110915.log `
   -Delimiter " "`
   -header "date","time","s-ip","cs-method","cs-uri-stem","cs-uri-query",`
        "s-port","cs-username","c-ip","cs(User-Agent)","sc-status","sc-substatus","sc-win32-status","time-taken"

Wie flexibel die IIS Abfragen sind zeigt das folgende Beispiel, welches alle Fehlgeschlagenen EWS-Anfragen von authentifizierten Benutzern anzeigt

Import-Csv `
   -path .\u_ex130226.log `
   -Delimiter " "`
   -header "date","time","s-ip","cs-method","cs-uri-stem","cs-uri-query",`
        "s-port","cs-username","c-ip","cs(User-Agent)","sc-status","sc-substatus","sc-win32-status","time-taken" `
| where { ($_."cs-uri-stem" -eq "/ews/exchange.asmx") `
     -and ($_."cs-username" -ne "-") `
     -and ($_."sc-status" -match "[45]..")`
   }

Hängt man noch "| group cs-username | select count,name" dran, dann kann man schön sehen, wie oft welcher Benutzer das Problem hat. Sollte ein Benutzer gar nicht auftauchen, dann kann es an einer fehlerhaften Authentifizierung liegen (Proxy, Kerberos Ticket Size)

CSV mit ANSI

Per Default kann Import-CSV leider nur UNICODE-Dateien einlesen. Wer aber mit CSVDE und anderen Tools arbeitet, bekommt sehr oft nur ANSI oder gar ASCII-Dateien geliefert. Solch eine Unstimmigkeit erkennen Sie daran, dass z. B. Umlaute mit einem "?" gekennzeichnet werden. Aber auch hier gibt es recht einfache Hilfe ohne die Datei zu verändern oder temporär anderswo speichern zu müssen. Ein kleiner Umweg über Get-Content hilft

Get-Content csvdatei.csv `
    -Encoding:string `
| convertfrom-csv `
    -delimiter ","

# Erste Zeile entfernen und Header manuell vorgeben
Get-Content csvdatei.csv `
    -Encoding:string `
| select `
    -skip 1 `
| convertfrom-csv `
    -delimiter "," `
    -header "feld1","feld2","feld3"

Und schon bekommt man die CSV-Elemente auch von ANSI-Dateien problemlos importiert und natürlich weiter verarbeitet

CSV selbst erstellen

Natürlich können Sie eine leere CSV-Datei einfach mit "OUT-FILE" erstellen. In der Regel haben Sie aber ja irgendwie ein Datenmodell in Form eines "CustomObjekt", z.B. in der folgenden Form:

# Custom Objekt anlegen
$newrow = New-Object PSObject -Property @{
   Name = "Carius"
   Vorname = "Frank"
   Alter = 99
}

# Export nach CSV
$newrow | Export-Csv .\test.csv -notypeinformation

# Anzeige
PS C:\> type test.csv
"Vorname","Name","Alter"
"Frank","Carius","99"

Damit ist die CSV-Datei natürlich gleich schöner erstellt. Sie können aber auch eine Liste anlegen und diese Objekte einfach anhängen

$csvlist =@()
1..5 | %{
   $newrow = New-Object PSObject -Property @{
      Name = ("nachname"+$_)
      Vorname = ("Vorname"+$_)
      Alter = $_
   }
   $csvlist += $newrow
}

$csvlist | ft -AutoSize

Vorname Name Alter
------- ---- -----
Vorname1 nachname1 1
Vorname2 nachname2 2
Vorname3 nachname3 3
Vorname4 nachname4 4
Vorname5 nachname5 5

$csvlist | export-csv -path ".\peronen.csv" - notypeinformation

Das war noch gar nicht schwer

Sortieren mit Sort-Object

Powershell kennt das Commandlet Sort-Object, zu dem es auch den Alias "sort" gibt. So können Sie eine Liste schnell nach einem Feld sortieren

#Sortierte Ausgabe auf den Bildschirm
$csvlist | sort nachname

# Sortierte Ausgabe in einer anderen Variablen speichern 
$sortlist = ($csvlist | sort nachname)

CSV als Datenbank nutzen

Interessant wird die CSV-Datei in einer Variable aber, wenn Sie ihre Eignung als Datenbank im Speicher betrachten. Sie können in der CSV-Datei nämlich auch relativ einfach mit "where" suchen.

$csvlist | where {$_.Name -eq "User2"}

Interessant wird das ganze aber, wenn Sie das Ergebnis einer Variablen zuweisen und diese ändern.

$ergebnis = $csvlist | where {$_.Name -eq "nachname4"}
$ergebnis[0]

Beachten Sie aber, dass das Ergebnis nicht immer nur genau eine Zeile sein muss, sondern auch mehrere Treffer haben kann. Aber was passiert, wenn Sie nun den Inhalt von "$ergebnis" verändern ?

# Wert einer Zeile aender
$ergebnis[0].Alter ="100"

# geaenderte Zeile ausgeben
$ergebnis

# komplette Tabelle ausgeben
$csvliste

Überrascht ?. die Suche in der CSV-Liste mit Where liefert als Ergebnis keine Kopie der Inhalte, sondern nur einen Pointer auf die gleiche Speicherstelle (ByRef statt ByVal). Eine Änderung des Ergebnis verändert auch die originale Quelle. Das ist in diesem Fall gut, denn es spart Speicher und wir haben einen einfachen Weg, eine durch die Suche ermittelten Teilmenge zu verändern. Damit haben wir in etwa das nachgebildet, was in einer echten Datenbank ein "Select" ist

Natürlich ist das hier bei weitem nicht so flexibel wie eine echte Datenbanktabelle. So gibt es kein Index, keine Typisierung der Felder, keine Prüfung der Eindeutigkeit etc., aber für kleine Datenbestände ist dies durchaus nutzbar. Allerdings müssen Sie sich immer daran erinnern, dass die Feldinhalte selbst "Strings" sein können. Wer also damit rechnen will, muss Typkonvertierungen vorsehen.

# falsch da die 1 einfach angehängt wird. Aus 4 wird 41
$ergebnis[0].Alter +=1

# besser. Hier wird aus der 4 eine [int] und mit dem +1 eine 5
[int]($ergebnis[0].Alter) +=1

Weitere Links

Keywords:Powershell CSV