常駐型PowerShellプログラムをタスクトレイに入れる

#概要
下記のような構造を持つ常駐型のPowerShellプログラム（例えば[こちら](https://qiita.com/earthdiver1/items/a221321b002d89c3a8bf)のスクリプトなど）が、（最小化しても）常にタスクバー上に表示されていると目障りなのでタスクトレイ（正式には"通知領域"）に入れます。

```powershell:
while ($True) {
    ... #何かの処理
    ...
    Start-Sleep <n>
}
```

#実装
以下のソースを対象のスクリプト（拡張子 .ps1）の先頭部分に追加（埋め込み）し、実行（右クリックして「PowerShellで実行」)することでタスクトレイの中に入れることができます。最後のコメント行は削除しないでください（2桁目の"!"が重要）。
　なお、対象スクリプトには引数がなく、したがってparam文を持たないものとします（常駐型なら少しの工夫によりそのような形式で書ける筈）。

　※こちらの[記事](https://blogs.technet.microsoft.com/heyscriptingguy/2015/11/02/creating-pop-ups-by-using-powershell/)を参考にしました。

```powershell:
#@Powershell -NoP -W Hidden -C "$PSCP='%~f0';$PSSR='%~dp0'.TrimEnd('\');&([ScriptBlock]::Create((gc '%~f0'|?{$_.ReadCount -gt 1}|Out-String)))" %* & exit/b
# by earthdiver1  V1.05
if ($PSCommandPath) {
    $PSCP = $PSCommandPath
    $PSSR = $PSScriptRoot
    $code = '[DllImport("user32.dll")]public static extern bool ShowWindowAsync(IntPtr hWnd,int nCmdShow);'
    $type = Add-Type -MemberDefinition $code -Name Win32ShowWindowAsync -PassThru
    [void]$type::ShowWindowAsync((Get-Process -PID $PID).MainWindowHandle,0) }
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
$menuItem = New-Object System.Windows.Forms.MenuItem "Exit"
$menuItem.add_Click({$notifyIcon.Visible=$False;while(-not $status.IsCompleted){Start-Sleep 1};$appContext.ExitThread()})
$contextMenu = New-Object System.Windows.Forms.ContextMenu
$contextMenu.MenuItems.AddRange($menuItem)
$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
$notifyIcon.ContextMenu = $contextMenu
$notifyIcon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($PSCP)
$notifyIcon.Text = (Get-ChildItem $PSCP).BaseName
$notifyIcon.Visible = $True
$_syncHash = [hashtable]::Synchronized(@{})
$_syncHash.NI   = $notifyIcon
$_syncHash.PSCP = $PSCP
$_syncHash.PSSR = $PSSR
$runspace = [RunspaceFactory]::CreateRunspace()
$runspace.ApartmentState = "STA"
$runspace.ThreadOptions  = "ReuseThread"
$runspace.Open()
$runspace.SessionStateProxy.SetVariable("_syncHash",$_syncHash)
$scriptBlock = Get-Content $PSCP | ?{ $on -or $_[1] -eq "!" }| %{ $on=1; $_ } | Out-String
$action=[ScriptBlock]::Create(@'
#   param($Param1, $Param2)
    Start-Transcript -LiteralPath ($_syncHash.PSCP -Replace '\..*?$',".log") -Append
    Function Start-Sleep { [CmdletBinding(DefaultParameterSetName="S")]
        param([parameter(Position=0,ParameterSetName="M")][Int]$Milliseconds,
              [parameter(Position=0,ParameterSetName="S")][Int]$Seconds,[Switch]$NoExit)
        if ($PsCmdlet.ParameterSetName -eq "S") {
            $int = 5
            for ($i = 0; $i -lt $Seconds; $i += $int) {
                if (-not($NoExit -or $_syncHash.NI.Visible)) { exit }
                Microsoft.PowerShell.Utility\Start-Sleep -Seconds $int }
        } else {
            $int = 100
            for ($i = 0; $i -lt $Milliseconds; $i += $int) {
                if (-not($NoExit -or $_syncHash.NI.Visible)) { exit }
                Microsoft.PowerShell.Utility\Start-Sleep -Milliseconds $int }}}
    $script:PSCommandPath = $_syncHash.PSCP
    $script:PSScriptRoot  = $_syncHash.PSSR
'@ + $scriptBlock)
$PS = [PowerShell]::Create().AddScript($action) #.AddArgument($Param1).AddArgument($Param2)
$PS.Runspace = $runspace
$status = $PS.BeginInvoke()
$appContext = New-Object System.Windows.Forms.ApplicationContext
[void][System.Windows.Forms.Application]::Run($appContext)
exit
#! ---------- ScriptBlock (Line No. 28) begins here ---------- DO NOT REMOVE THIS LINE
```

- バッチファイルとして実行（スタートアップに登録しやすい。ダブルクリックも可能）したい場合は、先頭行のコメントをはずして以下のコマンドを有効にします。

```batch:
@Powershell -NoP -W Hidden -C "$PSCP='%~f0';$PSSR='%~dp0'.TrimEnd('\');&([ScriptBlock]::Create((gc '%~f0'|?{$_.ReadCount -gt 1}|Out-String)))" %* & exit/b
```

- 起動した後、プログラムを終了する必要が生じた場合には、タスクトレイ上のアイコンを右クリックして Exit を選択します。待機状態時に終了させるために Start-Sleep コマンドレットをカスタマイズしています。既定では待機時間が秒単位の場合は5秒毎、ミリ秒単位の場合は100ミリ秒毎に終了判定を行いますが、必要に応じて36,41行目の $int 変数の値を調整してください。
途中終了させたくないStart-Sleepコマンドレットがある場合は、当該 Start-Sleep に -NoExit パラメータを追加してください。
- 一瞬、黒いコンソール画面が表示されてしまうのがどうしても気になる方は、ショートカットを作成してプロパティの「実行時の大きさ」を「最小化」としてください。

(2018.5.1修正)  \$PSScriptRoot を script スコープで参照可能としました。


クリエイティブ・コモンズ 表示 - 継承 4.0 国際