PowerShell Transpose Objects und Memoryleak
Hallo,
weiß jemand wie ich folgenden Powershell Code etwas optimieren kann?
Ich habe sehr viele Eventlog Einträge > 100.000 die ich verarbeiten will (Domänen Controller), leider kann ich die erforderlichen Daten nur aus dem Xml entnehmen...
Dieser Teil:
war mein versuch das Memoryleak zu stopfen, hat nur nicht funktioniert... Vorher habe ich anstelle des Csv Exports die Object weiter über die Pipe geschoben.
Ich denke, dass das Memoryleak durch das Transposen der Tabelle ausgelöst wird, weiß aber nicht wie ich das beheben soll. Abgesehen davon lastet mein Skript aktuell die CPU zu 100% aus, das lässt sich aber vermutlich nicht vermeiden, oder?
Mit freundlichen Grüßen,
agowa338
weiß jemand wie ich folgenden Powershell Code etwas optimieren kann?
Ich habe sehr viele Eventlog Einträge > 100.000 die ich verarbeiten will (Domänen Controller), leider kann ich die erforderlichen Daten nur aus dem Xml entnehmen...
$xmlEvent = @(); $xmlEvents = @();
$filter = @{
Logname = "Security";
ID = 4624;
StartTime = (Get-Date).AddDays(-10);
}
$filterSid = "S-1-5-32" # Actually the Sid of a specific service account and not of the System account
$i = 0;
Get-WinEvent -FilterHashtable $filter | ForEach-Object {
$xmlEvent = ([XML]$_.ToXml()).Event.EventData.Data;
$xmlEvents += [psCustomObject][Ordered] @{
TargetUserSid = [String]($xmlEvent | Where-Object {$_.Name -eq 'TargetUserSid'}).'#text';
TargetUserName = [String]($xmlEvent | Where-Object {$_.Name -eq 'TargetUserName'}).'#text';
TargetDomainName = [String]($xmlEvent | Where-Object {$_.Name -eq 'TargetDomainName'}).'#text';
IpAddress = [String]($xmlEvent | Where-Object {$_.Name -eq 'IpAddress'}).'#text';
LogonProcessName = [String]($xmlEvent | Where-Object {$_.Name -eq 'LogonProcessName'}).'#text';
AuthenticationPackageName = [String]($xmlEvent | Where-Object {$_.Name -eq 'AuthenticationPackageName'}).'#text';
TimeStamp = [String]($_.TimeCreated);
};
$i += 1;
if (!($i % 1000)) {
$xmlEvents | Where-Object {$_.TargetUserSid -eq $filterSid | Export-Csv -Delimiter ';' -LiteralPath ".\LogonEvent.csv" -Append;
Write-Host "`r$i Events processed - Last Event: $($_.TimeCreated)" -NoNewline;
$xmlEvents = @();
};
};
Dieser Teil:
$i += 1;
if (!($i % 1000)) {
$xmlEvents | Where-Object {$_.TargetUserSid -eq $filterSid | Export-Csv -Delimiter ';' -LiteralPath ".\LogonEvent.csv" -Append;
Write-Host "`r$i Events processed - Last Event: $($_.TimeCreated)" -NoNewline;
$xmlEvents = @();
};
Ich denke, dass das Memoryleak durch das Transposen der Tabelle ausgelöst wird, weiß aber nicht wie ich das beheben soll. Abgesehen davon lastet mein Skript aktuell die CPU zu 100% aus, das lässt sich aber vermutlich nicht vermeiden, oder?
Mit freundlichen Grüßen,
agowa338
Bitte markiere auch die Kommentare, die zur Lösung des Beitrags beigetragen haben
Content-ID: 316232
Url: https://administrator.de/contentid/316232
Ausgedruckt am: 21.11.2024 um 17:11 Uhr
11 Kommentare
Neuester Kommentar
Hallo Agowa,
ich würde für sowas die Einträge direkt mit einer XPath Query ausfiltern anstatt sie erst alle mit custom objects in ein Array zu parken was natürlich Zeit kostet und den Speicher unnötig füllt:
oder wenn man die Zeit auch noch in die XPath Query einbauen will um es noch weiter zu optimieren:
Grüße Uwe
ich würde für sowas die Einträge direkt mit einer XPath Query ausfiltern anstatt sie erst alle mit custom objects in ein Array zu parken was natürlich Zeit kostet und den Speicher unnötig füllt:
Get-WinEvent -FilterXPath "Event[System[(EventID=4624)] and EventData[Data[@Name='TargetUserSid'] = 'S-1-5-32']]" -LogName Security | ?{$_.TimeCreated -gt (get-date).AddDays(-10)}
$starttime = (Get-date).AddDays(-10).Date.ToString('o')
Get-WinEvent -FilterXPath "Event[System[EventID=4624 and TimeCreated[@SystemTime >= '$starttime']] and EventData[Data[@Name='TargetUserSid'] = 'S-1-5-32']]" -LogName Security
Jipp hatte ich oben noch nachkorrigiert, sorry.
Custom Objects kosten nunmal einiges an Speicher.
Mit XPath filterst du schon vorher die relevanten Einträge heraus, das reduziert die Datenmenge dann schon erheblich.
Meinst du dass FilterHashtable schuld am Memoryleak ist?
Nein, eher das erstellen der Custom-Objects für alle Einträge und das Filtern erst hinterher.Custom Objects kosten nunmal einiges an Speicher.
Mit XPath filterst du schon vorher die relevanten Einträge heraus, das reduziert die Datenmenge dann schon erheblich.
Aber irgendwas ist da immer noch komisch:
Naja hier kennt ja keiner deine Umgebung Zur Info: Garbage-Collection kannst du auch manuell anstoßen.
[GC]::Collect()
Windows gibt Speicher nicht immer direkt wieder frei sondern markiert diesen und behält Teile im Cache wenn man mit NET-Objekten arbeitet, die Freigabe erfolgt nach einiger Zeit auch automatisch.
Zitat von @agowa338:
Das heißt im Umkehrschluss, wenn ich im Test durch "[GC]::Collect()" den Speicher frei bekomme, ist alles gut.
Der wird auch automatisch durch Windows wieder aus dem Cache wieder freigegeben, geschieht im Hintergrund sobald ein Programm Speicher anfordert. Der manuelle Aufruf ist hier normalerweise nicht nötig, ich nutze Ihn nur wenn z.B. ein Handle auf eine Datei unbedingt geschlossen sein muss (kann in bestimmten Fällen schon mal vorkommen).Das heißt im Umkehrschluss, wenn ich im Test durch "[GC]::Collect()" den Speicher frei bekomme, ist alles gut.
Hast du eventuell ach noch einen Tipp für die Transposition, oder geht das nicht besser?
Was meinst du mit Transposition?Du könntest natürlich auch ohne Custom-Objects arbeiten bzw. diese nicht in einem Array speichern sondern direkt in der Pipeline weitergeben. Man kanns aber auch übertreiben
Get-WinEvent ist einfach nicht besonders sparsam und eher langsam im Vergleich zu anderen CMDLets.
Ach so du meinst "Transponieren", OK. Du kannst die Knoten auch mit $xml.SelectSingleNode() auswählen anstatt sie durch eine erneute Pipeline mit where object zu leiten.
Und speichere wie oben die Objekte nicht in einem Array sondern leite sie direkt durch die Pipeline das spart Speicher.
In deiner Variante legst du erst alle Elemente in einem Array ab um sie hinterher erneut zu verarbeiten, wenn du sie stattdessen via Pipeline direkt an export-csv leitest ist das zwar minimal langsamer aber dafür speichereffizienter als alle Daten erst in einer Variablen abzulegen.
Aber wie gesagt Get-WinEvent ist selber einfach nicht sehr effizient, der Optimierung sind da bei bestimmten Datenmengen Grenzen gesetzt.
Du kannst es mal hiermit versuchen:
Felder des customobjects nur zur Demo gekürzt.
So ist das ganze Konstrukt eine einzige Pipeline bei der die Objekte nacheinander direkt in die CSV fließen ohne dauerhaft Speicher zu belegen wie ein Array aus Objekten.
Und speichere wie oben die Objekte nicht in einem Array sondern leite sie direkt durch die Pipeline das spart Speicher.
In deiner Variante legst du erst alle Elemente in einem Array ab um sie hinterher erneut zu verarbeiten, wenn du sie stattdessen via Pipeline direkt an export-csv leitest ist das zwar minimal langsamer aber dafür speichereffizienter als alle Daten erst in einer Variablen abzulegen.
Aber wie gesagt Get-WinEvent ist selber einfach nicht sehr effizient, der Optimierung sind da bei bestimmten Datenmengen Grenzen gesetzt.
Du kannst es mal hiermit versuchen:
$starttime = (Get-date).AddDays(-10).Date.ToString('o')
Get-WinEvent -FilterXPath "Event[System[EventID=4624 and TimeCreated[@SystemTime >= '$starttime']] and EventData[Data[@Name='TargetUserSid'] = 'S-1-5-32']]" -LogName Security | %{
$xml = [xml]$_.toXML()
$ns = (new-Object System.Xml.XmlNamespaceManager $xml.NameTable)
$ns.AddNamespace("ns",$xml.DocumentElement.NamespaceURI)
[pscustomobject]@{
TargetUserSid=$xml.SelectSingleNode("ns:Event/ns:EventData/ns:Data[@Name = 'TargetUserSid']",$ns).innerText
TargetUsername=$xml.SelectSingleNode("ns:Event/ns:EventData/ns:Data[@Name = 'TargetUserName']",$ns).innerText
# usw.
}
} | export-csv '.\LogonEvent.csv' -Delimiter ";" -NoType -Encoding UTF8
So ist das ganze Konstrukt eine einzige Pipeline bei der die Objekte nacheinander direkt in die CSV fließen ohne dauerhaft Speicher zu belegen wie ein Array aus Objekten.
Nicht das ich wüsste. Kann man sich aber selbst bauen , für das obige Beispiel
schneller ist weil die Objekte alle direkt aus dem Speicher verarbeitet werden, aber im Gegenzug mehr Speicher beansprucht weil alle Objekte erst in einer Variablen zwischengespeichert werden.
Hier gilt es also abzuwägen ob Speicher sparen Priorität hat oder das letzte Quäntchen Performance auf Kosten einer höheren Speicherauslastung.
https://blogs.technet.microsoft.com/heyscriptingguy/2009/07/22/hey-scrip ...
Möchtest du noch mehr Kontrolle über Speicherauslastung etc. und das letzte Quentchen Performance würde ich dir empfehlen das ganze direkt mit einer nativen App c#/c++ umzusetzen.
Grüße Uwe
$obj = [pscustomobject]@{}
$xml.Event.EventData.Data | %{$obj | Add-Member -Membertype NoteProperty -Name $_.Name -Value $_.'#text'}
Wieso ist eine Pipe langsamer als in Variable speichern und anschließend durch die Pipe exportieren? Ist ja eigentlich ein schritt mehr...
Das liegt an der Funktionsweise der Pipeline. Ein Objekt wandert vom ersten zum letzten CMDLet, bevor das nächste Objekt dran ist (bei manchen CMDLets lässt sich auch festlegen wie viele Objekte gleichzeitig über die Pipeline wandern). Es kommt hier auch auf die Datenmenge an. Eine Pipeline spart bei einer enormen Anzahl an Objekten Speicher wohingegen das Speichern in einer Variablen und das Verarbeiten bei einer überschaubaren Anzahl an Objekten und Verarbeitung per Foreach ohne Pipeline mitforeach($itm in $items){
}schneller ist weil die Objekte alle direkt aus dem Speicher verarbeitet werden, aber im Gegenzug mehr Speicher beansprucht weil alle Objekte erst in einer Variablen zwischengespeichert werden.
Hier gilt es also abzuwägen ob Speicher sparen Priorität hat oder das letzte Quäntchen Performance auf Kosten einer höheren Speicherauslastung.
https://blogs.technet.microsoft.com/heyscriptingguy/2009/07/22/hey-scrip ...
Möchtest du noch mehr Kontrolle über Speicherauslastung etc. und das letzte Quentchen Performance würde ich dir empfehlen das ganze direkt mit einer nativen App c#/c++ umzusetzen.
Grüße Uwe