Создаем уведомление в виде баннера в Windows 10 с Powershell


24 ноября 2021


Создания баннера с уведомлением с Powershell в Windows 10

Powershell может использовать классы .NET. Один из таких классов, NotifyIcon, может выводить привычный баннер в нижней части экрана. В этой статье будет рассмотрен способ создания такого баннера с вашим текстом, а так же разные варианты его использования.

В итоге этой статьи можно будет создать баннер с похожим уведомлением:

Используем BalloonTipIcon в Powershell

Оно так же будет оставаться в панели уведомлений.

 

Где можно использовать

Обычно такие баннеры можно видеть при обновлении, освобождении места, почтовые уведомления и т.д. Учитывая, что Powershell хорошо интегрирован с серверными ролями - такой баннер может отлично подойти под разные мониторинги сервисов. Например вы можете проверять заблокированных пользователей и выводить их если это произошло.

Сложность, с которой я столкнулся, связана с удаленным выполнением такого скрипта средствами Powershell (WinRM). Вывод каких либо окон у удаленного пользователя, используя Powershell и .NET, задача не такая простая.

Самый простой и интересный способ выводить такие окна массово - использование REST. В предыдущих статьях был пример создания такого сервиса на Pode.

Если баннер у вас не выводится - возможно у вас отключены уведомления. В моем случае причиной оказалось следующая настройка, но она не единственная которая может повлиять на вывод:

Включение уведомлений в Windows

 

Создание баннера

Первым делом мы должны импортировать класс "System.Windows.Forms" через Add-Type. Без этой команды у вас может появиться ошибка. Далее мы создаем объект (копию/инстанс) из класса 'NotifyIcon':

Add-Type -AssemblyName System.Windows.Forms

$banner = New-Object System.Windows.Forms.NotifyIcon

После создания объекта баннера - нам нужно указать у него 4 свойства:

  • Icon - иконка, которая будет отображаться вместе с баннером;
  • BalloonTipIcon  - уровень уведомлений: ошибка (Error), предупреждение (Warning), уведомление (Info) или None;
  • BalloonTipText - текст, который будет выводиться в баннере;
  • BalloonTipTitle - заголовок в баннере;
  • Visible - отображение;

Иконку можно указать 2-мя способами: указав прямой путь до файла с расширением '*.ico' или скопировать ее у одного из приложений:

# импортируем класс для иконки
Add-Type -AssemblyName System.Drawing

# 1) Указываем путь до существующей картинки формата '.ico'
$ico = 'G:\banner.ico'
$banner.Icon = New-Object System.Drawing.Icon($ico)

# 2) или вытаскиваем иконку с существующего приложения
# (в данном примере Powershell)
$ico = "$env:WINDIR\system32\WindowsPowerShell\v1.0\powershell.exe"
$banner.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($ico)

Так же объявим остальные свойства описанные выше:

# Уровень сообщения
$banner.BalloonTipIcon  = [System.Windows.Forms.ToolTipIcon]'Info'
$banner.BalloonTipTitle = 'Заголовок'
$banner.BalloonTipText  = 'Наше сообщение'
# будет ли выводится баннер
$banner.Visible = $true

Для вызова баннера используется метод "ShowBalloonTip", в котором нужно указать длительность отображения баннера. В примере ниже стоит 200 миллисекунд. По истечении этого времени баннер исчезнет:

$banner.ShowBalloonTip(200)

Вывод баннера в Powershell

 

Скрытие иконки

Одна из проблем кода выше заключается в иконке, которая выводятся вместе с баннером. Она не исчезает после вывода баннера. Ее так же нельзя скрыть по нажатию. Единственный способ закрыть их - завершить сессию Powershell в которой она была создана (закрыв окно).

Правильным способом скрытием иконки будет вызов метода 'dispose', который предназначен именно для этого. Этот метод будет вызываться при событии (тригере) в виде двойного нажатия на иконку в трейе.

Событие создается с помощью 'Register-ObjectEvent'. В нем мы должны указать объект (NotifyIcon), событие на которое необходима реакция (двойное нажатие мышки) и название. При нажатии мы выполняем блок прописанный в 'Action':

Add-Type -AssemblyName System.Windows.Forms

if (-NOT $banner) {
    $banner = New-Object System.Windows.Forms.NotifyIcon
    [void](Register-ObjectEvent -InputObject $banner -EventName MouseDoubleClick -SourceIdentifier BannerClick -Action {
        # При двойном клике удаляем все созданные объекты
        $banner.dispose()
        Unregister-Event -SourceIdentifier BannerClick
        Remove-Job -Name BannerClick
        Remove-Variable -Name banner
    })
}

# ...
# Остальной код
# ...

Альтернативно можно закрыть баннер вызвав метод "$banner.dispose()" в конце скрипта, но в этом случае иконка вместе с сообщением пропадет полностью после указанного времени.

 

Готовая команда

Целый скрипт находится на GitHub. Его можно использовать разными способами. Вы можете поместить его в папку с модулями или импортировать:

Import-Module "F:\powershell\banner\Invoke-Banner.ps1"

Варианты использования:

# информационное сообщение
Invoke-Banner -Title 'Заголовок' -Text 'Простое информационное сообщение'

# вывод ошибки
Invoke-Banner -Title 'Заголовок' -Text 'Ошибка' -Type Error

# указываем длительность работы баннера и свою иконку
Invoke-Banner -Title 'Заголовок' -Text 'Простое информационное сообщение' -Type Error -Duration 700 -Icon "G:\banner.ico"

Использование команды Invoke-Banner в Powershell

 

Дополнительные возможности

Сам класс 'NotifyIcon' предназначен не только для вывода баннера. Основная работа класса связана с взаимодействием с иконкой.  К этой иконке можно привязать контекстное меню и вызвать какие-то другие функции. Для примера возьмем команды, которые выведут список процессов в GUI:

Get-Process | Out-GridView

Вывод процессов с Powershell в GUI

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

    $banner.BalloonTipText  = $Text
    # --- предыдущий код ---
    # создаем объект с меню
    $menu = New-Object System.Windows.Forms.ContextMenuStrip
    # создаем кнопку с названием
    $exit = $menu.Items.Add('Вывод процессов')
    # привязываем событие, которое сработает при нажатии на кнопку
    $exit.add_Click({ 
        Get-Process | Out-GridView
        # Закрытие работы приложения
        $appContext.ExitThread()
    })
    # привязываем меню к кнопке
    $banner.ContextMenuStrip = $menu
    $banner.Visible = $true
    $banner.ShowBalloonTip($Duration)
    # запускаем как приложение
    $appContext = New-Object System.Windows.Forms.ApplicationContext
    [void][System.Windows.Forms.Application]::Run($appContext)

}

Создание кнопки в Powershell в GUI

Можно установить другие события скрывающие иконку или выполняющие определенные действия. Все из них можно увидеть следующим образом:

Add-Type -AssemblyName System.Windows.Forms
$banner = New-Object System.Windows.Forms.NotifyIcon
$banner | Get-Member

NotifyAction это класс .NET и вы можете почитать документацию для поиска других применений.

 

Использование удаленно

Вызывать подобный скрипт удаленно, используя WinRM (PSRemoting), не получится. Это связано с разными сеансами, в которых работает сам пользователи и Powershell. Если вы ищете простой вариант вывода сообщений удаленно - вам может больше подойти статься по программе msg и ее использование в Powershell, которая работает в этом случае исправно.

Можно использовать программу 'psexec' от SysInternals, но это так же достаточно сложный процесс. В первую очередь мы должны узнать номер сессии в которой работает пользователь. Самый простой способ сделать это - использовать 'quser':

# локально
quser

# удалено
quser /server:localhost

О quser и другие способах подключения описывалось в предыдущих статьях. Удаленный вариант, с quser, сработает только в том случае, если вы являетесь привилегированным пользователем для подключения к удаленному компьютеру. Использовать эту команду с WinRM, скорее всего, приведет к проблемам с кодировками. Из результата работы команды вам нужно значение с ID. 

Имея этот идентификатор и скаченную утилиту 'psexec', вы сможете запустить командлет следующим образом:

$module_path = "C:\banner.ps1"
$command = "Invoke-Banner -Title Hello -Text World"
$session_id = 2

.\PsExec64.exe -s -i $session_id \\192.168.2.111 -u domain\администратор -p пароль /accepteula cmd /c "powershell -command Import-Module $module_path; $command"

 

Пример, который я сделал для видео:

...

Теги: #powershell


Каналы
Telegram FixMyPc Telegram Лента FixMyPC RSS Rss
Популярные тэги
О блоге
Этот блог представляет собой конспекты выученного материала, приобретённого опыта и лучшие практики в системном администрировании и программировании.