PowerShell でオレオレカラーピッカー

# 概要

Windows PowerShell 用のカラーピッカー関数を作成してみました。
PowerShell v2.0 以降なら動くと思います。

実行するとコンソールに以下のように表示されます（型はSystem.Drawing.Color です）。

```text:Qiitaのヘッダー色
R             : 85
G             : 197
B             : 0
A             : 255
IsKnownColor  : False
IsEmpty       : False
IsNamedColor  : False
IsSystemColor : False
Name          : ff55c500
```


## 注意事項

- 今時`System.Windows.Form`と`System.Drawing`を使用しています
- `System.Windows.Form`を使用しているため、PowerShell v2.0ではSTAモードで起動する必要があります
- マルチディスプレイ環境で試していないため意図しない動作をする可能性があります

# コード

```posh
# 必要なアセンブリロード
Add-Type -AssemblyName System.Windows.Forms, System.Drawing

<#
.Synopsis
カーソルのある位置の色を System.Drawing.Color として取得する

.EXAMPLE
PS > Select-DisplayColor
# 現在のマウスの位置の色を取得

.EXAMPLE
PS > Select-DisplayColor -WaitUserSelect
# ユーザーがクリックするまで待機して、クリックした位置の色を取得

.OUTPUTS
System.Drawing.Color
#>
function Select-DisplayColor {
    [CmdletBinding()]param (
        # 指定するとユーザーがクリックするまで処理を待機し、クリックした色の位置を返す
        [switch]$WaitUserSelect,

        # 基準の位置。既定は現在のカーソルの位置
        [Drawing.Point]$PickPoint = [Windows.Forms.Cursor]::Position   
    )
    
    # 画面キャプチャ
    [Drawing.Rectangle]$scrRect = [Windows.Forms.Screen]::FromPoint( $PickPoint ).Bounds
    
    [Drawing.Bitmap]$scrBmp = 
        New-Object -TypeName Drawing.Bitmap -ArgumentList $scrRect.Width, $scrRect.Height
    
    [Drawing.Graphics]$gpc = [Drawing.Graphics]::FromImage( $scrBmp )
    $gpc.CopyFromScreen(
            $scrRect.Location.X, $scrRect.Location.Y, # Source
            0, 0, # Dest
            $scrRect.Size)
    $gpc.Dispose()
    

    [Drawing.Color]$selectColor = [Drawing.Color]::Empty
    if ( !$WaitUserSelect ) {
        $selectColor = $scrBmp.GetPixel( $PickPoint.X , $PickPoint.Y )
        $scrBmp.Dispose()
        return $selectColor
    }

    # 画面を覆うフォームを作成
    [Windows.Forms.Form]$frm = 
        New-Object -TypeName Windows.Forms.Form -Property @{
            BackgroundImage = $scrBmp
            FormBorderStyle = [Windows.Forms.FormBorderStyle]::None
            WindowState     = [Windows.Forms.FormWindowState]::Maximized
        }
    
    # クリックイベント定義
    $frm.add_Click({
        try {
            [Drawing.Bitmap]$bmp = $this.BackgroundImage
            
            # 1階層親のスコープの変数に値をセット
            Set-Variable -Name selectColor -Scope 1 -Value $bmp.GetPixel( $_.X, $_.Y )
        } finally { $this.Close() }
    })
    
    $frm.ShowDialog() > $null
    $frm.Dispose() ; $scrBmp.Dispose()
    Write-Output -InputObject $selectColor
}
```

# 使用例

Qiitaのヘッダー部分にカーソルを置いて実行すると、以下の結果になります。

```text
PS C:\> Select-DisplayColor


R             : 85
G             : 197
B             : 0
A             : 255
IsKnownColor  : False
IsEmpty       : False
IsNamedColor  : False
IsSystemColor : False
Name          : ff55c500
```

`WaitUserSelect`スイッチを指定すると、画面キャプチャをしたフォームを表示して、クリックした位置から色を取得します。

```text
PS C:\> Select-DisplayColor -WaitUserSelect #「投稿する」などの濃い緑色の部分をクリック


R             : 59
G             : 138
B             : 0
A             : 255
IsKnownColor  : False
IsEmpty       : False
IsNamedColor  : False
IsSystemColor : False
Name          : ff3b8a00
```

# その他

`WaitUserSelect`スイッチを指定時に、フォームのクリックイベントで色を取得しています。
しかし、イベント処理の中から関数内の変数を直接変更できないため

```posh
# 1階層親のスコープの変数に値をセット
Set-Variable -Name selectColor -Scope 1 -Value $bmp.GetPixel( $_.X, $_.Y )
```

のように無理矢理、値を設定しているのが気になります。
もう少し上手い手はあるのでしょうか……