PowerShell-Formularmenü: Schneller Zugriff auf Ihre Skripte

Es ist wieder Zeit für ein Wochenendprojekt, und heute lernen Sie, wie Sie ein leichtes System Tray PowerShell Formularmenü erstellen, mit dem Sie Ihre begehrtesten PowerShell-Skripte schnell und einfach starten können. Das Endergebnis sehen Sie unten.

Launching PowerShell scripts via a system tray menu icon

In diesem Artikel erfahren Sie, wie Sie Ihr eigenes PowerShell-Menü erstellen, indem Sie den Prozess Schritt für Schritt aufschlüsseln.

Umgebung und Wissensanforderungen

Bevor Sie eintauchen, stellen Sie bitte sicher, dass Sie die folgenden Mindestanforderungen erfüllen:

Für dieses Projekt ist die gute Nachricht, dass Sie sich nicht wirklich auf Visual Studio, PoshGUI oder ein anderes UI-Entwicklungstool verlassen müssen, da sich dieses Projekt hauptsächlich auf die folgenden Hauptkomponenten verlässt:

  • NotifyIcon – Dies wird unser anpassbares Symbol im System Tray repräsentieren, mit dem der Benutzer interagieren kann.
  • Kontextmenü – Container für das Rechtsklicken des Benutzers auf das Benachrichtigungssymbol.
  • Menüpunkt – Einzelne Objekte für jede Option im Rechtsklick-Menü.

Öffnen Sie Ihren bevorzugten PowerShell-Skripteditor und lassen Sie uns beginnen!

Für dieses Projekt erstellen Sie drei Funktionen: zwei Funktionen zum Anzeigen/Ausblenden der Konsole, um eine übersichtlichere Benutzererfahrung zu bieten, und eine Funktion zum Hinzufügen von Elementen zu Ihrem Systray-Menü. Diese Funktionen dienen als Grundlage für spätere Verwendungen, um Ihnen das Leben später in diesem Artikel zu erleichtern.

Konsolefenster anzeigen/ausblenden

Wenn nicht ausgeblendet, wird beim Starten eines PowerShell-Skripts die vertraute PowerShell-Konsole angezeigt. Da die Menüpunkte im von Ihnen erstellten PowerShell-Formular Skripte starten werden, sollten Sie sicherstellen, dass die Konsole nicht angezeigt wird. Sie möchten nur, dass sie ausgeführt wird.

Wenn ein Skript ausgeführt wird, können Sie das PowerShell-Konsolenfenster ein- oder ausblenden, indem Sie ein wenig .NET verwenden.

Zuerst fügen Sie den Fenster .NET-Typ zur aktuellen Sitzung hinzu. Verwenden Sie dazu etwas C#, wie unten gezeigt. Die beiden Methoden, die Sie in den Kontext laden müssen, sind GetConsoleWindow und ShowWindow. Durch das Laden dieser DLLs in den Speicher werden bestimmte Teile der API freigelegt. Dadurch können Sie sie im Kontext Ihres PowerShell-Skripts verwenden:

 # DLLs in den Kontext der aktuellen Konsolensitzung laden
 Add-Type -Name Window -Namespace Console -MemberDefinition '
    [DllImport("Kernel32.dll")]
    public static extern IntPtr GetConsoleWindow();
 
    [DllImport("user32.dll")]
    public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);

Erstellen Sie zwei Funktionen, die die oben geladenen mit der GetConsoleWindow()– und ShowWindow()-Methode verwenden, wie unten gezeigt.

 function Start-ShowConsole {
    $PSConsole = [Console.Window]::GetConsoleWindow()
    [Console.Window]::ShowWindow($PSConsole, 5)
 }
 
 function Start-HideConsole {
    $PSConsole = [Console.Window]::GetConsoleWindow()
    [Console.Window]::ShowWindow($PSConsole, 0)
 }

Mit diesen beiden Funktionen haben Sie nun eine Möglichkeit geschaffen, das Konsolenfenster nach Belieben anzuzeigen oder auszublenden.

Hinweis: Wenn Sie die Ausgabe der über das Menü ausgeführten Skripte sehen möchten, können Sie PowerShell-Transkripte oder andere textbasierte Protokollfunktionen verwenden. Dies ermöglicht es Ihnen, die Kontrolle zu behalten, anstatt die PowerShell-Sitzung nur mit dem WindowStyle-Parameter zum Ausblenden auszuführen.

Beginnen Sie nun mit dem Aufbau des Skriptcodes, indem Sie Start-HideConsole aufrufen. Wenn das menügesteuerte PowerShell-Formularskript ausgeführt wird, stellt dies sicher, dass das PowerShell-Konsolenfenster nicht angezeigt wird.

<# 
	Initialisierung von Funktionen und Objekten, die in den Speicher geladen werden
	Textbasierte Ladeleiste anzeigen oder Write-Progress an die Konsole schreiben
#>
 
Start-HideConsole
 
<# 
	Code zum Anzeigen Ihres Formulars/Systembereichs-Symbols
	Dies wird die Konsole hier halten, bis sie geschlossen ist
#>

Menüoptionen erstellen

Jetzt ist es an der Zeit, die Menüoptionen zu erstellen. Stellen Sie sicher, dass Sie später problemlos neue Optionen erstellen können, indem Sie eine weitere Funktion namens New-MenuItem erstellen. Wenn Sie diese Funktion aufrufen, erstellt sie ein neues MenuItem .NET-Objekt, das Sie später dem Menü hinzufügen können.

Jede Menüoption startet ein anderes Skript oder beendet den Startvorgang. Um diese Funktionalität zu berücksichtigen, hat die New-MenuItem-Funktion drei Parameter:

  • Text – Die Bezeichnung, auf die der Benutzer klicken wird
  • MyScriptPath – Der Pfad zum auszuführenden PowerShell-Skript
  • ExitOnly – Die Option, den Startvorgang zu beenden.

Fügen Sie den folgenden Funktionsschnipsel zum Menüskript hinzu.

 function New-MenuItem{
     param(
         [string]
         $Text = "Placeholder Text",
 
         $MyScriptPath,
         
         [switch]
         $ExitOnly = $false
     )

Setzen Sie den Aufbau der Funktion New-MenuItem fort, indem Sie dem MenuItem-Objekt eine Variable zuweisen.

 # Initialisierung
 $MenuItem = New-Object System.Windows.Forms.MenuItem

Weisen Sie anschließend das Textlabel dem Menüpunkt zu.

 # Gewünschten Text anwenden
 if($Text) {
 	$MenuItem.Text = $Text
 }

Fügen Sie nun dem MenuItem eine benutzerdefinierte Eigenschaft mit dem Namen MyScriptPath hinzu. Über diesen Pfad wird das Skript aufgerufen, wenn auf den Menüpunkt geklickt wird.

 #Wenden Sie die Klickereignislogik an
 if($MyScriptPath -and !$ExitOnly){
 	$MenuItem | Add-Member -Name MyScriptPath -Value $MyScriptPath -MemberType NoteProperty

Fügen Sie ein Klickereignis zum MenuItem hinzu, das das gewünschte Skript startet. Start-Process bietet eine saubere Möglichkeit, dies innerhalb eines try/catch-Blocks zu tun, damit Sie sicherstellen können, dass etwaige Fehler beim Starten des Skripts (wie das Fehlen von PowerShell oder das Nichtvorhandensein des Skripts am angegebenen Pfad) in Ihren Catch-Block fallen.

   $MenuItem.Add_Click({
        try{
            $MyScriptPath = $This.MyScriptPath #Wird verwendet, um den richtigen Pfad während des Klickereignisses zu finden
            
            if(Test-Path $MyScriptPath){
                Start-Process -FilePath "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -ArgumentList "-NoProfile -NoLogo -ExecutionPolicy Bypass -File `"$MyScriptPath`"" -ErrorAction Stop
            } else {
                throw "Could not find at path: $MyScriptPath"
            }
        } catch {
          $Text = $This.Text
          [System.Windows.Forms.MessageBox]::Show("Failed to launch $Text`n`n$_") > $null
        }
  })

Fügen Sie die verbleibende Logik hinzu, um eine Beendigungsbedingung für den Launcher bereitzustellen, gefolgt von der Rückgabe Ihres neu erstellten MenuItems, um einer anderen Variablen zur Laufzeit zugewiesen zu werden.

    #Bieten Sie eine Möglichkeit zum Beenden des Launchers
    if($ExitOnly -and !$MyScriptPath){
        $MenuItem.Add_Click({
            $Form.Close()
    
            #Behandeln Sie alle hängenden Prozesse
            Stop-Process $PID
        })
    }
 
 	 #Geben Sie unser neues MenuItem zurück
    $MenuItem
 }

Sie sollten jetzt die New-MenuItem-Funktion erstellt haben! Die endgültige Funktion sollte so aussehen:

 function New-MenuItem{
     param(
         [string]
         $Text = "Placeholder Text",
 
         $MyScriptPath,
         
         [switch]
         $ExitOnly = $false
     )
 
     #Initialisierung
     $MenuItem = New-Object System.Windows.Forms.MenuItem
 
     #Gewünschten Text anwenden
     if($Text){
         $MenuItem.Text = $Text
     }
 
     #Klickereignislogik anwenden
     if($MyScriptPath -and !$ExitOnly){
         $MenuItem | Add-Member -Name MyScriptPath -Value $MyScriptPath -MemberType NoteProperty
     }
 
     $MenuItem.Add_Click({
             try{
                 $MyScriptPath = $This.MyScriptPath #Wird verwendet, um den richtigen Pfad während des Klickereignisses zu finden
             
                 if(Test-Path $MyScriptPath){
                     Start-Process -FilePath "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -ArgumentList "-NoProfile -NoLogo -ExecutionPolicy Bypass -File `"$MyScriptPath`"" -ErrorAction Stop
                 } else {
                     throw "Could not find at path: $MyScriptPath"
                 }
             } catch {
                 $Text = $This.Text
                 [System.Windows.Forms.MessageBox]::Show("Failed to launch $Text`n`n$_") > $null
             }
         })
 
     #Einen Weg bereitstellen, um den Startbildschirm zu verlassen
     if($ExitOnly -and !$MyScriptPath){
         $MenuItem.Add_Click({
                 $Form.Close()
    
                 #Mit hängenden Prozessen umgehen
                 Stop-Process $PID
             })
     }
 
     #Unser neues MenuItem zurückgeben
     $MenuItem
 }

Testen Sie die Funktion New-MenuItem, indem Sie den obigen Code in Ihre PowerShell-Konsole kopieren und einfügen und die Funktion unter Verwendung einiger erfundener Parameterwerte ausführen. Sie sehen, dass ein .NET MenuItem-Objekt zurückgegeben wird.

 PS51> (New-MenuItem -Text "Test" -MyScriptPath "C:\test.ps1").GetType()
 
 IsPublic IsSerial Name                                     BaseType
 -------- -------- ----                                     --------
 True     False    MenuItem                                 System.Windows.Forms.Menu

Erstellen eines Startformulars

Möchten Sie mehr Tipps wie diesen? Besuchen Sie meinen persönlichen PowerShell-Blog unter: https://nkasco.com/FriendsOfATA

Jetzt, da Sie problemlos neue Menüpunkte erstellen können, ist es an der Zeit, einen System-Tray-Startbildschirm zu erstellen, der das Menü anzeigt.

Erstellen Sie ein einfaches Formularobjekt, um Komponenten hinzuzufügen. Dies muss nichts Besonderes sein, da es für den Endbenutzer ausgeblendet wird und die Konsole im Hintergrund weiterläuft.

 #Formular erstellen, das als Container für unsere Komponenten dient
 $Form = New-Object System.Windows.Forms.Form
 ​
 #Konfigurieren Sie unser Formular, um verborgen zu sein
 $Form.BackColor = "Magenta" #Passen Sie diese Farbe an die TransparencyKey-Eigenschaft an, um Transparenz für Ihr Formular zu erhalten
 $Form.TransparencyKey = "Magenta"
 $Form.ShowInTaskbar = $false
 $Form.FormBorderStyle = "None"

Als Nächstes erstellen Sie das Symbol, das im Systembereich angezeigt wird. Im folgenden Beispiel habe ich mich für das PowerShell-Symbol entschieden. Zur Laufzeit erstellt der folgende Code ein tatsächliches Symbol im Systembereich. Dieses Symbol kann nach Belieben angepasst werden, indem die Variable SystrayIcon auf das gewünschte Symbol festgelegt wird.

Schauen Sie sich die Dokumentation für die Klasse System.Drawing.Icon an, um weitere Methoden zum Laden eines Symbols in den Speicher zu sehen.

 #Initialisieren/Konfigurieren der erforderlichen Komponenten
 $SystrayLauncher = New-Object System.Windows.Forms.NotifyIcon
 $SystrayIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe")
 $SystrayLauncher.Icon = $SystrayIcon
 $SystrayLauncher.Text = "PowerShell Launcher"
 $SystrayLauncher.Visible = $true

Wenn das Skript ausgeführt wird, sollte das PowerShell-Symbol im Systembereich angezeigt werden, wie unten gezeigt.

Erstellen Sie nun einen Container für Ihre Menüpunkte mit einem neuen ContextMenu-Objekt und erstellen Sie alle Ihre Menüpunkte. In diesem Beispiel soll das Menü zwei Skripte zum Ausführen und eine Option zum Beenden enthalten.

 $ContextMenu = New-Object System.Windows.Forms.ContextMenu
 ​
 $LoggedOnUser = New-MenuItem -Text "Get Logged On User" -MyScriptPath "C:\scripts\GetLoggedOn.ps1"
 $RestartRemoteComputer = New-MenuItem -Text "Restart Remote PC" -MyScriptPath "C:\scripts\restartpc.ps1"
 $ExitLauncher = New-MenuItem -Text "Exit" -ExitOnly

Fügen Sie als Nächstes alle gerade erstellten Menüpunkte dem Kontextmenü hinzu. Dadurch werden alle Menüoptionen im Formular-Kontextmenü angezeigt.

 #Menüpunkte dem Kontextmenü hinzufügen
 $ContextMenu.MenuItems.AddRange($LoggedOnUser)
 $ContextMenu.MenuItems.AddRange($RestartRemoteComputer)
 $ContextMenu.MenuItems.AddRange($ExitLauncher)#Komponenten unserem Formular hinzufügen
 $SystrayLauncher.ContextMenu = $ContextMenu

Zeigen Sie das Launcher-Formular an.

Jetzt, da das Formular vollständig ist, müssen Sie es nur noch anzeigen und sicherstellen, dass das PowerShell-Konsolenfenster nicht angezeigt wird. Dies erreichen Sie, indem Sie Start-HideConsole verwenden, das Launcher-Formular anzeigen und dann die Konsole wieder mit Start-ShowConsole anzeigen, um einen blockierten powershell.exe-Prozess zu verhindern.

#Starten
Start-HideConsole
$Form.ShowDialog() > $null
Start-ShowConsole

Möchten Sie mehr Tipps wie diesen? Schauen Sie sich meinen persönlichen PowerShell-Blog an unter: https://nkasco.com/FriendsOfATA

Der vollständige Code in seiner Gesamtheit ist hier zu finden: https://github.com/nkasco/PSSystrayLauncher

Ihre Mitnahmen

Herzlichen Glückwunsch, Sie haben dieses Projekt abgeschlossen! In diesem Artikel haben Sie gelernt:

  1. Wie man Komponenten der Windows API freilegt.
  2. Wie man mit Kontextmenüs über WinForms arbeitet und anschließend Menüpunkte hinzufügt.
  3. Wie man ein System Tray-Symbol in PowerShell erstellt.

Dieses Projekt sollte Ihnen genügend Verständnis und Erfahrung geben, um Ihr eigenes Systray-Menü für Ihre PowerShell-Skripte zu erstellen!

Source:
https://adamtheautomator.com/powershell-form/