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

PowerShellで既存のPingやPingツールとは違うPing表示方法はないか模索していた所
かの現在開催中の展示会「Interop
」で過去に用いられたという
CLIの死活監視ツール「Deadman」なるツールを見つけました。
ツールの画面を見て、中身や並列処理はともかく
見た目だけならPowerShellで似たようなツールを作れるのでは
と本能が感じたので作成(リスペクト)してみました。
1.概要/仕様
・最小限のDeadmanの機能を付ける
・見た目も可能な限りDeadmanに寄せる
・Ping結果の記号表示の波は絶対/色も絶対
2.コード - Simple版
コードを最小限にするため機能を必要最低限に絞ったバージョンです。
################################
# 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 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のオリジナルに近くなります。
# 応答時間に応じて表示する記号を返す関数
# 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
▁▂▃▄▅▆▇█
【オマケ】コード - ワンライナー版
$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版 - 実行結果
■ Detail版 - 実行結果
※末尾2つのIPは適当なPingが飛ぶ海外プロキシサーバです。
国内のあらゆるサイトでPingの戻り値が良すぎたため入れました。
■ ワンライナー版 - 実行結果
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をワンライナーで書きたい
こちらの方でワンライナーという考え方を知りました。
これは素晴らしい発想ですね。