Menu de Formulário do PowerShell: Acesso Rápido aos Seus Scripts

É hora do projeto de fim de semana novamente e hoje você aprenderá como construir um menu de formulário PowerShell leve na bandeja do sistema, onde você pode lançar rapidamente e facilmente seus scripts do PowerShell mais desejados. Você pode ver abaixo o resultado final.

Launching PowerShell scripts via a system tray menu icon

Neste artigo, você aprenderá como construir seu próprio menu GUI do PowerShell, dividindo o processo em etapas.

Requisitos de Ambiente e Conhecimento

Antes de começar, certifique-se de atender aos seguintes requisitos mínimos:

  • Windows 7 ou posterior
  • Windows PowerShell 3 ou posterior – A versão mais recente do .NET Core 3.0 com visualização do PowerShell 7 pode funcionar no Windows devido ao suporte adicionado recentemente para WPF e WinForm, mas não foi testada.
  • .NET Framework 4.5 ou posterior
  • A familiarity with Windows Forms (WinForms)  You can, however, due this with WPF too though.

Para este projeto, a boa notícia é que você realmente não precisará depender do Visual Studio, PoshGUI ou qualquer outra ferramenta de desenvolvimento de interface de usuário, pois os principais componentes deste projeto dependerão do seguinte:

  • NotifyIcon – Isso representará nosso ícone de bandeja do sistema personalizável para o usuário interagir.
  • Menu de Contexto – Contêiner para quando o usuário clica com o botão direito no ícone da bandeja.
  • Item do Menu – Objetos individuais para cada opção dentro do menu de clique com o botão direito.

Abra o seu editor de script PowerShell favorito e vamos começar!

Para este projeto, você vai construir três funções: duas funções para mostrar/ocultar o console para proporcionar uma experiência de usuário mais limpa e uma para adicionar itens ao menu da bandeja do sistema. Essas funções servirão como base para uso posterior, facilitando bastante sua vida, como você aprenderá um pouco mais tarde neste artigo.

Mostrar/Ocultar Janela do Console

A menos que esteja oculto, quando você inicia um script PowerShell, o familiar console do PowerShell aparecerá. Como os itens de menu no formulário PowerShell que você criará iniciarão scripts, você deve garantir que o console não apareça. Você só quer que ele execute.

Quando um script é executado, você pode alternar a exibição da janela do console do PowerShell usando um pouco de .NET.

Primeiro, adicione o tipo Window .NET na sessão atual. Para fazer isso, você usará algum C# como verá abaixo. Os dois métodos que você precisa carregar no contexto são GetConsoleWindow e ShowWindow. Ao carregar esses DLLs na memória, você está expondo certas partes da API, o que permite que você as use no contexto do seu script do PowerShell:

 #Carregar dlls no contexto da sessão atual do console
 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);

Crie duas funções usando os carregados acima usando o método GetConsoleWindow() e ShowWindow() conforme mostrado abaixo.

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

Com essas duas funções, agora você criou uma maneira de mostrar ou ocultar a janela do console quando desejar.

Nota: Se desejar ver a saída dos scripts executados via menu, você pode usar transcrições do PowerShell ou outras funcionalidades de registro baseadas em texto. Isso permite que você mantenha o controle versus apenas executar a sessão do PowerShell com o parâmetro WindowStyle para ocultar.

Agora comece a construir o código do script chamando Start-HideConsole. Quando o script do formulário do PowerShell executar, isso garantirá que a janela do console do PowerShell não apareça.

<# 
	Inicialização das funções e carregamento de objetos na memória
	Exibir uma barra de carregamento baseada em texto ou Write-Progress no host
#>
 
Start-HideConsole
 
<# 
	Código para exibir o ícone do seu formulário/systray
	Isso manterá o console aqui até ser fechado
#>

Criar Opções de Menu

Agora é hora de criar as opções de menu. Garantindo que você possa criar facilmente novas opções posteriormente, crie outra função chamada New-MenuItem. Quando você chama esta função, ela criará um novo objeto MenuItem .NET que você pode adicionar ao menu posteriormente.

Cada opção de menu lançará outro script ou sairá do lançador. Para acomodar essa funcionalidade, a função New-MenuItem tem três parâmetros:

  • Text – O rótulo no qual o usuário clicará
  • MyScriptPath – O caminho para o script PowerShell a ser executado
  • ExitOnly – A opção para sair do lançador.

Adicione o trecho de função abaixo ao script de menu.

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

Continuando a construção da função New-MenuItem, crie um objeto MenuItem atribuindo-o a uma variável.

 #Inicialização
 $MenuItem = New-Object System.Windows.Forms.MenuItem

Em seguida, atribua o rótulo de texto ao item de menu.

 # Aplicar o texto desejado
 if($Text) {
 	$MenuItem.Text = $Text
 }

Agora adicione uma propriedade personalizada ao MenuItem chamada MyScriptPath. Este caminho será chamado quando o item for clicado no menu.

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

Adicione um evento de clique ao MenuItem que inicia o script desejado. Start-Process fornece uma maneira limpa de fazer isso dentro de um bloco try/catch para garantir que quaisquer erros ao iniciar o script (como o PowerShell não estar disponível ou o script não existir no caminho fornecido) caiam para o bloco catch.

   $MenuItem.Add_Click({
        try{
            $MyScriptPath = $This.MyScriptPath #Usado para encontrar o caminho adequado durante o evento de clique
            
            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
        }
  })

Adicione a lógica restante para fornecer uma condição de saída para o iniciador, seguida pelo retorno do seu novo MenuItem para ser atribuído a outra variável em tempo de execução.

    #Fornecer uma maneira de sair do iniciador
    if($ExitOnly -and !$MyScriptPath){
        $MenuItem.Add_Click({
            $Form.Close()
    
            #Lidar com processos suspensos
            Stop-Process $PID
        })
    }
 
 	 #Retornar nosso novo MenuItem
    $MenuItem
 }

Agora você deve ter a função New-MenuItem criada! A função final deve se parecer com isso:

 function New-MenuItem{
     param(
         [string]
         $Text = "Placeholder Text",
 
         $MyScriptPath,
         
         [switch]
         $ExitOnly = $false
     )
 
     #Inicialização
     $MenuItem = New-Object System.Windows.Forms.MenuItem
 
     #Aplicar texto desejado
     if($Text){
         $MenuItem.Text = $Text
     }
 
     #Aplicar lógica de evento de clique
     if($MyScriptPath -and !$ExitOnly){
         $MenuItem | Add-Member -Name MyScriptPath -Value $MyScriptPath -MemberType NoteProperty
     }
 
     $MenuItem.Add_Click({
             try{
                 $MyScriptPath = $This.MyScriptPath #Usado para encontrar o caminho adequado durante o evento de clique
             
                 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
             }
         })
 
     #Fornecer uma maneira de sair do iniciador
     if($ExitOnly -and !$MyScriptPath){
         $MenuItem.Add_Click({
                 $Form.Close()
    
                 #Lidar com quaisquer processos suspensos
                 Stop-Process $PID
             })
     }
 
     #Retornar nosso novo MenuItem
     $MenuItem
 }

Teste a função New-MenuItem copiando e colando o código acima em seu console do PowerShell e executando a função fornecendo alguns valores de parâmetro falsos. Você verá que é retornado um objeto MenuItem .NET.

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

Criando um formulário de inicialização

Quer mais dicas como essa? Confira meu blog pessoal de PowerShell em: https://nkasco.com/FriendsOfATA

Agora que você pode facilmente criar novos itens de menu, é hora de criar um iniciador de bandeja do sistema que exibirá o menu.

Crie um objeto de formulário básico para adicionar componentes. Isso não precisa ser nada extravagante, pois será oculto para o usuário final e manterá o console em execução em segundo plano também.

 #Criar formulário para servir como um contêiner para nossos componentes
 $Form = New-Object System.Windows.Forms.Form
 ​
 #Configurar nosso formulário para ficar oculto
 $Form.BackColor = "Magenta" #Corresponder esta cor à propriedade TransparencyKey para transparência em seu formulário
 $Form.TransparencyKey = "Magenta"
 $Form.ShowInTaskbar = $false
 $Form.FormBorderStyle = "None"

Em seguida, crie o ícone que aparecerá na bandeja do sistema. Abaixo, optei por usar o ícone do PowerShell. Durante a execução, o código abaixo cria um ícone real na bandeja do sistema. Este ícone pode ser personalizado conforme desejado, definindo a variável SystrayIcon para o ícone desejado.

Consulte a documentação para a classe System.Drawing.Icon para ver outros métodos nos quais você pode carregar um ícone na memória.

 #Inicializar/configurar componentes necessários
 $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

Ao executar o script, você deverá ver um ícone do PowerShell aparecer na bandeja do sistema, como mostrado abaixo.

Agora, crie um contêiner para seus itens de menu com um novo objeto ContextMenu e crie todos os seus itens de menu. Para este exemplo, o menu terá dois scripts para executar e uma opção de saída.

 $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

Em seguida, adicione todos os itens de menu recém-criados ao menu de contexto. Isso garantirá que cada opção de menu apareça no menu de contexto do formulário.

 #Adicionar itens de menu ao menu de contexto
 $ContextMenu.MenuItems.AddRange($LoggedOnUser)
 $ContextMenu.MenuItems.AddRange($RestartRemoteComputer)
 $ContextMenu.MenuItems.AddRange($ExitLauncher)#Adicionar componentes ao nosso formulário
 $SystrayLauncher.ContextMenu = $ContextMenu

Mostrar o Formulário de Lançamento

Agora que o formulário está completo, a última coisa a fazer é mostrá-lo, garantindo que a janela do console do PowerShell não apareça. Faça isso usando seu Start-HideConsole, exibindo o formulário de lançamento e depois mostrando o console novamente com Start-ShowConsole para evitar um processo powershell.exe pendurado.

#Lançamento
Start-HideConsole
$Form.ShowDialog() > $null
Start-ShowConsole

Quer mais dicas como esta? Confira meu blog pessoal de PowerShell em: https://nkasco.com/FriendsOfATA

O código completo pode ser encontrado aqui: https://github.com/nkasco/PSSystrayLauncher

Seus Tópicos

Parabéns, você concluiu este projeto! Neste artigo, você aprendeu:

  1. Como expor componentes da API do Windows.
  2. Como trabalhar com menus de contexto via WinForms e adicionar itens de menu subsequentes.
  3. Como criar um ícone na bandeja do sistema em PowerShell.

Este projeto deve proporcionar entendimento e experiência suficientes para criar seu próprio menu de bandeja do sistema para seus scripts em PowerShell!

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