Powershell Progressbar erstellen

derlorenz

Commander
Dabei seit
März 2011
Beiträge
2.498
Hallo CB-Forum 🙂

ich würde gerne folgende cmd-Schnipel in Powershell verpacken und als einzige Ausgabe eine progress bar anzeigen lassen:

Bash:
cd C:\path\to\naps2\App
call NAPS2.Console.exe -o "H:\scan.pdf" --force
call "C:\Program Files\Tool\Zum\Einlesen.exe" "H:\scan.pdf"

Hintergund:

NAPS2 ist ein OpenSource Scan-Tool. Wir nutzen dies um eben aus einem anderen Programm heraus den Scan-Vorgang anzustoßen und das PDF im Anschluss in ein anderes Programm einzulesen.
Ich könnte natürlich auch die DOS-Box ausblenden etc. aber so eine Fortschrittsanzeige wäre ganz nett.

Vielleicht habt Ihr ja eine Idee.

Danke schon mal bis hier her für's lesen. 😉

PS:
Folgende How-To's hatte ich schon gefunden, aber mir fehlt die Erfahrung das Ganze ordentlich zu "verpacken":
1. https://adamtheautomator.com/building-progress-bar-powershell-scripts/
2. https://docs.microsoft.com/en-us/pr....0/ff730943(v=technet.10)?redirectedfrom=MSDN
3. https://devblogs.microsoft.com/scripting/add-a-progress-bar-to-your-powershell-script/
 

AndrewPoison

Admiral
Dabei seit
Jan. 2005
Beiträge
8.449
  • Gefällt mir
Reaktionen: derlorenz

derlorenz

Commander
Ersteller dieses Themas
Dabei seit
März 2011
Beiträge
2.498
@AndrewPoison ich sag dann mal ganz dezent DANKE! 🙈
Ergänzung ()

Danke @sikarr für den Link, den schaue ich mir dennoch an - der Hinweis mit "--progress" ist im Prinzip ausreichend.
 
R

RalphS

Gast
Für alle, die aus ihrem "Extra-Tool" keine Fortschrittsleiste rauskriegen:

- PowerShell kennt ein cmdlet Write-Progress. Damit kann man auf den Progress-Ausgabestrom schreiben und erhält eine Progressbar am oberen Fensterrand (des Konsolenfensters).

- Write-Progress ist aber ein simples cmdlet. Man muß es aufrufen, und damit es etwas Erkennbares tut, muß es in einer Schleife ausgeführt werden (egal wie die beschaffen ist). Hier gibt es jetzt zwei grundlegende Ansätze, mit denen man seine Progressbar ansteuern kann.

A

Wir haben eine Serie von (spätestens zur Laufzeit bestimmbar) vielen einzelnen Aktionen, deren Ausführungsstand wir mit Write-Progress dokumentieren wollen. Das ist der synchrone Ansatz.
Beispiel: Alle Dateien aus einer Ordnerstruktur sollen gelöscht werden.
Implementierung (grundlegend; das Beispiel ist konstruiert):
PowerShell:
$Files = Get-Childitem -Recurse -Path C:\ # Bestimme Menge von zu verarbeitenden Objekten
1 .. $Files.Count | # Modell einer index-basierten FOR-Schleife in PS
% { 
[int] $index = $_
Write-Progress -PercentComplete ( $index * 100 / Files.Count ) ....  # Standardisiere Position in der Liste auf einen Prozentwert (als ganze Zahl)
$Files[$index] | Remove-Item  # Eigentliche Aktion
}
Vorteil: Der Ansatz ist ziemlich einfach und funktioniert fast überall, wenn die Vorbedingungen stimmen und ich Zugriff auf die Schleife hab.
Nachteil: Ich brauche Zugriff auf die Schleife. Einen einzelnen lange dauernden Prozeß kann ich so nicht visualisieren.

B

Für einzelne, lange dauernde Prozesse brauche ich einen asynchronen Ansatz. Beispiel: Ich will eine Progressbar haben für die Position in einer Mediendatei.
Hier muß ich mit Threads arbeiten oder eine der Möglichkeiten von PS nutzen, eine Aufgabe im Hintergrund ausführen zu können. Das geht unter anderem mit den *-Job cmdlets.

Dieser Ansatz ist individueller. Er funktioniert fast immer, solange ich nur in der Lage bin, Fortschrittsinformationen aus dem laufenden Task abzuleiten oder selbst zu generieren. Hierbei ist aber darauf zu achten, daß der Abgleich nicht aufwendiger wird als der eigentliche Task, denn sonst warte ich auf die Synchronisation und nicht auf die Erledigung meiner Aufgabe.

Der Nachteil ist, daß es kaum pauschale Vorgehensweisen gibt. Man muß für jedes Problem einen eigenen Abgleich finden, je nachdem, was mir die Implementierung meines Tasks ermöglicht und was nicht.

Beispiel 1
Ich hab die (fiktiven) Methoden zur Taskausführung
++ object Execute()
++ Task BeginExecute() und
++ object EndExecute(Task) zur Verfügung.

Execute() ist die synchrone Variante - die Aktion wird ausgeführt und ich warte aufs Ergebnis. Es gibt einen Rückgabewert (das Ergebnis). Das ist das normale Vorgehen.
BeginExecute() ist die asynchrone Variante. Die liefert ein Taskobjekt, welches mir sagt, was der Prozeß grad macht.
EndExecute(Task) schließt den Task ab.

Für den Fortschritt kann ich jetzt
  • BeginExecute ausführen. Die Aufgabe wird gestartet und im Hintergrund ausgeführt.
  • Write-Progress ausführen auf Basis der Information aus dem Task-Objekt. Write-Progress läuft nun parallel zur Aufgabe.
Problem: Wenn das Taskobjekt mir keine Infos liefert zum Umfang der Aufgabe, dann muß ich das vorher bestimmen und das kostet wieder Zeit; Zeit, in der die Aufgabe schon hätte ausgeführt werden können.


Beispiel 2
ich hab solche Statusinformationen nicht zur Verfügung. Dann muß ich den Status zur Laufzeit selber ermitteln und mich fragen, wie ich das am besten anstelle, ohne daß mir das zeitlich aus dem Ruder läuft.
Also fange ich einfach mit der Aufgabe an und laß die im Hintergrund laufen.
Und jetzt hole ich mir den Fortschritt selber zur Laufzeit. Was wurde schon verarbeitet? Was nicht? Das ist mein Fortschritt und den muß ich mit Write-Progress visualisieren.
Dabei muß ich aber aufpassen, daß ich keinen Deadlock verursache, weil mein Überwachungstask mir Dinge blockiert, die der Ausführungstask selber blockieren muß.

Für diese Situation ist es daher im Gegensatz zum synchronen Ansatz vorteilhaft, sich seine Updatehäufigkeit für den Fortschrittsbalken gut zu überlegen. Der synchrone Ansatz aktualisiert mit erledigter Teilaufgabe. Die haben wir hier aber nicht. Stattdessen müssen wir uns das Teilergebnis periodisch selber ermitteln und wenn wir das einmal pro Millisekunde tun würden, haben wir wenig Informationsgewinn und viel Overhead; wenn wir aber zu selten prüfen, ist die Aufgabe fertig, ohne daß wir den Fortschrittsbalken zu Gesicht bekommen.


C

Der Multithreaded-Ansatz. Der ist gleichzeitig "einfach" und "der schwerste". Ich muß mein Problem in Teilaufgaben zerlegen und versorge Write-Progress mit der Anzahl der erledigten Teilaufgaben.

Beispiel: Ich will einen Ordner samt Unterordnern löschen.
Also erstelle ich einen Task für jeden unmittelbaren Unterordner und lösche die jeweils rekursiv. Als Ordnerstruktur komm ich nicht in die Verlegenheit, daß sich die einzelnen Tasks gegenseitig stören.
Meine Ausgabe für Write-Progress wird jetzt den Verlauf abschätzen, indem nicht die Anzahl der Dateien, sondern die jeweils erfolgreich gelöschten Unterordner protokolliert werden; das hängt dann davon ab, wieviele Einzeltasks ich erstellen konnte. Nur einer und ich hab gar nichts gewonnen.
 
  • Gefällt mir
Reaktionen: konkretor, Tzk, Sparta8 und 2 weitere Personen

DPXone

Lieutenant
Dabei seit
Mai 2009
Beiträge
552
Ich möchte zu @RalphS noch anmerken, dass man bei großen Datenmengen die ProgressBar-Aktualisierung von PowerShell reduzieren muss, weil die ProgressBar sonst wirklich enorm an der Performance frisst. (Am Besten man macht das Generell)

Das lässt sich leicht durch ein mathematisches MODULO (in PowerShell durch "%") von 10% der Gesamtgröße lösen.
Im Beispiel unten wird die ProgressBar nur alle 10% aktualisiert.

Wenn ihr im Beispiel die If-Bedingung im Code entfernt und nur Write-Progress und "$progressBarCounter++" lasst, merkt ihr ihr den Performance Unterschied.


PowerShell:
Write-Host 'Get ChildItems for C:\. Please wait ...' 
$testInput = Get-ChildItem 'C:\' -Depth 3 -ErrorAction Ignore 

write-host 'Progress let''s go'
$progressBarCounter = -1 
$progressBarMax = @($testInput).Count 
$progressBarInterval = [math]::Ceiling($progressBarMax * 0.1) # 10% of Max

Foreach ($item In $testInput) { 
	$progressBarCounter++ 
	
	If ($progressBarCounter % $progressBarInterval -eq 0) { 
		Write-Progress 'Input' -PercentComplete($progressBarCounter / $progressBarMax * 100) 
	} 
	
	# '<<< Code >>>' 
}
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: RalphS, konkretor und derlorenz
R

RalphS

Gast
Ja, das war das, was ich meinte mit "man muß ein Auge drauf haben" und "aufpassen, daß man nicht mehr Overhead für die Darstellung generiert und am Ende auf die Progressbar wartet statt auf Abschluß des Tasks".

Das Progress-Control ist in seiner Auflösung arg beschränkt; in der ISE gibts noch einen graphischen Balken, den man ggfs. auf Bildschirmbreite auseinanderziehen kann, aber in PS selbst hat man eine ASCII-Repräsentation davon (mit "o") und halbe "o" gibt es nicht.

Im selben Atemzug versteht write-progress auch nur ganze Zahlen als Argument zu -PercentComplete, 0 bis 100, jeweils inclusive. Das sind zusammen 101 verschiedene Zustände. Mehr gehen nicht.

Entsprechend, wenn man 1000 Iterationen hat und aber nur 100 Zustände darstellen kann, dann ist in neun von zehn Fällen ganz einfach keine Änderung darstellbar und man kann diese neun unnützen Updates auch weglassen.

Ob man da in seiner Implementation optimieren kann muß man von Fall zu Fall unterscheiden. Von der Sache her könnte man mit einem Logarithmus kommen und so in Abhängigkeit der Schleifenlänge die Updatehäufigkeit dynamisch anpassen.... kann man machen, wenn man sowas ständig braucht und wenn einem tatsächlich ein signifikantes Performanceproblem auffällt, aber ich glaub im Normalfall ist das unnötig.
 
  • Gefällt mir
Reaktionen: derlorenz und areiland

new Account()

Banned
Dabei seit
Mai 2018
Beiträge
7.198
Zitat von RalphS:
Ob man da in seiner Implementation optimieren kann muß man von Fall zu Fall unterscheiden. Von der Sache her könnte man mit einem Logarithmus kommen und so in Abhängigkeit der Schleifenlänge die Updatehäufigkeit dynamisch anpassen.... kann man machen, wenn man sowas ständig braucht und wenn einem tatsächlich ein signifikantes Performanceproblem auffällt, aber ich glaub im Normalfall ist das unnötig.
Oder man setzt einfach eine zeitabhängige Updaterate statt basierend auf den Schleifendurchläufen ;)
 
  • Gefällt mir
Reaktionen: derlorenz
R

RalphS

Gast
Eh... kann man machen, aber zeitabhängig, da rennt man in mehr Probleme, als man sie löst. Schließlich muß man die Situation bereits im Vorhinein oder zhumindest währenddessen abschätzen.

Ein guter Ansatz ist schon, wie @DPXone bemerkt, die Updates funktional auf 100 = f(Iterationen) abzubilden.

Eine weitere Möglichkeit wäre, wenn man weiß, was vor sich geht (also der Verlauf nicht gerade totale Black Box ist), daß man sagt, ich hab eine quadratische/nlogn/lineare Laufzeit/sonstwas und das berücksichtigt.

Oder wie oben schon im Ansatz angedeutet, wir klassifizieren Iterationen nach ihrer Größenordnung und haben dann die Option, für die Progressbar nicht über alle Tasks zu iterieren, sondern stattdessen über 0 bis [math]::log($Zahl_der_Iterationen, $Größenordnung) * 100 (Prozent), oder unterwegs zu prüfen, ob wir in bezug auf besagte Größenordnung "jetzt" etwas ausgeben sollen oder nicht. Diese letztere Option ist die intuitivere.

Zusammenhang haben wir ja schon:
  • 101 Zustände gibt es als diskrete Menge, sprich am Ende suchen wir eine Hashfunktion
  • für Inputlängen bis 100 haben wir als 1:1 Länge nach Update
  • für Inputlängen bis 1000 haben wir 10:1 Länge nach Update
  • bis 10'000 dann 100:1

also können wir für den trivialen Fall (Größenordnung 10) mit sowas wie
PowerShell:
$index % [Math]::ceil(($inputlänge / 100)) -eq 0
kommen und müßten für andere GO mit Logarithmusfunktionen ran.
Mit ceil() und -eq 0 haben wir sogar Längen < 100 abgefrühstückt, da wir uns auf x mod 1 equivalent 0 für alle x berufen können.

Dann müssen wir uns nur noch fragen, ob uns das so gefällt, insbesondere mit den Randwerten (0 und 100%). Aber am Ende ist es einfach eine Abschätzung, eine Visualisierung; Exaktheit ist nicht erforderlich.
 
  • Gefällt mir
Reaktionen: derlorenz

new Account()

Banned
Dabei seit
Mai 2018
Beiträge
7.198
Zitat von RalphS:
Eh... kann man machen, aber zeitabhängig, da rennt man in mehr Probleme, als man sie löst.
Jein, man muss eigentlich nur das folgende machen:
Code:
Time updateInterval = time.Milliseconds(1.0/fps)

TimeStamp lastUpdate = time.now()
for (item in items){

    TimeStamp currentTime = time.now()
    if (lastUpdate + updateInterval < time.now()){
        updateProgressBar()
        lastUpdate = currentTime
    }
}
Oder übsehe ich etwas?

Tatsächlich macht man aber damit deutlich mehr Updates als mit seiner Methode, es sei denn die Progressbar läuft sehr schnell durch (dann ist aber die Zeit eh egal).

Zitat von RalphS:
  • 101 Zustände gibt es als diskrete Menge, sprich am Ende suchen wir eine Hashfunktion
Nicht wirklich eine Hashfunktion, eine Hashfunktion würde es auch erlauben den Zustand mehrfach zu treffen (und zudem die Reihenfolge durcheinander bringen).
Geeigneter wäre, wenn man den aktuellen Zustand berechnen würde und mit dem neuen abgleichen würde. Mit anderen Worten, wenn man den Fortschritt direkt auf [0;100] abbilden würde und prüfen würde, ob der Fortschritt dem vorherigen entspricht:
Code:
func currentProgressBarState(currentCount, maxCount) Interval<0,100> {
    return int(round((100*currentCount)/float(maxCount)))
}

int amountOfFiles = files.count()
int currentCount = 0
Interval<0,100> previousState = currentProgressBarState(currentCount, amountOfFiles)
for (file in files){
    doStuff()
    Interval<0,100> currentState = currentProgressBarState(++currentCount, amountOfFiles)
    if (currentState != previousState){
        updateProgressbar()
        previousState = currentState
    }
}
Exaktheit und nur sichtbare Updates
 
Zuletzt bearbeitet:
R

RalphS

Gast
Natürlich wird der Zustand mehrfach getroffen, dafür sorgt der Modulo. Darauf basiert das Ganze. Wenn ein gewisser Bucket beworfen wird, wird die Progressbar aktualisiert. Es gibt keine umkehrbare Zuordnung mehr, sondern meine Indexliste 1 bis n mit n linear gegen unendlich wird auf n gegen langsamer als linear gen unendlich eingestampft.
 
R

RalphS

Gast
Es geht nicht um den Prozentwert, es geht nur und ausschließlich um einen Trigger fürs Update. Die Zeit vergeht von ganz alleine und der Prozentwert für die Darstellung muß natürlich zusätzlich bestimmt werden.

Anspruch ist nur, daß es für sagen wir 100’000 Iterationen, dh potentiell 100’000 Updates der Fortschrittsanzeige, NICHT 100’000 dieser Updates gibt, sondern idealerweise nur so viele Updates, wie Zustände angezeigt werden können.

Das geht ultimativ so weit, daß wenn ich aus beliebiger Eingabelänge exakt 100 Matches ableiten kann, dann muß ich keinen Prozentwert berechnen, sondern kann den eingangs mit 0 initialisieren und dann ++ sagen. Funktioniert.

Alles was ich brauche sind 100 Teilintervalle. Jedes Intervall hat exakt ein Update. Exakt das ermittelt eine Hashfunktion - die muß aber natürlich zunächst bestimmt werden.

Und nein, md5 oder sha sind nicht gemeint, sondern eine richtige echte Hashfunktion zur Klassifizierung der Eingabe.
 

new Account()

Banned
Dabei seit
Mai 2018
Beiträge
7.198
Ah stimmt, damit macht es Sinn:
Zitat von RalphS:
sondern eine richtige echte Hashfunktion zur Klassifizierung der Eingabe

Zitat von RalphS:
die muß aber natürlich zunächst bestimmt werden.
Jup -> alternative: Prozentzahl direkt ausrechnen - wie von mir gezeigt (einfach, robust, exakt, etwas langsamer (falls die für dich perfekte Hashfunktion existieren sollte)
 
  • Gefällt mir
Reaktionen: RalphS
R

RalphS

Gast
So jetzt bin ich halbwegs aufnahmefähig :D und kapier erstmal richtig, was Du mit Zeitabhängigkeit meinst @new Account() .

Klar, einfach X Updates per Zeiteinheit geht natürlich auch. Wird unschärfer mit kürzerer Laufzeit, aber interessiert ja keinen, schon gar nicht, wenn es eh schon vorbei ist.

Danke für den Hinweis. :daumen:
 
Top