Меню формы PowerShell: Быстрый доступ к вашим сценариям

Это снова время для выходных проектов, и сегодня вы узнаете, как создать легкое меню формы PowerShell для системного лотка, где вы можете быстро и легко запускать ваши самые желанные сценарии PowerShell. Вы можете увидеть конечный результат ниже.

Launching PowerShell scripts via a system tray menu icon

В этой статье вы узнаете, как создать свое собственное графическое меню PowerShell, разбивая процесс на шаги.

Требования к окружению и знаниям

Прежде чем приступить, убедитесь, что вы соответствуете следующим минимальным требованиям:

  • Windows 7 или более поздняя версия
  • Windows PowerShell 3 или более поздняя версия – Последняя версия .NET Core 3.0 с предварительным просмотром PowerShell 7 может работать на Windows из-за недавно добавленной поддержки WPF и WinForm, но это не проверено.
  • .NET Framework 4.5 или более поздняя версия
  • A familiarity with Windows Forms (WinForms)  You can, however, due this with WPF too though.

Для этого проекта хорошая новость в том, что вам действительно не нужно полагаться на Visual Studio, PoshGUI или любое другое средство разработки пользовательского интерфейса, поскольку основные компоненты, на которых будет полагаться этот проект, включают следующее:

  • NotifyIcon – Это будет представлять наш настраиваемый значок системного лотка, с которым пользователь может взаимодействовать.
  • Контекстное меню – Контейнер для случая, когда пользователь щелкает правой кнопкой мыши по значку в трее.
  • ПунктМеню – Отдельные объекты для каждой опции в контекстном меню правой кнопки мыши.

Откройте свой любимый редактор сценариев PowerShell, и давайте начнем!

Для этого проекта вам нужно создать три функции: две функции для отображения/скрытия консоли для более чистого пользовательского опыта и одну для добавления элементов в меню вашего системного лотка. Эти функции послужат основой для последующего использования и значительно упростят вашу жизнь, как вы узнаете немного позже в этой статье.

Показать/Скрыть окно консоли

Если не скрыто, при запуске сценария PowerShell появится знакомая консоль PowerShell. Поскольку пункты меню в форме PowerShell, которую вы создадите, будут запускать сценарии, вы должны убедиться, что консоль не отображается. Вам просто нужно, чтобы она выполнялась.

При выполнении сценария вы можете переключать отображение окна консоли PowerShell с помощью небольшой .NET.

Сначала добавьте тип Window .NET в текущую сессию. Для этого вы будете использовать некоторый C#, как показано ниже. Два метода, которые вам нужно загрузить в контекст, это GetConsoleWindow и ShowWindow. Загружая эти DLL в память, вы раскрываете определенные части API, что позволяет вам использовать их в контексте вашего сценария PowerShell:

 # Загрузка dll в контекст текущей консольной сессии
 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);

Создайте две функции, используя загруженные выше методы GetConsoleWindow() и ShowWindow(), как показано ниже.

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

Теперь у вас есть две функции, с помощью которых можно показать или скрыть окно консоли по желанию.

Примечание: если вы хотите видеть вывод выполненных сценариев через меню, вы можете использовать транскрипты PowerShell или другие функции регистрации текста. Это позволит вам поддерживать контроль, в отличие от простого запуска сеанса PowerShell с параметром WindowStyle для скрытия.

Теперь приступим к созданию кода сценария, вызывая Start-HideConsole. При выполнении сценария формы PowerShell меню это позволит убедиться, что окно консоли PowerShell не открывается.

<# 
	Инициализация функций и объектов, загрузка в память
	Отображение текстового загрузочного бара или Write-Progress на хосте
#>
 
Start-HideConsole
 
<# 
	Код для отображения вашей формы/иконки в системном лотке
	Это удерживает консоль здесь до закрытия
 #>

Создание пунктов меню

Теперь пришло время создать пункты меню. Чтобы обеспечить возможность легкого создания новых опций впоследствии, создайте еще одну функцию, на этот раз назовите ее New-MenuItem. При вызове этой функции будет создан новый объект MenuItem .NET, который затем можно добавить в меню.

Каждая опция меню запускает другой сценарий или выходит из загрузчика. Для учета этой функциональности функция New-MenuItem имеет три параметра:

  • Текст – Метка, по которой пользователь нажмет
  • ПутьМоегоСценария – Путь к сценарию PowerShell для выполнения
  • ТолькоВыход – Опция выхода из загрузчика.

Добавьте следующий фрагмент функции к скрипту меню.

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

Продолжая создание функции New-MenuItem, создайте объект MenuItem, присвоив его переменной.

 #Инициализация
 $MenuItem = New-Object System.Windows.Forms.MenuItem

Затем присвойте текстовую метку элементу меню.

 # Применить желаемый текст
 if($Text) {
 	$MenuItem.Text = $Text
 }

Теперь добавьте пользовательское свойство к элементу меню с именем ПутьМоегоСценария. Этот путь будет вызван, когда элемент будет нажат в меню.

 #Применить логику события нажатия
 if($MyScriptPath -and !$ExitOnly){
 	$MenuItem | Add-Member -Name MyScriptPath -Value $MyScriptPath -MemberType NoteProperty

Добавьте событие нажатия на элемент меню, которое запускает нужный сценарий. Start-Process предоставляет чистый способ сделать это в блоке try/catch, чтобы вы могли убедиться, что любые ошибки при запуске сценария (например, недоступность PowerShell или отсутствие сценария по указанному пути) попадают в блок catch.

   $MenuItem.Add_Click({
        try{
            $MyScriptPath = $This.MyScriptPath #Используется для поиска правильного пути во время события нажатия
            
            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
        }
  })

Добавьте оставшуюся логику для предоставления условия выхода из запускающего устройства, а затем верните ваш новый элемент меню для присвоения другой переменной во время выполнения.

    #Предоставить способ выхода из запускающего устройства
    if($ExitOnly -and !$MyScriptPath){
        $MenuItem.Add_Click({
            $Form.Close()
    
            #Обработать зависшие процессы
            Stop-Process $PID
        })
    }
 
 	 #Вернуть наш новый элемент меню
    $MenuItem
 }

Теперь у вас должна быть создана функция New-MenuItem! Финальная функция должна выглядеть так:

 function New-MenuItem{
     param(
         [string]
         $Text = "Placeholder Text",
 
         $MyScriptPath,
         
         [switch]
         $ExitOnly = $false
     )
 
     #Инициализация
     $MenuItem = New-Object System.Windows.Forms.MenuItem
 
     #Применить желаемый текст
     if($Text){
         $MenuItem.Text = $Text
     }
 
     #Применить логику события нажатия
     if($MyScriptPath -and !$ExitOnly){
         $MenuItem | Add-Member -Name MyScriptPath -Value $MyScriptPath -MemberType NoteProperty
     }
 
     $MenuItem.Add_Click({
             try{
                 $MyScriptPath = $This.MyScriptPath #Используется для поиска правильного пути во время события нажатия
             
                 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
             }
         })
 
     #Предоставить способ выхода из лаунчера
     if($ExitOnly -and !$MyScriptPath){
         $MenuItem.Add_Click({
                 $Form.Close()
    
                 #Обработать все зависшие процессы
                 Stop-Process $PID
             })
     }
 
     #Вернуть наш новый элемент меню
     $MenuItem
 }

Протестируйте функцию New-MenuItem, скопировав и вставив вышеуказанный код в вашу консоль PowerShell и запустив функцию, предоставив некоторые фиктивные значения параметров. Вы увидите, что возвращается объект MenuItem .NET.

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

Создание формы лаунчера

Хотите больше подобных советов? Посетите мой личный блог PowerShell по адресу: https://nkasco.com/FriendsOfATA

Теперь, когда вы можете легко создавать новые элементы меню, пришло время создать лаунчер системного трея, который будет отображать меню.

Создайте базовый объект формы, чтобы добавить к нему компоненты. Это не должно быть ничего особенного, так как оно будет скрыто от конечного пользователя и будет сохранять консоль работающей в фоновом режиме.

 #Создание формы для службы контейнером для наших компонентов
 $Form = New-Object System.Windows.Forms.Form
 ​
 #Настройка нашей формы на скрытость
 $Form.BackColor = "Magenta" #Сопоставьте этот цвет с свойством TransparencyKey для прозрачности вашей формы
 $Form.TransparencyKey = "Magenta"
 $Form.ShowInTaskbar = $false
 $Form.FormBorderStyle = "None"

Далее создайте значок, который будет отображаться в системном лотке. Ниже я выбрал использовать значок PowerShell. Во время выполнения код ниже создает фактический значок в системном лотке. Этот значок можно настроить по вашему вкусу, установив переменную SystrayIcon в нужный значок.

Ознакомьтесь с документацией по классу System.Drawing.Icon, чтобы увидеть другие методы загрузки значка в память.

 #Инициализация/настройка необходимых компонентов
 $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

При запуске скрипта вы должны увидеть значок PowerShell в системном лотке, как показано ниже.

Теперь создайте контейнер для ваших пунктов меню с новым объектом ContextMenu и создайте все ваши пункты меню. В этом примере в меню будут два сценария для выполнения и опция выхода.

 $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

Затем добавьте все только что созданные пункты меню в контекстное меню. Это гарантирует, что каждая опция меню появится в контекстном меню формы.

 #Добавление пунктов меню в контекстное меню
 $ContextMenu.MenuItems.AddRange($LoggedOnUser)
 $ContextMenu.MenuItems.AddRange($RestartRemoteComputer)
 $ContextMenu.MenuItems.AddRange($ExitLauncher)#Добавление компонентов в нашу форму
 $SystrayLauncher.ContextMenu = $ContextMenu

Показать форму запуска

Теперь, когда форма готова, последнее, что нужно сделать, – это показать ее, убедившись, что окно консоли PowerShell не появится. Сделайте это, используя Start-HideConsole, отображая форму запуска, а затем снова показывая консоль с Start-ShowConsole, чтобы избежать зависания процесса powershell.exe.

#Запуск
Start-HideConsole
$Form.ShowDialog() > $null
Start-ShowConsole

Хотите получать больше подсказок? Загляните на мой личный блог PowerShell по адресу: https://nkasco.com/FriendsOfATA

Весь код целиком можно найти здесь: https://github.com/nkasco/PSSystrayLauncher

Ваши выноски

Поздравляю, вы завершили этот проект! В этой статье вы узнали:

  1. Как раскрывать компоненты Windows API.
  2. Как работать с контекстными меню через WinForms и добавлять последующие пункты меню.
  3. Как создать значок в системном лотке в PowerShell.

Этот проект должен дать вам достаточное понимание и опыт для создания собственного меню в системном лотке для ваших сценариев PowerShell!

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