PowerShell Skript ist zu langsam?
Der Code ist für unser Betriebs Netzwerk zu langsam. Lokal funktioniert alles super. Wie kann man den Code schneller machen?
Code:
$desktopFolder=[Environment]::GetFolderPath('Desktop')
$VonOrdner="\\deploy\deployment$\log\EmpPackages"
$global:NachOrdner="$desktopFolder\test2"
$Dateiname="Alle2.log"
Copy-Item "$VonOrdner\$Dateiname" $global:NachOrdner
#Inhalt der KopiertestServer.txt Datei einlesen
$global:VergleichsString=try{[System.IO.File]::ReadAllText("$VonOrdner\$Dateiname")}catch{}
New-Item $global:NachOrdner -ItemType "directory" -force #Ordner anlegen falls er nicht existiert
$watcher=New-Object System.IO.FileSystemWatcher
$watcher.Path=$VonOrdner
$watcher.Filter=$Dateiname
$watcher.IncludeSubdirectories=$false
$watcher.EnableRaisingEvents=$false
$action={
$changeType = $Event.SourceEventArgs.ChangeType
$VonPfad = $Event.SourceEventArgs.FullPath
$SourceDatei = $Event.SourceEventArgs.Name
$NachPfad = "$global:NachOrdner\$SourceDatei"
#Den neuen Inhalt einlesen
$NeuerInhalt=try{[System.IO.File]::ReadAllText($VonPfad)}catch{}
Write-Host "Start"
#Ab hier hängt sich auf
#---------------------------------------------------------------------------------------------------------------------
$Unterschied=$NeuerInhalt -replace "^$global:VergleichsString",''
#---------------------------------------------------------------------------------------------------------------------
#Ausgabe des Unterschiedes
Write-Host "Unterschied: $Unterschied" -fo red
[System.IO.File]::AppendAllText($NachPfad,$Unterschied)
#vergleichsstring updaten
$global:VergleichsString=$NeuerInhalt
}
Register-ObjectEvent $watcher "Changed" -Action $action|Out-Null
while ($true) {
sleep 1
}
2 Antworten
Ich hatte gestern Abend leider keine Zeit mehr das Ding umzuschreiben.
Der letzte mögliche große Zeitfresser war das Vergleichen der riesigen Strings per RegEx. Bei extrem großen Dateien sehr Aufwändig.
Gestern war mir eingefallen das Dies garnicht nötig ist (auch ich habe gelegendlich einen Lapsus🥵)
Die Größe der Datei liefert ja bereits das Dateisystem und ich muss nur soviele Bytes vom Anfang des der neu eingelesenen Datei abschneiden, wie die Datei zuvor hatte.
$desktopFolder=[Environment]::GetFolderPath('Desktop')
$SourceFolder="$desktopFolder\test1"
$FileFilter="KopiertestServer.txt" #kann auch ein Wildcard sein. zB."*.txt"(überwcht alle .txt Dateien im Ordner)
#$FileFilter="*.txt"
$global:DestinationFolder="$desktopFolder\test2"
$Null=New-Item $global:DestinationFolder -ItemType "directory" -force #anlegen, wenn er nich existiert
#das folgende stellt Sicher, das die Aktionroutine auch bei Verwendung von Wildcards in $FileFilter mit den Werten der richtigen Datei arbeitet
#ob man die Daten einer oder mehrerer Dateien ermittelt spielt an dieser Stell keine Rolle
[Array]$global:SoureFiles = Get-ChildItem -Path $SourceFolder -Filter $FileFilter -File |
Select-Object -Property Directory,FullName,Name,Length #wir brauchen nur die genannten Eigenschaften der kopierten Datei(en)
#erstmal wie gehabt die Datei(en) normal kopieren
$global:SoureFiles|
ForEach-Object {
Copy-Item $_.FullName $global:DestinationFolder
}
# hier siehst Du was Du in den Eventhandler mitnimmst, (Pillepalle)
'ueberwachte Dateien:'
$global:SoureFiles|fl *
#Watcher initialisieren
$watcher=New-Object System.IO.FileSystemWatcher
$watcher.Path=$SourceFolder
$watcher.Filter=$FileFilter
$watcher.IncludeSubdirectories=$False
$watcher.EnableRaisingEvents=$False
$action={
$changeType = $Event.SourceEventArgs.ChangeType
$SourcePath = $Event.SourceEventArgs.FullPath
$SourceFile = $Event.SourceEventArgs.Name
$DestinationPath = "$global:DestinationFolder\$SourceFile"
Write-Host "$SourcePath was $changetype at $(get-date)"
#ermittle den Index der Datei, mit deren Daten gearbeitet werden soll (Bei einer einzigen Datei immer 0)
$SoureFilesIndex=[array]::IndexOf($global:SoureFiles.FullName,$SourcePath)
#ist Diese nicht in der Liste, zb weil sie nach dem Start des Scripts erzeugt wurde, wird $SoureFilesIndex=-1 zurückgegen
#eine solche Datei wird ignoriert!!!
if ($SoureFilesIndex -ge 0) {
$OldFileSize=$global:SoureFiles[$SoureFilesIndex].Length
#es kann passieren das gespeichert wird ohne was zu ändern, dann gibt es auch keinen Grund etwas zu tun
$NewFileSize=(Get-Item $SourcePath).Length
Write-Host Bytes Neu :$NewFilesize -fo green
Write-Host Bytes to Skip:$OldFileSize -fo green
if ($NewFileSize -gt $OldFileSize) {
#mal das gewählte DataSet anzeigen
Write-Host Index:$SoureFilesIndex Dataset:$($global:SoureFiles[$SoureFilesIndex]) -fo magenta
#aufgepasst: lese Daten jetz im Byte-/BinärModus!!! (so muss ich nicht zweimal casten und kann im ByteArray mit Select -Skip x Elemente überspringen ...billig)
$NeuerInhalt=try{[System.IO.File]::ReadAllBytes($SourcePath)}catch{}
#Jetz wo wir die neue Anzahl Bytes haben, können wir Die Dateigöße updaten.
$global:SoureFiles[$SoureFilesIndex].Length=$NewFileSize
$UnterschiedBytes=$NeuerInhalt|
Select-Object -Skip $OldFileSize #einfach den alten Kram übersprignen
#ermittle das aktuelle Encoding mit dem die Bytes in Text umgewandelt und in die Datei geschrieben werden
$Encoder=[Text.Encoding]::GetEncoding($OutputEncoding.WindowsCodePage)
#es gibt kein AppendAll... für ByteArrays also einfach Text draus machen (plump aber geht)
$UnterschiedText=try{$Encoder.GetString($UnterschiedBytes)}catch{Write-Host GetString Error}
Write-Host "Unterschied als HexList: $($UnterschiedBytes|%{'{0:X2}'-f $_})" -fo red
Write-Host "Unterschied als String : $UnterschiedText" -fo red
[System.IO.File]::AppendAllText($DestinationPath,$UnterschiedText,$Encoder)
}
else {Write-Host "...mache Nichts..." -fo darkred}
}
else {Write-Host $SourcePath -NoNewLine;Write-Host " wird nicht ueberwacht!...mache Nichts..." -fo darkred}
}
Register-ObjectEvent $watcher "Changed" -Action $action|Out-Null
while ($true) {
sleep 2
}
pause
$VonOrdner="\\deploy\...
Liest Du die riesige Datei etwa über einen langsame Verbindung ein?
Eigentlich war ja der Sinn der ganzen Sache, dass die Quelldatei vom lokalen Rechner schnell gelesen wird und nur die hinzugekommenen Daten an die langsame Datei angehängt werden.
Ich habe ja bereits in den kommentaren...die Vermutung geäußert, Das in Deinem Fall die Verwendung von Ereignissen zeitlich oder Seitens meines "Change-basierten" auch zum Nachteil werden kann, wenn ein Ereignis eintritt, bevor das vorherige abgearbeitet ist. dann treten sich die Aktionen gegenseitig in die Hacken. (wie bei einem Stau auf der Autobahn).
Ich bin inzwischen schon wieder Unterwegs. Ich mache mir mal Gedanken wie ich dein Szenario mit der langsamen Verbindung irgendwie simuliere.
In jedem Fall ist wichtig das das Script nicht die Originaldaten vom langsamen Gerät liest, sondern vom schnellen liest...und die reduziert Datenmenge zum langsamen Gerät schreibt ansonsten ist das alles fü die Katz.
Verwende native .NET-Klassen anstelle von PowerShell-Cmdlets. Dadurch kannst du die Ausführung deines Skripts beschleunigen, da die Klassen direkt auf die .NET-Bibliotheken zugreifen können.
Erstelle Background-Jobs anstelle von einem Endlosschleifen-Skript. So wird sichergestellt, dass dein Skript nicht blockiert wird und gleichzeitig andere Aufgaben ausführen kann.
Verwende das -Raw-Flag bei der Verwendung des Cmdlets Get-Content. Dadurch wird der Inhalt der Datei in einem einzigen Vorgang gelesen und als Ganzes zurückgegeben, was die Ausführung beschleunigt.
Verwende reguläre Ausdrücke anstelle von -replace. Reguläre Ausdrücke können eine effektivere Methode zum Entfernen von Zeichenfolgen aus einer Datei sein als die Verwendung von -replace.
Reduziere die Anzahl der Schreibvorgänge auf die Festplatte. Du kannst den Inhalt der Datei im Arbeitsspeicher halten, bis ein bestimmter Schwellenwert erreicht ist, bevor der Inhalt auf die Festplatte geschrieben wird. Dadurch wird die Leistung verbessert.
Hier ist eine optimierte Version des Skripts, das einige dieser Optimierungen umsetzt:
Code:
# Verwenden von .NET-Klassen
$desktopFolder = [Environment]::GetFolderPath('Desktop')
$VonOrdner = "\\deploy\deployment$\log\EmpPackages"
$global:NachOrdner = "$desktopFolder\test2"
$Dateiname = "Alle2.log"
$global:VergleichsString = [System.IO.File]::ReadAllText("$VonOrdner\$Dateiname")
# Verwenden von Background-Jobs
Start-Job -ScriptBlock {
# Verwenden von .NET-Klassen
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $VonOrdner
$watcher.Filter = $Dateiname
$watcher.IncludeSubdirectories = $false
$watcher.EnableRaisingEvents = $false
# Verwenden von regulären Ausdrücken
$regex = [regex]::Escape($global:VergleichsString)
$regex = "^$regex"
$NachPfad = "$global:NachOrdner\$Dateiname"
$counter = 0
$threshold = 10
# Schleife zum Überwachen der Änderungen an der Datei
while ($true) {
$event = Wait-Event -Timeout 1
if ($event) {
$changeType = $event.SourceEventArgs.ChangeType
$VonPfad = $event.SourceEventArgs.FullPath
# Den neuen Inhalt einlesen
$NeuerInhalt = [System.IO.File]::ReadAllText($VonPfad)
# Verwenden von regulären Ausdrücken
$Unterschied = [regex]::Replace($NeuerInhalt, $regex, '')
...und was soll der BackgroundJob bewirken? (wen er den funktionieren sollte)
Das Starten eines Powershell-Jobs dauert bei großen Datenmengen enorm lange. Jobs machen keinen Sinn, wen der Flaschenhals bei der Geschwindigkeit der gelieferten Daten liegt.
Die .Net RegEx-Methode ist bei riesigen Strings nicht wesentlich schneller als Powershells -relpace.
wenn ein Transfer einer Datei 40 Sekunden Dauert (so Lezer66u), ist es irrelevant wie schnell Deine oder meine Routinen sind... Letztlich könnten sich per Event ausgelöste Aktionen als Schädlich erweisen, weil Eventhandler asynchron sind, und sich gegenseitig "überholen" könnten. dann gibts erstrecht einen Stau.
Auch ich hatte eine solch Paradoxe Situation noch nicht. (und auch keinen so Datentransfer)
außerdem läuft das irgendwie nicht in einer Schleife es schließt sich direkt nach 1 Sekunde, könnte aber auch daran liegen das ich die Klammern Falsch gesetzt habe.
könntest du die fehlenden Klammern noch hinzufügen das ich das ganze mal testen könnte? xD
Unser Netzwerk ist leider echt langsam was ich mir auch nicht erklären kann. Wenn ich jetzt etwas ändere in der großen Datei dauert es exakt 40 Sekunden bis die Datei geändert wurde. Das ist schon eine sehr große Verbesserung Danke.