PowerShell Skript ist zu langsam?

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.

Woher ich das weiß:eigene Erfahrung – Ich mach das seit 30 Jahren
Lenzer66u 
Fragesteller
 30.03.2023, 08:39

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.

0
Erzesel  30.03.2023, 09:40
@Lenzer66u

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.

0

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, '')

Lenzer66u 
Fragesteller
 30.03.2023, 07:57

könntest du die fehlenden Klammern noch hinzufügen das ich das ganze mal testen könnte? xD

0
Lenzer66u 
Fragesteller
 30.03.2023, 08:06

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.

0
Erzesel  30.03.2023, 09:22

...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)

0