PowerShell: CPU情報を変数に代入するところでハマる

PowerShell を使い始めて一番戸惑ったのが、ログの特定フィールドの切り出しのようなこと。

Get-WmiObject(gwmi) コマンドと Format-List(fl)コマンドを組み合わせて切り出した値の扱い。以下のコマンドで、Win32_Processor クラスを使えば、CPU情報を表示できます。

> gwmi -ComputerName . -class win32_processor

Caption           : x86 Family 6 Model 54 Stepping 1
DeviceID          : CPU0
Manufacturer      : GenuineIntel
MaxClockSpeed     : 2127
Name              : Intel(R) Atom(TM) CPU D2700   @ 2.13GHz
SocketDesignation : CPUSocket

1台のPC情報を知りたい場合はこれでいいのですが、10台以上ともなるとPCごとにチマチマコマンドを発行する気になりません。100台、1000台ともなると、絶対に嫌!例えば、社内のPC毎に

PC名, CPU, メモリ容量, HDD容量, ビデオチップ・・・・・

とCSVファイルを作って、最終的にExcel などの表計算ソフトに読み込ませたいというのは、PCメンテナンスを行う人間にとっては当然の要求でしょう。
gwmi コマンドと fl コマンドの組み合わせによって、必要な「行」だけ切り出して表示することが出来ます。
欲しいのは行全部の情報ではなく、コロン右側の値です。

> gwmi -ComputerName . -class win32_processor | fl Name

Name : Intel(R) Atom(TM) CPU D2700   @ 2.13GHz

これを、PC毎に変数に代入して、最終的に -ComputerName オプションで指定するPC名と並べて出力すれば、簡単にPCスペック一覧表が出来上がるはずと言うわけです。

こういう感じの定型フォーマットの一覧情報から値を切り出すというのは、Unix の文字処理コマンドの得意なところ。
次の3行は、Webサーバーのログファイルですが、「ホスト名、ユーザー名、クッキー、日時、URL・・・・・」という感じで並んでいます。

ec2-52-3-127-144.compute-1.amazonaws.com - - [01/Aug/2016:00:11:58 +0900] "GET /robots.txt HTTP/1.1" 200 270 "-" "ltx71 - (http://ltx71.com/)"
ec2-52-3-127-144.compute-1.amazonaws.com - - [01/Aug/2016:00:12:45 +0900] "GET /notes/2009/08/15/ HTTP/1.1" 200 50614 "-" "ltx71 - (http://ltx71.com/)"
crawl-66-249-71-161.googlebot.com - - [01/Aug/2016:00:13:06 +0900] "GET /notes/feed/ HTTP/1.1" 304 - "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"

例えば、ここから 3行目の、タイムスタンプ項目だけを抽出したい場合、head, tail, awk という3つのコマンドの組み合わせで、1行で取り出せます。

> cat test.txt | head -3 | tail -1 |awk '{print $4}'
[01/Aug/2016:00:13:06

カギ括弧という余分なものが頭に付いていますけど、それは、sed コマンドを入れて削除すればこのとおり。

> cat test.txt | head -3 | tail -1 | awk '{print $4}' | sed s/\\[//
01/Aug/2016:00:13:06

このタイムスタンプを、変数 $ts に入れたければ、シングルバッククォテーションで囲んで、set で変数に代入。変数に入れてしまえば、後で好きな時に取り出して使えます。

> set ts=`cat test.txt | head -3 | tail -1 | awk '{print $4}' | sed s/\\[//`
> echo $ts
01/Aug/2016:00:13:06

PowerShell でもこういう流れで処理すればいいんだろう と考えていたので、大はまり。

つまり、Name : で始まるCPU情報の行を、変数 $CPU に代入し、頭に付いている “Name : ” という文字を replace コマンドで消せばいいだろうと考えたものだから、やってみて出力された “Microsoft・・・・・・” という文字の羅列に「これ何????????」と何十時間も悩んでしまいました。

> $cpu = gwmi -ComputerName . -class win32_processor | fl Name

> $cpu -replace "Name : ", ""
Microsoft.PowerShell.Commands.Internal.Format.FormatStartData
Microsoft.PowerShell.Commands.Internal.Format.GroupStartData
Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData
Microsoft.PowerShell.Commands.Internal.Format.GroupEndData
Microsoft.PowerShell.Commands.Internal.Format.FormatEndData

PowerShell のパイプ( |) はテキストフィルターじゃなく、オブジェクトフィルターだと書いてある解説を読んでもさっぱり意味不明。

次のように書けばいいという検索結果にたどり着くまでに2,3日掛かりました。

> $cpu = (gwmi -ComputerName . -class win32_processor).Name
> echo $cpu
Intel(R) Atom(TM) CPU D2700   @ 2.13GHz

なんじゃこりゃ!この簡単さは何!?

つまり、gwmi コマンドと、その出力結果の表左側のタイトルをピリオド(.) で結べば、右側の値が得られるって事。(Unixライクに echo コマンドを書いていますけど、echo はあってもなくても結果は同じ。)

> gwmi -ComputerName . -class win32_processor

Caption           : x86 Family 6 Model 54 Stepping 1
DeviceID          : CPU0
Manufacturer      : GenuineIntel
MaxClockSpeed     : 2127
Name              : Intel(R) Atom(TM) CPU D2700   @ 2.13GHz
SocketDesignation : CPUSocket

CPUメーカーを取り込みたければ、(gwmi -ComputerName . -class win32_processor).Manufacturer と書けばいいわけです。

> (gwmi -ComputerName . -class win32_processor).Manufacturer
GenuineIntel

一度、変数に取り込んだ後、その変数と. でつないでも結果は同じ。複数の値を変数に代入したい場合はこっちの方が便利。

> $manufacturer_line = gwmi -ComputerName . -class win32_processor
> $manufacturer_line.Manufacturer
GenuineIntel

なんというPowerShellのお手軽さ。
Unixシェルスクリプトで考えてきた私には、こういう方法がすぐにわからないのね。

余談ですが、この結論にたどり着くまでにどうしていたかというと、一度結果をファイルに出力して、再度プログラムに取り込んでいました。ファイルに吐き出してしまえば余分な属性は消えてしまうので、もう一度読み込めば引き続き処理を続けられました。

コメントを残す