Archives for : WindowsPowerShell

UNIX shell scriptからのWindows PowerShell

備忘録というか、人に対して説明する練習として書いてみる。
どっかの身内Wikiに投稿した内容の転載。

UNIXでシェルスクリプトばっかり触ってたけど急にWindowsServer触る事になってしまい、
周りっくどいGUI操作ばっかりで自動化したくて発狂しそうな人が読むと得するかも知れませんし、
やっぱり発狂するかも知れません。

■Windows PowerShellとは

Windows Serverでのコマンドラインといえばcmd、
つまりコマンドプロンプトでしたが、WindowsServer2008(及びWindows7)以降は、
Windows PowerShell」というシェルと、スクリプトを使用する事ができます。
※WindowsXPでもインストールによって使用可能です。

これはMicrosoftが開発したコマンドラインおよびスクリプト言語で、
実態は.NET Frameworkを基盤として動くものであり、
強力な.NET Frameworkの機能にアクセスことすら出来ます。

WindowsServer2008以降、標準に組み込まれています。むしろ削除できません
実はWindows Server2008R2の管理ツールは
裏側でPowerShellコマンドを発行しているのです。(←聞いた話)
同じくシステム根幹であるコマンドプロンプトに代わるものであり、
近い将来コマンドプロンプトは無くなってしまうことでしょう。
PowerShellに組み込まれる形で残るでしょうけど。

つまりはWindows PowerShellは、今後のWindows Server管理において極めて重要な位置づけとなるのです。
だって2012のGUI、すんげー使いづらいし・・・。

■Windows PowerShellにおけるオブジェクト指向

私はプログラマーでも何でもないので、詳細は本職の方にお任せするのですが、
UNIXのシェルと異なり、コマンドレット(Powershellにおけるコマンドの呼称)の
実行結果(戻り値)はオブジェクトなのです。
このオブジェクトを変数に格納して処理に使用する事ができます。なんのこっちゃ。

例えば、時間についての処理を行う場合、変数NOWTIMEに時間を入れたいとします。

PowerShell: $NOWTIME = Get-Date

上記のコマンドを実行すると、変数NOWTIMEに日時分秒の「情報」(オブジェクト)が格納されます。

もちろん、UNIXのBornShellでも同じことが出来ます

UNIX : NOWTIME = `date`

上記のコマンドを実行すると、変数NOWTIMEに日時分秒の「文字列」が格納されます。
ここがぜんぜん違うところで、
例えば1つの変数から「秒」を取り出したい場合、
UNIXの文字列だとawkやsedなどのストリームエディタを駆使し、
文字列の何文字目から何文字目までを切り出して〜」みたいな事をしなければなりませんが、
Powershellだと以下のコマンドを実行する事で秒を取り出すことが出来ます。

PowerShell: $NOWTIME.Seconds

これは変数に文字列ではなくて、日時分秒のオブジェクトが格納されている為にこんな事ができます。
他にも年を取り出す、月を取り出すなど様々なメソッド(上記例でいうSecond)が準備されており、
これらを使うことで、超簡単に結果を得られるようになるのです。
(ま、UNIXでも date +’%s’で秒を取得できますが。)

時間のオブジェクト(System.DateTimeっていうらしい)の、ごく一部のメソッドを取り上げてみます。
AddYearsメソッド:年を加減する
AddDaysメソッド:日を加減する
DayOfWeekメソッド:曜日を返す

この「年や日を加減する」ところでもオブジェクトの恩恵を受ける事ができます。
例えば

PowerShell: $YESTERDAY = $NOWTIME.AddDays( -1 )

たったこれだけで、今日の1日前、つまり昨日を表すオブジェクトが変数「YESTERDAY」に代入されました。
このオブジェクトは別のコマンドレットに流用する事ができますので、
「7日より前のファイルを全部削除」みたいな簡単に記述する事ができるのです。これについては後述します。
(ま、UNIXでもfind使えば出来ますが。)

ここでは時間のオブジェクトを取り上げましたが、様々なオブジェクトやメソッドが用意されています。
サーバ管理のスクリプトを作る上で、オブジェクトを深くまで
理解する必要は薄いと私は思ってますが、(I’m not programa..)
メソッドはそうではありません。サポートされるメソッドの一覧は
以下のコマンドレットで確認する事ができます。

PowerShell: $NOWTIME | Get-Member

メソッドはコマンドみたいなものなので、プログラミングというより、
コマンドの組み合わせが重要なシェルスクリプトにおいては、
メソッドをどれだけ使えるかがキモだと考えています。

ところでオブジェクト指向って結局なんなの・・?

■Windows PowerShellとパイプライン

先ほどの例で以下のような記述をしました。

PowerShell: $NOWTIME | Get-Member

UNIXやコマンドプロンプトをお使いの方には馴染みの深いパイプです。
もちろんPowerShellでも使用できます。しかもUNIXやコマンドプロンプトよりずっと強力に。

UNIXやコマンドプロンプトのパイプには文字列が流れます。
勘の良い方ならお気づきでしょうが、PowerShellのパイプにはオブジェクトが流れるのです。

文字列が流れないので、複雑な処理を感覚的に行うことが出来ます。
基本的に一覧取得→絞り込む→目的のコマンドを実行という流れになることが多いでしょう。

・特定の条件に当てはまるプロセスの停止

PowerShell: Get-Process | Where-Object { $_.CPU -gt 50 } | Stop-Process

 

最初にプロセス一覧取得(Get-Process)、稼働時間、使用メモリ等でフィルタリング(Where-Object)、
フィルタリングしたプロセスの停止(Stop-Process) とコマンドを組み合わせてしようします。
Get-Processは ps 、Where-Objectは ? 、Stop-Processは kill の別名(エイリアス)があり、
つまり以下でも全く同じ命令になります。

PowerShell: ps | ? { $_.CPU -gt 50 } | kill

最後のStop-Processを外せば、該当するプロセス一覧を表示だけする事ができますし、
表示してから | Stop-Processだけ付け加えれば、対象を間違える事もありませんね。

・・・しかし他のPowerShellを説明する文献でも良く例として出てくる、
CPUやメモリ使用量によるプロセスの停止ですが、そんな危険なコマンドをサーバで打つものなのでしょうか?
私はそんなコマンド打つくらいなら名前決め打ちで指定しますけど。

PowerShell: ps hoge | kill

 

・7日前のファイルを削除する
フォルダ一覧取得(Get-ItemChild)→最終更新時刻でフィルタリング→フィルタリングしたファイルの削除

PowerShell: $7DAYS_AGO = $(Get-Date).AddDays( -7 )
Get-ChildItem D:¥hoge | Where-Object { $_.LastWriteTime -lt $7DAYS_AGO } | Remove-Item

 

最初に、7日前の日付を求めます。$()の中にコマンドを書くと、
真っ先に実行され、その戻り値をメソッドで加工して変数に代入できます。(日本語むずかしい)
日付を求めたらフォルダD:¥hogeの一覧取得(Get-ChildItem)で取得、
次に最終更新時刻が7日以上前のものをフィルタ(Where-Object)し、最後に削除します(Remove-Item)

同じくエイリアスがあるので以下のように、Unixライクに書き換えできます。

PowerShell: dir D:¥hoge | ? { $_.LastWriteTime -lt $7DAYS_AGO } | rm

または

PowerShell: ls D:¥hoge | ? { $_.LastWriteTime -lt $7DAYS_AGO } | rm

 

PowerShellは.NET Frameworkを基盤としているため、
.NETのインターフェースを用いて容易にシステム情報にアクセスする事が出来ます。

ここまで強力な面をご紹介してきたPowerShellですが、弱点としては文字列操作でしょうか。
単純に文字列加工を行いたい場合、空白行の処理などに甘いところがあり、
例えばテキストを読み込んで加工して出力するような場合、意図した結果を得るのが中々難しかったりします。
置換メソッドなどもいろいろありますし、私が知らないだけでしょうけど。
この点はsedやawkがあるUNIXに軍配があがると思います。

WindowsPowerShellでCSVをDBっぽく扱う

CSVファイルに対して、特定カラムを抽出したり、条件で抽出したりしたいとき、
Excelでオートフィルタ掛けるのが多分最速の解決方法だと思いますが、
Windows Power ShellのConvertFrom-CSVを使うと、まるでDBでSQLを扱うかのように操作できます。
DBほど速くないですが。

以下のようなCSVを使って説明します。一行目はヘッダです。
[test.csv]

名前,メーカー,価格
バーチャルボーイ,任天堂,15000
メガジェット,SEGA,15000
光速船,ATARI,54800
ドリームキャスト,SEGA,29900
PCエンジンスーパーグラフィックス,NEC,39800
サテラビュー,任天堂,18000

 

まずCSVファイルを変数に読み込みます。(エンコードはString)
$CSV_DATA = Get-Content -Path test.csv -Encoding String

読み込んだCSVをpowershellオブジェクトに変換します。
$CSV_DATA = $CSV_DATA | ConvertFrom-Csv

ここまではパイプラインで1行で書けます。
$CSV_DATA = Get-Content -Path test.csv -Encoding String | ConvertFrom-Csv
ここで変数CSV_DATAを参照すると以下の結果になります。

名前                                    メーカー                                価格
—-                                    ——–                                —-
バーチャルボーイ                        任天堂                                  15000
メガジェット                            SEGA                                    15000
光速船                                  ATARI                                   54800
ドリームキャスト                        SEGA                                    29900
PCエンジンスーパーグラフィックス        NEC                                     39800
サテラビュー                            任天堂                                  18000

 

それぞれの情報は変数名(配列)にドットでアクセス出来るようになります。
$CSV_DATA[0].名前
【結果】
バーチャルボーイ

Where-Object( ? )による条件式を使ってフィルタすることもできます。
$CSV_DATA | ? { $_.名前 -eq "ドリームキャスト" }

【結果】

名前                                    メーカー                                価格
—-                                    ——–                                —-
ドリームキャスト                        SEGA                                    29900

 

フィルタした情報を修正することすらできます。
$( $CSV_DATA | ? { $_.名前 -eq "ドリームキャスト" } ).価格 = 9900
【結果】

名前                                    メーカー                                価格
—-                                    ——–                                —-
バーチャルボーイ                        任天堂                                  15000
メガジェット                            SEGA                                    15000
光速船                                  ATARI                                   54800
ドリームキャスト                        SEGA                                    9900
PCエンジンスーパーグラフィックス        NEC                                     39800
サテラビュー                            任天堂                                  18000

これはちょっと式が面倒臭いのですが、$()とするとまず中身が評価、実行されます。
この場合は「$CSV_DATA | ? { $_.名前 -eq "ドリームキャスト" }」が実行され、
名前が「ドリームキャスト」のものをフィルタし、それ以外を削除します。
ですので、$()の結果は「ドリームキャスト」のオブジェクトだけが残る事になります。

次に「$(「ドリームキャスト」のオブジェクト).価格 = 9900」で、
「ドリームキャスト」のオブジェクトの価格プロパティにアクセスし、”9900″という数字を代入します。
結果的に「ドリームキャスト」の価格プロパティが9900に更新されます。

しかし、長々説明しといてなんですが、この方法には問題があり、フィルタ結果が複数となる場合は使えないのです。
例えば・・・
$( $CSV_DATA | ? { $_.メーカー -eq "任天堂" } ).価格 = 0
全部0になりそうなものですが・・。

【結果】

このオブジェクトにプロパティ ‘価格’ が見つかりません。プロパティが存在し、設定可能なことを確認してください。
発生場所 行:1 文字:40
+ $( $CSV_DATA | ? { $_.メーカー -eq “任天堂” } ). <<<< 価格 = 0
+ CategoryInfo : InvalidOperation: (価格:String) []、RuntimeException
+ FullyQualifiedErrorId : PropertyNotFound

怒られた。

確実に処理するにはForEach-Object ( % )を使って、読み込み行( $_ )を1行ずつ処理する事になります。
$CSV_DATA | ? { $_.メーカー -eq "任天堂" } | % { $_.価格 = 99999 }

【結果】

名前                             メーカー  価格
—-                             ——–  —-
バーチャルボーイ                 任天堂   99999
メガジェット                     SEGA     15000
光速船                           ATARI    54800
ドリームキャスト                 SEGA      9900
PCエンジンスーパーグラフィックス NEC      39800
サテラビュー                     任天堂   99999

 

ということで、「ConvertFrom-CSV」コマンドレットを使うと、CSVをDBみたいに使うことが出来ます。
複数のCSVを読み込んで連結とか、マッチング処理とか、構文チェックとか、集計とかも自在に出来ます。
ディスクアクセスは最初の読み込みと、最後の出力(ConvertTo-CSVでCSVに戻せる)だけです。

これは最近CSVを扱うことが増えたので、備忘録として書いたんだからね!
私はプログラマーでもDB使いでもないから細かいことはわからないんだからね!