PowerShell GUI?
Hallo, bin gerade im Ferialpraktikum und habe die Aufgabe bekommen für dutzende (vlt sogar 100) PowerShell Skripte eine GUI zu erstellen. Man soll quasi Parameter und so etwas visuell eintragen können. Mein Chef meinte: "Er weiß auch noch nicht, ob es überhaupt möglich ist".
Welche einfachen und zeiteffizienten Wege gibt es dafür?
2 Antworten
Mit Poweshell hast Du vollzugriff auf alle .Net-Objekte, so auch auf die Formsbibliothek
In einfachsten Fall baust Du mit 3 Zeilen ein einfache GUI-Fenster.
Add-Type -AssemblyName System.Windows.Forms #einbinden der Bibliothek
$Form = New-Object System.Windows.Forms.Form #neues Form-Objekt
$Form.ShowDialog() #anzeigen
Natürlich kommst du mit dem Basisobjekt nicht weit, du musst zum Fenster weitere Objekte einfügen:
Add-Type -AssemblyName System.Windows.Forms #einbinden der Bibliothek
$Form = New-Object System.Windows.Forms.Form #neues Form-Objekt
$Form.Text = 'Irgendwas' #Titel
$Form.Size = '800,400' #größe (simplifiziert)
$Label = New-Object 'System.Windows.Forms.Label'
$Label.Location = '10,10'
$Label.AutoSize = $True
$Label.Text = 'Ich bin ein Label'
$TextBox = New-Object 'System.Windows.Forms.TextBox'
$TextBox.Location = '110,10'
$TextBox.Size = '180,20'
$Button1 = New-Object System.Windows.Forms.Button
$Button1.Location = '300,10'
$Button1.Size = '70,20'
$Button1.Text = 'Blubb'
$Button2 = New-Object System.Windows.Forms.Button
$Button2.Location = '300,30'
$Button2.Size = '70,20'
$Button2.Text = 'Blah'
$Label,$TextBox,$Button1,$Button2|
ForEach-Object{$Form.Controls.Add($_)} #bequem: alle zuvor definierten Objekte in einem Rutsch einbinden
$Form.ShowDialog() #anzeigen
Was nützen die beste Forms-Objekte , wen die nix machen? Hier kommt einer der wenigen Unterschiede zum hinzufügen von Ereignissen (gegenüber C#). Da Powershell über keine Zeiger auf den compilierten Code von Scriptblöcken hat, können diese auch nicht direkt an den Eventhandler übergeben werden. Deshalb gib es die Hilfsfunktion "Add_EventName( $Action_Scriptblock)"
Die Auszulösende Aktion (Delegat) wird in einem gewöhnlichen Scriptblock oder einer Funktion definiert.
Add-Type -AssemblyName System.Windows.Forms #einbinden der Bibliothek
$Form = New-Object System.Windows.Forms.Form #neues Form-Objekt
$Form.Text = 'Irgendwas' #Titel
$Form.Size = '800,400' #größe (simplifiziert)
$Label = New-Object 'System.Windows.Forms.Label'
$Label.Location = '10,10'
$Label.AutoSize = $True
$Label.Text = 'gib was ein:'
$OutputLabel = New-Object 'System.Windows.Forms.Label'
$OutputLabel.Location = '10,100'
$OutputLabel.AutoSize = $True
$OutputLabel.Text = 'Ich bin noch Leer :)'
$TextBox = New-Object 'System.Windows.Forms.TextBox'
$TextBox.Location = '110,10'
$TextBox.Size = '180,20'
$Button1 = New-Object System.Windows.Forms.Button
$Button1.Location = '300,10'
$Button1.Size = '70,20'
$Button1.Text = 'Click mich'
#was passieren soll wenn der Knopf gedrückt wird:
$Button1_Click = {
$OutputLabel.Text = $TextBox.Text #eingegebenen Text im Label der GUI anzeigen
Write-Host $TextBox.Text -fo green #dito in die Console schreiben
$global:blubb=$TextBox.Text #da mit dem schließen das komplette GUI-Objekt (und auch dessen Funktionsstack/-variablen) zerstört wird, in eine global gültige Variable speichern
}
$Button1.Add_Click($Button1_Click)
$Label,$OutputLabel,$TextBox,$Button1|
ForEach-Object{$Form.Controls.Add($_)} #alle zuvor definierten Objekte einbinden
$null=$Form.ShowDialog() #anzeigen
$global:blubb #unsere Variable abrufen
pause
Hinsichtlich der Definition der Objekteigenschaften ist Powershell sogar einfacher als C#.
Wenn Du etwas googlst, wirst Du Definitionen wie:
$Button.Size = New-Object System.Drawing.Size(120,23)
...finden. übertriebener Schwachsinn! Powershell ist die Sprache für Faulpelze oder Administratoren, welche sich nicht mit Nebensachen abgeben wollen. PowerShell gibt sich damit zufrieden, wenn man die Eigenschaften in einem String übergibt:
$Button.Size = "120,23"
Powershell erkennt den Datentyp Drawing.Size/ Drawing.Point / etc. automatisch und konvertiert den übergebenen String in#s passende Objekt
Geil....
Man kann das Ganze noch auf die Spitze treiben: Powershell-Batch-Hybrid... (kann man mit Doppelclick starten und unter Powershell <6.0 Umlaute verwenden )
clock.cmd (ja ist eine Batch di nichts weiter tut als sich srelbst als Powerschellsript zu übergeben😅)
<# : Batch Abschnitt ,Batch sieht in dieser Zeile die Eingabe aus der Datei # an ein SprungLabel, Powershell sieht den begin eines Kommentars.
:: etwas Tricky um Consolenfenster nur so kurz wie Möglich sichtbar zu lassen
:: erstmal minimierten Start von Powershell erzwingen ( bis Powershell den Parameter -WindowStyle Hidden ausführen kann)
:: akzeptiert UTF8 gespeicherte Scripte
start "" /min powershell -WindowStyle hidden "iex (gc '%~f0' -Encoding UTF8 -Raw | out-string)" &rem -ReadCount 0
::
exit /b
: Ende Batch und Powershellkommentar #>
using namespace System.Windows.Forms
using namespace System.Drawing
$WindowTitle='DesktopUhr'
#Wenn es bereits läuft raus
$Mut = New-Object System.Threading.Mutex($True, $WindowTitle)
if (!$Mut.WaitOne(0)) {
$Mut.Close();
(New-Object -c Wscript.Shell).Popup("$WindowTitle is running",2,"",0)
exit
}
#folgende 3 Zeilen nur nötig, wenn Script nicht mit "-WindowStyle hidden" gestartet wird!
#$user32=Add-Type -m '[DllImport("user32.dll")] public static extern void ShowWindow(IntPtr hWnd, int nCmdShow);' -Name myAPI -passthru
#$hwnd=(gps -id $PID).MainWindowHandle
#$user32::ShowWindow($hwnd, 0)
Add-Type -a 'System.Windows.Forms'
$Form = New-Object 'Form'
$Form.Text = $WindowTitle
$Form.BackColor = '0,0,0'
$Form.TransparencyKey = $Form.BackColor
$Form.TopMost = $True
$Form.FormBorderStyle = 0
$Form.Size = '250,90'
$Form.StartPosition = 'manual'
$Form.Location = '30,30'
$ClockDisplay = New-Object 'Label'
$ClockDisplay.Font = [Font]::new("Consolas",32,[FontStyle]3)
$ClockDisplay.Text = '{0:HH\:mm\:ss}' -f (Get-Date)
$ClockDisplay.AutoSize = $True
$ClockDisplay.ForeColor = '255,0,0'
$Form.Controls.Add($ClockDisplay)
$Timer1 = New-Object 'Timer'
$Timer1.Enabled = $True
$Timer1.Interval = 100
$Timer1_Action={
$ClockDisplay.Text = '{0:HH\:mm\:ss}' -f (Get-Date)
}
$Timer1.add_Tick($Timer1_Action)
#ma frech solch ein Formsobjekt anschauen (gigantisch)
#$Form|Export-Clixml -Path 'demo.xml'
$Null=$Form.ShowDialog()
$Mut.ReleaseMutex();
$Mut.Dispose();
Die von @JokesOnYou angesprochene Verwendung von WPF ist relativ umständlich, da man den GUI-Editor von VisualStudio benötigt.
Das XML zu schreiben, welches Die GUI definiert ist von Hand einfach zu umständlich
[xml]$xaml = @"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window">
<Grid>
<Label x:Name="Label"
Content="Ich bin ein Label"
Width="197"
Margin="30,10,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top" />
<Button x:Name="Button"
Content="Click Mich!"
Width="130"
Margin="300,10,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Grid.Column="0"
Grid.Row="0" />
</Grid>
</Window>
"@
Add-Type -AssemblyName PresentationFramework
#xml in darstellbare Objekte umwandeln:
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
$window = [Windows.Markup.XamlReader]::Load($reader)
#Augenoperation über den Anus XD:
$Label = $Window.FindName("Label")
$Button = $Window.FindName('Button')
# der Scriptblock wie gehabt
$Button.add_Click({
$Label.Content = "Butten wurd geclickt"
})
$window.ShowDialog()
allein die Zuweisung der im xml definierten Objekte an Powershellvariablen ist eine Farce... (rektale Augenoperation)
Ich empfehle Dir einfach Forms zu verwenden, Das Positionieren von Elementen ist ja nun kein Hexenwerk, welches unbedingt eines grafischen Editors bedarf.
Mit etwas Fantasie schreibst Du Dir einfach eigene Vorlagen.... (zur Not malst Du erstmal auf das gute alte Millimeterpappier)
Solch eine Chance völlig "nackt" mit Objekten zu agieren bekommst Du nicht so schnell wieder
kleiner Tipp noch, Du musst nicht alle Properties der Objekte im Kopf haben einfach in die Poweshellconsole eintippen:
Add-Type -AssemblyName System.Windows.Forms
New-Object System.Windows.Forms.Form|fl *
New-Object System.Windows.Forms.Button|fl *
New-Object System.Windows.Forms.TextBox|fl *
oder alle Feder ,Events und Methoden:;
New-Object System.Windows.Forms.Button|gm *
New-Object System.Windows.Forms.TextBox|gm *
Ne simple GUI mit WPF oder Window schreiben mit den Shell skripten in Kategorien und Funktionien unterteilt. Je nach ausgewählter Funktion müssten dann die zur Verfügung stehenden Parameter auf ihre Validity überprüft werden. Hier ein Link zu Stackoverflow wo jemand Powershell Skripte in C# ausführen lassen wollte:
https://stackoverflow.com/questions/33654318/c-sharp-run-powershell-command-get-output-as-it-arrives
Warum erst ein C#-Programm basteln um damit Powershell auszuführen.
Hirnrissig in einer C# Anwendung einen Posh-Runspace zu kreiren. Allein das Initialisieren dauert ewig...😴
Jetzt kommt mein altbekannte "vom Ferrari auf ein rostiges Fahrrad umsteigen"- Vergleich. So nenne ich es wenn jemand von einer Höheren auf eine Niedere Sprache wechselt.
Powershell ist zwar bequem, aber bietet nicht unbedingt den Spielraum von C#.
Andersherum kann es von Vorteil sein in Powershell die Möglichkeiten von C# zu nutzen (Geschwindigkeit, Multithreading etc.) Dafür gibt es Add-Type .
Das manuelle einbinden von WPF in Powershell ist Umständlich und Fehleranfällig. Sollte man sich verkneifen...
Am besten und Saubersten ist es einfach Forms-Objekte direkt per Powershell zu steuern...
Fast lächerlich einfach...