LANに接続している機器のIPv4アドレスを列挙する(ping絨毯爆撃) Windows無情編

続きというか、以前はUnix環境での話だったので、今回はWindowspingで絨毯爆撃してLANに接続している機器のIPアドレスを列挙してみたい。

eel3.hatenablog.com

まずはシンプルに、バッチファイルで逐次実行する方法。

@echo off
setlocal

set ADDRBASE=192.0.2.

for /L %%I in (1, 1, 254) do (
    ping -n 1 -w 100 %ADDRBASE%%%I >nul
    if not errorlevel 1 (
        echo %ADDRBASE%%%I
    )
)
endlocal

Windowspingタイムアウト値をミリ秒単位で設定する。LANに接続している機器の応答性能との兼ね合いはあるが、タイムアウト値を短くすることで実行時間を短縮することができる。

実際、このバッチファイル(タイムアウト100ミリ秒)は手元の環境では2分10秒弱で完了した。

とはいえ逐次実行である以上、どうしても時間がかかってしまう。何とかできないものか
? コマンドプロンプトでは無理そうだが、PowerShellではどうだろうか?

PowerShellで並列処理する方法はいくつかあるが、素のPowerShell 5.1の場合は、バックグラウンドジョブを使う方法とWorkflowを使う方法の2つが考えられる。

バックグラウンドジョブを使う方法で、何も考えずにシンプルにコードを書くと、こんな感じになる。

Set-StrictMode -Version Latest

1..254 | %{
    $addr = "192.0.2.$_"
    Start-Job -ScriptBlock {
        param($addr)
        $out = ping -n 1 -w 500 $addr
        if ("$out" -cnotmatch '100%') {
            $addr
        }
    }
} | Wait-Job | Receive-Job

このスクリプト、手元の環境では5分待っても処理が終わらないのである。タスクマネージャーで確認したところ、大量のプロセスが生成されて動作していた。プロセスの生成数を制御した方がよさそうだ。

ということで、アレコレと試行錯誤してたどり着いた、手元の環境で最も高速なスクリプトはこんな感じ。

Set-StrictMode -Version Latest

Set-Variable -Name ADDRBASE -Value '192.0.2.' -Option Constant

$jobs = @()
for ($i = 1; $i -le 254; $i++) {
    $jobs += Start-Job -ArgumentList "$ADDRBASE$i" -ScriptBlock {
        param($addr)
        $out = ping -n 1 -w 500 $addr
        if ("$out" -cnotmatch '100%') {
            $addr
        }
    }
    $tmp = $jobs | ?{ $_.State -eq 'Running' }
    if (($tmp | Measure-Object).Count -ge 5) {
        Wait-Job $tmp -Any | Out-Null
    }
}
Wait-Job $jobs | Receive-Job | Write-Host
Remove-Job $jobs

信じられないかもしれないが、for文を使っているのもループ中でWait-Jobを実行するタイミングもループ中のWait-Jobのオプション-Anyも、全て手元の環境での高速化に結びついている。現物合わせでこうなった。

このスクリプトは……プロセスを常時5個以上立ち上げてCPUをガンガン占有して処理を行う割に、手元の環境では2分16秒ほどかかる。バッチファイルによる逐次実行版の方が数秒速い上に、CPUをほとんど占有しない。どういうことだ!

バックグラウンドジョブに失望したので、PowerShell 3.0で導入されたWorkflowを使ってみた。

Set-StrictMode -Version Latest

workflow Ping-All {
    ForEach -parallel ($i in @(1..254)) {
        $addr = "192.168.24.$i"
        $out = ping -n 1 -w 500 $addr
        if ("$out" -cnotmatch '100%') {
            $addr
        }
    }
}

Ping-All | Sort-Object { [Version] $_ }

リソースの占有具合はマシになったものの、やはり手元の環境ではそれなりに時間がかかる。とはいえバッチファイルによる逐次実行版よりは少し高速になった。

Windows付属のPowerShellでの並列処理の遅さには不満がある。標準でThreadJob入りになるとよいのだが……今後、PowerShell Coreベースの実装が標準添付されるようになるのかどうかも含めて、少し気になるところである。