Kategorieauswahl

Translator

Google +1

Bitte teilen Sie Google mit, wenn Ihnen diese Seite gefällt.

Powershell GUI Programmierung für Dummies Step-by-Step

Ich habe ein Weilchen überlegt, ob ich eine Modulsammlung mit einfachen Befehlen schreibe, oder lieber die GUI-Programmierung erkläre. Da es jede Menge Codebeispiele im Internet gibt um auf die Schnelle das ein oder andere grafische Element zu erstellen, habe ich mich für die Erklärung entschieden, da dies wesentlich mehr Möglichkeiten zur eigenen Anpassung bietet. Für ganz Eilige habe ich ein Modul programmiert, welches ein paar grundsätzliche Funktionen bereit stellt. Die finden Sie am Ende des Artikels.

Grundgerüst

Grundsätzlich brauchen Sie in jedem grafischen Element folgenden Aufbau:

# Die ersten beiden Befehle holen sich die .NET-Erweiterungen (sog. Assemblys) für die grafische Gestaltung in den RAM.

 

 

 

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 
# Die nächste Zeile erstellt aus der Formsbibliothek das Fensterobjekt.
$objForm = New-Object System.Windows.Forms.Form
# Die letzte Zeile sorgt dafür, dass unser Fensterobjekt auf dem Bildschirm angezeigt wird. Das muss immer am Ende stehen.
[void] $objForm.ShowDialog()
Zugegeben, dass ist nun wirklich nicht gerade weltbewegend, aber es soll einfach mal klar machen, was eben das Grundgerüst eines jeden GUI-Elements darstellt. Das [void] vor den .NET-Aufrufen sorgt dafür, dass Rückgabewerte aus dem .NET verschluckt werden. So wie wir es zurück bekommen würde es auch unser Script ausgeben und das wollen wir normaler Weise nicht (insbesondere wenn Sie Ihre GUI-Elemente als Funktion verpacken)!

Position und Größe festlegen

Alle weiteren Befehle tragen Sie in Ihr Script bitte nach der grün gekennzeichneten Zeile ein, da diese Zeile das Fensterobjekt erstellt und alle weiteren Codeschnippsel unser Hauptfenster anpassen. Sind unsere Anpassungen abgeschlossen, sorgen wir mit der blauen Zeile für die Anzeige unseres fertigen Objekts. Die Zeile mit der Eigenschaft Startposition legt fest, wo auf dem Bildschirm unser Fenster auftauchen soll.

$objForm.StartPosition = "CenterScreen"

Wenn Sie auf $objForm ein get-member folgen lassen, also $objForm | gm in einer Zeile nach der grünen, dann sehen Sie die gesamten Events, Methoden und Eigenschaften, die Sie mit dem Fenster verwenden können. Dort werden Sie auch die Eigenschaft (Property) StartPosition finden. In der rechten Spalte sehen Sie dann die Objektklasse System.Windows.Forms.FormStartPosition welche die Funktionen bereit stellt. Vielleicht haben Sie sich auch gefragt welche Werte Sie sonst noch verfüttern können außer CenterScreen. Sorry, aber ich kenne auch nicht das komplette .NET-Framework auswendig, also fragen wir doch einfach die Powershell mittels der Klasse System.Enum in Form von:

[System.Enum]::GetNames("System.Windows.Forms.FormStartPosition")

Das müssen Sie gar nicht im Script eintippen, sondern das können Sie ganz normal in die Konsole eintippen. Leider funktioniert dies nicht bei allem Objekteigenschaften, sondern nur bei denen mit Stichworten. Aber das ist besser als nichts. Eine detaillierte Erklärung der einzelnen Werte von Eigenschaft finden Sie bei MSDN. Googeln Sie einfach nach der .NET-Klasse, also beispielsweise: System.Windows.Forms.FormStartPosition und klicken Sie auf den Link bei dem in der URL MSDN mit drin steht.

Die Cleveren unter Ihnen haben sich bestimmt schon gefragt, warum das Assembly System.Drawing geladen wird, da das obere Beispiel auch ohne die Zeile mit dem DrawingAssembly funktioniert. Das Drawing Assembly ermöglicht uns Position (dazu später mehr) und Größe der grafischen Elemente zu bestimmen. Die Größe ist nämlich ein eigenes Unterobjekt von unserem Fenster. So ähnlich wie am „Objekt“ Mensch auch Unterobjekte wie Arme und Beine dran sind. $objForm ist also unser Fenster und der Eigenschaft Size erzeugen wir nun ein eigenes Objekt, das für die Verwaltung der Größe zuständig ist. In diesem Fall ist unser Fenster in der Ausgangsversion also 300 Bildpunkt breit und 200 Bildpunkte hoch.

$objForm.Size = New-Object System.Drawing.Size(300,200)

Das Fenster können Sie wie ein übliches Anwendungsfenster nun auch kleiner oder größer ziehen oder durch festhalten der Titelleiste die positionieren. Die Fensterknöpfe zum Minimieren (in die Taskleiste) und Maximieren als auch zum schließen funktionieren auch bereits, ohne dass Sie dafür etwas hätten tun müssen. Aber das das mit dem Schließen funktioniert haben Sie bestimmt sowieso schon herausgefunden. 😉

Titelleiste

Die Titelleiste beschriften können Sie mit:

$objForm.Text = "Überschrift"

Textinhalt

Um einen Text ins Fenster hinein zu schreiben brauchen wir die Objektklasse Label aus dem Forms Assembly.

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,50) 
$objLabel.Size = New-Object System.Drawing.Size(100,20) 
$objLabel.Text = "Hallo Welt"
$objLabel.Name = "Hallo Welt"
$objForm.Controls.Add($objLabel)

In der ersten Zeile wird ein neues Objekt aus der Objektklasse Label erstellt. Dies hat mit unserem Hauptfenster an dieser Stelle noch gar nichts zu tun!
In der zweiten Zeile greifen wir nun wieder in das Drawingsassembly und erstellen ein Objekt Location und kleben das an unser Labelobjekt dran. Durch die Werte 10 und 50 wird ein Offset festgelegt um wie viele Bildschirmpunkte das Label später bei der Anzeige nach rechts (10 Punkte) und nach unten (50 Punkte) versetzt angezeigt werden soll.
In der dritten Zeile kleben Sie in ähnlicher Form nun das Objekt Size an unseren Text. Damit reservieren Sie die Größe Ihrer Textbox. Sollte Ihr Text nicht komplett angezeigt werden kann dies daran liegen, dass der Bereich den Sie reserviert haben nicht groß genug für die Anzeige ist. Hier wurden 100 Bildpunkte horizontal und 20 Bildpunkte vertikal vorgegeben.
In der vierten Zeile legen Sie dann endlich den darzustellenden Text fest. Der kann natürlich auch gerne in Form einer Variablen übergeben werden.
In der fünften Zeile geben Sie dem Objekt einen Namen. Das könnte in diesem Beispiel auch entfallen, aber wenn man aus anderen Programmteilen später darauf zurückgreifen möchte, ist es immer gut einen Namen festzulegen.
In der letzten Zeile wird nun unser Labelobjekt ($objLabel) in unser Hauptfenster ($objForm) eingebaut.

Wenn Sie sich verwundern, warum die Positionen und Größen im Beispiel so seltsam sind, machen Sie doch gleich eine kleine Übung mit eigenen Texten und rücken Sie sich alles schön zurecht.

Buttons

Dann wollen wir doch noch einen Knopf annähen?!

$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Name = "OK"
$OKButton.DialogResult = "OK"
$OKButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($OKButton) 

Die ersten fünf Zeilen sind denen aus dem Beispiel Textinhalt ähnlich. Daher nur die Information, dass wir hier auf die Objektklasse Button zurückgreifen um einen Knopf zu erstellen. Wer hätte das gedacht? 😉
Erklärungsbedürftig ist dafür dann Zeile 5+6! In Zeile 5 geben Sie den Wert an, der dem aufrufenden Objekt bei einem Klick zurück geliefert werden soll. Nach einm Klick auf einen Knopf wird dann der beim Knopf gesetzte Wert von DialogResult in die Eigenschaft Dialogresult von $objForm geschrieben.
Wenn Sie auf $OKButton oder $objForm ein get-member absetzen finden Sie bei beiden den Event Click. Sie können also nicht nur auf den Knopf, sondern auch am Fenster insgesamt abfragen, ob man es angeklickt hat. Bleiben wir bei unserem Knopf! Um den Knopf mit einer Funktion auszustatten kleben Sie noch das Click Ereignis an den Knopf dran (Zeile 6). Selbstverständlich gehen auch die anderen Events wie MouseDoubleClick, Mousehover, MouseLeave, etc… was auch immer Sie vorhaben. Grundsätzlich passiert dies durch $OKButton.Add_Click({}). In den Klammern steht dann Ihr Powershellscriptcode den Sie ausführen möchten, wenn der Knopf angeklickt wurde. Im Beispiel steht in der geschweiften Klammer $objForm.Close(). Durch $objForm.Close() schließen Sie das Hauptfenster.
In der letzten Zeile wird der Knopf an das Hauptfenster geklebt.

Wenn Sie nun ganz am Ende des Scripts, also nach dem ShowDialog einfach $objForm.DialogResult hinten dran schreiben, wird der Text den Sie bei $OKButton.DialogResult bei anklicken zugewiesen haben auf der Konsole ausgegeben. Natürlich können Sie mit $OKButton.DialogResult auch alles andere anstellen. Es ist ja „einfach nur eine Textvariable“. Falls Sie größere Codeabschnitte beim anklicken ausführen möchten können Sie natürlich auch entweder über Funktionen arbeiten, oder es so schreiben:

$OKButton.Add_Click({
 # ....weiterer Code
})

So, nun haben Sie einen OK-Knopf! Wie wäre es, wenn Sie den Code-Abschnitt des OK-Knopfes einfach noch ein 2. Mal unter den OKButton kopieren, die Zahlen für die Positionierung anpassen und die Variable $OKButton durch $CancelButton im kopierten Abschnitt ersetzen. Schon haben Sie zwei Knöpfchen. Auch der Code, der durch Klick auf den CancelButton ausgeführt wird sollte angepasst werden, damit unterschiedliche Werte für $objForm.DialogResult zurück geliefert werden, je nachdem was angeklickt wird.

Sollen die beiden Knöpfe nun auch bei Enter (= OK) oder Escape (= Cancel) ausgelöst werden bietet sich folgendes an:

$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter") { $ObjForm.DialogResult="OK geklickt";$objForm.Close()} })
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape") { $ObjForm.DialogResult="Cancel geklickt";$objForm.Close()} })

Hier wird der KeyDown Event noch an Ihr Fenster montiert. Wird dann bei ausgelöstem Keydown Ereignis (if $_.KeyCode -eq „Enter“) Enter gedrückt, wird in $ObjForm.DialogResult wieder „OK geklickt“ rein geschrieben und falls die gedrückte Taste „Escape“ (ESC) ist (if $_.KeyCode -eq „Escape“) dann kommt eben „Cancel geklickt“ in $ObjForm.DialogResult. Bei beiden Knöpfen wird auch noch das Fenster geschlossen durch $objForm.Close(). $objForm.KeyPreview = $True müssen immer mit dazu schreiben, sonst klappt das eher schlecht als recht.

Texteingabefeld

Möchten Sie Texte oder Zahlen vom Benutzer abfragen, können Sie das natürlich auch über benutzerfreundliche Fenster tun. Alles was Sie dazu brauchen ist folgender Code in dem o.a. Grundgerüst:

$objTextBox = New-Object System.Windows.Forms.TextBox 
$objTextBox.Location = New-Object System.Drawing.Size(10,100) 
$objTextBox.Size = New-Object System.Drawing.Size(100,20) 
$objForm.Controls.Add($objTextBox)

Damit bauen Sie sich ein Eingabefeld dazu. Eine Erklärung ist hier wohl überflüssig, was die einzelnen Zeilen bedeuten. Schön, dass wir nun ein Eingabefeld haben, aber wo wird das, was der Benutzer eingetippt hat hinterlegt? In:

$objTextBox.Text

Wenn Sie bei dem ScriptBlock für das Eingabefeld die Zeile:

 $objTextBox.Text = "Vorgabe"

ergänzen, können Sie schon einen Standardtext vor belegen lassen. Das komplette Script könnte also aktuell etwa so aussehen:

[void] [System.Reflection.Assembly]::LoadWithPartialName(„System.Drawing“)
[void] [System.Reflection.Assembly]::LoadWithPartialName(„System.Windows.Forms“)
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = „Dateneingabe“
$objForm.Size = New-Object System.Drawing.Size(400,300)
$objForm.StartPosition = „CenterScreen“
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq „Enter“) { $objForm.DialogResult=“OK“;$objForm.Close()} })
$objForm.Add_KeyDown({if ($_.KeyCode -eq „Escape“) { $objForm.DialogResult=“Cancel“;$objForm.Close()} })

$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = „OK“
$OKButton.DialogResult = „OK“
$OKButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($OKButton)

$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = „Cancel“
$CancelButton.DialogResult = „Cancel“
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = „Bitte geben Sie etwas ein:“
$objForm.Controls.Add($objLabel)
$objTextBox = New-Object System.Windows.Forms.TextBox
$objTextBox.Location = New-Object System.Drawing.Size(10,40)
$objTextBox.Size = New-Object System.Drawing.Size(260,20)
$objTextBox.Text = „Vorgabe“
$objForm.Controls.Add($objTextBox)

[void] $objForm.ShowDialog()

If ($objForm.DialogResult -like „OK“) {$objTextBox.Text} else {„Abbruch geklickt“}

Auf dieselbe Art und Weise können Sie auch mehrere Textbereiche einfügen. Genauso wie mit den Knöpfen.

Auswahlliste

Wie wäre es mit einer Auswahlliste? Hier sind die Grundlagen wieder identisch. Im vollständigen nachfolgenden Script sind die Besonderheiten der Listbox orange hervorgehoben:

 

 

 

 

 

 

 

 $x = @()
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 
$objForm = New-Object System.Windows.Forms.Form 
$objForm.Text = "Data Entry Form"
$objForm.Size = New-Object System.Drawing.Size(300,200) 
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $True $objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter") 
     {
         foreach ($objItem in $objListbox.SelectedItems)
             {$x += $objItem}
         $objForm.Close()
     }
    })
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape") 
     {$objForm.Close()}})
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click(
    {
         foreach ($objItem in $objListbox.SelectedItems)
             {$x += $objItem}
         $objForm.Close()
    })
$objForm.Controls.Add($OKButton)
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20) 
$objLabel.Size = New-Object System.Drawing.Size(280,20) 
$objLabel.Text = "Suchen Sie sich etwas aus:"
$objForm.Controls.Add($objLabel) 
$objListbox = New-Object System.Windows.Forms.Listbox 
$objListbox.Location = New-Object System.Drawing.Size(10,40) 
$objListbox.Size = New-Object System.Drawing.Size(260,20) 
$objListbox.SelectionMode = "MultiExtended"
[void] $objListbox.Items.Add("1. Element")
[void] $objListbox.Items.Add("2. Element")
[void] $objListbox.Items.Add("3. Element")
[void] $objListbox.Items.Add("4. Element")
[void] $objListbox.Items.Add("5. Element")
$objListbox.Height = 70
$objForm.Controls.Add($objListbox) 
[void] $objForm.ShowDialog()
$x

1. orange Zeile: $x wird hier als Array und nicht als String definiert, da unter Umständen es mehrere Elemente die angeklickt (mit Strg-Taste) wurden übergeben muss.

Darauf folgen 2 orange Codeabschnitte, die identisch sind. Der erste enthält die Reaktion für das Drücken der Enter-Taste, der zweite die Reaktion für einen Klick auf den OK-Knopf. Da bei beiden Aktionen dasselbe stattfinden soll steht natürlich auch derselbe Inhalt drin. Die Erklärung hierfür möchte ich aber erst einmal hinten anstellen und zunächst den 3. orangen Codeabschnitt erklären in dem das Auswahlfeld zusammen gebaut wird.

Auch hier erstellen wir uns in der 1. Zeile aus dem Windows.Forms Assembly zunächst das ListBox Objekt. Aus Zeile 2. und 3. Size und Location sollte an dieser Stelle klar sein (siehe weiter oben).
In Zeile 4 finden wir den sog. SelectionMode. Damit können Sie festlegen, ob mehrere Elemente oder nur eines ausgewählt werden können. Welche weiteren Alternativen außer MultiExtended noch zur Verfügung können Sie wieder über die Klasse System.Enum herausfinden (weiter oben beschrieben).
In den darauf folgenden Zeilen werden die einzelnen darzustellenden Elemente hinzugefügt. Selbstverständlich können Sie anstatt den darzustellenden Text direkt einzugeben auch Variablen übergeben. Dann aber bitte nur die Klammern, ohne die Anführungszeichen! Eleganter wäre in der Form vielleicht eine Schleife gewesen. Das können Sie ja als kleine Übung betrachten. 😉
Listbox.Height legt die vertikale Größe der Listbox fest. Eigentlich passiert das doch durch die Size Anweisung aus dem Drawing-Assembly?! Kommentieren Sie die Zeile doch einfach einmal aus und starten Sie das Script. Das ist bestimmt eine interessante Überraschung!
Zu guter Letzt wird unsere Listbox ins Fenster geschraubt.

So, nun zu dem was passiert, wenn OK geklickt, oder Enter gedrückt wird. Zunächst einmal wird eine foreach-Schleife angelegt, die so lange durchlaufen wird, wie es ausgewählte Elemente gibt. In $objListbox.SelectedItems sind alle ausgewählten Elemente enthalten. Diese werden dann der Reihe nach bei jedem Schleifendruchlauf zu $objItem. Im Schleifenrumpf werden dann die Werte in den Array $x eingespeist. Sind alle Elemte in den Array überführt ist die Schleife zu Ende und es wird $objForm.Close() ausgeführt, was das Fenster schließt.

Am Ende des Scripts wird dann wieder $x als Rückgabewert mit den ausgewählten Elementen ausgegeben.

Dropdownfeld

Ein Dropdownfeld oder auch Combox genannt funktioniert ähnlich wie die Auswahlliste. Sie müssen lediglich die Zeile mit „MultiExtended“ entfernen (eine Mehrfachauswahl wird vom Dropdownfeld nicht unterstützt), den Begriff Listbox gegen Combobox tauschen und bei den selected-items Einträgen das s am Schluß entfernen. Also ist es eigentlich sogar einfacher als die Auswahlliste. Hier das komplette Script:

 

 

 

 

 

 

 

 

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 
$objForm = New-Object System.Windows.Forms.Form 
$objForm.Text = "Combobox"
$objForm.Size = New-Object System.Drawing.Size(300,200) 
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter") 
     {
         foreach ($objItem in $objCombobox.SelectedItem)
             {$x += $objItem}
         $objForm.Close()
     }
    })
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape") 
     {$objForm.Close()}})
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click(
    {
         foreach ($objItem in $objCombobox.SelectedItem)
             {$x += $objItem}
         $objForm.Close()
    })
$objForm.Controls.Add($OKButton)
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20) 
$objLabel.Size = New-Object System.Drawing.Size(280,20) 
$objLabel.Text = "Treffen Sie bitte eine Auswahl:"
$objForm.Controls.Add($objLabel) 
$objCombobox = New-Object System.Windows.Forms.Combobox 
$objCombobox.Location = New-Object System.Drawing.Size(10,40) 
$objCombobox.Size = New-Object System.Drawing.Size(260,20) 
[void] $objCombobox.Items.Add("Item 1")
[void] $objCombobox.Items.Add("Item 2")
[void] $objCombobox.Items.Add("Item 3")
[void] $objCombobox.Items.Add("Item 4")
[void] $objCombobox.Items.Add("Item 5")
$objCombobox.Height = 70
$objForm.Controls.Add($objCombobox) 
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
$x

Kalenderauswahl

Benutzer nach einem Datum fragen ist eine Kunst für sich! Da kann man tausend Beispiele schreiben wie das Datum einzugeben ist und trotzdem machen sie es falsch. Abgesehen davon hat man die Probleme mit Schaltjahren und sonstigem Brimbamborium rund um die Datumsverwaltung. Obendrein müsste man den Text erst in ein Datumsformat konvertieren. Das ist alles gaaaanz gruselig. Also machen wir es uns einfach!

 

 

 

 

 

 

 

 

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 
$objForm = New-Object Windows.Forms.Form
$objForm.Text = "Wählen Sie ein Datum aus" 
$objForm.Size = New-Object System.Drawing.Size (190,200) 
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $True $objForm.Add_KeyDown({
     if ($_.KeyCode -eq "Enter") 
         {
             $WahlDatum=$objCalendar.SelectionStart
             $objForm.Close()
         }
     })
$objForm.Add_KeyDown({
     if ($_.KeyCode -eq "Escape") 
         {
             $objForm.Close()
         }
     })
$objCalendar = New-Object System.Windows.Forms.MonthCalendar 
$objCalendar.ShowTodayCircle = $False
$objCalendar.MaxSelectionCount = 1
$objForm.Controls.Add($objCalendar) 
[void] $objForm.ShowDialog() 
if ($WahlDatum)
     {
         Write-Host "Ausgewähltes Datum: $WahlDatum"
     }

Fertig ist der Kalender! Auch hier habe die neuen Stellen wieder orange markiert. Der Einfachheit halber sind keine OK oder Cancel-Buttons eingebaut. Es reagiert nur auf Enter oder ESC. Diese Funktion können Sie aber gerne als kleine Vertiefung nachrüsten. 😉

Die erste eingefärbte Zeile holt aus dem Kalenderobjekt (das wir weiter unten erstellen) das ausgewählte Datum ab und schreibt es in die Variable $WahlDatum. Das drumherum sollten Sie erkennen. Es sorgt dafür, dass dies nur passiert, wenn jemand Enter gedrückt hat.

Im nach folgenden Block wird das Kalenderobjekt erzeugt und eingestellt. ShowTodayCircle=$False blendet nur ein hässliches Viereck aus. MaxSelectionCount=1 sorgt dafür, dass nur ein Datum abgeholt wird. Der Rest des Blocks sollte klar sein.

Am Ende wird nur dann ein Datum zurückgegeben, wenn auch eins ausgewählt wurde.

Baumansicht

Die sog. Baumansicht oder auch Treeview genannt läßt sich auch recht einfach implementieren:

[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
$form1 = New-Object System.Windows.Forms.Form
$treeView1 = New-Object System.Windows.Forms.TreeView
$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
$form1.Text = "Überschrift"
$form1.Name = "form1"
$form1.DataBindings.DefaultDataSourceUpdateMode = 0
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 284
$System_Drawing_Size.Height = 262
$form1.ClientSize = $System_Drawing_Size
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 260
$System_Drawing_Size.Height = 238
$treeView1.Size = $System_Drawing_Size
$treeView1.Name = "treeView1"
$System_Windows_Forms_TreeNode_1 = New-Object System.Windows.Forms.TreeNode
$System_Windows_Forms_TreeNode_2 = New-Object System.Windows.Forms.TreeNode
$System_Windows_Forms_TreeNode_3 = New-Object System.Windows.Forms.TreeNode
$System_Windows_Forms_TreeNode_3.Text = "SubSub"
$System_Windows_Forms_TreeNode_3.Name = "Knoten2"
$System_Windows_Forms_TreeNode_2.Nodes.Add($System_Windows_Forms_TreeNode_3)|Out-Null
$System_Windows_Forms_TreeNode_2.Text = "SubA"
$System_Windows_Forms_TreeNode_2.Name = "Knoten1"
$System_Windows_Forms_TreeNode_1.Nodes.Add($System_Windows_Forms_TreeNode_2)|Out-Null
$System_Windows_Forms_TreeNode_4 = New-Object System.Windows.Forms.TreeNode
$System_Windows_Forms_TreeNode_4.Text = "SubB"
$System_Windows_Forms_TreeNode_4.Name = "Knoten3"
$System_Windows_Forms_TreeNode_1.Nodes.Add($System_Windows_Forms_TreeNode_4)|Out-Null
$System_Windows_Forms_TreeNode_5 = New-Object System.Windows.Forms.TreeNode
$System_Windows_Forms_TreeNode_5.Text = "SubC"
$System_Windows_Forms_TreeNode_5.Name = "Knoten4"
$System_Windows_Forms_TreeNode_1.Nodes.Add($System_Windows_Forms_TreeNode_5)|Out-Null
$System_Windows_Forms_TreeNode_1.Text = "SubB"
$System_Windows_Forms_TreeNode_1.Name = "Knoten0"
$treeView1.Nodes.Add($System_Windows_Forms_TreeNode_1)|Out-Null
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 12
$System_Drawing_Point.Y = 12
$treeView1.Location = $System_Drawing_Point
$treeView1.DataBindings.DefaultDataSourceUpdateMode = 0
$treeView1.TabIndex = 0
$form1.Controls.Add($treeView1)
$InitialFormWindowState = $form1.WindowState
$form1.add_Load($OnLoadForm_StateCorrection)
$form1.ShowDialog()| Out-Null
$treeView1.SelectedNode

Sollten Sie noch eine Erklärung benötigen hinterlassen Sie doch einen Kommentar.

Statusleiste und Karteikartenreiter

Hier noch ein kleines Beispiel für Karteikartenreiter und eine Statusleiste. Erklärung durch Kommentarzeilen im Script:

 [reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
$form1 = New-Object System.Windows.Forms.Form
$statusBar1 = New-Object System.Windows.Forms.StatusBar
$tabControl1 = New-Object System.Windows.Forms.TabControl
$tabPage1 = New-Object System.Windows.Forms.TabPage
$comboBox1 = New-Object System.Windows.Forms.ComboBox
$tabPage2 = New-Object System.Windows.Forms.TabPage
$button1 = New-Object System.Windows.Forms.Button
$form1.Text = "Reiter und Statusleiste"
$form1.Name = "form1"
$form1.DataBindings.DefaultDataSourceUpdateMode = 0
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 284
$System_Drawing_Size.Height = 262
$form1.ClientSize = $System_Drawing_Size
# Statusleiste anlegen und mit Ausgangswert (Text) belegen
$statusBar1.Name = "statusBar1"
$statusBar1.Text = "Auswahl: Noch nicht getroffen"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 284
$System_Drawing_Size.Height = 22
$statusBar1.Size = $System_Drawing_Size
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 0
$System_Drawing_Point.Y = 240
$statusBar1.Location = $System_Drawing_Point
$statusBar1.DataBindings.DefaultDataSourceUpdateMode = 0
$statusBar1.TabIndex = 1
$form1.Controls.Add($statusBar1)
# Auswahlreiter anlegen
$tabControl1.TabIndex = 0
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 228
$System_Drawing_Size.Height = 162
$tabControl1.Size = $System_Drawing_Size
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 23
$System_Drawing_Point.Y = 35
$tabControl1.Location = $System_Drawing_Point
$tabControl1.DataBindings.DefaultDataSourceUpdateMode = 0
$tabControl1.Name = "tabControl1"
$tabControl1.SelectedIndex = 0
$form1.Controls.Add($tabControl1)
# Auswahl Seite anlegen und an Auswahlreiter binden
$tabPage1.TabIndex = 0
$tabPage1.UseVisualStyleBackColor = $True
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 220
$System_Drawing_Size.Height = 136
$tabPage1.Size = $System_Drawing_Size
$tabPage1.Text = "Auswahlreiter"
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 4
$System_Drawing_Point.Y = 22
$tabPage1.Location = $System_Drawing_Point
$System_Windows_Forms_Padding = New-Object System.Windows.Forms.Padding
$System_Windows_Forms_Padding.All = 3
$System_Windows_Forms_Padding.Bottom = 3
$System_Windows_Forms_Padding.Left = 3
$System_Windows_Forms_Padding.Right = 3
$System_Windows_Forms_Padding.Top = 3
$tabPage1.Padding = $System_Windows_Forms_Padding
$tabPage1.Name = "tabPage1"
$tabPage1.DataBindings.DefaultDataSourceUpdateMode = 0
$tabControl1.Controls.Add($tabPage1)
# ComboBox auf Auswahltabseite kleben
$comboBox1.FormattingEnabled = $True
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 121
$System_Drawing_Size.Height = 21
$comboBox1.Size = $System_Drawing_Size
$comboBox1.DataBindings.DefaultDataSourceUpdateMode = 0
$comboBox1.Name = "comboBox1"
$comboBox1.Items.Add("Eins")|Out-Null
$comboBox1.Items.Add("Zwei")|Out-Null
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 18
$System_Drawing_Point.Y = 29
$comboBox1.Location = $System_Drawing_Point
$comboBox1.TabIndex = 0
$comboBox1.add_DropDownClosed({$statusBar1.Text = "Auswahl: "+$comboBox1.SelectedItem})
$tabPage1.Controls.Add($comboBox1)
# Beendenreiter anlegen
$tabPage2.TabIndex = 1
$tabPage2.UseVisualStyleBackColor = $True
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 220
$System_Drawing_Size.Height = 136
$tabPage2.Size = $System_Drawing_Size
$tabPage2.Text = "Beenden"
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 4
$System_Drawing_Point.Y = 22
$tabPage2.Location = $System_Drawing_Point
$System_Windows_Forms_Padding = New-Object System.Windows.Forms.Padding
$System_Windows_Forms_Padding.All = 3
$System_Windows_Forms_Padding.Bottom = 3
$System_Windows_Forms_Padding.Left = 3
$System_Windows_Forms_Padding.Right = 3
$System_Windows_Forms_Padding.Top = 3
$tabPage2.Padding = $System_Windows_Forms_Padding
$tabPage2.Name = "tabPage2"
$tabPage2.DataBindings.DefaultDataSourceUpdateMode = 0
$tabControl1.Controls.Add($tabPage2)
# Beenden Seite anlegen und an Beendenreiter binden
$button1.TabIndex = 0
$button1.Name = "button1"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 75
$System_Drawing_Size.Height = 23
$button1.Size = $System_Drawing_Size
$button1.UseVisualStyleBackColor = $True
$button1.Text = "Fertig"
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 28
$System_Drawing_Point.Y = 51
$button1.Location = $System_Drawing_Point
$button1.DataBindings.DefaultDataSourceUpdateMode = 0
$button1.add_Click({$form1.close();write-host $comboBox1.SelectedItem})
$tabPage2.Controls.Add($button1)
# Fenster anzeigen
$form1.ShowDialog()| Out-Null

Anregungen

Weiter oben hatte ich schon einmal erwähnt, dass man mit get-member ganz interessante Dinge über die Objekte herausfinden kann. Wenn Sie in Ihr Script z.B. bei dem Objekt $objForm | get-memeber einfügen werden Sie feststellen, dass man auch die Eigenschaften Fore- und Backcolor einstellen kann. Schreiben Sie doch einfach mal eine Zeile wie $objForm.Backcolor=“Yellow“ in Ihr Script. Welche Farben gibt’s? Nun, das ist doch sicher eine begrenzte Liste an Farben…Aus welcher Objektklasse die Farbe bereit gestellt wird steht beim get-member ja in der rechten Spalte. Ja, richtig! Da steht System.Drawing.Color als Objektklasse. Wie wäre es nun mit einem [System.Enum]::GetNames(„System.Drawing.Color“)? Schwupps…schon wissen Sie welche Farben Sie einstellen können. Ein Farbauswahlbox gibt es auch schon fertig im .NET-Laden zu „kaufen“:

[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
$colorDialog1 = New-Object System.Windows.Forms.ColorDialog
$colorDialog1.ShowDialog()
$colorDialog1.color

Auch das folgende Script sollte noch ein paar Anregungen geben:

function GenerateForm {
 # Assemblys laden
 [reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
 [reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
 # Objekte für Formen generieren
 $form1 = New-Object System.Windows.Forms.Form
 $label1 = New-Object System.Windows.Forms.Label
 $Open = New-Object System.Windows.Forms.Button
 $Cancle = New-Object System.Windows.Forms.Button
 $OK = New-Object System.Windows.Forms.Button
 $progressBar1 = New-Object System.Windows.Forms.ProgressBar
 $GruppeA = New-Object System.Windows.Forms.GroupBox
 $radioButton3 = New-Object System.Windows.Forms.RadioButton
 $radioButton2 = New-Object System.Windows.Forms.RadioButton
 $radioButton1 = New-Object System.Windows.Forms.RadioButton
 $GruppB = New-Object System.Windows.Forms.GroupBox
 $checkBox3 = New-Object System.Windows.Forms.CheckBox
 $checkBox2 = New-Object System.Windows.Forms.CheckBox
 $checkBox1 = New-Object System.Windows.Forms.CheckBox
 $openFileDialog1 = New-Object System.Windows.Forms.OpenFileDialog
 # Wenn Sie speichern Knöpfe haben möchten: $saveFileDialog1 = New-Object System.Windows.Forms.SaveFileDialog
 $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
 $checkbox=0,0,0 # Vorgabe in Form eines Arrays für die Checkboxen
 $radiobutton=1  # Vorgabe in Form einer Variablen für die Radiobuttons
 # Ereigniscode für die einzelnen Knöpfe in Variablen hinterlegt
 $Cancle_OnClick= 
 {
  # Wir wollen gar nicht wissen was mit dem Fenster passiert ist. Nur weg hier... ;-)
  $form1.close()
 }
 $OK_OnClick= 
 {
  # Bei Klick auf OK werden zum Schluß ein paar Status-Infos zurück gegeben
  $form1.close()
  write-host "Radiobutton: "$Radiobutton
  write-host "Checkboxstatus: "$checkbox -nonewline
  write-host
  write-host $label1.Text
  write-host "Fortschritt: "$progressBar1.Value
 }
 $Open_OnClick= 
 {
  # Hier wird der Datei öffnen Dialog erstellt. Ein openFileDialog1 | gm verrät mehr.
  $openFileDialog1.ShowHelp = $True
  $openFileDialog1.Title = "Datei öffnen"
  $openFileDialog1.InitialDirectory = "c:"
  $openfileDialog1.ShowDialog()
  $label1.Text=$openFileDialog1.FileName
  # In den nachfolgenden 5 Zeilen wird der Forschrittsbalken bei jeder Aktion etwas weitergerückt bis er voll ist und dann auf 0 zurück gesetzt
  if ($progressBar1.Value -lt 100) {
   $progressBar1.Value += 10
  } else {
   $progressBar1.Value = 0
  }
 }
 $OnLoadForm_StateCorrection=
 {# Damit das Fenster nicht versehentlich maximiert startet.
     $form1.WindowState = $InitialFormWindowState
 }
 $form1.BackgroundImageLayout = 3 # Hintergrundbild gekachelt, gestreckt, oder wie hätten wir's denn gerne?
 $form1.Font = New-Object System.Drawing.Font("Mistral",8.25,0,3,0) # Schriftart festlegen
 $form1.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon('C:\Pfad\IconDatei.ico') # Bild für das Icon festlegen
 $form1.Text = "Überschrift"
 $form1.Name = "form1"
 $form1.BackgroundImage = [System.Drawing.Image]::FromFile('C:\Pfad\Hintergrundbild.jpg') # Hintergrundbild einsetzen
 $form1.DataBindings.DefaultDataSourceUpdateMode = 0
 $form1.ForeColor = [System.Drawing.Color]::FromArgb(255,0,0,255) # Schriftfarbe festlegen
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 332
 $System_Drawing_Size.Height = 264
 $form1.ClientSize = $System_Drawing_Size
 $form1.Opacity = 0.8 # Legt die Durchsichtigkeit fest 1=100% 0.1=10%
 $label1.TabIndex = 6 # Tabindex legt die Tabulatorsprungreihenfolge fest
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 100
 $System_Drawing_Size.Height = 23
 $label1.Size = $System_Drawing_Size
 $label1.Text = "Keine Datei ausgewählt"
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 24
 $System_Drawing_Point.Y = 184
 $label1.Location = $System_Drawing_Point
 $label1.DataBindings.DefaultDataSourceUpdateMode = 0
 $label1.Name = "label1"
 $form1.Controls.Add($label1)
 $Open.TabIndex = 5
 $Open.Name = "Open"
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 91
 $System_Drawing_Size.Height = 22
 $Open.Size = $System_Drawing_Size
 $Open.UseVisualStyleBackColor = $True
 $Open.Text = "Open"
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 24
 $System_Drawing_Point.Y = 229
 $Open.Location = $System_Drawing_Point
 $Open.DataBindings.DefaultDataSourceUpdateMode = 0
 $Open.add_Click($Open_OnClick)
 $form1.Controls.Add($Open)
 $Cancle.TabIndex = 4
 $Cancle.Name = "Cancle"
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 75
 $System_Drawing_Size.Height = 23
 $Cancle.Size = $System_Drawing_Size
 $Cancle.UseVisualStyleBackColor = $True
 $Cancle.Text = "Cancle"
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 243
 $System_Drawing_Point.Y = 229
 $Cancle.Location = $System_Drawing_Point
 $Cancle.DataBindings.DefaultDataSourceUpdateMode = 0
 $Cancle.add_Click($Cancle_OnClick)
 $form1.Controls.Add($Cancle)
 $OK.TabIndex = 3
 $OK.Name = "OK"
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 75
 $System_Drawing_Size.Height = 23
 $OK.Size = $System_Drawing_Size
 $OK.UseVisualStyleBackColor = $True
 $OK.Text = "OK"
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 150
 $System_Drawing_Point.Y = 229
 $OK.Location = $System_Drawing_Point
 $OK.DataBindings.DefaultDataSourceUpdateMode = 0
 $OK.add_Click($OK_OnClick)
 $form1.Controls.Add($OK)
 # Eine Fortschrittsanzeige, die sich verändert je öfter man den Dateiöffnen Dialog aufruft
 $progressBar1.DataBindings.DefaultDataSourceUpdateMode = 0
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 100
 $System_Drawing_Size.Height = 23
 $progressBar1.Size = $System_Drawing_Size
 $progressBar1.TabIndex = 2
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 24
 $System_Drawing_Point.Y = 153
 $progressBar1.Location = $System_Drawing_Point
 $progressBar1.Name = "progressBar1"
 $progressBar1.Value = 0 # Die Skala geht von 0-100, die Änderung am Wert wird durch das Click-Ereignis beim Dateiöffnendialog vorgenommen
 $form1.Controls.Add($progressBar1)
 # Durch Einbau der Gruppierung, weiß .NET automatisch, dass innerhalb einer Gruppe immer nur 1 Radiobutton geklickt sein darf im Gegensatz zu den Checkboxen.
 $GruppeA.Name = "GruppeA" # Benamung des linke Gruppenfelds
 $GruppeA.Text = "Gruppe1"  # Überschrift für das linke Gruppenfeld
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 119
 $System_Drawing_Size.Height = 135
 $GruppeA.Size = $System_Drawing_Size
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 12
 $System_Drawing_Point.Y = 12
 $GruppeA.Location = $System_Drawing_Point
 $GruppeA.BackColor = [System.Drawing.Color]::FromArgb(255,153,180,209) # Hintergrundfarbe festlegen
 $GruppeA.TabStop = $False
 $GruppeA.TabIndex = 0
 $GruppeA.DataBindings.DefaultDataSourceUpdateMode = 0
 $form1.Controls.Add($GruppeA)
 $radioButton3.TabIndex = 2
 $radioButton3.Name = "radioButton3"
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 148
 $System_Drawing_Size.Height = 31
 $radioButton3.Size = $System_Drawing_Size
 $radioButton3.UseVisualStyleBackColor = $True
 $radioButton3.Text = "radioButton3"
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 16
 $System_Drawing_Point.Y = 86
 $radioButton3.Location = $System_Drawing_Point
 $radioButton3.DataBindings.DefaultDataSourceUpdateMode = 0
 $radioButton3.TabStop = $True
 $radioButton3.add_Click({$radiobutton=3})
 $GruppeA.Controls.Add($radioButton3)
 $radioButton2.TabIndex = 1
 $radioButton2.Name = "radioButton2"
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 149
 $System_Drawing_Size.Height = 27
 $radioButton2.Size = $System_Drawing_Size
 $radioButton2.UseVisualStyleBackColor = $True
 $radioButton2.Text = "radioButton2"
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 16
 $System_Drawing_Point.Y = 53
 $radioButton2.Location = $System_Drawing_Point
 $radioButton2.DataBindings.DefaultDataSourceUpdateMode = 0
 $radioButton2.TabStop = $True
 $radioButton2.add_Click({$radiobutton=2})
 $GruppeA.Controls.Add($radioButton2)
 $radioButton1.TabIndex = 0
 $radioButton1.Name = "radioButton1"
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 148
 $System_Drawing_Size.Height = 27
 $radioButton1.Size = $System_Drawing_Size
 $radioButton1.UseVisualStyleBackColor = $True
 $radioButton1.Text = "radioButton1"
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 17
 $System_Drawing_Point.Y = 20
 $radioButton1.Location = $System_Drawing_Point
 $radioButton1.DataBindings.DefaultDataSourceUpdateMode = 0
 $radioButton1.TabStop = $True
 $radioButton1.add_Click({$radiobutton=1})
 $GruppeA.Controls.Add($radioButton1)
 # Bei den Checkboxen hätte man auch auf die Gruppierung verzichten können. Ist so aber übersichtlicher - finde ich.
 $GruppB.Name = "GruppB" # Benamung des rechten Gruppenfelds
 $GruppB.Text = "Gruppe2" # Überschrift für das rechte Gruppenfeld
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 168
 $System_Drawing_Size.Height = 195
 $GruppB.Size = $System_Drawing_Size
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 150
 $System_Drawing_Point.Y = 12
 $GruppB.Location = $System_Drawing_Point
 $GruppB.BackColor = [System.Drawing.Color]::FromArgb(255,100,149,237)
 $GruppB.TabStop = $False
 $GruppB.TabIndex = 1
 $GruppB.DataBindings.DefaultDataSourceUpdateMode = 0
 $form1.Controls.Add($GruppB)
 $checkBox3.UseVisualStyleBackColor = $True
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 104
 $System_Drawing_Size.Height = 24
 $checkBox3.Size = $System_Drawing_Size
 $checkBox3.TabIndex = 2
 $checkBox3.Text = "checkBox3"
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 14
 $System_Drawing_Point.Y = 112
 $checkBox3.Location = $System_Drawing_Point
 $checkBox3.DataBindings.DefaultDataSourceUpdateMode = 0
 $checkBox3.Name = "checkBox3"
 $checkBox3.add_Click({$checkbox[2]=1})
 $GruppB.Controls.Add($checkBox3)
 $checkBox2.UseVisualStyleBackColor = $True
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 104
 $System_Drawing_Size.Height = 24
 $checkBox2.Size = $System_Drawing_Size
 $checkBox2.TabIndex = 1
 $checkBox2.Text = "checkBox2"
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 15
 $System_Drawing_Point.Y = 66
 $checkBox2.Location = $System_Drawing_Point
 $checkBox2.DataBindings.DefaultDataSourceUpdateMode = 0
 $checkBox2.Name = "checkBox2"
 $checkBox2.add_Click({$checkbox[1]=1})
 $GruppB.Controls.Add($checkBox2)
 $checkBox1.UseVisualStyleBackColor = $True
 $System_Drawing_Size = New-Object System.Drawing.Size
 $System_Drawing_Size.Width = 104
 $System_Drawing_Size.Height = 24
 $checkBox1.Size = $System_Drawing_Size
 $checkBox1.TabIndex = 0
 $checkBox1.Text = "checkBox1"
 $System_Drawing_Point = New-Object System.Drawing.Point
 $System_Drawing_Point.X = 15
 $System_Drawing_Point.Y = 23
 $checkBox1.Location = $System_Drawing_Point
 $checkBox1.DataBindings.DefaultDataSourceUpdateMode = 0
 $checkBox1.Name = "checkBox1"
 $checkBox1.add_Click({$checkbox[0]=1})
 $GruppB.Controls.Add($checkBox1)
 # Status des Fensters sichern
 $InitialFormWindowState = $form1.WindowState
 # Die Funktion bei Aufruf des aufrufen
 $form1.add_Load($OnLoadForm_StateCorrection)
 # Fenster anzeigen
 $form1.ShowDialog()| Out-Null
 }

# Aufruf des Fenster, denn es wurde ja in eine Funktion eingebaut
GenerateForm
So, nun haben Sie genug Basiswissen. Noch ein kleiner Tipp: Suchen Sie mal im Internet nach „Primal Forms Community Edition„. Primal Script selbst kostet Geld, aber die Community Edition finden Sie an einigen Ecken des Internet zum kostenlosen Download. Da ich nicht sicher bin, ob man dies einfach so verbreiten darf, habe ich den direkten Link hier leider weg lassen müssen. Damit haben Sie einen grafischen Editor, der Ihnen den Powershellcode für komplexe GUI-Elemente zusammen baut. Sie brauchen Ihn nur noch nach Ihren Wünschen anzupassen.

Wie versprochen für die ganz Eiligen ein Modul mit einfachen Powershellbefehlen um ein Fenster mit Knöpfen, Texten, Inputboxen und sogar einem Kalender zu erstellen. Folgende Befehle werden derzeit unterstützt:

new-guiwindow (legt das Hauptfenster an)
new-guibutton (erstellt einen Knopf)
new-guitextbox (erstellt einen Textabschnitt)
new-guiinputbox (erstellt ein Eingabefeld)
new-guicalendar (erstellt eine Kalenderauswahl für Datumsangaben)
new-guidropdown (erstellt eine DropDownbox)
close-guiwindow (kann interaktiv ein Fenster schliessen)
set-guitextbox (kann interaktiv den Inhalt eine Textbox anpassen)
get-guiinputbox (kann interaktiv den Inhalt aus einer Inputbox abrufen)
show-guiwindow (zeigt das zusammengebaute Fenster an und liefert Rückgabewerte in Form eines Hashs)

Sollte das Zip-File mit den Scripten und Installationshinweisen nicht zum Download angezeigt werden, klicken Sie bitte auf die Artikelüberschrift.

GuiHelper

Wenn Sie weitere interessante Praxistipps zu PowerShell lesen möchten oder PowerShell von Grund auf lernen möchten, werfen Sie doch einmal einen Blick in mein kostenloses Buch über PowerShell Programmierung. Auch das Kapitel über GUI-Programmierung mit PowerShell geht weit (z.B. Baumansichten – treeview, allerdings noch nicht veröffentlicht – nur für Spender ist die neueste Version zum download verfügbar) über das hier hinaus.

51 Antworten auf Powershell GUI Programmierung für Dummies Step-by-Step

  • Tabes sagt:

    Hey Martin,

    Perfekt ! Danke dir, nun läuft alles.

  • Tabes sagt:

    Hey Martin,

    irgendwie konnte ich bis jetzt noch nicht Antworten.

    Erst einmal VIELEN Dank für deine Hilfe.

    Das Script sieht so weit auch Perfekt aus, leider funktioniert es noch nicht so ganz.

    Er bringt beim „Senden“ immer den Fehler

    catch {
    # Wenn dabei was schief geht (z.B. SMTP nicht erreichbar)
    [System.Windows.Forms.MessageBox]::Show(“Ein Fehler ist aufgetreten beim Versenden der Mail” , “Mail”, 0, “Error”)
    }

    Also das „etwas“ nicht in Ordnung ist.

    Der SMTP Server ist zu 100% Erreichbar. Und die verwendeten Mailadressen sind ebenfalls gültig.

    Hast du da eine Idee woran das liegen könnte?

    Vielen dank !

    PS: Die Powershell zeigt garkeinen Fehler.

    • admin sagt:

      Hmmm…ich schätze mal es liegt an diesen beiden Zeilen, die ich einfach aus dem Beispiel übernommen habe:
      Send-MailMessage -to “test@test.com” -from “test1@test.com” -Subject $Betreff.text -body $Nachricht.text
      $PSEmailServer = “smtp.test.de”
      Bei Send-MailMessage ist kein SMTP-Server angegeben und danach wird eine Variable definiert, die wohl den Server angibt. Soll bestimmt eher so in der Art aussehen:
      Send-MailMessage -to “test@test.com” -from “test1@test.com” -Subject $Betreff.text -body $Nachricht.text -SmtpServer “smtp.test.de”
      Wenn es dann immer noch nicht geht, einfach mal das Try{} aussen herum entfernen und das komplette Catch {} auskommentieren. Dann wird der eigentliche Fehler (z.B. nicht berechtigter Absender) ausgegeben.

  • Tabes sagt:

    Hallo Martin,

    erst einmal vielen Dank für die Mühe die du dir hier gemacht hast!

    Ich habe folgendes Problem,

    Ich bin gerade dabei bzw gerade am verzweifeln und dabei über deinen Beitrag gestoßen.

    Mein Anliegen ist es eine GUI zu bauen, mit der unsere User ganz Simpel, mit Angabe von Betreff und Fehler eine Mail verfassen können.

    Der reine Code für diese Funktion ist eigentlich denkbar einfach, unter Angabe von Absendermail (welche schon fest hinterlegt sein soll) und Zieladresse (ebenfalls schon fest im Script hinterlegt) und SMTP Server (ebenfalls fest in Script hinterlegt), soll der User nun nur noch Subject und Body selbst eintippen.
    Eventuell noch ein Feld für den Namen des Users {(mehr aber auch nicht, das verwirrt Sie nur…;-)}

    Dann noch ein Button für Ok (Senden) oder Exit (Abbrechen) und fertig.

    Hier mal der Befehl der mit ausführen in der PS simpel zum Erfolg führt.

    Ich bekomm es einfach nicht hin dafür eine Oberfläche zu bauen.

    Send-MailMessage -to „test@test.com“ -from „test1@test.com“ -Subject „Test“ -body „Test“

    $PSEmailServer = „smtp.test.de“

    Kannst du mir irgendwie bei meinem Dilemma helfen ?

    Vielen Dank schon mal im voraus !!

    • admin sagt:

      Kein Dilemma, nur 5 Minuten 😉

      # Rahmen erstellen
      [void] [System.Reflection.Assembly]::LoadWithPartialName(„System.Drawing“)
      [void] [System.Reflection.Assembly]::LoadWithPartialName(„System.Windows.Forms“)
      $Fenster=New-Object System.Windows.Forms.Form
      $Fenster.Location=New-Object System.Drawing.Size(10,120)
      $Fenster.Size=New-Object System.Drawing.Size(420,400)
      $Fenster.Text=“Überschrift“

      # OK-Knopf rein
      $OKKnopf=New-Object System.Windows.Forms.Button
      $OKKnopf.Location=New-Object System.Drawing.Size(10,300)
      $OKKnopf.Size=New-Object System.Drawing.Size(75,23)
      $OKKnopf.Text=“OK“
      $OKKnopf.Name=“OK“
      $OKKnopf.DialogResult=“OK“
      $OKKnopf.Add_Click({$Fenster.Close()})
      $Fenster.Controls.Add($OKKnopf)

      # Cancel- bzw. Abbruch Knopf rein
      $CancelKnopf=New-Object System.Windows.Forms.Button
      $CancelKnopf.Location=New-Object System.Drawing.Size(300,300)
      $CancelKnopf.Size=New-Object System.Drawing.Size(75,23)
      $CancelKnopf.Text=“Cancel“
      $CancelKnopf.Name=“Cancel“
      $CancelKnopf.DialogResult=“Cancel“
      $CancelKnopf.Add_Click({$Fenster.Close()})
      $Fenster.Controls.Add($CancelKnopf)

      # Überschrift Betreff
      $InfoBetreff=New-Object System.Windows.Forms.Label
      $InfoBetreff.Location=New-Object System.Drawing.Size(10,10)
      $InfoBetreff.Size=New-Object System.Drawing.Size(75,23)
      $InfoBetreff.Text=“Betreff“
      $Fenster.Controls.Add($InfoBetreff)

      # Betreff Feld zum ausfüllen
      $Betreff=New-Object System.Windows.Forms.Textbox
      $Betreff.Location=New-Object System.Drawing.Size(10,35)
      $Betreff.Size=New-Object System.Drawing.Size(380,23)
      $Betreff.Text=“Betreff“
      $Betreff.Name=“Betreff“
      $Fenster.Controls.Add($Betreff)

      # Überschrift Nachricht
      $InfoNachricht=New-Object System.Windows.Forms.Label
      $InfoNachricht.Location=New-Object System.Drawing.Size(10,62)
      $InfoNachricht.Size=New-Object System.Drawing.Size(75,23)
      $InfoNachricht.Text=“Nachricht“
      $Fenster.Controls.Add($InfoNachricht)

      # Nachrichtenfeld zum ausfüllen
      $Nachricht=New-Object System.Windows.Forms.Textbox
      $Nachricht.Location=New-Object System.Drawing.Size(10,85)
      $Nachricht.Size=New-Object System.Drawing.Size(380,200)
      $Nachricht.Text=“Nachricht“
      $Nachricht.Name=“Nachricht“
      $Nachricht.Multiline=$true
      $Fenster.Controls.Add($Nachricht)

      # Fenster anzeigen
      [void] $Fenster.ShowDialog()

      if ($Fenster.dialogresult -eq „OK“) {
      # schick die Mail
      try {
      Send-MailMessage -to “test@test.com” -from “test1@test.com” -Subject $Betreff.text -body $Nachricht.text
      $PSEmailServer = “smtp.test.de”
      [System.Windows.Forms.MessageBox]::Show(„Mail wurde erfolgreich verschickt“ , „Mail“, 0, „Information“)
      }
      catch {
      # Wenn dabei was schief geht (z.B. SMTP nicht erreichbar)
      [System.Windows.Forms.MessageBox]::Show(„Ein Fehler ist aufgetreten beim Versenden der Mail“ , „Mail“, 0, „Error“)
      }
      } else {
      # schick keine Mail
      [System.Windows.Forms.MessageBox]::Show(„Mail wurde nicht verschickt. Abbruch durch den Benutzer“ , „Mail“, 0, „Exclamation“)
      }

  • tobias.krenz sagt:

    Vielen Dank für diese Anleitung! Sie ist ein guter Einstieg in die Materie der GUI-Programmierung in PowerShell.

    Durch das Anwenden tauch allerdings bei mir ein Problem in Zusammenhang mit der Listbox auf… Folgendes Script:
    [void] [System.Reflection.Assembly]::LoadWithPartialName(„System.Windows.Forms“)
    [void] [System.Reflection.Assembly]::LoadWithPartialName(„System.Drawing“)

    $Hauptfenster = New-Object System.Windows.Forms.Form
    $Hauptfenster.Text = „Backuptool“
    $Hauptfenster.StartPosition = „CenterScreen“
    $Hauptfenster.Size = New-Object System.Drawing.Size(800,600)

    $objListbox = New-Object System.Windows.Forms.Listbox
    $objListbox.Location = New-Object System.Drawing.Size(10,100)
    $objListbox.Size = New-Object System.Drawing.Size(250,200)
    $objListbox.SelectionMode = „MultiSimple“
    $objListbox.Font = New-Object System.Drawing.Font(„Arial“, 10)

    [void] $objListbox.Items.Add(„1“)
    [void] $objListbox.Items.Add(„2“)
    [void] $objListbox.Items.Add(„3“)
    [void] $objListbox.Items.Add(„4“)

    $Hauptfenster.Controls.Add($objListbox)

    $x = @()
    $LokaleSicherung = New-Object System.Windows.Forms.Button
    $LokaleSicherung.Location = New-Object System.Drawing.Size(100,500)
    $LokaleSicherung.Size = New-Object System.Drawing.Size(150,50)
    $LokaleSicherung.Text = „Lokales Backup“
    $LokaleSicherung.Name = „LokaleSicherung“
    $LokaleSicherung.DialogResult = „Yes“
    $LokaleSicherung.Font = New-Object System.Drawing.Font(„Arial“, 11)
    $LokaleSicherung.Add_Click(
    {

    foreach($objItem in $objListbox.SelectedItems)
    {
    $x += $objItem

    }
    $Hauptfenster.Close()

    })
    $Hauptfenster.Controls.Add($LokaleSicherung)

    [void] $Hauptfenster.ShowDialog()

    Das ist nur ein kleiner Teil dessen, was es mal werden soll. Mein Problem ist jetzt, dass $x leer ist, obwohl ich Punkte angewählt habe und den Button „LokaleSicherung“ angeklickt habe. Wie kann ich an die entsprechenden Informationen kommen?

    Vielen Dank im Voraus 🙂

    • admin sagt:

      Nur ein bisschen zu kompliziert gedacht 😉
      So sollte es wohl aussehen:
      [void] [System.Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”)
      [void] [System.Reflection.Assembly]::LoadWithPartialName(“System.Drawing”)

      $Hauptfenster = New-Object System.Windows.Forms.Form
      $Hauptfenster.Text = “Backuptool”
      $Hauptfenster.StartPosition = “CenterScreen”
      $Hauptfenster.Size = New-Object System.Drawing.Size(800,600)

      $objListbox = New-Object System.Windows.Forms.Listbox
      $objListbox.Location = New-Object System.Drawing.Size(10,100)
      $objListbox.Size = New-Object System.Drawing.Size(250,200)
      $objListbox.SelectionMode = “MultiSimple”
      $objListbox.Font = New-Object System.Drawing.Font(“Arial”, 10)

      [void] $objListbox.Items.Add(“1″)
      [void] $objListbox.Items.Add(“2″)
      [void] $objListbox.Items.Add(“3″)
      [void] $objListbox.Items.Add(“4″)

      $Hauptfenster.Controls.Add($objListbox)

      $x = @()
      $LokaleSicherung = New-Object System.Windows.Forms.Button
      $LokaleSicherung.Location = New-Object System.Drawing.Size(100,500)
      $LokaleSicherung.Size = New-Object System.Drawing.Size(150,50)
      $LokaleSicherung.Text = “Lokales Backup”
      $LokaleSicherung.Name = “LokaleSicherung”
      $LokaleSicherung.DialogResult = “Yes”
      $LokaleSicherung.Font = New-Object System.Drawing.Font(“Arial”, 11)
      $LokaleSicherung.Add_Click({
      < # foreach($objItem in $objListbox.SelectedItems) { $x += $objItem } #>
      $Hauptfenster.Close()

      })
      $Hauptfenster.Controls.Add($LokaleSicherung)

      [void] $Hauptfenster.ShowDialog()
      $objListbox.SelectedItems

      Das hier auskommentierte foreach war zuviel des Guten und das was Sie in $x erhofften finden Sie in der letzten Zeile.
      Weiterhin würde ich Ihnen das Buch empfehlen, dass inzwischen noch besser und aktueller die GUI-Programmierung beschreibt (auch im kostenfreien Teil bereits enthalten).
      http://www.martinlehmann.de/wp/computer/powershell-openbook-kostenlos-online/

      • tobias.krenz sagt:

        Vielen Dank für die schnelle und einfache Antwort!
        Da hab ich wirklich etwas zu kompliziert gedacht und stand auf dem Schlauch…
        Mit dem Buch werde ich mich auch noch genauer beschäftigen 🙂

  • moses-ms sagt:

    Hallo Martin,

    mit Erschrecken habe ich festgestellt das ich mich für Deine Tips noch gar nicht bedankt habe – DANKE- und alles funktioniert super.
    Aber zwei weitere Frage habe ich doch noch (war nicht anders zu erwarten ;-))

    1. Ist es möglich den Eintrag in der Startzeile zu unterdrücken? Die GUI läuft permanent und die weitere Steuerung Start/Exit würde ich über ein Tray Icon ermöglichen.

    2. Desweiteren habe ich ein Problem bei der Weitergabe / Übernahme einer Variable bei Pipeline Befehl. Folgendes:

    $Array |where {$_.Spalte -eq „SuchText“} oder $Array |where {$_.Spalte -ne „SuchText“} = Beide Abfragen funktionieren ohne Probleme, wenn diese manuell oder im Script eingegeben werden. Die Vergleichsoperatoren und evtl auch der „SuchText“ werden mittels Checkboxen definiert und hier liegt das Problem! Den SuchText in eine Variable packen funktioniert auch noch aber nicht den Vergleichoperator. Versuche ich es manuell und führe die Variable aus wird alles richtig angezeigt aber es funktioniert leider nicht beim Pipeline.

    Folgende Tests habe ich gemacht:
    $Test = “ -eq „“SuchText“““ – ohne Erfolg
    [string]$Test = “ -eq „“SuchText“““ – ohne Erfolg
    $Test = “ -eq „‚SuchText'““ – ohne Erfolg
    Das sind die an die ich mich noch erinnere :-/

    Gruß & Danke im Voraus

    moses-ms

    Achso gibt es etwas neues zum Buch?

    • admin sagt:

      Hi,

      leider habe ich bei beiden Fragen Probleme diese zu verstehen bzw. nachzuvollziehen.

      zu 1.) Welcher Eintrag in welcher Startzeile? Vielleicht hilft es mir, wenn Du mir einen Screenshot mailst.
      zu 2.) Da wirst Du einen Verbesserungsvorschlag bei Microsoft einreichen müssen ;-). Denn:
      PS C:\Users\lm4037x> &(„`“$a`“ $vgl `“$b`““)
      & : The term ‚“abc“ -eq „abc“‚ is not recognized as the name of a cmdlet, function, script file, or operable program.
      Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
      At line:1 char:2
      + &(„`“$a`“ $vgl `“$b`““)
      + ~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : ObjectNotFound: („abc“ -eq „abc“:String) [], CommandNotFoundException
      + FullyQualifiedErrorId : CommandNotFoundException
      in diesem ^^ Beispiel scheint er ja alles korrekt geliefert zu bekommen, zu expandieren und er versucht es auch auszuführen, erkennt aber, warum auch immer, den Ausdruck „abc“ -eq „abc“ nicht als gültigen Befehl an. Das Einzige wäre ein Workaround in dem Du für jede Vergleichsversion, die Du anbietest, ein Switch oder If formulierst. Z.B.:
      if ($Vergleich -eq „-eq„) { $Array | where {$_.Spalte -eq $Suchtext} }
      elseif ($Vergleich -eq „-ne„) { $Array | where {$_.Spalte -ne $Suchtext} }
      else { throw „Wo kommt der denn her?“ }

      • moses-ms sagt:

        Danke für die Antwort zu 2., das macht die Sache „etwas“ komplizierter.
        Bei der ersten Frage meine ich folgendes:
        Startet man ein Programm (die meisten jedenfalls) wird in der Startzeile ein „Feld/Button/Eintrag…“ erzeugt. Über diesen kann ich dann das Prog. in die Ansicht holen oder ablegen. Einige Tools erzeugen auch nur ein TrayIcon und werden gar nicht in der Startzeile angezeigt. Ich möchte meine GUI so gestalten, dass diese nur ein TrayIcon erzeugt.

        ScreenShot möglich, wenn ja wohin?

        • admin sagt:

          Ah…Knopf…Rechts/Unten inder Ecke, wo normaler Weise auch die Uhr steht, richtig?! Kein Problem:
          Auch hier hilft uns wieder die System.Windows.Forms-Klasse weiter:

          [void] [System.Reflection.Assembly]::LoadWithPartialName(„System.Windows.Forms“) # Unterstützung laden
          $trayicon=New-Object windows.forms.notifyicon # Objekt anlegen, Klassenbeschreibung gibt’s hier: https://msdn.microsoft.com/de-de/library/system.windows.forms.notifyicon%28v=vs.110%29.aspx
          $trayicon=“C:\Pfad\zur\Datei.ico“ # Achtung!!! Da kann man nicht irgendein Bildchen reinpappen, dass sollte schon eine Datei im „ico“ Format sein.
          $Menuitem=New-Object System.Windows.Forms.MenuItem # Textmenü-Eintrag anlegen
          $Menuitem.text=“Quit“ # Beschriften
          $Menuitem.add_click({$TrayIcon.visible=$false}) # Was passiert, wenn man den Menüeintrag anklickt
          $Menu=New-Object System.Windows.Forms.ContextMenu # Das Kontextmenü anlegen
          $Menu.menuitems.addrange($Menuitem) # Den Menüeintrag, dem Menü hinzufügen
          $TrayIcon.contextmenu=$Menu # Das Menü, dem TrayIcon hinzufügen
          $TrayIcon.visible=$True # Scotti…auf den Schirm

          Minimal-Beispiel. Wie üblich geht da noch viel mehr!!! 😉 Z.b. diese Sprechblasen anzeigen, etc…

          • moses-ms sagt:

            Moin,
            ja, das mit dem Tray Icon hatte ich schon für mich gelöst, aber dafür auch Danke. Mein Anliegen ist aber ein anderes. Starte doch einfach mal ein Programm. Dann wird in der Taskleiste ein „Symbol“ erzeugt und darüber kann ich das Programm mit der re. Maustaste schließen etc.
            Ich würde gern das Symbol in der Taskleiste, für meine GUI, ausblenden.

          • admin sagt:

            Moin auch,

            ich glaub das sollte es sein, was Du meinst, oder?

            [void] [System.Reflection.Assembly]::LoadWithPartialName(„System.Drawing“)
            [void] [System.Reflection.Assembly]::LoadWithPartialName(„System.Windows.Forms“)
            $objForm = New-Object System.Windows.Forms.Form
            $objForm.ShowInTaskbar=$false
            $objForm.ShowDialog()

  • Andy sagt:

    Hoi Martin

    Bezüglich der Windows Forms Problematik in PS 3.0 mit leeren Returnwerten hier mal der Workaround für die Skripte
    Bsp Textboxinhalt statt
    $x=$objTextBox.Text
    Variable mit Scope definieren
    $script:x=$objTextBox.Text

    Dann bekommt $x auch einen Returnwert in Powershell 3.0
    hat mich ne Menge Haare gekostet 😉

    siehe auch hier :
    http://social.technet.microsoft.com/Forums/windowsserver/en-US/bc95fb6c-c583-47c3-94c1-f0d3abe1fafc/powershell-30-rtm-appears-to-break-windows-forms?forum=winserverpowershell

    • admin sagt:

      Das glaube ich gerne. Das was hier über GUI steht ist leider schon sehr alt. Wie das alles wesentlich eleganter, z.B. auch ohne den Umstand mit $script Scope-Bezeichner geht steht inzwischen alles in der PDF-Version des Buches, dass am Ende des Artikels erwähnt ist.

  • Carl Schreiber sagt:

    Hallo,

    ich bin auf de Suche nach einer geeigneten GUI auch auf Ihrer Seite gelandet – sehr interessant, allerdings geht das nicht auch mit viel weniger Programmierzeilen? Erstens wäre das viel leichter zu warten, zweitens (hängt mit erstens zusammen) viel übersichtlicher und drittens bin ich schreibfaul 😉 das ist aber zugegeben nur eine persönliche Kategorie.

    Ich bin jetzt auf der Suche nach einer GUI (will eine machen), die mehrere Socketverbindungen in gleicher Weiser sichtbar macht. klingt jetzt nach einem GUI-Script, das zunächst das Fenster definiert (s.o.):
    # Load external assemblies
    [void][Reflection.Assembly]::LoadWithPartialName(„System.Windows.Forms“)
    [void][Reflection.Assembly]::LoadWithPartialName(„System.Drawing“)

    $myGUI = New-Object System.Windows.Forms.Form
    $myGUI.Font = New-Object System.Drawing.Font(„Mistral“,8.25,0,3,0) # Schriftart festlegen
    $myGUI.Text = „Socket-Traffic“
    $myGUI.Name = „myGUI“
    $myGUI.DataBindings.DefaultDataSourceUpdateMode = 0
    $myGUI.ForeColor = [System.Drawing.Color]::FromArgb(255,0,0,255) # Schriftfarbe festlegen
    $System_Drawing_Size = New-Object System.Drawing.Size
    $System_Drawing_Size.Width = 332
    $System_Drawing_Size.Height = 264
    $myGUI.ClientSize = $System_Drawing_Size

    aber dann per Function zu jedem Socket, dessen relevanten Daten in jew. ’seinem‘ Hash abgelegt sind in dieser GUI ’sein‘ Tab erzeugt mit für jeden Hash (= Socket) gleichen Anzeigegruppen:

    $Sockets = $HASH1,$HASH2,$HASH3,$HASH4
    $n = 0 # do I need that?
    foreach ($h in $Sockets) {
    makeTab $myGUI $h $n
    $n++
    }
    function makeTab {
    param (
    [parameter(Mandatory=$true)]
    [PSObject] $gui,
    [parameter(Mandatory=$true)]
    [PSObject] $hashTable, # with all info to connect and vars.
    [parameter(Mandatory=$true)]
    [int] $numTab # brauch ich das?
    )
    ….
    }

    Es sollten (auch als Beispiel, um von da in andere Verwendungen selbst weiter zu entwickeln) folgende Anzeigegruppen erzeugt werden:

    $x_OnClick = { Write-host „Button geklickt, mach. ‚was..“ }

    1) Send-Zeile Gruppe # Texteingabe
    a) Line to enter test meant to sent.
    b) Button to send # kein ‚cancle‘
    2) Login-Gruppe:
    a) status: Login=Green.ico, Connected=Orange.ico, nothing=red.ico
    b) Button: Login (Connect (IP & Port vom Hash) & Login = Sende LoginZeile)
    c) Button: Logout (Logout & Disconnect)
    3) Logging-Gruppe:
    a) letzte 2 gesendete Zeilen
    b) letzte 2 erhaltenen Zeilen
    4) Status Gruppe
    a) Text zeilen mit versch Status informationen
    b) Text zeilen mit versch Status informationen
    c) Text zeilen mit versch Status informationen

    5) Ein kleine Gruppe mit 2,3 Radiobuttons
    6) Ein letzte Gruppe mit Check-Boxen
    7) Zum Schluss natürlich
    a) Button das Script zu beenden (Inkl. Logout & Disconnect)

    • admin sagt:

      Hallo Herr Schreiber,

      es gibt diverse Programme mit denen man GUIs zeichnen kann und dann im XAML-Format abspeichern. Im Buch PowerShell 123 (auf das am Ende des Artikels verwiesen wird, sind in der PDF-Fassung bereits Angaben, wie man XAML-Dateien in Powershell einbindet vorhanden. Für einfache Dinge könnten Sie auch das Modul GUI-Helper.psm1 (ebenfalls am Ende des Artikels) herunterladen. Das bietet dann so einfache Cmdlets, wie wie New-GUIWindows, oder New-GUIButton, falls Sie das mit kürzer meinten. Im obigen Beispielscript würde ich die Function einfach in die Foreach-Schleife stecken, um es etwas einfacher zu gestalten.

  • moses-ms sagt:

    Hallo,
    Dein Buch hat mir bei der Erstellung meiner GUI sehr geholfen! Natürlich werden die „Wünsche“ bei der GUI immer größer. Das spornt einen natürlich auch an. Leider befinde ich mich gerade in einer Sackgasse und hoffe Du kannst mir da etwas weiterhelfen. Ich habe folgendes Problem:
    Ich prüfe eine Netzwerkverbindung mittels „Test-Connection“, besteht diese importiere ich mittels „Import-CSV“ eine CSV Datei. Besteht die Verbindung nicht wird eine TextBox und ein Button über „…Controls.Remove($…)“ weggenommen und ein anderer Button eingeblendet. Bis dahin alles OK und klappt auch wunderbar. Beim klick auf den Button wird die Netzwerkverbindung wieder geprüft und bei erfolgreichem Verbindungstest die CSV Datei importiert. Und genau da ist mein Problem. Nach „….Add_Click({…}) macht er alles! aber er importiert die CSV Datei nicht. 🙁 Hier ist es egal ob die Datei im Netz oder auf der lokalen Platte liegt. In der ISE werden keinerlei Fehlermeldungen dazu angezeigt. Auch kommt es zu keinen Fehlern, wenn ich dei CSV-Datei im Debugmodus manuell importiere.

    Für einen Tipp wäre ich hier dankbar.

    • admin sagt:

      Hi Mike,

      da stehe ich gerade etwas auf dem Schlauch. Könntest Du mal das gesamte Script posten. Wenn da „geheime“ Sachen drin stehen, natürlich vorher entfernen. 😉

      • moses-ms sagt:

        Anbei der Code für den Button:
        #Definition des NoConnectionButtons
        $NoConnection=New-Object System.Windows.Forms.Button
        $NoConnection.Location=New-Object System.Drawing.Point($Rand1,5)
        $NoConnection.Size=New-Object System.Drawing.Size(205,37)
        $NoConnection.Font=“Georgia,12″
        $noConnection.Text=“NoConnection, Retry?“
        $NoConnection.Name=“NoConnection“
        $NoConnection.ForeColor =“Red“
        $NoConnection.Add_Click({
        if(Test-Connection „FileServer“ -Count 2 -Quiet){
        $Fenster.Controls.Remove($NoConnection)
        $Inhalt=Get-Content \\FileServer\Pfad\CSV-Datei -Encoding:String |ConvertFrom-Csv -delimiter „,“
        #$Inhalt=import-csv \\FileServer\Pfad\CSV-Datei -delimiter „,“
        . („C:\Users\“ + $env:Username +“\Gadget3.0\Gadget_Small.ps1″)
        #$Inhalt=import-csv C:\LokalerPfad\CSV-Date -delimiter „,“
        Write-Host „Nach dem Import“
        }
        # else{
        # . („C:\Users\“ + $env:Username +“\Gadget3.0\NoConnection_Button.ps1″)
        # }

        })
        #Extern $Fenster.Controls.Add($NoConnection)

        Weder Import-CSV, von lokaler Platte oder Netzwerk, noch Get-Content wird ausgeführt. Write-Host erscheint aber ohne Probleme.

        Danke im voraus

        • admin sagt:

          Hmm…also wenn ich das mal so probiere, geht’s bei mir:
          [void] [System.Reflection.Assembly]::LoadWithPartialName(„System.Drawing“)
          [void] [System.Reflection.Assembly]::LoadWithPartialName(„System.Windows.Forms“)
          $Fenster = New-Object System.Windows.Forms.Form
          $NoConnection=New-Object System.Windows.Forms.Button
          $NoConnection.Location=New-Object System.Drawing.Point(5,5)
          $NoConnection.Size=New-Object System.Drawing.Size(205,37)
          $NoConnection.Font=“Georgia,12″
          $noConnection.Text=“NoConnection, Retry?“
          $NoConnection.Name=“NoConnection“
          $NoConnection.ForeColor =“Red“
          $NoConnection.Add_Click({
          if (Test-Connection „localhost“ -Count 2 -Quiet) {
          $Fenster.Controls.Remove($NoConnection)
          $Inhalt=Get-Content ‚\\localhost\C$\Users\Name\Desktop\test.csv‘ -Encoding:String | ConvertFrom-Csv -delimiter „,“
          Write-Host $Inhalt
          } else {
          write-host „No Connect“
          }
          })
          $Fenster.Controls.Add($NoConnection)
          $Fenster.ShowDialog()

          Vielleicht irgendwo ein Tipper?

          • moses-ms sagt:

            Hallo Martin,
            nach mehreren Tagen / Nächten habe ich doch endlich den Fehler gefunden. Ich dachte wenn ich das 2. Script über dot-Sourcing einbinde sind die Variablen allgemein gültig, aber das war falsch.
            Lösung: $global:inhalt und schon funktioniert alles. 🙂

            Nun habe ich aber doch noch 2 Fragen:
            Wie kann ich eine Eingabe in einer InputBox automatisch groß Schreiben?
            ToUpper bringt mich hier nicht weiter, da es nur die Weiterverarbeitung betrifft. Also wenn ich ein „a“ eingebe soll automatisch ein „A“ angezeigt werden.

            Die zweite Frage ist:
            Wie kann ich die Funktion „.Add_Keydown({if ($_.keycode -eq „Enter“)“ so umfunktionieren, dass ein Button in meiner GUI geklickt wird? Ein Event .OnClick gibt es leider nicht. 🙁

            Und noch einmal vielen Dank im voraus!!

          • admin sagt:

            Hi Mike,

            Automatisch groß schreiben? Wahrscheinlich viel zu kompliziert gedacht ;-). Es gibt eine Eigenschaft namens CharacterCasing. Diese kann die Werte Upper, Lower oder Normal zugewiesen bekommen. Zum groß schreiben:
            $InputBox.CharacterCasing=“Upper“
            Zu Deiner 2. Frage: “.Add_Keydown({if ($_.keycode -eq “Enter”) { $Button.PerformClick() } })”

  • Urs sagt:

    Bei verschiedenen Beispielen (Dropdownfeld, usw.) kriege ich auf meinem PC (W7 Enterprise SP1, Powershell 3.0 oder 4.0) einfach kein Resultat zurück. Bei einer mir zur Verfügung stehenden Citrix-Umgebung funktionieren die Scripts aber problemlos. Woran könnte das wohl liegen?

  • Anonymous sagt:

    Wow! Vielen Dank!
    Bisher war ich immer an die das Commandfenster der Powershell gefesselt und meine Kollegen waren nicht begeistert wenn meine AD oder Exchange Tools nur über ebendiese zu bedienen waren. Jetzt kann ich ihnen eine nette kleine GUI basteln

  • moses-ms sagt:

    Hallo,
    ich bin’s noch einmal: Hat Dein Buch schon ein paar mehr Beiträge zur GUI Programmierung mit PowerShell bekommen? Ich such mir echt einen Wolf um dazu etwas zu finden. Man denkt ein Buch gefunden zu haben dann gibt es das nicht mehr :-/

    Könntest Du eine Code posten der auf Basis des Dropdownfeldes funktioniert.
    Nach der Auswahl im Dropdown soll eine weiter Anzeige Aktualisiert werden, bzw gefiltert werden.

  • moses-ms sagt:

    Hallo, ist es möglich mittels Powershell eine Grafik zu programmieren, die sich automatisch an die Fenstergröße anpasst? Ich meine verkleinern bis zu einem bestimmten Punkt und vergrößern so weit der BS reicht 😉 Die Grafik bzw. Formular habe ich mit Sapien PrimalForms schon fast fertiggestellt.

  • spax@gmx.ch sagt:

    Danke für dein Script! Bei mir wird jedoch am Ende der Wert nicht zurück gegeben?!

    • admin sagt:

      Kannst Du ein bisschen genauer sein, welches der vielen Skripte zur GUI-programmierung Du meinst, oder vielleicht das GUI-Helper Modul?

      • zhch sagt:

        Ich verwende das erste einfache Script mit dem Texteingabefeld, OK Button und Cancel Button. Beim Klick auf OK wird der Wert der Variable $x am ende in der Powershell Konsole nicht zurück gegeben.

        • zhch sagt:

          in powershell v2 gehts. in v3 gehts leider nicht. schade

        • admin sagt:

          Herzlichen Dank für den Hinweis. Leider sind die Beiträge schon recht alt. In früheren Versionen haben die auch wunderbar funktioniert. In aktuellen Umgebungen scheinen aber die Variablenscopes schärfer getrennt zu werden. Daher habe ich zumindest bis zu Ihrer Fragestellung den Beitrag aktualisiert. Bei Fragen zu Scopes schauen Sie doch einmal in mein PowerShell-Buch. Dort werde ich auch das komplette GUI Thema noch einmal überarbeitet veröffentlichen.

  • Daniel sagt:

    Vielen Dank hierfür!!!
    Perfekt!!

  • Franz sagt:

    Sehr hilfreich, herzlichen Dank

  • Markus sagt:

    Hi,

    Super von dir, dass du auf deiner hp leuten hilfst.
    Eine frage habe ich leider auch:

    Ich verwende PrimalCE für die GUI.
    Leider musste ich feststellen, dass die Felder die ich an eine bestimmten stelle definiert habe auf einem anderen pc wo anders sind.
    Oder die beschriftungsfelder werden daher abgeschnitten.

    Z.b: „Name“ Eingabefeld
    Beim anderen pc steht dann „Nam“ und gleich darauf das Eingabefeld.

    Wie kann ich das beheben?
    Damit die GUI auf allen PCs mit powershell gleich aussieht.

    Danke im vorraus.

    Lg

    • admin sagt:

      Puh, damit wirst Du sehr viel Arbeit haben! Grundsätzlich sind die Angaben im .NET in Pixel nicht in %. Das bedeutet, wenn bei verschiedenen Computern unterschiedliche Bildschirmauflösungen verwendet werden, dann sieht das natürlich überall anders aus. Willst Du dass, dies nicht der Fall ist, müsstest Du Dir eigene Cmdlets schreiben, so wie mit dem GUIHelper, die dann prozentuale Angaben entgegen nehmen und dann zunächst die Auflösung feststellen und dann die % für die jeweilige Auflösung in Pixel umrechnen. Bevor jemand die Frage stellt: Nein, ich werde dem GUI-Helper keine solche Logik spendieren! 😉
      Möglicher Weise hast Du aber auch Fonts zugewiesen, die auf dem anderen Rechner nicht vorhanden sind. Dann nimmt der andere Rechner einfach eine Ersatzschrift, die aber anders skaliert. Wenn man mit solchen Frontends arbeitet, sollte man doch schon einmal einen Blik hinterher in den generierten Code werfen, um festzustellen, was da genau gemacht wird. Dann sieht man u. U. auch was da genau schief läuft. Wenn Du magst poste einmal ein Beispiel des Problemfalls und ich schaue es mir an (bitte nicht das ganze Script, sondern nur den Teil mit dem Grafikkrams).

  • Flo sagt:

    Echt Respekt. Super Zusammenfassung, geile Code-Snippets.
    Auf jeden Fall danke 😉

  • Martin sagt:

    Klasse Arbeit, hat mir sehr geholfen. Eine Frage hätte ich noch. Ist es möglich das Konsolenfenster beim Aufruf eines Fenster im Hintergrund verschwinden zulassen? Sodass der User der das Skript ausführt das Konsolenfenster garnicht erst sieht?

    • admin sagt:

      Herzlichen Dank! Anfang kommenden Jahres werde ich wohl ein Buch zu PowerShell 1.0, 2.0 und 3.0 veröffentlichen. Da sind dann jede Menge solcher Tipp&Tricks drin und natürlich wird auch alles erklärt was man so im täglichen Leben braucht.

      So, doch nun zu der gestellten Frage: Ja, das ist möglich indem Sie die Ihr Script mittels:
      Powershell -WindowStyle Hidden -file NameIhresSkripts
      aufrufen. Wichtig dabei ist, dass der Schalter -WindowStyle am Anfang steht.

  • Kraftmuschel sagt:

    Der GuiHelper ist eine coole Sache. Sowas habe ich bisher vermisst.

    Gibt es eine Möglichkeit, auch ein DropDown und mehrzeilige Textfelder einzubauen?

    Dann wäre es für meine Zwecke genial.

    • admin sagt:

      Herzlichen Dank! Das freut mich, dass es Ihnen gefällt. Die genannten Funktionen wollte ich über kurz oder lang sowieso implementieren. Doch leider fehlte mir bisher die Zeit. Ab und zu muss ich ja auch ein bisschen Geld verdienen ;-). Die Implementierung von Drop-Down Felder gestaltet sich allerdings wesentlich aufwändiger, als die anderen Elemente. Mehrzeilige Textfelder kann ich wahrscheinlich recht schnell (2-3 Monate) implementieren, aber die Drop-Down Geschichte wird wohl länger brauchen. Die benötigte Zeit für das Drop-Down-Feld wird wohl 1-2 Tage dauern, aber die muss ich neben meiner normalen Arbeit erst einmal finden.

    • admin sagt:

      Mehrzeiliges Textfeld ist in der neuen Version 0.92 verfügbar und auch ein paar kleine Bugs wurden beseitigt. Darüber hinaus wurden die Befehle der Standard-Syntax angepasst. Sorry, wegen der Umbennenung, ich denke das wird die erste und letzte Umbenennung gewesen sein. Deswegen hat das Ganze ja auch noch ein 0 vor dem Komma ;-). Ab Version 1.0 gelobe ich Abwärtskompatibilität.

    • admin sagt:

      So, ging doch schneller als erwartet. Dropdownfeld und mehrzeilige Textfelder sind nun in der aktuellen Version 0.93 verfügbar. Viel Spaß/Erfolg damit!

  • hht sagt:

    Ich „spiele“gerade mit den Skripten die zum Download bereitgestellt wurden. Eine Frage habe ich dazu. Die Übergabewerte für die Inpitboxen sind fest definiert. Was muss ich tun damit die Eingabe als Variable gespeichert wird und somit damit weiter gearbeitet werden kann?
    Vielen Dank

    • admin sagt:

      Bei guihelper werden die Rückgabewerte als Hash geliefert. Also beispielsweise $a=guishow $fenster. Dann ist in $a.Inputbox1 der Text enthalten. Inputbox1 ist natürlich abhängig vom Namen der für die Inputbox vergeben wurde.
      Beim einfachen Scriptbeispiel für Inputbox steht der Rückgabewert bereits in $x.

      • hht sagt:

        vielen Dank für die schnelle Antwort.

        Also wenn ich $a. eingebe taucht nach dem (.) „Punkt“ kein „Inputbox1“ auf.

        Ich benutze PowerGUI der mir ja alle möglichen Auswähloptionen anzeigt.

        Wenn ich $a eingeben werden mit alle Werte geliefert. Ich könnte über Pipen den gewünschten Wert herausfiltern, aber das ist etwas umständlicher.

        • admin sagt:

          Es steht eine neue Version 0.91 zum Download zur Verfügung, die das Problem beseitigt. Ab sofort wird kein Hashwert, sondern ein .NET-Objekt zurück geliefert. Des Weiteren räumt die neue Version mit Variablen auf.

  • dcm sagt:

    Geile Sache dass sowas geht. Aber warum würde man das tun? 😛

Gerne können Sie Fragen stellen, oder Verbesserungsvorschläge machen