Powershell Progressbar erstellen

derlorenz

Lt. Commander
Dabei seit
MĂ€rz 2011
BeitrÀge
1.109
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/
 

derlorenz

Lt. Commander
Ersteller dieses Themas
Dabei seit
MĂ€rz 2011
BeitrÀge
1.109
@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.
 

RalphS

Lt. Commander
Dabei seit
Feb. 2015
BeitrÀge
1.464
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.
 

DPXone

Lt. Junior Grade
Dabei seit
Mai 2009
BeitrÀge
492
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:

RalphS

Lt. Commander
Dabei seit
Feb. 2015
BeitrÀge
1.464
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.
 

new Account()

Rear Admiral
Dabei seit
Mai 2018
BeitrÀge
5.662
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 ;)
 

RalphS

Lt. Commander
Dabei seit
Feb. 2015
BeitrÀge
1.464
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.
 

new Account()

Rear Admiral
Dabei seit
Mai 2018
BeitrÀge
5.662
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).

  • 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:

RalphS

Lt. Commander
Dabei seit
Feb. 2015
BeitrÀge
1.464
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.
 

new Account()

Rear Admiral
Dabei seit
Mai 2018
BeitrÀge
5.662

RalphS

Lt. Commander
Dabei seit
Feb. 2015
BeitrÀge
1.464
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()

Rear Admiral
Dabei seit
Mai 2018
BeitrÀge
5.662
Ah stimmt, damit macht es Sinn:
sondern eine richtige echte Hashfunktion zur Klassifizierung der Eingabe
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)
 

RalphS

Lt. Commander
Dabei seit
Feb. 2015
BeitrÀge
1.464
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