Windows PowerShell1 行ずつスクリプトを作成する

Don Jones

これまでのコラムで、Windows PowerShell はシェルであることを強調してきました。おそらく既にご存知の cmd.exe (コマンド プロンプト) シェルとは違って、Windows PowerShell は対話型で使用するように設計されています。それでいて、Windows PowerShell は cmd.exe のバッチ言語より強力なスクリプト言語もサポートします。そのため、

VBScript のような言語と同等あるいはそれ以上に強力な言語になっています。しかし、Windows PowerShell™ がシェルであるという事実により、スクリプトの学習が非常に簡単になっています。スクリプトはシェルの中で対話型で 1 行ずつ書いて、その結果を直ちに確認しながら開発できます。

この対話型スクリプト作成技法によってデバッグも容易になっています。スクリプトの結果を直ちに確認できるので、思いどおりの結果にならなければ手早く変更できます。

このコラムでは、例を挙げて Windows PowerShell でスクリプトを対話型で作成する過程を説明します。例として作成するのは、テキスト ファイルからサービスの名前を読み取り、各サービスのスタートアップの種類を無効に設定するスクリプトです。

ここで学んでいただきたいことは、大きなスクリプト全体に取り組むのではなく、小さな単位の積み重ねで Windows PowerShell スクリプトを作成するという考え方です。必要な管理タスクを構成要素に分解して、個々の構成要素を単独で実行できるようにすることが可能です。Windows PowerShell ではそのような個別のスクリプトを統合する優れた方法が提供されています。小さな単位に分解して作業することによって、最終的なスクリプトの開発が容易になるということがわかると思います。

ファイルから名前を読み取る

Windows PowerShell では、テキスト ファイルを読み取る方法を突き止めるのは容易ではありません。「Help *file*」を実行すると、ファイルにテキストを送るための Out-File コマンドレットの説明が表示されるだけで、テキストを読み取る方法の説明がありません。まったく役に立ちません。でも、Windows PowerShell のコマンドレット名は一定のルールに従っているので、それを利用できます。Windows PowerShell では、何かを取り出すのに使用するコマンドレットの名前は通常 Get で始まります。「Help Get*」を実行すると、このグループのコマンドレットの一覧が表示されるので、スクロールして Get-Content を見つけることができます。いけそうですね。次に「Help Get-Content」を実行してその説明を表示します (図 1 を参照)。必要な情報が含まれているようです。

図 1 「Help Get-Content」を実行して詳細ヘルプを表示したところ

図 1** 「Help Get-Content」を実行して詳細ヘルプを表示したところ **(画像を拡大するには、ここをクリックします)

Windows PowerShell では、ほとんどすべてのものがオブジェクトとして扱われます。テキスト ファイルも例外ではありません。テキスト ファイルは厳密には行の集合です。ファイルの中の個々の行は一種の独立オブジェクトとして動作します。C:\services.txt という名前のテキスト ファイルを作成して、1 行に 1 サービス名の形式で名前を入れると、Windows PowerShell では Get-Content コマンドレットを使用して名前を 1 つずつ読み取ることができます。このデモンストレーションの目的は、対話型でスクリプトを作成する方法を示すことにあるので、テキスト ファイルに対して Get-Content を実行し、その結果を調べることから始めます。

PS C:\> get-content c:\services.txt
messenger
alerter
PS C:\>

期待どおり、Windows PowerShell はファイルを読んで名前を表示しています。もちろん、単に名前を表示することが本来の目的ではありませんが、これで Get-Content が期待どおりに動作することがわかりました。

サービスを変更する

次にすることは、サービスのスタートアップの種類を変更することです。ここでも、適切なコマンドレットを見つけることから始めます。それには「Help *Service*」を実行します。短いリストが戻り、Set-Service コマンドレットが該当する唯一のコマンドレットのように見えます。このコマンドレットをスクリプトに取り入れる前に、その動作を確認するためのテストをします。「Help Set-Service」を実行するとその使用方法が表示されます。簡単にテストして確認します。

PS C:\> set-service messenger -startuptype
    disabled
PS C:\>

構成要素を統合する

サービス名を読み取る機能と Set-Service コマンドレットを統合する必要があります。ここではWindows PowerShell の強力なパイプライン機能を使用します。パイプラインを使用すると 1 つのコマンドレットからの出力を別のコマンドレットへ入力として渡すことができます。パイプラインはオブジェクト全体を渡します。オブジェクトの集合がパイプラインに入力されると、オブジェクトはパイプラインを通して 1 つずつ渡されます。これは、Get-Content の出力はオブジェクトの集合ですが、それを Set-Service にそのままパイプできることを意味します。Get-Content は集合を渡すので、集合の中の個々のオブジェクトつまりテキスト行が 1 つずつ Set-Service にパイプされます。その結果、テキスト ファイルの中の行ごとに Set-Service が実行されます。コマンドは次のようになります。

PS C:\> get-content c:\services.txt | 
 set-service -startuptype disabled
PS C:\>

これで何が行われるかを下に説明します。

  1. Get-Content コマンドレットが実行され、ファイル全体が読み取られます。ファイルの中の個々の行は個別のオブジェクトとして扱われ、全体でオブジェクトの集合を形成します。
  2. そのオブジェクトの集合が Set-Service にパイプされます。
  3. パイプラインは Set-Service コマンドレットを各入力オブジェクトに対して実行します。それが実行されるたびに、1 つの入力オブジェクトが Set-Service の最初のパラメータであるサービス名としてそのコマンドレットに渡されます。
  4. Set-Service が実行されます。入力オブジェクトが最初のパラメータとして使用され、その他に指定されているパラメータがあればそれも使用されます。このケースでは -startuptype パラメータが使用されています。

お気付きになりましたでしょうか。この時点で既に目的が達成されています。でもまだスクリプトは書いていません。これと同じ作業を Cmd.exe シェルで実行するのは容易ではありません。VBScript では 12 行のコードを書くことになります。Windows PowerShell ではこれを 1 行で処理します。でも、これで終わりではありません。ステータスの出力やフェードバックがほとんど提供されていません。エラーが出なかったことはわかりますが、実際何が行われたかはよくわかりません。必要なタスクを処理するための機能を理解したので、もう少し見栄えがするスクリプトの作成を開始できます。

Michael Murgolo による Windows PowerShell Prompt Here

Microsoft® PowerToys for Windows® の中で最も人気があるのは Open Command Window Here ツールです。私も気に入っています。Microsoft PowerToys for Windows XP または Windows Server® 2003 Resource Kit Tools の一部として提供されてる Open Command Window Here を使用すると、Windows エクスプローラの中でフォルダやドライブを右クリックすることによってその場所をポイントするコマンド ウィンドウを開くことができます。

Windows PowerShell を勉強したとき、それと同じ機能があれば良いのにと思いました。そこで、Open Command Window Here のセットアップ .inf ファイルの cmdhere.inf を Windows Server 2003 Resource Kit Tools から取り出し、Windows PowerShell Prompt Here コンテキスト メニューとして使用できるように変更しました。このサイドバーの元になっている .inf ファイルは元のブログ記事に含まれています(leeholmes.com/blog/PowerShellPromptHerePowerToy.aspx で提供されています)。インストールするには、.inf ファイルを右クリックして [Install] (インストール) を選択するだけです。

このツールの Windows PowerShell 版を作成するとき、元のツールにバグを見つけました。Open Command Window Here をアンインストールすると、必要なくなったコンテキスト メニュー エントリが後に残るというバグがあることがわかりました。その結果、元の cmdhere.inf の修正版も元のブログ記事で提供することになりました。

これらの PowerToys はどちらも、コンテキスト メニュー エントリがレジストリ キーの中で設定されディレクトリおよびドライブのオブジェクトの種類に関連付けられているという事実を利用しています。これは、コンテキスト メニューのアクションがファイルの種類に関連付けられているのと同じです。たとえば、.txt ファイルを右クリックするといくつかのアクション (開く、印刷、編集など) が一覧の一番上に表示されます。これらの項目がどのように設定されているかを理解するために、HKEY_CLASSES_ROOT レジストリ ハイブを見てみましょう。

レジストリ エディタを開いて、HKEY_CLASSES_ROOT フォルダを展開すると、.doc、.txt、などのファイルの種類に対応したキーの名前を見ることができます。.txt キーをクリックすると、"(既定)" の値が txtfile (図 A を参照) になっていることがわかります。これが .txt ファイルに関連付けられているオブジェクトの種類です。下へスクロールして txtfile キーを展開し、その中の shell キーを展開すると、.txt ファイルのコンテキスト メニュー エントリ用に名付けられたキーが表示されます (コンテキスト メニューの作成方法は他にもあるので、すべてのエントリは表示されません)。そのキーの中には command キーがあります。command キーの "(既定)" の値はそのコンテキスト メニューを選択したときに Windows が実行するコマンド行です。

cmd プロンプトでも Windows PowerShell プロンプトでもこの方法で Open Command Window Here と Windows PowerShell Prompt Here のコンテキスト メニュー エントリを作成するツールを使用します。ドライブやディレクトリに関連付けられているファイルの種類はありませんが、HKEY_CLASSES_ROOT の中にはこれらのオブジェクトに関連付けられている Drive キーと Directory キーがあります。

図 A レジストリの中で設定されているコンテキスト メニュー エントリ

図 A** レジストリの中で設定されているコンテキスト メニュー エントリ **(画像を拡大するには、ここをクリックします)

Michael Murgolo は Microsoft Consulting Services のシニア インフラストラクチャー コンサルタントです。専門はオペレーティング システム、展開、ネットワーク サービス、Active Directory、システム管理、自動化、およびパッチ管理です。

対話型でスクリプトを作成する

C:\services.txt のリストに含まれている各サービスに対して実行するコマンドレットを複数用意する必要があります。サービス名やその他の情報を出力してスクリプトの進行状況を追跡できるようにするためです。

以前はパイプラインを使用してオブジェクトをコマンドレットからコマンドレットへと渡しました。今度は、Foreach コンストラクトという (先月のコラムで紹介した) もっとスクリプト的な手法を使用します。Foreach コンストラクトはオブジェクトの集合を受け取ることができ、集合の中の各オブジェクトに対して複数のコマンドレットを実行します。ループが繰り返されるときに現在のオブジェクトを示す変数を指定しておきます。たとえば、コンストラクトは次のようにして始まります。

foreach ($service in get-content c:\services.txt)

Get-Content コマンドレットを使用してテキスト ファイルの内容を取り出している点は以前と同じです。ただし、今度は Foreach コンストラクトで Get-Content が戻すオブジェクトの集合を処理するようになっています。ループは各オブジェクトに対して 1 回実行され、現在のオブジェクトが変数 $service に入れられます。後は、ループの中で実行するコードを指定するだけです。元の 1 行コマンドをここでも繰り返してみます。これは複雑にならないように、また元の機能を失わないようにするためです。

PS C:\> foreach ($service in get-content c:\services.txt) {
>> set-service $service -startuptype disabled
>> }
>>
PS C:\>

ここで注目していただきたいことは、Foreach コンストラクトの最初の行が中かっこの開始 ({)で終わっていることです。中かっこの内部にあるものはすべて Foreach ループの内部にあると見なされ、各オブジェクトに対して 1 回実行されます。「{」を入力して Enter キーを押した後、Windows PowerShell のプロンプトが >> に変わったことに注意してください (図 2 を参照)。これは、コンストラクトが始まったこと、そしてそれが終わるのを待っていることを示しています。次に Set-Service コマンドレットが入力されています。ここでは、$service がテキスト ファイルから読み取られた現在のサービス名を示すので、$service を最初のパラメータとして使用しています。次の行では、中かっこを閉じてコンストラクトを終了しています。Enter キーを 2 回押すと、そのコードが直ちに実行されます。

図 2 コンストラクトが開始されたことを示す Windows Powershell 画面

図 2** コンストラクトが開始されたことを示す Windows Powershell 画面 **(画像を拡大するには、ここをクリックします)

そうです。直ちに実行されるのです。上の例はスクリプトのように見えますが、実際はライブで実行されているのであって、テキスト ファイルとして保存されたものを実行しているわけではありません。次に、全体を再度入力し直して、サービス名を出力するコードの行を追加します。

PS C:\> foreach ($service in get-content c:\services.txt) {
>> set-service $service -startuptype disabled
>> "Disabling $service"
>> }
>>
Disabling messenger
Disabling alerter
PS C:\>

今度は、テキストを表示しようとしていることに注意してください。そのテキストは二重引用符で囲まれています。二重引用符は、それがテキスト文字列であってコマンドではないことを示します。ただし、単一引用符ではなく二重引用符を使用すると、Windows PowerShell は変数のテキスト文字列をスキャンします。それが見つかると、その変数の実際の値を変数の名前の代わりに表示します。その結果、このコードが実行されると、現在のサービス名が表示されます。

でも、これはまだスクリプトではありません。

ここまでは、Windows PowerShell を対話型で使用してきました。これは書いたコードの結果を直ちに見ることのできるのでたいへん便利です。しかし、やがて何度も同じコードの入力を繰り返すのが面倒になってきます。そのため Windows PowerShell ではスクリプトの実行もできるようになっています。Windows PowerShell スクリプトではスクリプトという言葉が最も厳密な文字どおりの意味で使われています。Windows PowerShell は基本的にはスクリプトのテキスト ファイルを読み取って、あたかも手で入力したかのようにその各行を "入力" します。ということは、これまでに書いたものをすべてスクリプト ファイルに貼り付けることができるということです。それでは、メモ帳を使用して disableservices.ps1 というファイルを作成し、下のコードを貼り付けてみます。

foreach ($service in get-content c:\services.txt) {
 set-service $service -startuptype disabled
 "Disabling $service"
 }

このファイルは C:\test というフォルダに入れました。これを Windows PowerShell の中から実行してみましょう。

PS C:\test> disableservices
'disableservices' is not recognized as a cmdlet, function, operable program, or
<script file.
At line:1 char:15
+ disableservices <<<<
PS C:\test>

エラーになりましたね。何が間違っていたのでしょうか。Windows PowerShell の現在の場所は C:\test フォルダになっていましたが、スクリプトが見つからなかったようです。なぜでしょうか。セキュリティ保護のために、Windows PowerShell では現在のフォルダにあるスクリプトを実行できないようになっています。これはスクリプトによってオペレーティング システム コマンドがハイジャックされないようにするためです。たとえば、dir.ps1 という名前のスクリプトを作成して通常の dir コマンドに優先させることはできません。現在のフォルダからスクリプトを実行するには、相対パスを指定する必要があります。

PS C:\test> ./disableservices
The file C:\test\disableservices.ps1 cannot be loaded. 
The execution of scripts is disabled on this system. 
Please see "get-help about_signing" for more details.
At line:1 char:17
+ ./disableservices <<<<
PS C:\test>

今度は何が問題なのでしょうか。まだ実行してくれませんね。パスが正しいにもかかわらず、Windows PowerShell はスクリプトを実行できないと言っています。これは、既定では、Windows PowerShell ではスクリプトを実行できないようになっているからです。これも、セキュリティ保護のための予防措置で、VBScript で見られたような問題を避けるためです。悪意のあるスクリプトは、既定では Windows PowerShell で実行できません。既定ではどのスクリプトも実行できないようになっています。スクリプトを実行するには実行ポリシーを明示的に変更する必要があります。

PS C:\test> set-executionpolicy remotesigned

RemoteSigned 実行ポリシーでは、署名されていないスクリプトをローカル コンピュータで実行できるようになっています。ただし、ローカルでもダウンロードされたスクリプトの実行には署名が必要です。もっと良いポリシーは AllSigned です。これなら信頼されている発行元からの証明書にデジタル署名があるスクリプトのみが実行されます。ただし、今は証明書が手元になくてスクリプトに署名できないので、ここでは RemoteSigned を使用するしかありません。それでは、再度スクリプトを実行してみましょう。

PS C:\test> ./disableservices
Disabling messenger
Disabling alerter
PS C:\test>

ここで使用している RemoteSigned 実行ポリシーは良いポリシーですが優れたポリシーとは言いがたいことを指摘しておきます。もっと良い解決策があります。コード署名証明書を取得した方がはるかに安全です。Windows PowerShell の Set-AuthenticodeSignature コマンドレットを使用してスクリプトに署名し、実行ポリシーをはるかに安全な AllSigned ポリシーに設定できます。

Unrestricted というポリシーもありますが、これは避けるべきです。このポリシーはすべてのスクリプト、リモートからの悪意のあるスクリプトでも自由にローカル コンピュータで実行できるようにします。これは非常に危険な状態です。ですから、Unrestricted ポリシーの使用はどのような理由があってもお勧めできません。

直ちに結果を見る

Windows PowerShell が提供する対話型スクリプト作成能力によって、スクリプトのプロトタイプでもスクリプトの小さな構成要素でも簡単に作成できます。結果が直ちにわかるので、希望どおりの結果になるようにスクリプトを調整するのも簡単です。それが終わったら、将来簡単にアクセスできるようにコードを .ps1 ファイルに保存できます。.ps1 ファイルにデジタル署名を入れることも忘れないでください。そうすれば、Windows PowerShell を最も安全な AllSigned 実行ポリシーに設定したままスクリプトを実行できます。

Don Jones は、SAPIEN Technologies 社でスクリプト作成チームのリードを務めるスクリプトの権威であり、Windows PowerShell: TFM の共著者です (www.SAPIENPress.com を参照)。連絡先 : don@sapien.com

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