Windows PowerShell進行状況の報告

Don Jones

最近、私はかなり長くて複雑な Windows PowerShell スクリプトを作成していましたが、このスクリプトが実行中にほとんど応答しなくなりました。このスクリプトは、スケジュールされたタスクとして作成したので、表示される出力はそれほど多くありません。それでも、最初の大きなテストでこのスクリプトを実行したとき、

誤って無限ループやその他の問題のあるコードを記述したのではないかと少し不安になりました。

シェルがその場で痛ましく小さなカーソルを点滅させているので、私は「強制終了しようかな」と考えました。確かに私は自分に自信がありませんでした。すぐに <B>Ctrl</B> キーを押しながら <B>C</B> キーを押してスクリプトを終了したのですから。今こそ進行状況の報告機能を追加するときです。

詳細情報を表示する

まず、スクリプトの実際の処理がわかるように、一連の状態メッセージを追加します。シェルで Write-Verbose コマンドレットを使用すると、この処理を非常に簡単に実行できます。次のように、このコマンドレットをシェルで使用してみましょう。

Write-Verbose "Test Message"

ちょっと試してみれば、このコマンドレットが何も出力しないことに気が付くでしょう。この理由は、Write-Verbose はオブジェクトを特別な Verbose パイプラインに渡すからです。既定では、このパイプラインは出力を表示しません。このパイプラインを制御するには、$VerbosePreference という組み込みのシェル変数を使用します。この変数の既定値は SilentlyContinue です。この値に設定されている場合、詳細情報は表示されません。しかし、次のように値を Continue に設定すると、パイプラインの内容の表示を有効にすることができます。

$VerbosePreference = "Continue"

これで、一連の Write-Verbose ステートメントをスクリプトに追加して実行時の処理内容を詳しく知ることができます。この手法のすばらしい点は、テストとトラブルシューティングが完了したらスクリプトの冒頭で $VerbosePreference を再び SilentlyContinue に設定して、それ以上詳細情報が表示されないようにできることです。

すべての Write-Verbose ステートメントを探して削除する必要はありません。実際、このステートメントはスクリプト内に残っているので、スクリプトを手動で実行する場合はいつでも、必要に応じて簡単に Verbose パイプラインの内容の表示を再び有効にすることができます。

さらに本格的な進行状況の表示を提供する

スクリプトが無限ループに陥っていないことと、実際に完璧に動作していることを確認できたところで、Verbose パイプラインの内容の表示を無効にして、念のため再度スクリプトを実行しました。

今度の問題は、スクリプトの動作にまったく問題がないことがわかっていても、点滅するカーソルをずっと見ていられないということです (私は注意力が長続きしないたちです。絶望的な気分になり、暇つぶしに塗りたてのペンキが乾く様子でも眺めようかと思い、辺りを探しました)。

必要なのは、スクリプトの全体的な進行状況と完了予定時間に関する情報でした。要するに、進行状況バーのようなものが必要だったということです。

さいわい、Windows PowerShellTM には Write-Progress コマンドレットが含まれています。このコマンドレットを実行すると、Windows® のようなグラフィカルな進行状況バーは表示されませんが、それでも図 1 のようなすばらしい進行状況バーが表示されます。これは Windows Server® 2003 や Windows XP のセットアップでテキストベースの操作を行うときに表示される、ファイルのコピーの進行状況バーに少し似ています。

図 1 スクリプトの進行状況

図 1** スクリプトの進行状況 **(画像を拡大するには、ここをクリックします)

Write-Progress を使用するには、少し説明が必要です。例を挙げるとさらにわかりやすいと思います。次のスクリプトについて考えてみましょう。

for ($a=1; $a -lt 100; $a++) {
  Write-Progress -Activity "Working..." `
   -PercentComplete $a -CurrentOperation
   "$a% complete" `
   -Status "Please wait."
  Start-Sleep 1
}

このスクリプトは、Write-Progress を使用して進行状況バーを表示しています。Start-Sleep を使用して、ループが 1 周するたびにスクリプトを 1 秒ずつ停止したので、進行状況を目で見て確認できるほどゆっくりとスクリプトが実行されます。一時停止しなかった場合、ループの回数がすぐに 0 から 100 に達してしまうので、進行状況バーは画面に一瞬表示されるだけです。

ご覧のように、Activity に設定した文字列 (この場合は Working...) が進行状況バーの一番上に表示されます。Status に設定した文字列はそのすぐ下、CurrentOperation に設定した文字列は一番下に表示されます。シェルでは、進行状況バーの表示が一度に 1 つのみサポートされています。Write-Progress を使用すると、進行状況バーが表示されていない場合は新しい進行状況バーが作成され、進行状況バーが表示されている場合はその進行状況バーが更新されます。

やり残していることは、終了したら姿を消すようバーに指示することです。これを行うには、次のコードをスクリプトの末尾に追加するだけです。

Write-Progress -Activity "Working..." `
 -Completed -Status "All done."

一般的に、スクリプトが完了すると進行状況バーは自動的に消滅します。しかし、スクリプトにいくつかの処理が含まれている場合は、ある処理が完了したときに進行状況バーが表示されなくなるようにすると役立つでしょう。このような場合、-Completed パラメータを使用すれば画面から進行状況バーを消すことができます。

カウントダウン

Write-Progress のもう 1 つの一般的な使用方法は、実際の進行状況バーの代わりに "残り秒数" を表示することです。次に例を示します。

for ($a=100; $a -gt 1; $a--) {
  Write-Progress -Activity "Working..." `
   -SecondsRemaining $a -CurrentOperation
   "$a% complete" `
   -Status "Please wait."
  Start-Sleep 1
}

ここでは、ループのカウンタが 100 から 1 まで減少していくように変更し、Write-Progress に PercentComplete パラメータではなく SecondsRemaining パラメータを使用しているだけです。図 2 はこのコマンドの結果を示しています。ご覧のように、進行状況バーが消え、タイマーに置き換えられています。シェルは残り秒数の合計を自動的に時、分、秒に変換して、さらにわかりやすい情報を表示します。この例で表示される完了率は、実際に 100 からカウントダウンされます。これは単純に CurrentOperation パラメータで指定しているからです。実際の完了率が計算されているわけではなく、Windows PowerShell が $a の現在の値の後に文字列 "% complete" (% 完了) を表示しているだけです。

図 2 スクリプトが完了するまでの時間

図 2** スクリプトが完了するまでの時間 **(画像を拡大するには、ここをクリックします)

処理内容を表示するスクリプト

私は処理内容を表示するスクリプトの作成が大好きです。処理内容を表示するには、詳細情報の出力や、単純な進行状況バーを使用します。これは、私自身の気の短さに対処するためでもあり、数か月後に他の人が私のスクリプトを実行することになったときのためでもあります。最終的に、状態と進行状況に関するなんらかの情報を表示することで、大きなメリットが提供されるでしょう。

今月のコマンドレット : Tee-Object

今月は、私が気に入っているトラブルシューティング用コマンドレットの 1 つを紹介します。たとえば、次のスクリプトについて考えてみましょう。

Get-WMIObject Win32_Service | Where { $_.State -ne "Running"
-and $_.StartMode -eq "Automatic" } | ForEach-Object { $_.Start() }

一見このスクリプトは、自動的に開始するように設定したサービスのうち、なんらかの理由で開始されていないすべてのサービスを起動するように思えます。しかし、実際にはこのスクリプトは動作しません。しかもパイプライン内で実行されている処理を詳しく確認できないので、動作しない原因を探すことは困難です。つまり、Tee-Object を使用しない限り、パイプライン内で実行されている処理を詳しく確認することはできません。

Tee-Object はオブジェクトをファイル (または変数) にリダイレクトし、パイプラインに渡します。たとえば、次のようなコードを記述します。

Get-WMIObject Win32_Service | Tee-Object AllServices.csv | Where 
{ $_.State -ne "Running" -and $_.StartMode -eq "Automatic" } | 
Tee-Object FilteredServices.csv | ForEach-Object { $_.Start() }

このように変更すると、各パイプライン コマンドを実行した後にその結果を確認できます。すぐに、FilteredServices.csv ファイルに何も含まれていないことがわかりました。このスクリプトが動作しなかったのも無理はありません。もう少し調べたところ、問題の根本的な原因がわかりました。StartMode は "Automatic" ではなく "Auto" でした。Tee-Object のおかげで、問題のある箇所を正確に特定できました。

Don Jones は TechNet Magazine の編集に携わっており、『Windows PowerShell: **TFM』(SAPIEN Press、2007 年) の共著者でもあります。また、彼は Windows PowerShell に関するトレーニングの講師も務めています (www.ScriptingTraining.com を参照)。ScriptingAnswers.com で公開されている Web サイトから、彼と連絡を取ることができます。

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; 許可なしに一部または全体を複製することは禁止されています.