Test-Connectionの最速を目指す

## でも、pingでいいじゃない？

そんな！　元も子もないことを！

確かに、PowerShellだろうとBashだろうと「ping.exe」は握手と同じくらいのネットワーク技術者の共通言語、四則計算レベルの常識と言って然るべきです。  
でも、あえて、WindowsでPowerShellを使いたい、という欲求もある人もいるんです。そのはずです！

なので、ちょろっと考えてみました。ご一読をどうぞ。

## Test-Connection

PowerShellでのping.exeに相当するものは、Test-Connectionです。以下の使い方で構いません。  

``` PowerShell
Test-Connection localhost
```

だが、試してもらうとわかりますが、これの使い勝手が悪い。というか、遅い。  
localhostならまだいいですが、たまに正常に動いていてもすぐCtrl+Cを押したくなるくらいに遅い。

> Test-Connectionが遅い理由
> ではどこでNetBIOS Name Queryが発行されているのかというと、その原因はTest-Connectionの戻り値にあります。
>
> （中略）
>
> プロパティ内で[System.Net.Dns]::GetHostEntry()メソッドを発行しIPアドレスの逆引きをしていることがわかります。
> このメソッドは内部でgethostbyaddr()関数を使用していますのでNetBIOS Name Queryを含めた名前解決*2が実行され、その結果待ちにより処理が遅くなってしまうのです。

[Test\-Connectionが遅い理由と対策方法について \- しばたテックブログ](https://blog.shibata.tech/entry/2016/06/16/231239)


上記ブログにもありますが、戻り値の **IPV4Address** と **IPV6Address** がこういう悪さ？をしているようです。

> ``` PowerShell
> TypeName   : System.Management.ManagementObject#root\cimv2\Win32_PingStatus
> Name       : IPV4Address
> MemberType : ScriptProperty
> Definition : System.Object IPV4Address {get=$iphost = [System.Net.Dns]::GetHostEntry($this.address)
>                          $iphost.AddressList | ?{ $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork
>               } | select -first 1;}
> 
> TypeName   : System.Management.ManagementObject#root\cimv2\Win32_PingStatus
> Name       : IPV6Address
> MemberType : ScriptProperty
> Definition : System.Object IPV6Address {get=$iphost = [System.Net.Dns]::GetHostEntry($this.address)
>                          $iphost.AddressList | ?{ $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork
>              V6 } | select -first 1;}
> ```

とどのつまり、わざわざ戻り値を返すタイミングで、DNS逆引きをしているからということですね。そりゃ重くなるわけだ。

## 解決法

というわけで、素早く便利に使うには思わぬ仕様の障害があるわけです。
これの解決法に関しては、上のブログに書いてあるのでそれも参照してみてうださい。  

さて、私のオススメとして紹介するのは、"みんな大好き"MSDNに記載されている以下の例示の方法が一番興味深いコードです。

> Example 5: Run a test as a background job
> 
> This example shows how to run a Test-Connection command as a PowerShell background job.
> 
> PowerShell
> 
> ```
> $job = Start-Job -ScriptBlock { Test-Connection -TargetName (Get-Content "Servers.txt") }
> if ($job.JobStateInfo.State -ne "Running") { $Results = Receive-Job $job }
> ```
> 

参照元：[Test\-Connection](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/test-connection?view=powershell-6)

要するに、-asjobオプションでバックグラウンドで動かしてあげる方法です。バックグラウンドで動かせば当然戻り値の表示は行いませんから、戻り値のスクリプトも動かないし重くないということです。

ただ、その情報を取得するときに **IPV4Address** と **IPV6Address** を表示させては、元の木阿弥ですから、Select-Objectで表示させないよう回避するわけです。

## 実際のコマンド

では、実際にコマンドを打ってみましょう。

まず前提として、以下のテキストファイル「testfile.txt」があるとします。  
192.168.0.1,2,3は疎通でき、4,5は疎通できないとします。

``` texsfile.txt
192.168.0.1
192.168.0.2
192.168.0.3
192.168.0.4
192.168.0.5
```

その上で先のMSDN Exammple.5のコマンドをちょろっと工夫して打ってみましょう。

``` PowerShell
PS E:\workspace> $job = Test-Connection (Get-Content .\testfile.txt) -asjob -count 1
PS E:\workspace> if ($job.JobStateInfo.State -ne "Running" ){ $result = Receive-Job $job }  else { echo "Running..."}
PS E:\workspace> $result | Select-Object Address, StatusCode

Address       StatusCode
-------       ----------
192.168.0.1           0
192.168.0.2           0
192.168.0.3           0
192.168.0.4       11010
192.168.0.5       11010
```

0が正常疎通、それ以外は何らかのエラーです。こんな感じですね。  
もっとも、結果を出したいだけなら結果だけを表示する -Quiet オプションを使ってあげたほうが楽です。
ただ、うまい具合に組んであげないと、実際に試したIPが分からず、本当にTrue or Falseしか返ってきません。

``` PowerSHell
PS E:\workspace> Test-Connection (Get-Content .\testfile.txt) -Quiet -Count 1
True
True
True
False
False
PS E:\workspace>
PS E:\workspace> Get-Content .\testfile.txt | % { $_ + "," + (Test-Connection $_ -Quiet -Count 1) }
192.168.0.1,True
192.168.0.2,True
192.168.0.3,True
192.168.0.4,False
192.168.0.5,False
```

Test-Connectionで返ってくる他の情報が欲しい場合には、バックグラウンドで動かしてやってから、取るのがいい方法でしょう。  
あるいは、Test-Connectionの結果をもとに、さらにコマンドを動かしたいというのであれば、batで組むよりかは遥かに楽でしょう。

## 時間測定

最後に、Measure-Commandで時間を調べてみた結果を示します。

### 確認方法

- 対象：ローカルネットワークに対する5つのIPアドレス
- 比較するコマンド内容：
   1. -AsJobオプションによるバックグラウンド実行
   1. -Quietのみの疎通確認実行
  - 具体的なコードは、当ブログ最下部をご参照
- 実行回数：３回実行
- 比較対象：3回の実行時間の平均

今回は、コマンドの時間比較が目的ですので、当方の環境は割愛します。当然、他の環境では秒数は変わると思います。ただ、傾向は変わらないことでしょう。

### 実行結果

|実行方法|時間（３回平均）|
|:--:|---:|
|バックグラウンド実行|3.795 秒|
|ーQuietのみ|11.807 秒|

明確に、-asjobオプションによってバックグラウンドで実行したほうが速いことがわかりました。

当記事は、以上です。これが最速かわかりませんので、
**「これが一番早いと思います」**
という保険をかけておきます。

以下は、実行結果をそのまま貼り付けています。興味のある方はどうぞ。

## 参考：実行結果

``` PowerShell
PS E:\workspace> Measure-Command {
    $job = Test-Connection (Get-Content .\testfile.txt) -asjob -count 1
    While ($job.JobStateInfo.State -eq "Running" ){ 
        ## 何もしない
    }
    $result = Receive-Job $job
    $result | Select-Object Address, StatusCode
}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 4
Milliseconds      : 6
Ticks             : 40062152
TotalDays         : 4.63682314814815E-05
TotalHours        : 0.00111283755555556
TotalMinutes      : 0.0667702533333333
TotalSeconds      : 4.0062152
TotalMilliseconds : 4006.2152




PS E:\workspace> Measure-Command {
    $job = Test-Connection (Get-Content .\testfile.txt) -asjob -count 1
    While ($job.JobStateInfo.State -eq "Running" ){ 
        ## 何もしない
    }
    $result = Receive-Job $job
    $result | Select-Object Address, StatusCode
}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 3
Milliseconds      : 706
Ticks             : 37062980
TotalDays         : 4.28969675925926E-05
TotalHours        : 0.00102952722222222
TotalMinutes      : 0.0617716333333333
TotalSeconds      : 3.706298
TotalMilliseconds : 3706.298




PS E:\workspace> Measure-Command {
    $job = Test-Connection (Get-Content .\testfile.txt) -asjob -count 1
    While ($job.JobStateInfo.State -eq "Running" ){ 
        ## 何もしない
    }
    $result = Receive-Job $job
    $result | Select-Object Address, StatusCode
}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 3
Milliseconds      : 675
Ticks             : 36750682
TotalDays         : 4.25355115740741E-05
TotalHours        : 0.00102085227777778
TotalMinutes      : 0.0612511366666667
TotalSeconds      : 3.6750682
TotalMilliseconds : 3675.0682




PS E:\workspace> Measure-Command {
    Test-Connection (Get-Content .\testfile.txt) -Quiet -Count 1
}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 11
Milliseconds      : 776
Ticks             : 117768934
TotalDays         : 0.000136306636574074
TotalHours        : 0.00327135927777778
TotalMinutes      : 0.196281556666667
TotalSeconds      : 11.7768934
TotalMilliseconds : 11776.8934




PS E:\workspace> Measure-Command {
    Test-Connection (Get-Content .\testfile.txt) -Quiet -Count 1
}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 11
Milliseconds      : 916
Ticks             : 119163149
TotalDays         : 0.000137920311342593
TotalHours        : 0.00331008747222222
TotalMinutes      : 0.198605248333333
TotalSeconds      : 11.9163149
TotalMilliseconds : 11916.3149




PS E:\workspace> Measure-Command {
    Test-Connection (Get-Content .\testfile.txt) -Quiet -Count 1
}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 11
Milliseconds      : 729
Ticks             : 117294046
TotalDays         : 0.000135756997685185
TotalHours        : 0.00325816794444444
TotalMinutes      : 0.195490076666667
TotalSeconds      : 11.7294046
TotalMilliseconds : 11729.4046




PS E:\workspace> 

```