はじめに
きっかけは@Pyromaniaさんのこのツイートから。
Test-Connection でQuiet オプションつけないと、ICMPのあとにNetBIOS Name Query なげててLinux系の鯖だと時間かかるっぽいという知見をえた。
— Pyromania 🏢〜☁️ (@Pyromaniaxxx) 2016年6月15日
困った時のWiresharkさまさまやでー(
ツイートではLinuxサーバーについて触れていますが時間がかかるのはWindowsに対しても同様です。
本エントリではPowerShellにおけるPingであるTest-Connectionコマンドレットの動作が遅い理由とその対策について触れていきます。
Test-Connectionの実装
ILSpy等でTest-Connectionの実装を調べてみると、Test-ConnectionはWMIのWin32_PingStatusクラスを使ってPingを行っており、厳密には一致していませんが以下の様なWQLを内部で発行しています。
SELECT * FROM Win32_PingStatus WHERE Address = `[-Destination]` //複数宛先ある場合はORで連結 AND TimeToLive = [-TimeToLive] AND BufferSize = [-BufferSize]
単純にWin32_PingStatusを使うだけであればICMPプロトコルしか使わないため、最初に触れた様なNetBIOS Name Queryは発行されません。
コマンドレットの実装だけを見ると遅くなる要因が無い様に見受けられます。
Test-Connectionが遅い理由
ではどこでNetBIOS Name Queryが発行されているのかというと、その原因はTest-Connectionの戻り値にあります。
Test-Connectionでは-Quietパラメーターを指定しない場合はWin32_PingStatusクラス*1のオブジェクトをそのまま返します。
この戻り値に対してPowerShell側でIPV4AddressおよびIPV6AddressというScriptPropertyが付与されており、Get-Memberを使って定義を確認してみると、
PS C:\> Test-Connection 192.168.133.12 -Count 1 | Get-Member -View Extended | Format-List ・・・(中略)・・・ 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;}
とプロパティ内で[System.Net.Dns]::GetHostEntry()メソッドを発行しIPアドレスの逆引きをしていることがわかります。
このメソッドは内部でgethostbyaddr()関数を使用していますのでNetBIOS Name Queryを含めた名前解決*2が実行され、その結果待ちにより処理が遅くなってしまうのです。
この問題は、
Test-Connection Performance With IP and Output
Votes from Connect: 3
Original Date Submitted: 7/23/2015 12:20:40 AM
Description:
********Contact Information********
Handle: John.Bevan
Site Name: PowerShell
Feedback ID: 1578010
***************************************
Frequency: PowerShell ISE
Regression: Run the below code / vie...
と既にフィードバックされているのですが、あまり対応される空気を感じません...
Test-Connectionは普通に使うと遅いものと割り切るのが現実的な気がします。
対策方法
Test-Connectionコマンドレットの遅さに対しては幾つかの対策を行う事ができますので以下に記載していきます。
1. Pingコマンドを使う
身も蓋もない方法ですが、単純にコンソール上からPingの結果だけ見たいのであればTest-Connectionを使わずに従来通りPingコマンドを使うのが一番手っ取り早いでしょう。
2. -Quietオプションを使う
スクリプト中でPingによる疎通確認を行いその結果だけが必要であれば-Quietオプションを指定するのが現実的です。
-Quietオプションを指定した場合のTest-Connectionの戻り値はBoolean型になりますので遅延の原因であるIPV4Address、IPV6Addressプロパティを気にせずに済みます。
3. コマンドレットの結果を変数に代入する
Test-Connectionの結果を使いつつ遅延を防ぎたい場合は、実行結果を一度変数に代入するのが効果的です。
Test-ConnectionではIPV4Address、IPV6Addressプロパティがコンソール上での表示対象になっており、表示の際にプロパティへのアクセスが発生して[System.Net.Dns]::GetHostEntry()メソッドが呼び出されてしまいます。
一旦結果を変数に代入すればIPV4Address、IPV6Addressプロパティへのアクセスを抑止できます。
4. Select-Objectで出力するプロパティを絞る
前項の方法と考え方は同じです。
コンソール上でTest-Connectionを使う場合、Select-Objectを使ってコンソールに表示するプロパティを絞ることでIPV4Address、IPV6Addressプロパティへのアクセスを抑えることができます。
コンソールに表示させなければ良いので、Select-Objectの代わりにFormat-*なコマンドレットで絞っても同様の効果を得ることができます。
【2016/06/17追記】5. Remove-TypeDataを使う
本エントリを公開後、どうにかしてIPV4Address、IPV6Addressプロパティを削除できないか調べたところ、Remove-TypeDataが使えることがわかりました。
Remove-TypeDataはPowerShellで独自に追加された型データの情報を削除するコマンドレットになります。
削除はそのセッション中のみ有効でpowershell.exeを再起動するなどして新しいセッションができると型データの情報は復活します。
最初にWin32_PingStatusクラスの型System.Management.ManagementObject#root\cimv2\Win32_PingStatusの型データの情報を確認してみると、
PS C:\> (Get-TypeData System.Management.ManagementObject#root\cimv2\Win32_PingStatus).Members | ft -AutoSize Key Value --- ----- IPV4Address System.Management.Automation.Runspaces.ScriptPropertyData IPV6Address System.Management.Automation.Runspaces.ScriptPropertyData
の様にIPV4Address、IPV6Addressプロパティがあることがわかります。
これに対して、Remove-TypeDataを以下の様に実行します。
PS C:\> Remove-TypeData System.Management.ManagementObject#root\cimv2\Win32_PingStatus
これでSystem.Management.ManagementObject#root\cimv2\Win32_PingStatusに対する型データは消えるのでTest-Connectionを実行してもIPV4Address、IPV6Addressプロパティが付与されることは無くなります。
【2016/06/20追記】
あえとすさんよりUpdate-FormatDataを使った方法を指摘して頂きました。
上のエントリ内で指摘されている様にIPV4Address、IPV6Addressプロパティに依存する処理があった場合Remove-TypeDataを使う方法ではエラーとなってしまうので対応としては確かに乱暴だと思います。
Remove-TypeDataは極力使わずUpdate-FormatDataを使う方が良いでしょう。
【追記ここまで】
実際に確認してみる
簡単な環境で動作を確認してみましたのでその結果を記載します。
2台のWindows Server 2012 R2(PowerShell 4.0)の仮想マシン、fromsv(192.168.133.11)からwindestsv(192.168.133.12)に対してPingおよびTest-Connectionを実行し、その結果をWiresharkでキャプチャしました。
簡単のためにIPV6は無効にし、DNSの設定*3もしていません。
検証環境として雑だと自分でも思っていますので結果については軽く見てもらえると助かります。
0. Test-Connectionを使った場合
まずはTest-Connectionを普通に使った場合を見てみます。
試行回数はデフォルトの4回を一応明示しています。DNSは設定していませんが念のためにipconfig /flushdnsをしています。
ipconfig /flushdns Test-Connection 192.168.133.12 -Count 4
実行結果はこんな感じです。

IPV4Address、IPV6Addressプロパティがコンソールに表示されアクセスされるのが遅延の原因であるためMeasure-Commandによる時刻計測はしていません。
処理時間はパケットキャプチャの結果で判断しています。
キャプチャの結果は以下となります。

(中略)

ICMPパケット(赤色の部分)の合間にNetBIOS Name Queryのパケットが流れていることが分かります。
4回目のPingが終わった最後のパケットでは約22秒も経過していることがわかります。
圧倒的な遅さです(
1. Pingコマンドを使う
ここから各対策の結果を記載していきます。
最初はPingコマンドの結果です。
ipconfig /flushdns ping 192.168.133.12 -n 4


当然ですがICMPパケットしかありません。
時間は約3秒でこれがユーザーが期待する普通の結果でしょう。
2. -Quietオプションを使う
次に-Quietオプションを使った場合です。
ipconfig /flushdns Test-Connection 192.168.133.12 -Count 4 -Quiet


こちらもICMPパケットしか飛ばさないのでPingコマンドと同等の時間となっています。
3. コマンドレットの結果を変数に代入する
コマンドの結果を変数に代入した場合です。
ipconfig /flushdns $Results = Test-Connection 192.168.133.12 -Count 4


この場合もICMPパケットしか飛ばしていません。
ここで以下の様にプロパティにアクセスしてみると、
$Results[0].ResponseTime $Results[0].IPV4Address

IPV4Addressプロパティにアクセスした時点で以下の様にNetBIOS Name Queryが発行されました。

4. Select-Objectで出力するプロパティを絞る
最後は出力するプロパティを絞った場合です。
画面表示の都合、Select-Objectの代わりにFormat-Tableを使いました。
ipconfig /flushdns Test-Connection 192.168.133.12 -Count 4 | ft Address,ResponseTime -AutoSize


こちらも対策の効果が出ていることがわかります。
【2016/06/17追記】5. Remove-TypeDataを使う
Remove-TypeDataを使ってIPV4Address、IPV6Addressプロパティを削除した場合の結果です。
ipconfig /flushdns Remove-TypeData System.Management.ManagementObject#root\cimv2\Win32_PingStatus Test-Connection 192.168.133.12 -Count 4


コンソール上IPV4Address、IPV6Addressの表示欄はありますが値が設定されておらず、キャプチャの結果もICMPパケットしか飛んでいないことがわかります。
もちろん処理時間も改善されています。
最後に
とりあえずこんな感じです。
率直に言ってIPV4Address、IPV6AddressScriptPropertyを付けたのは万死に値するレベルの失策だと思います。
互換性を考えると実現は難しでしょうが、このプロパティを無くすかデフォルトの表示項目から外してほしい感じです。
1 vote
0 comments