1. Unstrukturierte Textdateien
         1.1 Inhalt einer Textdatei in einer Variable speichern
               1.1.1 Einlesen kleiner und mittelgroßer Textdateien
                        Beispiel 1: Wie viele Zeilen hat die Testdatei  (get-content)
                        Beispiel 2: Wie viele Zeilen hat die Testdatei  ([System.IO.File])
               1.1.2 Einlesen großer Dateien
                        Beispiel 1: Wie viele Zeilen hat eine Datei ${}
                        Beispiel 2: Wie viele Zeilen hat eine Datei  (get-content -readcount 0)
                        Beispiel 3: Wie viele Zeilen hat eine Datei  (System.IO.StreamReader - ReadToEnd)
         1.2 Inhalt einer Datei zeilenweise bearbeiten
               1.2.1 Analyse einer Datei mit Select-String
                         Beispiel 1: Alle Zeilen mit einem Suchbegriff aus einer Datei filtern
                         Beispiel 2: Alle Dateien, die einen Suchbegriff enthalten, finden
                1.2.2 Zeilenweise Bearbeiten einer Datei (Streamreader - ReadLine)
                         Beispiel 1: Zeilenweises Parsen und Analysieren einer Datei
         1.3 gesamten Text auf einmal bearbeiten (StreamReader - ReadAllText)
               Beispiel 1: Ersetzen eines Suchbegriffs in der gesamten Datei
               Beispiel 2: Wie oft kommt ein Suchstring in einer Datei vor?
         1.4 Zeilenübergreifende Textanalyse
               Beispiel 1: Ausgabe der passenden Zeile, sowie der Vorgänger- und Nachfolgerzeile
               Beispiel 2a: Suchen nach Textstellen, bei denen ein Suchbegriff in zwei aufeinanderfolgenden Zeilen vorkommt
               Beispiel 2b: Suchen nach Textstellen, bei denen ein Suchbegriff in drei aufeinanderfolgenden Zeilen vorkommt
 

1 unstrukturierte Textdateien

Eine häufige Aufgabe in der IT ist die Analyse von Textdateien. Meist handelt es sich dabei um textbasierte Logs, die in Form teilweiser recht großer Textdateien vorliegen, oft auf mehrere Textdateien verteilt.
Im Gegensatz zu strukturierten Dateien wie XML oder CSV Dateien haben diese Dateien meist keine Struktur. 

Wiesooft bieten .Net und Powershell eine Reihe von Methoden an, eine Textdatei zu analysieren und gegebenenfalls zu verändern. Grundsätzlich kann man eine Textdatei
a) komplett in den Arbeitsspeicher laden und erst anschließend weitere Schritte an den eingelesenen Daten durchzuführen
b) Zeile für Zeile lesen und sofort bearbeiten. Anschließend wird die gelesene Zeile wieder verworfen

Die Methode a) bietet Vorteile, wenn eine Datei mehrfach nacheinander analysiert werden soll, oder wenn je nach dem Ergebnis der Analyse noch einmal die Vorgängerzeilen benötigt werden. Ebenso ist es sinnvoll eine Datei komplett zu laden, wenn die Datei als Ganzes untersucht werden soll.
Eine Datei komplett zu laden, benötigt erstmal Zeit und belegt Arbeitsspeicher, was bei 32-bit Betriesbssystemen und großen Dateien prinzipiell eng werden kann. Die eingeladene Datei kann dafür umfangreich analysiert werden.

Die Methode b) kann man anwenden, wenn jede Zeile einmalig und unabhängig vom übrigen Inhalt der Datei analysiert werden soll.
Eine zeilenweise Analyse ist einfacher, benötigt weniger Arbeitsspeicher und ist für viele Aufgabenstellungen vollkommen ausreichend. Eine typische Aufgabenstellung wäre alle diejenigen Zeilen, die einen bestimmten Suchbegriff enthalten, auszugegeben.

 

1.1 Inhalt einer Textdatei in einer Variable speichern

Ich stelle vier Möglichkeiten vor, den Inhalt von Textdateien unter Powershell in den Arbeitsspeicher des Rechners einzulesen. Im Endergebnis liefern alle Methoden dasselbe Ergebnis, dennoch sind manche Methoden besser für kleinere, andere für große Textdateien geeignet

Hier nur ein kurzer Überblick. In den nachfolgenden Kapiteln behandle ich die Varianten dann detailliert.

a) $Lines = Get-Content "c:\temp\file_20kb.txt"
Dieser Befehl fügt jede neu Textzeile als Element dem Array a neu hinzu. 

b) $Lines = Get-Content "c:\temp\file_20kb.txt" -readcount 10
Beschleunigen lässt sich der Einlesevorgang durch Hinzufügen eines Readcounts. Dadurch werden hier zum Beispiel 10 Textzeilen als ein Arrayelement zu a hinzugefügt.

c) $AllText = [IO.File]::ReadAllText("c:\temp\file_20kb.txt")
Die statische .Net-Methode "ReadAllText liest das gesamte Textfile komplett ein.

d) $StreamReader = New-Object System.IO.StreamReader($FilePath)
    $Lines=$StreamReader.ReadToEnd()
Die StreamReader-Klasse bietet mehr Möglichkeiten als die File-Klasse

e)  $Lines = ${"c:\temp\file_20kb.txt"}
Man erhält jede Zeile als separates Arrayelement. Das Ergebnis ist identisch zu a)
 
Mit dem Skript unter  0 Einleitung -> Beispiel 2 habe ich einige Dateien verschiedener Größe erzeugt. Diese Textdateien enthalten neben Zufallsstrings am Beginn jeder Zeile einen Zeitstempel und zufällig verteilt die beiden Strings "Error: 220" und "Error: 225".
Die Beispieldateien sollen Logdateien simulieren.

Die beiden folgenden Unterkapitel zeigen geeignete Methoden, um einerseits kleine bis mittlere, andererseits große Dateien einzulesen.

Große Textdateien definiere ich als Dateien mit einer Größe von mehr als 20 MB oder mehr als 100.000 Zeilen. Die Grenze ist willkürlich gezogen, weil auf meinem Testrechner ab etwa dieser Grenze bei einigen Methoden (Get-Content) die ersten Unterschiede auftauchen.
Die auftretenden Probleme sind erstens der Zeitbedarf zum Einlesen, sowie zweitens Speicher(-RAM)Fehler. Speicherprobleme hängen stark von der verwendeten Betriebssystemarchitektur (32-/ 64-bit) aber
Letztlich muß man selbst aus der Vielzahl der Möglichkeiten auswählen, welche Methode im konkreten Fall am schnellsten und zuverlässigsten arbeitet.

 

1.1.1 Einlesen kleiner und mittelgroßer Textdateien

 

Beispiel 1: Wie viele Zeilen hat die Testdatei  (Get-Content)

Set-StrictMode -Version "2.0"
Clear-Host

$Path = "C:\Temp\Big150.txt"
$Lines = Get-Content -path $Path
"Number of Lines: {0}" -f $(1+$Lines.Count)
#mögliche Ausgabe
Number of Lines: 105

Für kleinere und mittlere Dateien bietet das cmdlet get-content in dieser Form den größten Komfort, siehe Technet: Get-Content. Für größere Dateien seht euch die anderen Beispiele hier an.

Man erhält ein Array, bei dem jedes Arrayelement eine Textzeile enthält. 

Eine 2 MB große Textdatei war auf meinem Testrechner innerhalb 0.4 Sekunden eingelesen, eine 20 MB große Textdatei in gut 2 Sekunden.
Werden die Dateien noch größer, steigt der Zeitaufwand auch weiter und das Skript wird immer unkomfortabler. Daher sollte man bei größeren Dateien auf die im nächsten Kapitel 1.1.3 Einlesen großer Dateien gezeigten Methoden umsteigen.

Beispiel 2: Wie viele Zeilen hat die Testdatei  ([System.IO.File])

Set-StrictMode -Version "2.0"
Clear-Host

$Path = "C:\Temp\Big150.txt"
$AllText = [IO.File]::ReadAllText($Path)
$Lines = $AllText.Split("`n")
"Number of Lines: {0}" -f $($Lines.Count)
#mögliche Ausgabe
Number of Lines: 105

[IO.File]::ReadAllText($Path) liest den Inhalt der Textdatei komplett in die Variable $AllText ein ein. Möchte man die Textdatei anschließend ebenso wie in Beispiel 1a als Array bearbeiten, so kann man den Text über den Split-Operator zeilenweise aufteilen.

Eine 2 MB große Textdatei war auf meinem Testrechner innerhalb 0.2 Sekunden eingelesen imd gesplittet, eine 20 MB große Textdatei in 0.5 Sekunden, also doch deutlich schneller als Beispiel 1 
Werden die Dateien noch größer, verbraucht diese Methode viel Arbeitsspeicher. Besonders auf 32-Bit Systemen kommt es dann zu Skriptabbrüchen aufgrund eines Speicherfehlers.

Ein praktisches Beispiel in der mittleren Größenklasse ist das Logfile des Windows Update Services unter "C:\Windows\WindowsUpdate.log", welches auf den meisten Windowsrechnern existieren dürfte.

 

1.1.2 Einlesen großer Dateien

Die folgenden Beispiele habe ich an einer Textdatei mit 1.000.000 Zeilen und einer Größe von etwa 200 MB Größe getestet. 
Ob Textfiles in dieser Größenordnung sinnvoll sind, sei dahingestellt. Da solche Dateien aber vorkommen, sollte man Werkzeuge für deren Behandlung zur Verfügung haben.

Beispiel 1: Wie viele Zeilen hat eine Datei ${}

Set-StrictMode -Version "2.0"
Clear-Host
 
$Lines = ${c:\temp\file_200MB.txt}

"Inhalt der Zeile/Arrayelement 1125 {0}: " -f $Lines[1125]
"Anzahl Zeilen/ Arrayelemente {0}/ {1}" -f $(1+$Lines.Count),$Lines.Count
#mögliche Ausgabe

Inhalt der Zeile/Arrayelement 1125 22.12.2011 21:28    Xj 1  lYE9C ...2 Error: 225 NU06 WfV0zrbl  iD: 
Anzahl Zeilen/ Arrayelemente 1000002/ 1000001

Jede Textzeile ist einem Arrayelement zugeordnet. Das Einlesen der Datei erfolgt etwas schneller als im nächsten Beispiel. 
Diese Methode hat den Nachteil, dass sich der Pfad zwischen den geschweiften Klammern ${...} nicht mit einer Variablen parametrisieren lässt.

 

Beispiel 2: Wie viele Zeilen hat eine Datei  (Get-Content -ReadCount 0)

Set-StrictMode -Version "2.0"
Clear-Host


$FilePath="c:\temp\File_200MB.txt"
$Lines = Get-Content -path $FilePath -ReadCount 0

"Inhalt der Zeile/Arrayelement 1125 {0}: " -f $Lines[1125]
"Anzahl Zeilen/ Arrayelemente {0}" -f $(1+$Lines.count)

Über den Parameter -ReadCount hat man die Möglichkeit festzulegen, wieviele Textzeilen in ein Arrayelement aufgenommen werden. 

"This parameter does not change the content displayed, but it does affect the time it takes to display the content. As the value of ReadCount increases, the time it takes to return the first line increases, but the total time for the operation decreases. This can make a perceptible difference in very large items."  aus Technet: Get-Content

Mit "-ReadCount 0" wird die gesamte Datei eingelesen. Dies ergibt bei größeren Textdateien einen merklichen Geschwindigkeitsgewinn.

 

Beispiel 3: Wie viele Zeilen hat eine Datei  (System.IO.StreamReader - ReadToEnd) 

Set-StrictMode -Version "2.0"
Clear-Host

$Path = "c:\temp\Big150.txt"
$StreamReader = New-Object System.Io.StreamReader($Path)
$AllText = $StreamReader.ReadToEnd()
$Lines = $AllText.Split("`n")
"Inhalt der Zeile/Arrayelement 1125 {0}: " -f $Lines[1125]
"Anzahl Zeilen/ Arrayelemente {0}" -f $Lines.Count

Sehr schnell arbeitet die StreamReader-Klasse mit der Methode ReadToEnd().
Um den einen gesamten String wieder in einzelne Zeilen zu zerlegen, splitte ich diesen an jedem Zeilenumbruch ("`n")

MSDN: StreamReader.ReadToEnd Method
 

1.2 Inhalt einer Datei zeilenweise bearbeiten

Um eine Textdatei zeilenweise abzuarbeiten, bietet Powershell

a) eine einfache Methode mit dem cmdlet Select-String an. Mit diesem cmdlet können sowohl einfache Strings, wie auch ganze Textdateien durchsucht werden. Zurückgegeben wird der gesamte String, der den Suchstring enthält.

b) über .Net bietet mit der StreamReader-Klasse verschiedene Methoden an, um eine Textdatei zeilenweise zu bearbeiten.

 

1.2.1 Analyse einer Datei mit Select-String

Technet: Select-String

Das cmdlet Select-String ist eine einfache Möglichkeit Dateien oder Strings nach Mustern zu durchsuchen und Treffer anzuzeigen. Mit Select-String können auch reguläre Ausdrücke verwendet werden.

"Mit dem Cmdlet "Select-String" werden Text und Textmuster in Eingabezeichenfolgen und -dateien gesucht. Sie können es wie Grep in UNIX und Findstr in Windows verwenden." (aus der Onlinehilfe)
 

Beispiel 1: Alle Zeilen mit einem Suchbegriff aus einer Datei filtern

Set-StrictMode -Version "2.0"
Clear-Host

$Patterns = @("Warning","Error")
$Path = "c:\windows\WindowsUpdate.log"
$OutPath="c:\temp\errors"

Foreach($Pattern in $Patterns){
    $OutPath="c:\temp\patterns"
    $OutPath = "$OutPath\$Pattern.log"
    $PatternLines = Select-String -path $Path -pattern $Pattern | ft LineNumber, Line -auto
    $PatternLines | Out-File -filepath $OutPath -encoding Default   #Ausgabe in ein File
}

Das Skript durchsucht die Datei "WindowsUpdate.log" und erstellt pro Pattern eine eigene Datei, die die Zeilen mit dem Pattern enthält

Wenn man die Zeilen nicht mehr weiterbearbeiten möchte, ist dies ein einfacher Weg, um Informationen zu finden.
 

Beispiel 2: Alle Dateien, die einen Suchbegriff enthalten, finden

Set-StrictMode -Version "2.0"
Clear-Host

$Path = "c:\windows\*"
$Pattern = "Error"

$PatternFiles = Get-ChildItem -Path $Path -include *.log | Select-String -pattern $Pattern -list
$PatternFiles | Format-Table Filename,Linenumber -Auto
#mögliche Ausgabe

Filename                LineNumber
--------                ----------
DtcInstall.log                   1
TSSysprep.log                    7
WindowsUpdate.log              617

das Skript durchsucht im angegebenen Verzeichnis alle *.log Dateien, ob dort der Suchbegriff "Error" vorkommt. Falls ja, wird der Dateiname samt Zeilennummer ausgegeben

 

1.2.2 Zeilenweise Bearbeiten einer Datei (Streamreader.ReadLine)

Beispiel 1: Zeilenweises Parsen und Analysieren einer Datei

Set-StrictMode -Version "2.0"
Clear-Host

$Path = "c:\temp\Big150.txt"
$LineNumber = 0
$SearchString = "*ErrorID:250 T*"

$StreamReader = New-Object System.IO.StreamReader($Path)
While ($StreamReader.Endofstream -eq $False){
  $LineNumber +=1
  $Line=$StreamReader.ReadLine()
  If($Line -like $SearchString){
    "LineNumber: {0} -- {1}" -f $LineNumber,$Line
   }
}
#mögliche Ausgabe

LineNumber: 53 -- 30.01.2013 13:47  zIl LnXmyF3rqwa ErrorID:225 VgSWDisjZ Ox9e p1C MBv uJ ErrorID:250 THKU6of8d5 Pk0G
LineNumber: 92 -- 30.01.2013 13:47  fpV2 MTJ0FW ErrorID:220 kI6KrXiHau 7v8S9ewsqygNz B3ExL U ErrorID:250 thPA4QRZoj
LineNumber: 109 -- 30.01.2013 13:47 20 ZXCi6I9DMUlNeq0bht5mjk ErrorID:250 TQLJyuW8Eg3a2 ErrorID:225  Bds7H GcPKxzRpo

Anmerkung 1:
Das Beispiel parst zeilenweise "$StreamReader.ReadLine()" durch die Datei und gibt die entsprechende Zeile aus, wenn die Bedingung "($Line -like $SearchString)" erfüllt ist.

Anmerkung 2:
Der Operator -like nicht mit dem Operator -contains verwechseln. Mit contains prüft man ein Array ab, ob eines der Elemente einen bestimmten Inhalt hat. siehe Technet: about_comparison_operators

Anmerkung 3:
 Der Operator -like unterstützt einige einfache Platzhalter wie "*", "?" oder "[a-d]". Um den vollen Umfang an regulären Ausdrücken nutzen zu können,  muss man den Operator -match verwenden. (siehe 2.1 Die Operatoren -Match, -NotMatch, -CMatch, -IMatch

 

1.3 gesamten Text auf einmal bearbeiten (StreamReader - ReadAllText)

 

Beispiel 1: Ersetzen eines Suchbegriffs in der gesamten Datei

Set-StrictMode -Version "2.0"
Clear-Host

$Path="c:\temp\Big150.txt"
$OriginalString="Error: 225"
$ReplaceString="Error: 325"

$AllText = [IO.File]::ReadAllText($Path)
$AllTextNew=$AllText.Replace($OriginalString,$ReplaceString)
#$AllTextNew | Out-File -FilePath $Path #Ausgangsdatei ersetzen
$AllTextNew | Out-File -FilePath "c:\temp\NewFile.txt"

   
Beispiel 2: Wieoft kommt ein Suchstring in einer Datei vor?

MSDN: String.IndexOf-Methode (String, Int32)

Set-StrictMode -Version "2.0"
Clear-Host

$Path = "c:\temp\Big150.txt"
$Searchstring = "Error"

$Positions=@()
$Position = 0

$Alltext = [IO.File]::ReadAllText($Path)
While ($Position -ne -1){
  $Position = $AllText.IndexOf($SearchString,$Position+1)
  $Positions += $Position
}

"Anzahl der Treffer: {0}" -f $Positions.count
"Position des ersten Treffers: {0}" -f $Positions[0]
"Position des letzten Treffers: {0}" -f $Positions[$Positions.Count-2]
#mögliche Ausgabe

Anzahl der Treffer: 70
Position des ersten Treffers: 411
Position des letzten Treffers: 10676
  • Das erste Element des Arrays "ArrPosition" besitzt den Index 0. 
  • Das letzte Element des Arrays "ArrPosition", auf das mit $ArrPosition[-1] zugegriffen werden kann, besitzt den Wert "-1". "-1" bedeutet gemäß dem MSDN-Artikel, dass kein weiterer Treffer mehr gefunden wird. 
  • Die genaue Berechnung der Treffer wäre demnach $ArrPosition.Count + 1 -1.
  • Die Methode IndexOf ist casesensitive. 
  •  

1.4 zeilenübergreifende Textanalyse

Die differenziertesten Möglichkeiten eine Datei zu analysieren, bietet Powershell, wenn jede Zeile als Element eines Arrays vorliegt. Hierbei können sogar Zeilen untereinander in Beziehung gesetzt werden.

 

Beispiel 1: Ausgabe der passenden Zeile, sowie der Vorgänger- und Nachfolgerzeile

Set-StrictMode -Version "2.0"
Clear-Host

$Path = "c:\temp\Big150.txt"
$Pattern = "k[a-y]K"
$Lines = @()

$Lines = Get-Content -path $Path -readcount 0
For($i=0; $i -lt $Lines.Count; $i+=1){
   If( $Lines[$i] | Select-String -Pattern $Pattern){
     "Vorgängerzeile: {0}  Text: {1}" -f $($i-1),$Lines[$i-1]
     "Zeile mit Treffer: {0}  Text: {1}" -f $i,$Lines[$i]
     "Nachfolgezeile: {0}  Text: {1}" -f $($i+1),$Lines[$i+1]
   }
}
#mögliche Ausgabe

Vorgängerzeile: 29  Text: 25.12.2011 19:04   S11Bs5K dYsSMQIfPIMbtdYqBUP....p7Z 4 Error: 250 nxLvoQxC9n8X
Zeile: 30  Text: 25.12.2011 19:04   R..68u Wf TQXmjX  J5syQBvyFB19azNBR  JO8o RjopNn 8SKUWC 4VCoZL2
Nachfolgezeile: 31  Text: 25.12.2011 19:04tfuTEsNIfl Vi0CjxIIBwxc25XNyPoH16....vgv80nd3pAH U wo7eTCLh b

 

Beispiel 2a: Suchen nach Textstellen, bei denen ein Suchbegriff in zwei aufeinanderfolgenden Zeilen vorkommt

Set-StrictMode -Version "2.0"
Clear-Host

$Path="C:\temp"                #Die Dateien in diesem Verzeichnis wird analysiert
$FileFilter="Big150.txt"   #Hier können auch Platzhalter angegeben werden z.B. "File*.txt"
$SearchString="*ErrorID:245*"   #Hier können reguläre Ausdrücke verwendet werden z.B. "E[q-s]ror: 225"
$OutPutFile="Result.log"

Remove-Item $Path\$OutPutFile -EA 0 #-Confirm #Löschen eines evtl. vorhandenen Files nach Bestätigung

Function Analyze{
 param($FullPath,$OutputFile,$SearchString)
 
 $LineNumber = 0
 $Hits=@()                   #Array mit den Zeilennummern, die den Searchtext enthalten
 
 "{0} {1}" -f $FullPath,[Environment]::NewLine #Dateiname über die Hits schreiben
 
 $Lines = @(Get-Content -path $Fullpath)  #Array mit allen Zeilen der Datei
 $Lines[-1]="Dummy"                       #Vorgängerzeile der ersten TextZeile
 
 $Lines | ForEach{                     #Jede Zeile (=jedes Element des Arrays) untersuchen
   $LineNumber += 1                     #Zeilennummer
   If($_ -like $SearchString){         #Prüfen, ob $Suchstring in der Zeile enthalten ist
     $Hits += $LineNumber              #Füge die Zeilennummer dem Array Hits hinzu
   }#If
 }#Lines
 
 For($i=0; $i -le $Hits.Count; $i++){  #Alle Zeilennummern mit Hits hochzählen
  $Value = ""
  If($Hits[$i] -eq $($Hits[$i-1]+1)) #Wenn zwei Zeilennummern mit Hits aufeinander folgen
    {
    $Value += "Zeile {0}: {1} {2}" -f $Hits[$i-1],$Lines[$Hits[$i]-2],[Environment]::NewLine
    $Value += "Zeile {0}: {1} {2}" -f $Hits[$i],$Lines[$Hits[$i]-1],[Environment]::NewLine
   
    $Value        #Eventuell auskommentieren, wenn keine Bildschirmausgabe gewünscht ist
    Add-Content -Path $Path\$OutPutFile -Value $Value  #auskommentieren, wenn keine Fileausgabe gewünscht ist   
    Remove-Variable Value -EA 0  
    }#if
  }#for
 
}#function
 
$LogFiles = Get-ChildItem -path $Path -filter $FileFilter
$LogFiles | ForEach{
   Analyze -FullPath $_.FullName -OutPutFile $OutPutfile -SearchString $SearchString #Funktionsaufruf
   } 
#mögliche Ausgabe 

C:\temp\File_20Kb.txt 
Zeile 9: 30.12.2011 22:13   aaa q77wwulC8pOT 6k49hZcW dIemH  6AL  YmxZfJbyesUiaB Error: 225 Y5 lDKxg6St1
Zeile 10: 30.12.2011 22:13    f5jy C3rlELSsAc SgK 5Cg8pZRj e9t fHV Error: 250 QsSXmO Error: 225 ytLUJtZ2M

Zeile 28: 30.12.2011 22:13   Shw6IejTuQhWr 43Wo uEw Error: 225  9dGEkJL6wjRHAIeFbmCBv8MmXsno D6 vJCZt
Zeile 29: 30.12.2011 22:13   I7ia pxLjc7 Error: 225 F uZVJhhr t gN6DxzcCNT2T gvw 2vZoQKmi0Y130CZ VQnf

 Kap1.4_Beispiel 2a.ps1.txt


Beispiel 2b: Suchen nach Textstellen, bei denen ein Suchbegriff in drei aufeinanderfolgenden Zeilen vorkommt

Ein Beispiel für eine zeilenübergreifendes Suchkriterium.

Set-StrictMode -Version "2.0"
Clear-Host
 

$Path = "C:\temp"                #Die Dateien in diesem Verzeichnis wird analysiert
$FileFilter = "Big150.txt"   #Hier können auch Platzhalter angegeben werden z.B. "File*.txt"
$SearchString = "*ErrorID:225*"   #Hier können reguläre Ausdrücke verwendet werden z.B. "E[q-s]ror: 225"
$OutPutFile = "Result.log"
 
Remove-Item $Path\$OutPutFile -EA 0 #-Confirm #Löschen eines evtl. vorhandenen Files nach Bestätigung
 
 Function Analyze{
   param($Fullpath,$OutputFile,$SearchString)
  
   $LineNumber = 0
   $Value = "{0} {1}" -f $FullPath,[Environment]::NewLine #Dateiname über die Treffer schreiben
   $Treffer=@()                   #Array mit den Zeilennummern, die den Searchtext enthalten
 
   $Lines = @(Get-Content -path $Fullpath) #Array mit allen Zeilen der Datei
   $Lines[-1] = "Dummy"                    #Vorgängerzeile der ersten TextZeile
   $Lines[-2] = "Dummy"  #VorVorgängerzeile, damit auch die erste Zeile auf Vorgängertreffer geprüft werden kann.
 
  $Lines | ForEach{                     #Jede Zeile (=jedes Element des Arrays) untersuchen
    $LineNumber = $LineNumber + 1                     #Zeilennummer
    if($_ -like $SearchString){         #Prüfen, ob $Suchstring in der Zeile enthalten ist
     $Treffer += $LineNumber              #Füge die Zeilennummer dem Array Treffer hinzu
    }#if
  }#Lines
     
  For($i=0; $i -le $Treffer.Count;$i++){  #Alle Zeilennummern mit Treffern hochzählen
   If($Treffer[$i] -eq $($Treffer[$i-1]+1)) #Wenn zwei Zeilennummern mit Treffern aufeinander folgen
   {
     If($Treffer[$i] -eq $($Treffer[$i-2]+2)) #Wenn drei Zeilennummern mit Treffern aufeinander folgen
     {
     $Value = ""
     $Value += "Zeile {0}: {1} {2}" -f $Treffer[$i-2],$Lines[$Treffer[$i]-3],[Environment]::NewLine
     $Value += "Zeile {0}: {1} {2}" -f $Treffer[$i-1],$Lines[$Treffer[$i]-2],[Environment]::NewLine
     $Value += "Zeile {0}: {1} {2}" -f $Treffer[$i],$Lines[$Treffer[$i]-1],[Environment]::NewLine
    
     $Value        #Eventuell auskommentieren, wenn keine Bildschirmausgabe gewünscht ist
     Add-Content -Path $Path\$OutPutFile -Value $Value    #Auskommentieren, wenn keine Fileausgabe gewünscht ist
     Remove-Variable Value -EA 0  
     }#If
   }#If
  }#For
 
 }#function
 
 $LogFiles = Get-ChildItem -path $Path -filter $FileFilter
 $LogFiles | ForEach{
   Analyze -FullPath $_.FullName -OutPutFile $OutPutfile -SearchString $SearchString  #Funktionsaufruf
   }    
#mögliche Ausgabe

Zeile 755: 30.12.2011 22:13   g1Y 6E9mynheBnN7Die 0DrwS B1if6lz949l4oA WnkTD  3l Error: 225 c jq dH  sLVi
Zeile 756: 30.12.2011 22:13   0XcQtYNkzL6EmWqWQejv4Y Z Error: 225  mv5rUs8g j EBCrn 1izldcr91gv8uE 7kco01
Zeile 757: 30.12.2011 22:13   H6 dCO0Vas2 i rBt g4oNFjPuWv qFOBQe7  Error: 225 rcsj LCw s5yWeQ1mLk51U 43 

Zeile 836: 30.12.2011 22:13    Error: 225 HyO3  sFe4GJCs0IqMjRApdhY sNkJyablQfP7hox0  c0vBrsH KZIiA8g t  
Zeile 837: 30.12.2011 22:13   VH wGzyYtQz dx3XbWBOgIajMT svycq6 Kaw Error: 225 M AeH ACl8fWP9v82S53yfYV 
Zeile 838: 30.12.2011 22:13   Icr 4nNhyiokejbsCL6 S W 3ib Error: 225 KPXkjARsMG10kepnwe 9XmMuL  b6GYmywZ

 Kap1.4_Beispiel 2b.ps1.txt

Das Skript liest die Datei zeilenweise in ein Array ein. Wird ein Treffer gefunden, so wird nachgesehen, ob in der Vor- und VorVorgängerzeile ebenfalls schon ein Treffer registriert wurde.