私powershellだけどタスクトレイの片隅でアイを叫ぶ

# はじめに

こちらの[記事](https://qiita.com/magiclib/items/cc2de9169c781642e52d)に記載したPowerShellでタスクトレイに常駐するスクリプトを運用していたところ、バルーン表示以外にもタスクトレイのアイコンを操作してユーザー通知する仕組みが欲しくなったので作ってみました。

# アイコンを差し替える方法

アイコンはSystem.Windows.Forms.NotifyIconのIconプロパティに指定しています。

私のテンプレートではExtractAssociatedIconを利用してアプリケーションアイコンを表示しています。
ネットのサンプルもこの方式が多いようでした。


```powershell

$path = Get-Process -id $pid | Select-Object -ExpandProperty Path
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path)

```

切り替える場合は、アイコンを取得して指定すればよいです。
アイコンの取得先は何でもよいですが、以下の２種が簡単そうです。

ICOファイルを読み込む方法

```powershell

$icon = new-object System.Drawing.Icon .\sample.ico

```

画像ファイルをアイコンとして利用する方法

```powershell

$image = [System.Drawing.Image]::FromFile(".\sample.png")
$icon = [System.Drawing.Icon]::FromHandle($image.GetHicon())
$image.Dispose()

```

通知したい場合に、アイコンの色を変える場合は上記の処理で問題ないと思います。


# 文字を切り替えて表示する

個人的には配布用する際にアイコンファイルや、画像ファイルをセットで配布するのが嫌なのでスクリプト内で解決する方法を実装しました。

スクリプト内で画像を描画して、アイコンに利用します。
文字列を描画することで、メッセージ表示を可能にしました。

```powershell

Function ShowTaskTrayString {
    [CmdletBinding()]
    PARAM (
        [Parameter(Mandatory=$true)][String] $message,
        [Parameter()][int] $interval = 1000
    )

    Write-Host $message

    Add-Type -AssemblyName System.Windows.Forms

    $application_context = New-Object System.Windows.Forms.ApplicationContext
    $timer = New-Object Windows.Forms.Timer

    # タスクトレイアイコン
    $notify_icon = New-Object System.Windows.Forms.NotifyIcon
    $notify_icon.Visible = $true
    $notify_icon.Text = $message
    function get_char_icon($image_text){

        Write-Host $image_text
        $image_pixel = 16 # アイコンの幅、高さのピクセル数 
        
        $brush_bg = New-Object Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0,0,0,0)) # Aを0にして透過させる 透過なのでRGBは何でもよい。
        $brush_text = New-Object Drawing.SolidBrush([System.Drawing.Color]::FromArgb(255,0,255,0)) # Aは255で透過させない 
        $font = new-object System.Drawing.Font("メイリオ", $image_pixel, "Bold","Pixel")
        
        $icon_image = new-object System.Drawing.Bitmap([int]($image_pixel)),([int]($image_pixel))
        $image_graphics = [System.Drawing.Graphics]::FromImage($icon_image)
        
        $format = [System.Drawing.StringFormat]::GenericDefault
        $format.Alignment = [System.Drawing.StringAlignment]::Center
        $format.LineAlignment = [System.Drawing.StringAlignment]::Center
        $rect = [System.Drawing.RectangleF]::FromLTRB(0, 0, $image_pixel, $image_pixel)

        # テキスト全体を描画する
        $image_graphics.FillRectangle($brush_bg, $rect)
        $image_graphics.DrawString($image_text, $font, $brush_text, $rect, $format)
        $icon_image.save("icon.bmp", [System.Drawing.Imaging.ImageFormat]::Bmp)
        
        $icon = [System.Drawing.Icon]::FromHandle($icon_image.GetHicon())
        $icon_image.Dispose()

        return $icon
    }
      
    $script:message_char_index = 0
    $notify_icon.Icon = get_char_icon($message.substring(0,1))

    # タイマーイベント.
    $timer.Enabled = $true
    $timer.Add_Tick({
        $timer.Stop()

        if( $message.Length -le $script:message_char_index ){
            $application_context.ExitThread()
            return
        }

        # アイコンを入れ替える
        $notify_icon.Icon = get_char_icon($message.substring($script:message_char_index,1))

        # インデックスを更新
        $script:message_char_index += 1

        # インターバルを再設定してタイマー再開
        $timer.Interval = $interval
        $timer.Start()
    })

    $timer.Interval = 1
    $timer.Start()

    [void][System.Windows.Forms.Application]::Run($application_context)

    $timer.Stop()
    $notify_icon.Visible = $false
}

```

## 動作確認

```powershell

ShowTaskTrayString -message あいしてる

```

![anime2.gif](https://qiita-image-store.s3.amazonaws.com/0/264524/60f1801e-f2ad-b67a-1385-c04059c831ed.gif)



# スクロール式

文字を描画することでメッセージ表示が可能になりましたが、１文字づつ切り替えるとスペース入りの文字列などで読みにくいので、電光掲示板のようにスクロールする方式も作りました。

```powershell

Function ScrollTaskTrayString {
    [CmdletBinding()]
    PARAM (
        [Parameter(Mandatory=$true)][String] $message,
        [Parameter()][int] $interval = 100　# １ピクセルスクロールする間隔 ミリ秒
    )

    Add-Type -AssemblyName System.Windows.Forms

    function get_text_bitmap($message,$dist_pixel){
        $brush_bg = New-Object Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0,0,0,0)) # Aを0にして透過させる 透過なのでRGBは何でもよい。
        $brush_text = New-Object Drawing.SolidBrush([System.Drawing.Color]::FromArgb(255,255,0,0))  # Aは255で透過させない 
        $font = new-object System.Drawing.Font("メイリオ", $dist_pixel, "Bold","Pixel")

        # テキスト描画に必要なサイズを取得
        $font_size = [System.Windows.Forms.TextRenderer]::MeasureText($message, $font)
        $image_width = $font_size.Width + ($dist_pixel * 2) # 左右に余白を用意したサイズを指定
        $image_height = $font_size.Height
        $text_image = new-object System.Drawing.Bitmap([int]($image_width)),([int]($image_height))
        $text_image_graphics = [System.Drawing.Graphics]::FromImage($text_image)

        $format = [System.Drawing.StringFormat]::GenericDefault
        $format.Alignment = [System.Drawing.StringAlignment]::Center
        $format.LineAlignment = [System.Drawing.StringAlignment]::Center
        $rect = [System.Drawing.RectangleF]::FromLTRB(0, 0, $image_width, $image_height)

        # テキスト全体を描画する
        $text_image_graphics.FillRectangle($brush_bg, $rect)
        $text_image_graphics.DrawString($message, $font, $brush_text, $rect, $format)

        return $text_image
    }

    function get_icon_from_image($image,$dist_pixel,$scroll_index){

        $icon_image = new-object System.Drawing.Bitmap([int]($dist_pixel)),([int]($dist_pixel))
        $icon_image_graphics = [System.Drawing.Graphics]::FromImage($icon_image)

        # タスクトレイに描画する部分を切り出し
        $rect = New-Object Drawing.Rectangle 0, 0, $dist_pixel ,$dist_pixel
        $icon_image_graphics.DrawImage($image, $rect, $scroll_index, 0, $dist_pixel, $dist_pixel, ([Drawing.GraphicsUnit]::Pixel))

        $icon = [System.Drawing.Icon]::FromHandle($icon_image.GetHicon())
        $icon_image.Dispose()

        return $icon
    }

    $application_context = New-Object System.Windows.Forms.ApplicationContext
    $timer = New-Object Windows.Forms.Timer

    # タスクトレイアイコン
    $notify_icon = New-Object System.Windows.Forms.NotifyIcon
    $notify_icon.Visible = $true
    $notify_icon.Text = $message

    $script:scroll_index = 0 # 描画位置
    $image_pixel = 16 # アイコンの幅、高さのピクセル数

    # テキスト全体のイメージを生成
    $src_image = get_text_bitmap $message $image_pixel
    $notify_icon.Icon = get_icon_from_image $src_image $image_pixel 0

    # タイマーイベント.
    $timer.Enabled = $true
    $timer.Add_Tick({
        $timer.Stop()

        # 端まで描画したら終了
        if( $src_image.Width -le $script:scroll_index ){
            $application_context.ExitThread()
            return
        }

        # アイコンを入れ替える
        $notify_icon.Icon = get_icon_from_image $src_image $image_pixel $script:scroll_index

        # インデックスを更新（１ピクセルのスクロール）
        $script:scroll_index += 1

        # インターバルを再設定してタイマー再開
        $timer.Interval = $interval
        $timer.Start()
    })

    $timer.Interval = 1
    $timer.Start()

    [void][System.Windows.Forms.Application]::Run($application_context)

    $timer.Stop()
    $notify_icon.Visible = $false
}

```


## 動作確認

```powershell

ScrollTaskTrayString -message あいしてる -interval 50

```
![scroll3.gif](https://qiita-image-store.s3.amazonaws.com/0/264524/426f7a54-4aaa-cbae-8acc-4de2180a7b2d.gif)



# さいごに

作ってみたけど、あんまり使い道はないかも！


# 私PowerShellだけど…シリーズ

[私PowerShellだけど、君のタスクトレイで暮らしたい](https://qiita.com/magiclib/items/cc2de9169c781642e52d)
[私PowerShellだけど「送る」からファイルを受け取りたい（コンテキストメニュー登録もあるよ）](https://qiita.com/magiclib/items/7529557f756af9c8d21b)
[私PowerShellだけど子を持つ親になるのはいろいろ大変そう](https://qiita.com/magiclib/items/ef0ab5c1fd8e0260b39d)

[サンプルソース置き場](https://github.com/part-time-penguin/qiita_powershell_samples)