PowerShell Formuliermenu: Snelle Toegang tot Uw Scripts

Het is weer tijd voor een weekendproject en vandaag leer je hoe je een lichtgewicht systeemvakmenu in PowerShell kunt bouwen, waar je snel en gemakkelijk je meest begeerde PowerShell-scripts kunt starten. Hieronder zie je het eindresultaat.

Launching PowerShell scripts via a system tray menu icon

In dit artikel leer je stap voor stap hoe je je eigen PowerShell-menu GUI kunt bouwen door het proces te doorlopen.

Vereisten qua omgeving en kennis

Voordat je aan de slag gaat, zorg ervoor dat je voldoet aan de volgende minimale vereisten:

Voor dit project is het goede nieuws dat je niet echt afhankelijk hoeft te zijn van Visual Studio, PoshGUI of een andere UI-ontwikkeltool, omdat de primaire componenten waar dit project op zal vertrouwen, de volgende zijn:

  • NotifyIcon – Dit zal ons aanpasbare systeemvakpictogram vertegenwoordigen waar de gebruiker mee kan interageren.
  • ContextMenu – Container voor wanneer de gebruiker met de rechtermuisknop op het systeemvakpictogram klikt.
  • MenuItem – Individuele objecten voor elke optie binnen het contextmenu dat verschijnt bij rechtsklikken.

Open je favoriete PowerShell-scripteditor en laten we beginnen!

Voor dit project ga je drie functies bouwen: twee functies om de console te tonen/verbergen voor een schonere gebruikerservaring, en één om items toe te voegen aan het systeemvakmenu. Deze functies zullen dienen als een basis voor later gebruik en zullen je leven veel gemakkelijker maken, zoals je later in dit artikel zult leren.

Tonen/verbergen van consolevenster

Tenzij verborgen, wanneer je een PowerShell-script start, wordt de vertrouwde PowerShell-console geopend. Aangezien de menu-items in het PowerShell-formulier dat je gaat maken scripts zullen starten, moet je ervoor zorgen dat de console niet wordt geopend. Je wilt gewoon dat het wordt uitgevoerd.

Wanneer een script wordt uitgevoerd, kun je het venster van de PowerShell-console wel of niet tonen met behulp van een beetje .NET.

Voeg eerst het .NET-type Window toe aan de huidige sessie. Hiervoor zul je wat C# gebruiken zoals hieronder te zien is. De twee methoden die je moet laden in de context zijn GetConsoleWindow en ShowWindow. Door deze DLL’s in het geheugen te laden, maak je bepaalde delen van de API toegankelijk, dit stelt je in staat om ze te gebruiken in de context van je PowerShell-script:

 #DLL's laden in de context van de huidige console sessie
 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);

Maak twee functies met behulp van de zojuist geladen methoden GetConsoleWindow() en ShowWindow() zoals hieronder getoond.

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

Met deze twee functies heb je nu een manier gecreëerd waarop je het consolevenster naar believen kunt tonen of verbergen.

Let op: Als je de uitvoer van de scripts die via het menu worden uitgevoerd wilt zien, kun je PowerShell-transcripts of andere op tekst gebaseerde loggingfuncties gebruiken. Dit stelt je in staat om controle te behouden in plaats van alleen de PowerShell-sessie uit te voeren met de WindowStyle-parameter om te verbergen.

Begin nu met het bouwen van scriptcode door Start-HideConsole aan te roepen. Wanneer het menu-gestuurde PowerShell-formulierscript wordt uitgevoerd, zorgt dit ervoor dat het PowerShell-consolevenster niet verschijnt.

<# 
	Initialisatie van functies en objecten laden in het geheugen
	Een tekstgebaseerde laadbalk weergeven of Write-Progress naar de host schrijven
#>
 
Start-HideConsole
 
<# 
	Code om je formulier/systeemvakpictogram weer te geven
	Dit houdt de console hier vast totdat deze wordt gesloten
#>

Maak menu-opties

Het is nu tijd om de menu-opties te maken. Zorg ervoor dat je later gemakkelijk nieuwe opties kunt maken, maak nog een functie genaamd New-MenuItem. Wanneer je deze functie aanroept, maakt het een nieuw MenuItem .NET-object aan dat je later aan het menu kunt toevoegen.

Elke menu-optie start een ander script of sluit de launcher af. Om deze functionaliteit mogelijk te maken, heeft de New-MenuItem-functie drie parameters:

  • Text – Het label waarop de gebruiker kan klikken
  • MyScriptPath – Het pad naar het PowerShell-script om uit te voeren
  • ExitOnly – De optie om de launcher af te sluiten.

Voeg onderstaand functiefragment toe aan het menuscript.

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

Ga verder met het bouwen van de New-MenuItem-functie door een MenuItem-object toe te wijzen aan een variabele.

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

Wijs vervolgens het tekstlabel toe aan het menu-item.

 # Gewenste tekst toepassen
 if($Text) {
 	$MenuItem.Text = $Text
 }

Voeg nu een aangepaste eigenschap toe aan het MenuItem genaamd MyScriptPath. Dit pad wordt aangeroepen wanneer het item in het menu wordt aangeklikt.

 #Pas klikgebeurtenislogica toe
 if($MyScriptPath -and !$ExitOnly){
 	$MenuItem | Add-Member -Name MyScriptPath -Value $MyScriptPath -MemberType NoteProperty

Voeg een klikgebeurtenis toe aan het MenuItem dat het gewenste script start. Start-Process biedt een nette manier om dit te doen binnen een try/catch-blok, zodat je ervoor kunt zorgen dat eventuele fouten bij het starten van het script (zoals PowerShell die niet beschikbaar is of het script dat niet bestaat op het opgegeven pad) naar je catch-blok gaan.

   $MenuItem.Add_Click({
        try{
            $MyScriptPath = $This.MyScriptPath #Gebruikt om het juiste pad te vinden tijdens de klikgebeurtenis
            
            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
        }
  })

Voeg de overgebleven logica toe om een exitconditie voor de starter te bieden, gevolgd door het teruggeven van je nieuw gemaakte MenuItem om aan een andere variabele toe te wijzen tijdens runtime.

    #Bied een manier om de starter af te sluiten
    if($ExitOnly -and !$MyScriptPath){
        $MenuItem.Add_Click({
            $Form.Close()
    
            #Behandel eventuele vastgelopen processen
            Stop-Process $PID
        })
    }
 
 	 #Geef ons nieuwe MenuItem terug
    $MenuItem
 }

Je zou nu de functie New-MenuItem gecreëerd moeten hebben! De uiteindelijke functie zou er zo uit moeten zien:

 function New-MenuItem{
     param(
         [string]
         $Text = "Placeholder Text",
 
         $MyScriptPath,
         
         [switch]
         $ExitOnly = $false
     )
 
     #Initialisatie
     $MenuItem = New-Object System.Windows.Forms.MenuItem
 
     #Pas de gewenste tekst toe
     if($Text){
         $MenuItem.Text = $Text
     }
 
     #Pas logica voor klikgebeurtenissen toe
     if($MyScriptPath -and !$ExitOnly){
         $MenuItem | Add-Member -Name MyScriptPath -Value $MyScriptPath -MemberType NoteProperty
     }
 
     $MenuItem.Add_Click({
             try{
                 $MyScriptPath = $This.MyScriptPath #Gebruikt om het juiste pad te vinden tijdens de klikgebeurtenis
             
                 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
             }
         })
 
     #Bied een manier om de starter af te sluiten
     if($ExitOnly -and !$MyScriptPath){
         $MenuItem.Add_Click({
                 $Form.Close()
    
                 #Behandel eventuele vastgelopen processen
                 Stop-Process $PID
             })
     }
 
     #Retourneer ons nieuwe MenuItem
     $MenuItem
 }

Test de New-MenuItem functie door de bovenstaande code te kopiëren en te plakken in uw PowerShell-console en de functie uit te voeren met wat valse parameterwaarden. Je zult zien dat er een .NET MenuItem-object wordt geretourneerd.

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

Het maken van een Launcher-formulier

Wil je meer tips zoals deze? Bekijk mijn persoonlijke PowerShell-blog op: https://nkasco.com/FriendsOfATA

Nu je gemakkelijk nieuwe menu-items kunt maken, is het tijd om een systeemvakstarter te maken die het menu zal weergeven.

Maak een basisformulierobject om componenten aan toe te voegen. Dit hoeft niets bijzonders te zijn, aangezien het verborgen zal zijn voor de eindgebruiker en de console op de achtergrond actief zal houden.

 #Maak een formulier om te dienen als een container voor onze componenten
 $Form = New-Object System.Windows.Forms.Form
 ​
 #Configureer ons formulier om verborgen te zijn
 $Form.BackColor = "Magenta" #Pas deze kleur aan aan de TransparencyKey-eigenschap voor transparantie naar je formulier
 $Form.TransparencyKey = "Magenta"
 $Form.ShowInTaskbar = $false
 $Form.FormBorderStyle = "None"

Volgende, maak het pictogram dat zal verschijnen in het systeemvak. Hieronder heb ik ervoor gekozen om het PowerShell-pictogram te gebruiken. Tijdens runtime maakt de onderstaande code een daadwerkelijk systeemvakpictogram aan. Dit pictogram kan naar wens worden aangepast door de variabele SystrayIcon in te stellen op het gewenste pictogram.

Bekijk de documentatie voor de klasse System.Drawing.Icon om andere methoden te zien waarmee je een pictogram in het geheugen kunt laden.

 #Initialize/configure benodigde componenten
 $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

Wanneer het script wordt uitgevoerd, zou je vervolgens een PowerShell-pictogram moeten zien verschijnen in je systeemvak, zoals je hieronder kunt zien.

Maak nu een container voor je menu-items met een nieuw ContextMenu-object en maak al je menu-items aan. Voor dit voorbeeld zal het menu twee scripts bevatten om uit te voeren en een optie om af te sluiten.

 $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

Voeg vervolgens alle zojuist gemaakte menu-items toe aan het contextmenu. Dit zorgt ervoor dat elke menuoptie wordt weergegeven in het contextmenu van het formulier.

 #Voeg menu-items toe aan contextmenu
 $ContextMenu.MenuItems.AddRange($LoggedOnUser)
 $ContextMenu.MenuItems.AddRange($RestartRemoteComputer)
 $ContextMenu.MenuItems.AddRange($ExitLauncher)#Voeg componenten toe aan ons formulier
 $SystrayLauncher.ContextMenu = $ContextMenu

Toon het Launcher-formulier

Nu het formulier compleet is, is het laatste wat je moet doen het weergeven ervan terwijl ervoor wordt gezorgd dat het PowerShell-consolevenster niet verschijnt. Doe dit door Start-HideConsole te gebruiken, toon het launcher-formulier en toon vervolgens opnieuw de console met Start-ShowConsole om te voorkomen dat er een vastgelopen powershell.exe-proces optreedt.

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

Wil je meer tips zoals deze? Bekijk mijn persoonlijke PowerShell-blog op: https://nkasco.com/FriendsOfATA

De volledige code is te vinden op: https://github.com/nkasco/PSSystrayLauncher

Wat je hebt opgepikt

Gefeliciteerd, je hebt dit project afgerond! In dit artikel heb je geleerd:

  1. Hoe je componenten van de Windows API blootlegt.
  2. Hoe je werkt met contextmenu’s via WinForms en vervolgens menu-items toevoegt.
  3. Hoe je een systeemvakpictogram maakt in PowerShell.

Deze tutorial zou je voldoende begrip en ervaring moeten geven om je eigen systeemvakmenu te maken voor je PowerShell-scripts!

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