ruvhGN2u3ZcPI3t56kst
2016/06/29 rssing.com削除用 9IEgA7QKeytjj6NzRLwN thanatophidia66.rssing.com
2016/08/18 再び現れたので削除
ruvhGN2u3ZcPI3t56kst
2016/06/29 rssing.com削除用 9IEgA7QKeytjj6NzRLwN thanatophidia66.rssing.com
2016/08/18 再び現れたので削除
きっかけは@Pyromaniaさんのこのツイートから。
Test-Connection でQuiet オプションつけないと、ICMPのあとにNetBIOS Name Query なげててLinux系の鯖だと時間かかるっぽいという知見をえた。
— Pyromania 🏢〜☁️ (@Pyromaniaxxx) 2016年6月15日
困った時のWiresharkさまさまやでー(
ツイートではLinuxサーバーについて触れていますが時間がかかるのはWindowsに対しても同様です。
本エントリではPowerShellにおけるPingである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は発行されません。
コマンドレットの実装だけを見ると遅くなる要因が無い様に見受けられます。
ではどこで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が実行され、その結果待ちにより処理が遅くなってしまうのです。
この問題は、
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
コマンドレットの遅さに対しては幾つかの対策を行う事ができますので以下に記載していきます。
身も蓋もない方法ですが、単純にコンソール上からPingの結果だけ見たいのであればTest-Connection
を使わずに従来通りPing
コマンドを使うのが一番手っ取り早いでしょう。
スクリプト中でPingによる疎通確認を行いその結果だけが必要であれば-Quiet
オプションを指定するのが現実的です。
-Quiet
オプションを指定した場合のTest-Connection
の戻り値はBoolean
型になりますので遅延の原因であるIPV4Address
、IPV6Address
プロパティを気にせずに済みます。
Test-Connection
の結果を使いつつ遅延を防ぎたい場合は、実行結果を一度変数に代入するのが効果的です。
Test-Connection
ではIPV4Address
、IPV6Address
プロパティがコンソール上での表示対象になっており、表示の際にプロパティへのアクセスが発生して[System.Net.Dns]::GetHostEntry()
メソッドが呼び出されてしまいます。
一旦結果を変数に代入すればIPV4Address
、IPV6Address
プロパティへのアクセスを抑止できます。
前項の方法と考え方は同じです。
コンソール上でTest-Connection
を使う場合、Select-Object
を使ってコンソールに表示するプロパティを絞ることでIPV4Address
、IPV6Address
プロパティへのアクセスを抑えることができます。
コンソールに表示させなければ良いので、Select-Object
の代わりにFormat-*
なコマンドレットで絞っても同様の効果を得ることができます。
本エントリを公開後、どうにかして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もしていません。
検証環境として雑だと自分でも思っていますので結果については軽く見てもらえると助かります。
まずは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秒も経過していることがわかります。
圧倒的な遅さです(
ここから各対策の結果を記載していきます。
最初はPing
コマンドの結果です。
ipconfig /flushdns ping 192.168.133.12 -n 4
当然ですがICMPパケットしかありません。
時間は約3秒でこれがユーザーが期待する普通の結果でしょう。
次に-Quiet
オプションを使った場合です。
ipconfig /flushdns Test-Connection 192.168.133.12 -Count 4 -Quiet
こちらもICMPパケットしか飛ばさないのでPingコマンドと同等の時間となっています。
コマンドの結果を変数に代入した場合です。
ipconfig /flushdns $Results = Test-Connection 192.168.133.12 -Count 4
この場合もICMPパケットしか飛ばしていません。
ここで以下の様にプロパティにアクセスしてみると、
$Results[0].ResponseTime $Results[0].IPV4Address
IPV4Address
プロパティにアクセスした時点で以下の様にNetBIOS Name Queryが発行されました。
最後は出力するプロパティを絞った場合です。
画面表示の都合、Select-Object
の代わりにFormat-Table
を使いました。
ipconfig /flushdns Test-Connection 192.168.133.12 -Count 4 | ft Address,ResponseTime -AutoSize
こちらも対策の効果が出ていることがわかります。
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
、IPV6Address
ScriptPropertyを付けたのは万死に値するレベルの失策だと思います。
互換性を考えると実現は難しでしょうが、このプロパティを無くすかデフォルトの表示項目から外してほしい感じです。
【2017/10/13追記】
Visual Studio Codeのバージョンも新しくなり、本エントリの注意点もあまり役に立たなくなってしまいました。
せっかくなのでVisual Studio Code PowerShell拡張の設定に関する以下のエントリのリンクを張っておきます。
Visual Studio Code上でPowerShellの挙動が変わった・おかしいと感じた時は下記エントリの設定を確認してみるのも良いかと思います。
【追記ここまで】
Visual Studio Codeのバージョン1.2.0からターミナル機能が搭載され、早速このターミナルをPowerShellに変える方法が紹介されています。
https://blogs.msdn.microsoft.com/ayatokura/2016/06/10/vscode_terminal_powershell/blogs.msdn.microsoft.com
本エントリではターミナルをPowerShellに変える際に注意すべきことについて記載します。
Windows版のVisual Studio Codeは32bitアプリケーションです。
【2017/07/21追記】
Visual Studio Code Ver.1.14.1からWindowsにおいても64bit版が提供されました。
以下の内容は32bit版のVisual Studio Codeに関するものとなります。
【追記ここまで】
64bitOSで上記エントリの手順を単純に設定して、
"terminal.integrated.shell.windows": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
とすると、起動されるpowershell.exe
は32bit版となります。
これはWOW64のシステムフォルダに対するリダイレクション機能によりC:\Windows\System32
フォルダへのアクセスがC:\Windows\SysWOW64
にリダイレクトされるためです。
リダイレクションの詳細についてはこちらを参考にしてください。
例としてターミナルを起動してみると、以下の図の様に$PSHOME
はWOW64配下になりIntPtrのサイズは4Byteとなっています。
このため64bitOS上で64bit版のpowershell.exe
を起動したい場合は、
"terminal.integrated.shell.windows": "C:\\Windows\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe"
と、C:\Windows\Sysnative
フォルダを指定する必要があります。
こうすることで32bitアプリから64bit版のpowershell.exe
を起動することができます。
【2016/07/08追記】 この問題はバージョン1.3.0で解決されました。
現時点でのターミナルでは日本語の表示に難がある様でターミナルのウィンドウサイズによって文字が潰れて表示されてしまう場合があります。
綺麗な表示の場合
潰れた表示の場合
こちらについては、
でCJKまとめた形で対応中の様です。
Issue自体はクローズに向かっているので次のリリースでは直っているんじゃないかと思います。
【2016/08/05追記】 この問題はバージョン1.4.0で解決されました。
1.4.0ではこの他にターミナル上でのコピペがサポートされる様になりました。
コピーしたい範囲を選択して、Ctrl+Shift+C
でコピー、Ctrl+Shift+V
でペーストできます。
右クリックでメニューも出ます。
最初にこのエントリを書いた当時は気がつかなかったのですが、現時点ではターミナル上でIMEが有効にならない問題があります。
こちらはバージョン1.4.0で修正予定だそうです。
とりあえずこんな感じです。
他にも何かあれば追記していきます。