Test-Connection という、対象のサーバーが生きてるかどうか Ping を投げてチェックするコマンドがあるわけですが、どうもこれが遅いらしいと。
原因と対策は、こちらのブログを見て頂くとして。
その対策に、週末にコメントすると言っといて、まだしていなかったので、慌てて書いているわけです。はい。
これは週末にコメントしようっと。 https://t.co/dCKgSyPG3j
— 空論力 (@aetos382) 2016年6月17日
どうして遅いのか
Test-Connection が返す Win32_PingStatus オブジェクトに対して、PowerShell が表示するときに余計なメンバーを付加していて、その情報の取得に時間がかかっているんだ、というわけです。
この余計なメンバーはどうやって付加されているかと言うと、PowerShell における型定義ファイルである types.ps1xml というファイル*1に、こう書かれていることによります。
<Type> <Name>System.Management.ManagementObject#root\cimv2\Win32_PingStatus</Name> <Members> <ScriptProperty> <Name>IPV4Address</Name> <GetScriptBlock> $iphost = [System.Net.Dns]::GetHostEntry($this.address) $iphost.AddressList | ?{ $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } | select -first 1 </GetScriptBlock> </ScriptProperty> <ScriptProperty> <Name>IPV6Address</Name> <GetScriptBlock> $iphost = [System.Net.Dns]::GetHostEntry($this.address) $iphost.AddressList | ?{ $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6 } | select -first 1 </GetScriptBlock> </ScriptProperty> </Members> </Type> ... <Type> <Name>Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatus</Name> <Members> <ScriptProperty> <Name>IPV4Address</Name> <GetScriptBlock> $iphost = [System.Net.Dns]::GetHostEntry($this.address) $iphost.AddressList | ?{ $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } | select -first 1 </GetScriptBlock> </ScriptProperty> <ScriptProperty> <Name>IPV6Address</Name> <GetScriptBlock> $iphost = [System.Net.Dns]::GetHostEntry($this.address) $iphost.AddressList | ?{ $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6 } | select -first 1 </GetScriptBlock> </ScriptProperty> </Members> </Type>
この余計なメンバーを削除してしまえばよいので、先の記事では、Remove-TypeData コマンドを使って、この types.ps1xml の内容を無効化してしまうというアプローチがとられています。
ただ、それってちょっと乱暴じゃない? と思うわけです。
Remove-TypeData で消してしまうと、プロパティそのものが無かったことになってしまいます。
表示するときに DNS クエリを行っているのが遅いので、表示だけ抑止してやれば良いのではないでしょうか。
場合によっては、Test-Connection コマンドがこれらのメンバーを返すことに依存しているスクリプトがあるかもしれませんし。
表示だけを無かったことにするには
PowerShell で Test-Connection と打つと、こんな表示になります。
PS> Test-Connection localhost Source Destination IPV4Address IPV6Address Bytes Time(ms) ------ ----------- ----------- ----------- ----- -------- xxxxxxxxx localhost 127.0.0.1 ::1 32 0 xxxxxxxxx localhost 127.0.0.1 ::1 32 0 xxxxxxxxx localhost 127.0.0.1 ::1 32 0 xxxxxxxxx localhost 127.0.0.1 ::1 32 0
テーブルみたいに表示されていますよね。
これは暗黙的に Format-Table コマンドによって書式設定されているからです。
明示的に書いてやっても同じ結果になります。
PS> Test-Connection localhost | Format-Table Source Destination IPV4Address IPV6Address Bytes Time(ms) ------ ----------- ----------- ----------- ----- -------- xxxxxxxxx localhost 127.0.0.1 ::1 32 0 xxxxxxxxx localhost 127.0.0.1 ::1 32 0 xxxxxxxxx localhost 127.0.0.1 ::1 32 0 xxxxxxxxx localhost 127.0.0.1 ::1 32 0
PowerShell でのオブジェクトの書式設定は、format.ps1xml というファイルに定義されています。
Win32_PingStatus オブジェクトであれば、DotNetTypes.format.ps1xml というファイル*2に書かれています。
<View> <Name>System.Management.ManagementObject#root\cimv2\Win32_PingStatus</Name> <ViewSelectedBy> <TypeName>System.Management.ManagementObject#root\cimv2\Win32_PingStatus</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader> <Label>Source</Label> <Width>13</Width> </TableColumnHeader> <TableColumnHeader> <Label>Destination</Label> <Width>15</Width> </TableColumnHeader> <TableColumnHeader> <Label>IPV4Address</Label> <Width>16</Width> </TableColumnHeader> <TableColumnHeader> <Label>IPV6Address</Label> <Width>40</Width> </TableColumnHeader> <TableColumnHeader> <Label>Bytes</Label> <Width>8</Width> </TableColumnHeader> <TableColumnHeader> <Label>Time(ms)</Label> <Width>9</Width> </TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <PropertyName>__Server</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>Address</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>IPV4Address</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>IPV6Address</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>BufferSize</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>ResponseTime</PropertyName> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> ... <View> <Name>Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatus</Name> <ViewSelectedBy> <TypeName>Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatus</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader> <Label>Source</Label> <Width>13</Width> </TableColumnHeader> <TableColumnHeader> <Label>Destination</Label> <Width>15</Width> </TableColumnHeader> <TableColumnHeader> <Label>IPV4Address</Label> <Width>16</Width> </TableColumnHeader> <TableColumnHeader> <Label>IPV6Address</Label> <Width>40</Width> </TableColumnHeader> <TableColumnHeader> <Label>Bytes</Label> <Width>8</Width> </TableColumnHeader> <TableColumnHeader> <Label>Time(ms)</Label> <Width>9</Width> </TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <PropertyName>ComputerName</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>Address</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>IPV4Address</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>IPV6Address</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>BufferSize</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>ResponseTime</PropertyName> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View>
なんとなく意味が分かると思います。分かれ。
ここから、IPV4Address と IPV6Address を削ってしまえばよいわけです。
こんな感じ。
<View> <Name>System.Management.ManagementObject#root\cimv2\Win32_PingStatus</Name> <ViewSelectedBy> <TypeName>System.Management.ManagementObject#root\cimv2\Win32_PingStatus</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader> <Label>Source</Label> <Width>13</Width> </TableColumnHeader> <TableColumnHeader> <Label>Destination</Label> <Width>15</Width> </TableColumnHeader> <!-- <TableColumnHeader> <Label>IPV4Address</Label> <Width>16</Width> </TableColumnHeader> <TableColumnHeader> <Label>IPV6Address</Label> <Width>40</Width> </TableColumnHeader> --> <TableColumnHeader> <Label>Bytes</Label> <Width>8</Width> </TableColumnHeader> <TableColumnHeader> <Label>Time(ms)</Label> <Width>9</Width> </TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <PropertyName>__Server</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>Address</PropertyName> </TableColumnItem> <!-- <TableColumnItem> <PropertyName>IPV4Address</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>IPV6Address</PropertyName> </TableColumnItem> --> <TableColumnItem> <PropertyName>BufferSize</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>ResponseTime</PropertyName> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> ... <View> <Name>Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatus</Name> <ViewSelectedBy> <TypeName>Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatus</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader> <Label>Source</Label> <Width>13</Width> </TableColumnHeader> <TableColumnHeader> <Label>Destination</Label> <Width>15</Width> </TableColumnHeader> <!-- <TableColumnHeader> <Label>IPV4Address</Label> <Width>16</Width> </TableColumnHeader> <TableColumnHeader> <Label>IPV6Address</Label> <Width>40</Width> </TableColumnHeader> --> <TableColumnHeader> <Label>Bytes</Label> <Width>8</Width> </TableColumnHeader> <TableColumnHeader> <Label>Time(ms)</Label> <Width>9</Width> </TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <PropertyName>ComputerName</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>Address</PropertyName> </TableColumnItem> <!-- <TableColumnItem> <PropertyName>IPV4Address</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>IPV6Address</PropertyName> </TableColumnItem> --> <TableColumnItem> <PropertyName>BufferSize</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>ResponseTime</PropertyName> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View>
ユーザー権限でやる
DotNetTypes.format.ps1xml というファイルはシステム ディレクトリにあって、ユーザー権限では書き換えることができません。
管理者権限であれば書き換えられますが、あまり推奨される方法ではありません。
というわけで、これをユーザー権限でできるようにしましょう。
まず空のテキストファイルを用意します。
そこに、IPv4Address と IPV6Address を削った XML をコピーします。
前後はルート要素で囲ってやる必要があります。
完成品がこちら。
<?xml version="1.0" encoding="UTF-8"?> <Configuration> <ViewDefinitions> <View> <Name>System.Management.ManagementObject#root\cimv2\Win32_PingStatus</Name> <ViewSelectedBy> <TypeName>System.Management.ManagementObject#root\cimv2\Win32_PingStatus</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader> <Label>Source</Label> <Width>13</Width> </TableColumnHeader> <TableColumnHeader> <Label>Destination</Label> <Width>15</Width> </TableColumnHeader> <TableColumnHeader> <Label>Bytes</Label> <Width>8</Width> </TableColumnHeader> <TableColumnHeader> <Label>Time(ms)</Label> <Width>9</Width> </TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <PropertyName>__Server</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>Address</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>BufferSize</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>ResponseTime</PropertyName> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatus</Name> <ViewSelectedBy> <TypeName>Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatus</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader> <Label>Source</Label> <Width>13</Width> </TableColumnHeader> <TableColumnHeader> <Label>Destination</Label> <Width>15</Width> </TableColumnHeader> <TableColumnHeader> <Label>Bytes</Label> <Width>8</Width> </TableColumnHeader> <TableColumnHeader> <Label>Time(ms)</Label> <Width>9</Width> </TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <PropertyName>ComputerName</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>Address</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>BufferSize</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>ResponseTime</PropertyName> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> </ViewDefinitions> </Configuration>
これを自分のドキュメント ディレクトリにある WindowsPowerShell ディレクトリ*3にでも置いておきましょう。
置いただけでは有効にならないので、Update-FormatData コマンドを打ってやります。
既定の表示定義を上書きするために、-PrependPath パラメーターに上記のパスを指定します。
PS> Update-FormatData -PrependPath .\Win32_PingStatus.format.ps1xml
こうしてから Test-Connection を実行すると、めでたくこうなります。
PS> Test-Connection localhost Source Destination Bytes Time(ms) ------ ----------- ----- -------- xxxxxxxxx localhost 32 0 xxxxxxxxx localhost 32 0 xxxxxxxxx localhost 32 0 xxxxxxxxx localhost 32 0
Update-FormatData を毎回タイプするのは面倒なので、PowerShell プロファイル*4に書いて、毎回自動実行されるようにしておきましょう。
その他の方法
上記の方法では、テーブル表示の場合だけ表示しないようにしているので、他の表示方法(Format-List とか)では消えません。
今回は敢えてそうしています。
ひょっとすると IP アドレスを知りたいことがあるかもしれないので、結果を詳細に表示するための Format-List には手を加えず、最も多く使われるであろう既定のテーブル表示のみを高速化すれば十分だろうと思ったためです。
リスト表示やその他の表示方法も、format.ps1xml を書き換えることで同様に可能です。
また、types.ps1xml でも表示するメンバーを制御することができます。
やり方は about_Format.ps1xml を見てください。
*1:PowerShell のインストール ディレクトリ ($PSHOME) にあります。
*2:types.ps1xml 同様、PowerShell のインストール ディレクトリにあります。
*3:標準では C:\Users\[ユーザー名]\Documents\WindowsPowerShell です。
*4:$PROFILE.CurrentUserAllHosts