1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

■ PowerShellでPing死活監視ツール ~💀Deadmanリスペクト🔥~

Last updated at Posted at 2025-06-13

PowerShell_banner.png


こんにちは :star2:
連日PowerShell投稿になっちゃいました 零壱(ゼロイチ)テクトです。

PowerShellで既存のPingやPingツールとは違うPing表示方法はないか模索していた所
かの現在開催中の展示会「Interop」で過去に用いられたという
CLIの死活監視ツール「Deadman」なるツールを見つけました。

ツールの画面を見て、中身や並列処理はともかく
見た目だけならPowerShellで似たようなツールを作れるのでは
と本能が感じたので作成(リスペクト)してみました。

1.概要/仕様

・最小限のDeadmanの機能を付ける
・見た目も可能な限りDeadmanに寄せる
・Ping結果の記号表示の波は絶対/色も絶対

2.コード - Simple版

コードを最小限にするため機能を必要最低限に絞ったバージョンです。

Deadman_Simple.ps1
################################
# Deadman Tool - Simple
###############################

# Ping監視対象
$targets = @("8.8.8.8","1.1.1.1","18.65.207.100") # Google, CloudFlare, Qiita

# 各種設定値
$interval = 500          # Ping間隔/ms
$repeat   = 10000        # 繰り返し回数
$maxDisplayCount = 30    # 表示するPing結果の最大履歴数

# コンソールウィンドウのサイズを指定
[console]::WindowWidth = 90
[console]::WindowHeight = 20

# 結果表示用のデータ構造を初期化
$symbolMap    = @{}  # 各対象のPing結果を記号で保持
$lineIndexMap = @{}  # 各対象の結果表示位置(コンソール行番号)を保持
$index = 0           # 表示行番号カウンタ

# ヘッダー部の項目名を表示
Write-Host ("{0,-15} {1,-30} {2,-8} {3}" -f "Target", "HostName", "Rtt", "Result")

# 各初期化処理
foreach ($target in $targets) {
    $symbolMap[$target] = @()            # Ping結果用の空配列
    $lineIndexMap[$target] = $index + 1  # 対象の表示行番号を保存(ヘッダーが0行目のため+1)
    Write-Host ""                        # 対象ごとに空行を挿入して表示領域を確保
    $index++                             # 行カウンタをカウントアップ
}

# 応答時間に応じて表示する記号を返す関数
# RTTが大きいほどブロックの大きさが大きくなる/ms
function Get-SymbolFromRTT($rtt) {
    if ($rtt -ge 1000) { return "▇" }  
    elseif ($rtt -ge 200) { return "▆" }
    elseif ($rtt -ge 100) { return "▅" }
    elseif ($rtt -ge 50)  { return "▃" }
    elseif ($rtt -ge 10)  { return "▂" }
    else { return "▁" }                 
}

# メイン監視:repeat回数分繰り返す
1..$repeat | ForEach-Object {
    # Ping対象を順次ループ
    foreach ($target in $targets) {
        Start-Sleep -Milliseconds $interval  # Ping間隔調整

        # ホスト名をDNSから取得
        try {
            $hostEntry = [System.Net.Dns]::GetHostEntry($target)
            $hostName = $hostEntry.HostName
        } catch {
            $hostName = "[ failed ]"     
        }

        # 取得ホスト名調整
        if ($hostName.Length -gt 27) {
            $hostName = $hostName.Substring(0, 27) + "..."
        }

        # Pingを1回実行して結果を取得
        $pingResult = Test-Connection -ComputerName $target -Count 1 -ErrorAction SilentlyContinue

        if ($pingResult) { # PingOK
            $return = "$($pingResult.ResponseTime)ms"                 # 応答時間を文字列化
            $symbol = Get-SymbolFromRTT($($pingResult.ResponseTime))  # RTTに応じた記号取得
        } else { # PingNG
            $return = "none"    # 応答なしの表示
            $symbol = "X"       # 失敗を「X」で表示
        }

        # Ping結果リストに追加
        $symbolMap[$target] += $symbol

        # Ping結果リストが最大表示数を超えたら結果を左にスライド
        if ($symbolMap[$target].Count -gt $maxDisplayCount) {
            $symbolMap[$target] = $symbolMap[$target][1..($symbolMap[$target].Count - 1)]
        }

        # コンソールのカーソル位置を対象ホストの表示行に移動
        $line = $lineIndexMap[$target]
        [Console]::SetCursorPosition(0, $line)

        # 実行結果更新(表示位置調整)
        Write-Host ("{0,-15} {1,-30} {2,6} {3,0} " -f $target, $hostName, $return, " ") -NoNewline

        # Ping結果の記号履歴を表示。応答なしは赤色/それ以外は緑色
        foreach ($s in $symbolMap[$target]) {
            if ($s -eq "X") {
                Write-Host $s -NoNewline -ForegroundColor Red
            } else {
                Write-Host $s -NoNewline -ForegroundColor Green
            }
        }

    }
}

3.コード - Detail版

Simple版だけでも良かったのですが、リスペクトもかねて
少しでもオリジナルに似た見た目になるよう頑張っちゃったバージョンです。

Deadman_Detail.ps1
################################
# Deadman Tool - Detail
################################

# Ping監視対象
$targets = @("8.8.8.8", "1.1.1.1","18.65.207.100") # Google, CloudFlare, Qiita

# 各種設定値
$interval = 500          # Ping間隔/ms
$repeat = 10000          # 繰り返し回数
$maxDisplayCount = 30    # 表示するPing結果の最大履歴数

# コンソールウィンドウのサイズを指定
[console]::WindowWidth = 107
[console]::WindowHeight = 20

# 結果表示用のデータ構造を初期化
$symbolMap    = @{}  # 各各対象のPing結果を記号で保持
$lineIndexMap = @{}  # 各対象の結果表示位置(コンソール行番号)を保持
$rttMap       = @{}  # RTT合計値と応答回数を格納するハッシュ(平均計算用)
$index = 0           # 表示行番号カウンタ

# ヘッダー部の項目名を表示(表示位置調整)
Write-Host ("{0,-15} {1,-30} {2,-6} {3,-8} {4,-8} {5}" -f "Target", "HostName", "Loss", "Avg", "Rtt", "Result")

# 各初期化処理
foreach ($target in $targets) {
    $symbolMap[$target] = @()                      # Ping結果用の空配列
    $rttMap[$target]    = @{ Sum = 0; Count = 0 }  # RTTの累積値とPing回数を初期化
    $lineIndexMap[$target] = $index + 1            # 対象の表示行番号を保存(ヘッダーが0行目のため+1)
    Write-Host ""                                  # 空行挿入して表示領域を確保
    $index++                                       # 行カウンタをカウントアップ
}

# 応答時間に応じて表示する記号を返す関数
# RTTが大きいほどブロックの大きさが大きくなる/ms
function Get-SymbolFromRTT($rtt) {
    if ($rtt -ge 1000) { return "▇" }  
    elseif ($rtt -ge 200) { return "▆" } 
    elseif ($rtt -ge 100) { return "▅" }
    elseif ($rtt -ge 50)  { return "▃" }
    elseif ($rtt -ge 10)  { return "▂" }
    else { return "▁" }                 
}

# メイン監視:repeat回数分繰り返す
1..$repeat | ForEach-Object {
    # Ping対象を順次ループ
    foreach ($target in $targets) {
        Start-Sleep -Milliseconds $interval  # Ping間隔調整

        # ホスト名をDNSから取得
        try {
            $hostEntry = [System.Net.Dns]::GetHostEntry($target)
            $hostName = $hostEntry.HostName
        } catch {
            $hostName = "[ failed ]"
        }

        # 取得ホスト名調整
        if ($hostName.Length -gt 27) {
            $hostName = $hostName.Substring(0, 27) + "..."
        }

        # Pingを1回実行して結果を取得
        $pingResult = Test-Connection -ComputerName $target -Count 1 -ErrorAction SilentlyContinue

        if ($pingResult) { # PingOK
            $rtt    = $pingResult.ResponseTime  # RTT取得
            $return = "$($rtt)ms"               # 表示用文字列作成
            $symbol = Get-SymbolFromRTT($rtt)   # RTTに応じた記号取得

            # 平均計算用にRTTを累積/応答回数をカウントアップ
            $rttMap[$target].Sum += $rtt
            $rttMap[$target].Count++
        } else { # PingNG
            $return = "none"    # 応答なしの表示
            $symbol = "X"       # 失敗を「X」で表示
        }

        # Ping結果リストに追加
        $symbolMap[$target] += $symbol

        # Ping結果リストが最大表示数を超えたら結果を左にスライド
        if ($symbolMap[$target].Count -gt $maxDisplayCount) {
            $symbolMap[$target] = $symbolMap[$target][1..($symbolMap[$target].Count - 1)]
        }

        # Loss率計算(履歴内のX記号の割合)
        $totalCount = $symbolMap[$target].Count                                    # Ping総数
        $lossCount = ($symbolMap[$target] | Where-Object { $_ -eq "X" }).Count     # PingNG数
        $lossPercent = "{0,3}%" -f [math]::Round(($lossCount / $totalCount) * 100) # パーセント表示に整形

        # RTT平均計算/未計測の場合は「n/a」
        if ($rttMap[$target].Count -gt 0) {
            $avgRtt = "{0,4}ms" -f [math]::Round($rttMap[$target].Sum / $rttMap[$target].Count)
        } else {
            $avgRtt = " n/a"
        }

        # コンソールのカーソル位置を対象ホストの表示行に移動
        $line = $lineIndexMap[$target]
        [Console]::SetCursorPosition(0, $line)

        # 実行結果更新(表示位置調整)
        Write-Host ("{0,-15} {1,-30} {2,-6} {3,-7} {4,7} {5,0}" -f $target, $hostName, $lossPercent, $avgRtt, $return,"  ") -NoNewline

        # Ping結果の記号履歴を表示。応答なしは赤色/それ以外は緑色
        foreach ($s in $symbolMap[$target]) {
            if ($s -eq "X") {
                Write-Host $s -NoNewline -ForegroundColor Red
            } else {
                Write-Host $s -NoNewline -ForegroundColor Green
            }
        }
    }
}

コードはお好きにお使いください。

【追加】コード - Detail版のグラフ表示詳細化(※2025/06/25追記※)

「グラフ」表示部の2か所を以下に置き換えるとより詳細なグラフ表示になります。
Deadmanのオリジナルに近くなります。

Get-SymbolFromRTT.add
# 応答時間に応じて表示する記号を返す関数
# RTTが大きいほどブロックの大きさが大きくなる/ms
# ▁▂▄▃▅▆▇█

function Get-SymbolFromRTT($rtt) {
    if ($rtt -ge 1000) { return "█" }
    elseif ($rtt -ge 800) { return "▇" }
    elseif ($rtt -ge 500) { return "▆" }
    elseif ($rtt -ge 200) { return "▅" }
    elseif ($rtt -ge 100) { return "▃" }
    elseif ($rtt -ge 50)  { return "▄" }
    elseif ($rtt -ge 10)  { return "▂" }
    else { return "▁" }                 
}

フォント依存なのは知ってますが、デフォルトのPowerShellで表示する階段状の特殊文字と
WEB・メモ帳で見える階段に違和感を感じていまして、
迷ったんですが公開します。

ちなみに違和感はコチラ。
ブラウザ上では、「表示2」の方が階段状に見えますが
これPowerShell上だと、3番と4番が逆転していて
表示1」の方が綺麗な階段に見えるんです。

▼表示1
▁▂▄▃▅▆▇█

▼表示2
▁▂▃▄▅▆▇█


【オマケ】コード - ワンライナー版

Deadman_Oneliner.ps1
$ip = "8.8.8.8";$sbs = @();1..10000 | ForEach-Object{Start-Sleep -Milliseconds 500;$ping=Test-Connection -ComputerName $ip -Count 1 -ErrorAction SilentlyContinue;$rtt="none";$sy="X";if ($ping) {$rtt="$($ping.ResponseTime)ms";$sy="▁▂▃▅▆▇"[[Math]::Min([Math]::Floor([Math]::Log([Math]::Max($ping.ResponseTime,5)/5)/[Math]::Log(3)),5)]};$sbs += $sy;if($sbs.Count -gt 50){$sbs = $sbs[1..($sbs.Count - 1)]};[console]::SetCursorPosition(0, 1);Write-Host("Target: {0,-8} RTT: {1,-8} Result:" -f $ip,$rtt) -NoNewline;foreach ($s in $sbs){$col=if($s -eq "X"){"Red"}else{"Green"};Write-Host $s -NoNewline -ForegroundColor $col}}

 ※ワンライナーという発想というか文化というかコードの書き方を知ってしまいました。
  とりあえず目先のコードをワンライナー化したので、オマケで載せます。
  機能を削るか迷って、コードのために対象IPを1つに絞って作りました。

 ※Ping結果の帯域が工夫ポイントです。
  IFを使わず計算で処理できるようPing戻り値を3倍数で計算・判定させていて
  つまりは細い方から太い方にかけて、5msを基準に
  15ms未満 → 45ms未満 → 135ms未満 → 405ms未満 → 1215ms未満 → それ以上
  の6段階に処理を丸めてコードを短くしました。
 

4.結果

■ Simple版 - 実行結果

Deadman_Simple_Result.png

■ Detail版 - 実行結果

Deadman_Detail_Result.png

 ※末尾2つのIPは適当なPingが飛ぶ海外プロキシサーバです。
  国内のあらゆるサイトでPingの戻り値が良すぎたため入れました。

■ ワンライナー版 - 実行結果

image.png

5.備考

✅ 改善案は……ありません。

  →あえて改良案というと、オリジナル要素を全て実装する、ぐらいでしょうか。

✅ 改善案ありました(2025/06/25追記)

  →PingのTiumeoutを1秒以上でカウントしたい場合
   このコードはPowerShell5.1で書かれているため
   簡単にTimeoutを伸ばせませんでしたので、それは改善案でした。

✅ オリジナルのconfファイル見るとホスト名は手動設定しているようですね。

  自動取得より楽なのでそこは改良案かもしれません。

・Ver5.1のPowerShellで開発なので並列処理はなく、オリジナルより遅いです。
 Ver7では並列処理可能みたいですが、実行できる方が減るので今回は無し

・よって挙動はご覧の通り、対象ホストへのPingはシングルタスクです。

・Pingの戻り値による線の太さは、見た目重視でオリジナルよりパターンが少ないです。
 →グラフ詳細化のコードを追加しました(2025/06/25)

・悔いはありません。想像以上に近しいものができて楽しかったです。

・例によってコメントは後付けです。Simple版とDetail版で差異があったらゴメンナサイ。


  それでは
       どこかの誰かの何かの足しになれば幸いです。
                        01000010 01011001 01000101

6.参考

Deadmanの挙動を知るため数々のサイト様を参考にさせて頂きました。
大変、感謝いたします。

@engishoma様:deadmanをサクッと構築してみる

@johejo (Mitsuo Heijo)様:pingツールを作った

■ Github/Deadman

■ ShowNet

■ GenkgGorosuke様:deadmanでshownet気分を味わう

■ Sig9様:deadman で Ping 監視する

@yugo-yamamoto(ゆうご 山本)様:PowerShellをワンライナーで書きたい

こちらの方でワンライナーという考え方を知りました。
これは素晴らしい発想ですね。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?