Menú de Formulario de PowerShell: Acceso Rápido a tus Scripts

Es hora de proyecto de fin de semana nuevamente y hoy aprenderás cómo construir un menú de formulario de PowerShell en la bandeja del sistema ligero donde puedes lanzar rápidamente tus scripts de PowerShell más codiciados de manera rápida y sencilla. Puedes ver abajo el resultado final.

Launching PowerShell scripts via a system tray menu icon

En este artículo, aprenderás cómo construir tu propio menú de PowerShell GUI desglosando el proceso paso a paso.

Requisitos de Entorno y Conocimiento

Antes de sumergirte, asegúrate de cumplir con los siguientes requisitos mínimos:

  • Windows 7 o posterior
  • Windows PowerShell 3 o posterior: La última versión de .NET Core 3.0 con PowerShell 7 preview puede funcionar en Windows debido al soporte agregado recientemente para WPF y WinForm, pero no está probado.
  • .NET Framework 4.5 o posterior
  • A familiarity with Windows Forms (WinForms)  You can, however, due this with WPF too though.

Para este proyecto, la buena noticia es que realmente no necesitarás depender de Visual Studio, PoshGUI, o cualquier otra herramienta de desarrollo de UI, ya que los componentes principales en los que se basará este proyecto son los siguientes:

  • NotifyIcon: Esto representará nuestro icono de bandeja del sistema personalizable para que el usuario interactúe.
  • Menú contextual – Contenedor para cuando el usuario hace clic derecho en el icono de la bandeja.
  • Elemento de menú – Objetos individuales para cada opción dentro del menú de clic derecho.

Abre tu editor de scripts de PowerShell favorito y ¡comencemos!

Para este proyecto, vas a construir tres funciones: dos funciones para mostrar/ocultar la consola y proporcionar una experiencia de usuario más limpia, y otra para agregar elementos al menú de la bandeja del sistema. Estas funciones servirán como base para un uso posterior, facilitándote la vida, como aprenderás más adelante en este artículo.

Mostrar/Ocultar Ventana de la Consola

A menos que esté oculta, al ejecutar un script de PowerShell, aparecerá la conocida consola de PowerShell. Dado que los elementos del menú en el formulario de PowerShell que crearás ejecutarán scripts, debes asegurarte de que la consola no aparezca. Solo quieres que se ejecute.

Cuando se ejecuta un script, puedes alternar la ventana de la consola de PowerShell para que se muestre o no, utilizando un poco de .NET.

Primero agregue el tipo Window .NET en la sesión actual. Para hacer esto, usará algo de C# como verá a continuación. Los dos métodos que necesita cargar en el contexto son GetConsoleWindow y ShowWindow. Al cargar estas DLL en memoria, está exponiendo ciertas partes de la API, lo que le permite usarlas en el contexto de su script de PowerShell:

 #Cargar dlls en el contexto de la sesión actual de la consola
 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);

Crear dos funciones utilizando las cargadas anteriormente con el método GetConsoleWindow() y ShowWindow() como se muestra a continuación.

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

Con estas dos funciones ahora ha creado una forma en la que puede mostrar u ocultar la ventana de la consola a voluntad.

Nota: Si desea ver la salida de los scripts ejecutados a través del menú, puede usar transcripciones de PowerShell u otras funciones de registro basadas en texto. Esto le permite mantener el control en lugar de solo ejecutar la sesión de PowerShell con el parámetro WindowStyle para ocultar.

Ahora comience a construir el código del script llamando Start-HideConsole. Cuando el script del menú impulsado por formulario de PowerShell se ejecute, esto asegurará que la ventana de la consola de PowerShell no se muestre.

<# 
	Inicialización de funciones y objetos cargados en memoria
	Mostrar una barra de carga basada en texto o Write-Progress en el host
#>
 
Start-HideConsole
 
<# 
	Código para mostrar tu icono de formulario/área de notificación del sistema
	Esto mantendrá la consola aquí hasta que se cierre
#>

Crear Opciones de Menú

Ahora es el momento de crear las opciones de menú. Asegurándote de que puedas crear fácilmente nuevas opciones más adelante, crea otra función esta vez llamada New-MenuItem. Cuando llames a esta función, creará un nuevo objeto MenuItem .NET que luego podrás agregar al menú más tarde.

Cada opción de menú lanzará otro script o saldrá del lanzador. Para acomodar esta funcionalidad, la función New-MenuItem tiene tres parámetros:

  • Texto – La etiqueta en la que el usuario hará clic
  • RutaMiScript – La ruta al script de PowerShell a ejecutar
  • SoloSalida – La opción para salir del lanzador.

Agrega el siguiente fragmento de función al script del menú.

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

Continuando con la construcción de la función New-MenuItem, crea un objeto MenuItem asignándolo a una variable.

 #Inicialización
 $MenuItem = New-Object System.Windows.Forms.MenuItem

A continuación, asigna la etiqueta de texto al elemento de menú.

 # Aplica el texto deseado
 if($Text) {
 	$MenuItem.Text = $Text
 }

Ahora agrega una propiedad personalizada al MenuItem llamada RutaMiScript. Esta ruta será llamada cuando se haga clic en el elemento del menú.

 #Aplicar lógica del evento de clic
 if($MyScriptPath -and !$ExitOnly){
 	$MenuItem | Add-Member -Name MyScriptPath -Value $MyScriptPath -MemberType NoteProperty

Añadir un evento de clic al MenuItem que inicie el script deseado. Start-Process proporciona una manera limpia de hacer esto dentro de un bloque try/catch para asegurarse de que cualquier error al iniciar el script (como PowerShell no estar disponible o el script no existir en la ruta proporcionada) caiga al bloque catch.

   $MenuItem.Add_Click({
        try{
            $MyScriptPath = $This.MyScriptPath #Utilizado para encontrar la ruta adecuada durante el evento de clic
            
            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
        }
  })

Añadir la lógica restante para proporcionar una condición de salida para el lanzador, seguido de devolver tu nuevo MenuItem recién creado para asignarlo a otra variable en tiempo de ejecución.

    #Proporcionar una manera de salir del lanzador
    if($ExitOnly -and !$MyScriptPath){
        $MenuItem.Add_Click({
            $Form.Close()
    
            #Manejar cualquier proceso colgado
            Stop-Process $PID
        })
    }
 
 	 #Devolver nuestro nuevo MenuItem
    $MenuItem
 }

Ahora deberías tener la función New-MenuItem creada. La función final debería verse así:

 function New-MenuItem{
     param(
         [string]
         $Text = "Placeholder Text",
 
         $MyScriptPath,
         
         [switch]
         $ExitOnly = $false
     )
 
     #Inicialización
     $MenuItem = New-Object System.Windows.Forms.MenuItem
 
     #Aplicar el texto deseado
     if($Text){
         $MenuItem.Text = $Text
     }
 
     #Aplicar la lógica del evento de clic
     if($MyScriptPath -and !$ExitOnly){
         $MenuItem | Add-Member -Name MyScriptPath -Value $MyScriptPath -MemberType NoteProperty
     }
 
     $MenuItem.Add_Click({
             try{
                 $MyScriptPath = $This.MyScriptPath #Usado para encontrar la ruta adecuada durante el evento de clic
             
                 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
             }
         })
 
     #Proporcionar una forma de salir del iniciador
     if($ExitOnly -and !$MyScriptPath){
         $MenuItem.Add_Click({
                 $Form.Close()
    
                 #Manejar cualquier proceso bloqueado
                 Stop-Process $PID
             })
     }
 
     #Devolver nuestro nuevo MenuItem
     $MenuItem
 }

Prueba la función New-MenuItem copiando y pegando el código anterior en tu consola de PowerShell y ejecutando la función proporcionando algunos valores de parámetros falsos. Verás que se devuelve un objeto MenuItem de .NET.

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

Creación de un formulario de inicio rápido

¿Quieres más consejos como este? Echa un vistazo a mi blog personal de PowerShell en: https://nkasco.com/FriendsOfATA

Ahora que puedes crear fácilmente nuevos elementos de menú, es hora de crear un iniciador de bandeja del sistema que mostrará el menú.

Crea un objeto de formulario básico para agregar componentes. Esto no necesita ser nada sofisticado, ya que estará oculto para el usuario final y mantendrá la consola en funcionamiento en segundo plano también.

 #Crear Formulario para servir como un contenedor para nuestros componentes
 $Form = New-Object System.Windows.Forms.Form
 ​
 #Configurar nuestro formulario para que esté oculto
 $Form.BackColor = "Magenta" #Coincida este color con la propiedad TransparencyKey para transparencia en su formulario
 $Form.TransparencyKey = "Magenta"
 $Form.ShowInTaskbar = $false
 $Form.FormBorderStyle = "None"

A continuación, crea el icono que aparecerá en la bandeja del sistema. A continuación, he elegido usar el icono de PowerShell. En tiempo de ejecución, el código a continuación crea un icono real en la bandeja del sistema. Este icono se puede personalizar a tu gusto configurando la variable SystrayIcon con el icono deseado.

Consulta la documentación para la clase System.Drawing.Icon para ver otros métodos en los que puedes cargar un icono en la memoria.

 #Inicializa/configura los componentes necesarios
 $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

Cuando se ejecute el script, deberías ver un icono de PowerShell aparecer en la bandeja del sistema, como se muestra a continuación.

Ahora, crea un contenedor para tus elementos de menú con un nuevo objeto ContextMenu y crea todos tus elementos de menú. Para este ejemplo, el menú tendrá dos scripts para ejecutar y una opción de salida.

 $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

A continuación, agrega todos los elementos de menú recién creados al menú contextual. Esto asegurará que cada opción de menú aparezca en el menú contextual del formulario.

 #Agrega elementos de menú al menú contextual
 $ContextMenu.MenuItems.AddRange($LoggedOnUser)
 $ContextMenu.MenuItems.AddRange($RestartRemoteComputer)
 $ContextMenu.MenuItems.AddRange($ExitLauncher)#Agrega componentes a nuestro formulario
 $SystrayLauncher.ContextMenu = $ContextMenu

Mostrar el formulario de lanzamiento

Ahora que el formulario está completo, lo último que queda por hacer es mostrarlo asegurándose de que la ventana de la consola de PowerShell no aparezca. Haz esto usando tu Start-HideConsole, mostrando el formulario de lanzamiento y luego mostrando la consola nuevamente con Start-ShowConsole para evitar que se cuelgue un proceso powershell.exe.

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

¿Quieres más consejos como este? ¡Visita mi blog personal de PowerShell en: https://nkasco.com/FriendsOfATA

El código completo en su totalidad se puede encontrar aquí: https://github.com/nkasco/PSSystrayLauncher

Tus Conclusiones

¡Felicidades, has terminado este proyecto! En este artículo aprendiste:

  1. Cómo exponer componentes de la API de Windows.
  2. Cómo trabajar con menús contextuales a través de WinForms y agregar elementos de menú subsiguientes.
  3. Cómo crear un icono en la bandeja del sistema en PowerShell.

¡Este proyecto debería darte suficiente comprensión y experiencia para crear tu propio menú de bandeja del sistema para tus scripts de PowerShell!

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