Windows PowerShell: スクリプト作成の短期集中講座

この拡大版の Windows PowerShell コラムでは、強力なテクノロジである Windows PowerShell について集中的に学習します。

Don Jones

Windows PowerShell に慣れて、その機能を理解している方が徐々に増えていると思います。それを踏まえて、今月は長めのコラムを執筆してみました。このコラムでは、Windows PowerShell を使用したスクリプト作成の概要を駆け足で説明します。その過程では、パラメーター化されたスクリプトを作成する方法についても取り上げます。Windows PowerShell コラムでは、今後数か月にわたり、今月のコラムを基盤とした特定のトピックに焦点を当てていきます。

Windows PowerShell コマンドをコンソールで実行するのに慣れていない方は、今回の内容がかなり高度に思われるかもしれませんが、少しずつでも読み進めてください。ただし、このコラムの内容を読み進める前に、Windows PowerShell のセキュリティ機能について十分に理解しておく必要があります。また、実行ポリシーとご自分が使用している設定についても理解しておいてください。RemoteSigned と AllSigned の違いと、特定の状況下で一方の実行ポリシーが他方の実行ポリシーより適している理由をご存じない方が、この先の内容を読み進めるのは難しいかもしれません。

スクリプトをシェルで実行する方法についても理解している必要があります。スクリプトを実行するためにはパスとファイル名を指定する必要があること、Integrated Scripting Environment (ISE) でスクリプトを実行することとコンソールでスクリプトを実行することがどのように異なるのかについても把握している必要があります。ISE では、スクリプトは、グローバル スコープで実行されますが、通常のシェルのコンソールでは、スクリプトごとにスコープが設定されます。スコープについては後ほど説明しますが、この意味と役割について把握している必要がありあす。

これらの知識は、私の著書『Learn Windows PowerShell in a Month of Lunches』(Manning Publications、2011 年) か、その書籍の関連情報を提供する Web サイト (英語) で修得していただくことができます。この 2 つのリソースを使用して、Windows PowerShell に関する基礎知識の基盤を構築してください。

このコラムを、頑張って理解しながら読み進め、例を実行してみてください。Windows PowerShell の ISE にサンプル スクリプトを 1 行目から入力すると (または、コピーして貼り付けると)、お使いの ISE では、行番号が、コラムで言及している行番号と一致します。

Windows PowerShell スクリプト ファイル

Windows PowerShell スクリプト ファイルは、.PS1 というファイル拡張子の単なるプレーン テキスト ファイルです。1 という値は、Windows PowerShell のバージョンではなく、言語エンジンのバージョンを表しています。Windows PowerShell 1.0 と 2.0 は、どちらも言語エンジンのバージョン 1 を使用します。そのため、バージョン 1.0 と 2.0 の両方が \Windows\System32\WindowsPowerShell の v1.0 フォルダーにインストールされています。

Windows PowerShell スクリプトは、コマンドラインから実行するバッチ ファイルと異なり、スクリプトを実行することと、スクリプトと同じ順番でコマンドを自分で実行することは、まったく同じわけではありません。試しに、コンソール ウィンドウを開き、次のコマンドを入力し、1 行ごとに Enter キーを押してみてください (行番号は入力しないようにしてください)。

Get-Service
Get-Process

今度は、まったく同じ文字列を、スクリプト ファイルか ISE のスクリプト編集ウィンドウに入力して、スクリプトを実行してください。異なる結果が表示されるはずです。Windows PowerShell では、Enter キーを押すたびに新しいパイプラインが開始され、入力したコマンドは、その単一のパイプラインで実行されます。Windows PowerShell では、パイプラインの終わりに、その内容をテキスト表示に変換します。通常のコンソールで 2 つのコマンドを実行した場合は、コマンドを 2 つの異なるパイプラインで実行したことになります。

Windows PowerShell では、出力ごとに、一意の表示を構築できます。しかし、2 つのコマンドがスクリプトに含まれている場合は、どちらも同じパイプラインで実行されます。Windows PowerShell の書式設定システムは、2 つの異なる結果セットに対して同じ一意の出力を構築できるほど精巧ではありません。次のコマンドをコンソールで実行してみてください。

Get-Service;Get-Process

この結果は、先ほど、2 つのコマンドを含むスクリプトを実行したときの結果と同じようになります。今回は、両方のコマンドが単一のパイプラインで実行されているので、両方のコマンドを含むスクリプトを実行したときと、同じ結果になりました。

つまり、スクリプトで生成する出力は、1 種類にする必要があります。ただし、書式設定システムの制限により、これはあまり良いとは言えません。考慮事項は他にもあり、複数の異なる種類のものを同時にパイプラインに渡すスクリプトは推奨されません。

以降の内容では、このことを規則として念頭に置きましょう。スクリプトで生成する出力を 1 種類にする必要があります。唯一の例外は、複数の関数の格納場所として使用されているスクリプトです。この場合、各関数で生成する出力を 1 種類にする必要があります。

変数

変数は、"箱" として考えてください。この箱には、1 つ以上のものを入れることが可能で、異なるものを混ぜても問題ありません。この箱には名前がありますが、Windows PowerShell では、ほぼ自由に名前を付けることができます。たとえば、Var でも {my variable} でもかまいません。2 つ目の例 ({my variable}) では、スペースを含む変数名が中かっこで囲まれていますが、あまり見栄えが良くないので、変数名には、文字、数値、およびアンダースコアのみを含めることをお勧めします。

変数名を使用すると、"箱" 全体が参照されます。箱全体ではなく箱の中身を参照する場合は、$var のようにドル記号を追加します。Windows PowerShell では、変数の先頭によくドル記号が付けられていますが、これは、変数が、そもそも箱の中身を参照するために使用されているからです。ですが、ドル記号は変数名の一部ではないことに注意してください。ドル記号は、箱ではなく中身を要求していることを Windows PowerShell に伝える単なる指示です。次に例を示します。

$var = 'hello'
$number = 1
$numbers = 1,2,3,4,5,6,7,8,9

この例では、代入演算子 (=) を使用して変数に項目を代入する方法を示しています。Windows PowerShell では、コンマで区切られた一覧をすべて項目の配列またはコレクションであると解釈するため、最後の行では、配列が作成されます。1 行目では、文字が引用符で囲まれているため文字列オブジェクトを代入します。

Windows PowerShell には、初心者が混乱する特徴が 1 つあります。それは、Windows PowerShell では、変数名に関連付けた意味が "理解" されないことです。変数を $computername という名前にしても、変数にコンピューター名が含まれることをシェルに "伝えられる" わけではありません。

同様に、変数を $numbers という名前にしても、変数に複数の数値が含まれることをシェルに "伝えられる" わけではありません。変数名を複数形にして、単一のものを代入しても、シェルには何の影響もありません。たとえば、次のステートメントはシェルで問題なく実行されます。

$numbers = 1

また、次のステートメントも問題ありません。

$numbers = 'fred.'

ただし、変数に複数の値が含まれている場合は、特別な構文を使用して、1 つの値にのみアクセスできます。最初の項目には $numbers[0] を、次の項目には $numbers[1] を、最後の項目には $numbers[-1] を、最後から 2 番目の項目には $numbers[-2] を使用してアクセスします。それ以外の順番の項目についても、このパターンに従ってアクセスできます。

引用符

特別な理由がない限り、変数の区切り文字には、単一引用符を使用することをお勧めします。二重引用符を使用しなければならないケースは、3 つあります。

1 つ目は、変数の内容を文字列に挿入する必要がある場合です。Windows PowerShell では、二重引用符のみで囲まれている文字列から $ を探し、$ の後から変数名で使用できない文字より前にある文字までを、変数名と仮定します。次のように、変数名と $ が、変数の内容で置き換えられます。

$name = 'Don'
$prompt = "My name is $name"

$name は変数の内容で置き換えられるため、$prompt 変数の値は My name is Don になります。これは、連結操作を行わずに文字列を統合する、たいへん便利な方法です。

Windows PowerShell では、二重引用符で囲まれている文字列から、Windows PowerShell のエスケープ文字であるアクサン グラーブも探し、見つけた場合は、適宜処理します。次に例を示します。

$debug = "`$computer contains $computer"
$head = "Column`tColumn`tColumn"

1 行目では、最初の $ が "エスケープ" されています。そのため、この $ には、変数のアクセサーとしての特別な意味がなくなります。$computer 変数に SERVER という値が格納されている場合、$debug 変数の値は "$computer contains SERVER" になります。

2 つ目の例では、't が水平タブ文字を表すので、Column という単語の間にタブ文字が挿入されます。その他の特殊なエスケープ文字の詳細については、シェルの about_Escape_Characters ヘルプ トピックを参照してください。

次のように文字列に単一引用符を含める必要がある場合にも、二重引用符を使用します。

$filter1 = "name='BITS'"
$computer = 'BITS'
$filter2 = "name='$computer'"

この例では、リテラル文字列は name='BITS' です。二重引用符は、すべてのものを囲んでいます。$filter1 変数と $filter2 変数の内容は最終的にはまったく同じになりますが、$filter2 変数では、二重引用符で囲まれた変数を置き換える方法を使用しています。実際に重要なのは、一番外側の引用符のみであることに注意してください。文字列に含まれる単一引用符は、Windows PowerShell に何の影響ももたらしません。文字列に含まれる単一引用符は、単なるリテラル文字列で、Windows PowerShell によって解釈されません。

オブジェクトのメンバーと変数

Windows PowerShell では、すべてのものがオブジェクトです。name などの単純な文字列も System.String 型のオブジェクトです。型の名前 (つまり、オブジェクトの種類) とメンバー (メンバーのプロパティとメソッド) を知るには、次のようにオブジェクトを Get-Member コマンドレットにパイプします。

$var = 'Hello'
$var | Get-Member

変数に格納されているオブジェクト全体にアクセスするのではなく、1 つのプロパティやメソッドにアクセスすることをシェルに通知するには、変数名の末尾にピリオドを付け、ピリオドの後に続けてプロパティやメソッド名を入力します。

メソッド名の後には、常に 1 組のかっこが続きます。メソッドの中には、入力引数を受け取るものがあり、入力引数は、かっこ内に配置します。入力引数が複数ある場合は、コンマで区切ります。引数が必要ないメソッドでは、次のように、かっこは空になりますが、必ず配置するようにします。

$svc = Get-Service
$svc[0].name
$name = $svc[1].name
$name.length
$name.ToUpper()

2 行目に注目してください。まず、$svc 変数に格納されている 1 つ目の項目にアクセスしています。ピリオドは、"オブジェクト全体ではなく、プロパティやメソッドにアクセスする" ことを Windows PowerShell に伝えるためのものです。ここでは、name プロパティにのみアクセスしています。5 行目では、ピリオドの後にメソッド名を指定して、その後にかっこを付け、メソッドにアクセスする方法を示しています。

ピリオドは、プロパティやメソッドにアクセスする必要があることを示すものなので、通常、変数名に、ピリオドは使用しません。つまり、以下の例では、2 行目は期待どおりに機能しません。

$service = 'bits'
$name = "Service is $service.ToUpper()"
$upper = $name.ToUpper()
$name = "Service is $upper"

2 行目では、$name 変数には Service is BITS.ToUpper() という文字列が格納されていますが、4 行目では Service is BITS という文字列が格納されます。

かっこ

Windows PowerShell のかっこは、オブジェクトのメソッドで使用する場合の役割とは別に、代数学と同じように実行順序を示します。つまり、かっこは、最初に実行するものをシェルに通知します。かっこを使った式全体は、その式が生成するものによって置き換えられます。次に、難解な例をいくつか紹介します。

$name = (Get-Service)[0].name
Get-Service -computerName (Get-Content names.txt)

1 行目では、$name 変数には、システムで実行されている 1 つ目のサービスの名前が格納されています。これを読み取るのには少々努力が必要です。まずは、かっこを使った式から始めましょう。Windows PowerShell も、ここから開始します。Get-Service コマンドレットは、サービスのコレクション (つまり、配列) に解決されます。[0] では、配列の最初の項目にアクセスするので、1 つ目のサービスにアクセスしています。この後にピリオドが続いているので、サービス オブジェクト全体ではなく、そのサービスのプロパティまたはメソッドにアクセスしていることがわかります。最後に、サービスの名前だけを取り出しています。

2 行目では、かっこを使った式で、テキスト ファイルの内容を読み取っています。このファイルの各行に 1 つのコンピューター名が含まれていると仮定すると、Get-Content コマンドレットではコンピューター名の配列を返します。コンピューター名の配列は、Get-Service コマンドレットの –computerName パラメーターに渡されます。–computerName パラメーターは文字列の配列を受け取るように設計されているため、ここで、シェルは、–computerName パラメーターに、文字列の配列を返すかっこを使った任意の式を渡すことができます。

スコープ

スコープは、コンテナリゼーション システム (コンテナを使った輸送システム) として機能するプログラミングの概念です。変数、エイリアス、PSDrives など、Windows PowerShell の要素は、すべてスコープに格納されます。シェルでは、スコープの階層を維持します。また、シェルには、スコープが相互に対話して情報を共有する方法を決定する一連の規則があります。

シェル自体は、グローバル スコープと呼ばれる単一のスコープです。スクリプトを実行すると、シェルによって新しいスコープが構成され、スクリプトがその中で実行されます。新しい変数など、スクリプトによって作成されるものはすべて、スクリプトのスコープに格納されます。上位のシェルは、スクリプトのスコープにアクセスすることはできません。

スクリプトの実行が完了すると、そのスコープは廃棄され、スコープで作成されたすべてのものが消失します。試しに、次のスクリプトを作成して (行番号は入力しないようにしてください)、コンソール ウィンドウから実行してみてください。

New-PSDrive -PSProvider FileSystem -Root C:\ -Name Sys
Dir SYS:

スクリプトを実行した後に、Dir SYS コマンドを手動で実行すると、エラーになります。これは、SYS ドライブがスクリプトで作成されたためです。スクリプトの実行が完了すると、スクリプトによって作成されたものはすべて廃棄されます。このため、SYS ドライブは存在しなくなっています。ただし、シェルのすべてのものにスコープが設定されるわけではありません。モジュールなどの項目は、常にグローバルに処理されます。スクリプトでモジュールを読み込むと、スクリプトの実行が完了した後も、モジュールは読み込まれた状態で維持されます。

スコープで作成されていないものにスコープでアクセスしようとすると、Windows PowerShell では、上位のスコープ (親スコープ) を参照します。先ほど入力したスクリプトで、Dir エイリアスが機能したのはこのためです。Dir エイリアスは、スクリプトのスコープには存在しませんでしたが、上位のスコープであるグローバル スコープには存在していました。スコープでは、上位のスコープに存在する項目と同じ名前の項目を作成できます。次のスクリプトを実行してみてください。

Dir
New-Alias Dir Get-Alias
Dir

奇妙に思われるかもしれませんが、Dir エイリアスが最初に実行されたとき、このエイリアスはスクリプトのスコープに存在していませんでした。スクリプトでは、上位のスコープに存在する Dir エイリアスを使用し、このエイリアスは Get-ChildItem コマンドレットをポイントするので、おなじみのディレクトリの一覧が表示されました。

その後、スクリプトでは Dir という新しいエイリアスが作成されます。この Dir エイリアスは、Get-Alias コマンドレットをポイントします。これが、次に実行されたことです。上位のスコープに存在する Dir エイリアスには影響を与えません。上記のスクリプトを実行した後に、シェルで Dir エイリアスを実行してみてください。この場合も、ディレクトリの一覧が表示されます。

スコープが変数と組み合わさると、特に混乱を招きます。通常、スコープでは、スコープ外の項目、特に変数へのアクセスが固く禁止されています。グローバル スコープの $var 変数に強制的にアクセスする $global:var を使用するなどの構文はありますが、非常に特殊な状況を除いてはお勧めできません。

Windows PowerShell のスクリプト言語

Windows PowerShell のスクリプト言語は非常に簡単で、キーワードは 12 個もありません。これは、約 300 個もキーワードがある、VBScript などの完全なプログラミング言語とは対照的です。

Windows PowerShell の言語は、簡略化されていますが、仕事を遂行するのには十分です。これから、主要なスクリプトのコンストラクトについて説明しますが、詳細については、シェルの対応する about トピックでいつでも確認できます。たとえば、Switch コンストラクトについての情報は about_switch で、If コンストラクトについての情報は about_if で確認できます。about トピックの一覧を表示するには、「help about*」コマンドを実行します。

If コンストラクト

If コンストラクトは、Windows PowerShell の主な意思決定を行うコンストラクトです。完全な形式は、次のようになります。

If ($this -eq $that) {
  # commands
} elseif ($those -ne $them) {
  # commands
} elseif ($we -gt $they) {
  # commands
} else {
  # commands
}

If キーワードは、このコンストラクトの必須要素です。かっこを使った式が後に続き、この式は True か False のいずれかに評価する必要があります。Windows PowerShell では、常に 0 を False と解釈し、0 以外の数値を True と解釈します。

また、Windows PowerShell では、組み込みの変数の $True と $False をブール値として認識します。かっこを使った式が True と評価されると、直後の中かっこに配置されているコマンドが実行されます。False の場合、コマンドは実行されません。適切な If コンストラクトに必要なものは、これがすべてです。

ElseIf セクションを 1 つ以上使用すると、もう少し多くのことを実行できます。ElseIf は、If コンストラクトと同じように動作しますが、ElseIf 専用のかっこを使った式が存在しています。この式が True と評価されると、直後の中かっこに配置されているコマンドが実行されます。False の場合は、実行されません。

前にあるブロックがすべて実行されなかった場合に実行される、Else ブロックを最後に付けることができます。この場合、最初の True 式に関連付けられているブロックのみが実行されます。たとえば、$this 変数の値が $that 変数の値と等しくなく、$those 変数の値が $them 変数の値と等しくない場合、4 行目のコマンドだけが実行されます。5 行目にある 2 つ目の elseif 式は評価されません。

# はコメント文字です。Windows PowerShell では、原則として、# から改行までのすべての文字が無視されます。また、コンストラクトで書式設定について配慮されている点にも注目してください。他の人が作成したスクリプトで、次のような書式設定がなされているものを見たことがあるかもしれません。

if ($those -eq $these)
{
  #commands
}

中かっこの位置は、どこに配置してもかまいませんが、配置を一貫させて、スクリプトを読みやすくすることは重要です。また、中かっこのすべての行では、同じレベルでインデントを設定することも重要です。

Windows PowerShell の ISE では、Tab キーを使用してインデントを設定できます。Tab キーを使用すると、既定で 4 文字のインデントが設定されます。コードにインデントを設定することは、重要なベスト プラクティスです。インデントを設定しないと、スクリプトが複雑な場合、開始と終了の中かっこがわかりにくくなります。また、Windows PowerShell を使用するスクリプト キディから、笑いものにされるかもしれません。書式設定が適切になされていない、次のスクリプトを参照してください。

function mine {
if ($this -eq $that){
get-service
}}

このコードは、解読、デバッグ、トラブルシューティング、およびメンテナンスが非常に困難です。閉じかっこの後のスペースは必須ではありませんが、スペースがあるとスクリプトが読みやすくなります。同様に、必須ではありませんが、スクリプトを読みやすくするため、コードにインデントも設定しましょう。次のコードを参照してください。

function mine {
 if ($this -eq $that){
  get-service
 }
}

必ずしも閉じる中かっこを各行に単独で配置する必要はありませんが、これはコードの読み手を配慮した書式設定です。適切に書式を設定すれば、スクリプトにおける問題が少なくなります。

Do While コンストラクト

これは、Windows PowerShell のループ コンストラクトです。なんらかの条件が True である限り、または条件が True になるまで、コマンドのブロックを繰り返し実行するように設計されています。次に基本的な使用方法を示します。

Do {
  # commands
} While ($this -eq $that)

このコンストラクトでは、中かっこに配置されているコマンドは、少なくとも必ず 1 回実行されます。While の条件は、1 回目の実行が終わるまで評価されませんが、次のように、While の位置は動かすことができます。この場合、最初の段階で条件が True である場合にのみコマンドが実行されます。

While (Test-Path $path) {
  # commands
}

この例では、-eq などの比較演算子を使用していない点に注目してください。これは、Test-Path コマンドレットで、最初に True または False を返すためです。Test-Path コマンドレットの結果と True または False を比較しなくても、式は機能します。

このようなスクリプトのコンストラクトを利用する、かっこを使った式は、True または False まで簡略化するだけでかまいません。True または False を必ず返す Test-Path コマンドレットなどのコマンドを使用している場合、その他に必要なものはありません。他のコンストラクトと同様、Do While コンストラクトの別の使用方法については、about トピックを参照してください。

ForEach コンストラクト

このコンストラクトは、ForEach-Object コマンドレットに動作が似ており、構文だけが異なります。ForEach コンストラクトの目的は、次のように、配列 (または、Windows PowerShell では配列と同じものと見なされるコレクション) を使用して、配列内のオブジェクトを列挙し、1 つずつ操作できるようにすることです。

$services = Get-Service
ForEach ($service in $services) {
  $service.Stop()
}

初心者の方は、このコンストラクトについて考え過ぎてしまうことがよくあります。services という複数形の英単語は、Windows PowerShell に対して何の意味も成さないことに注意してください。この変数名は、この変数に 1 つ以上のサービスが含まれていることをユーザーに示すためのものです。変数名が複数形だからと言って、シェルが特別な動作をするわけではありません。

2 行目の in キーワードは、ForEach 構文の構成要素です。2 行目では $service という名前の変数が作成されていますが、$fred や $coffee という名前にしても問題なく、同じように機能します。

2 つ目の変数 ($services) に格納されているオブジェクトごとに、コンストラクトのコマンド (中かっこで囲まれているもの) が 1 回実行されます。毎回、1 つのオブジェクトが、2 つ目の変数 ($services) から 1 つ目の変数 ($service) に配置されます。

このコンストラクトでは、個々のオブジェクトを操作するときに 1 つ目の変数 ($service) を使用します。3 行目のピリオドは、オブジェクト全体ではなく、特定のメンバー (Stop メソッドのみ) を操作することを示しています。

ForEach コンストラクトを使用することが避けられず、それが望ましい場合もありますが、プログラミングやスクリプト作成について多少経験があれば、ForEach コンストラクトが最適でない場合でも、このコントラクトを使用できることがあります。先ほどの例では、ForEach コンストラクトを使用するのが最適な選択肢ではありませんでした。たとえば、このようにした方が簡単だと思われませんか。

Get-Service | Stop-Service

ここで重要なのは、ForEach コンストラクトを使用してよいかどうかを判断することです。ForEach コンストラクトが、手持ちのタスクを完了する唯一の方法かどうかを見極めてください。おそらく ForEach コンストラクトしか使えない例をいくつか紹介します。

  • 多くのオブジェクトに対してメソッドを実行する必要があるが、同等の操作を実行するコマンドレットがない
  • 多くのオブジェクトがあり、それぞれに対して、連続した操作を実行する必要がある
  • 一度に 1 つのオブジェクトに対してしか実行できない操作があるが、スクリプトでは 1 つ以上のオブジェクトを操作する可能性があり、1 つ以上のオブジェクトを操作するかどうかを事前に把握できない

その他のコンストラクト

Windows PowerShell には、Switch、For など、ここで説明したもの以外にもスクリプトのコンストラクトがいくつかあります。これらはすべて、シェルの about ヘルプ トピックで情報が提供されています。ここで説明したコンストラクトを、その他のコンストラクトの代わりに使用できる場合もあります。たとえば、Switch コントラクトは、複数の ElseIf セクションを使用する If コンストラクトで代用できます。For コントラクトは、ForEach コントラクトや ForEach-Object コマンドレットで代用できます。例を挙げると、次のように、ちょうど 10 回実行するループを作ることが可能です。

1..10 | ForEach-Object -process {
  # code here will repeat 10 times
  # use $_ to access the current iteration
  # number
}

ご自分で、作業の遂行に最適だと思うコンストラクトを使ってください。また、インターネットでスクリプトを探す場合は、すべてのバリエーションを確認するようにしてください。

関数

関数は特別な種類のコンストラクトで、特定の 1 つのタスクを実行する一連の関連コマンドを格納する場所として使用します。一般的には、次のように、Windows PowerShell スクリプトを関数で "囲む" ことができます。

function Mine {
  Get-Service
  Get-Process
}
Mine

ここでは、Mine という名前の新しい関数を定義しています。これで、基本的に Mine はコマンドになります。つまり、関数の名前を入力するだけで関数を実行できます。それが行われているのが 5 行目で、ここで関数が実行されます。

通常、関数はスクリプト ファイルに含まれています。1 つのスクリプトに、複数の関数を含めることが可能で、関数の中に別の関数を含めることも可能です。

ただし、関数はスコープが設定された項目です。つまり、関数は、関数が作成されたスコープと同じスコープでしか使用できません。関数をスクリプトに配置して、そのスクリプトを実行すると、関数は、スクリプトが実行されている間にスクリプト内でしか実行できません。スクリプトの実行が完了すると、(スクリプトのスコープにあるすべてのものと同じように) 関数は消失します。1 つ例を紹介しましょう。

function One {
  function Two {
Dir
  }
  Two
}
One
Two

このコードを 1 つのスクリプト ファイルに入力し、そのスクリプトを実行したとします。7 行目では、1 行目から開始されている関数 One が実行されます。5 行目では、2 行目から開始されている関数 Two が実行されます。このスクリプトの実行結果では、ディレクトリの一覧が表示されます。この処理が実行されるのは、関数 Two に含まれている 3 行目です。

ただし、次に実行される 8 行目でエラーになります。スクリプトには、Two という関数が含まれていません。関数 Two は、関数 One の中に埋め込まれています。そのため、関数 Two は、関数 One のスコープに存在することになります。したがって、関数 One の中にあるものだけが関数 Two を参照することが可能で、それ以外の場所から関数 Two を呼び出そうとすると、エラーになります。

スクリプトにパラメーターを追加する

一般的に、実行するたびにまったく同じ処理を行うスクリプトを作成することはあまりありません。なんらかの変化するデータや動作を含むスクリプトを作成する場合の方が多いでしょう。このようなバリエーションを作るためには、パラメーターを使用します。

パラメーターは、スクリプトの先頭で、特別な方法を使って定義されます。この定義の前にコメントを追加することもできますが、コメントを追加しない場合は、パラメーターの定義が、スクリプトにおける最初の実行可能なコード行になります。パラメーターの定義領域内では、各パラメーターをコンマで区切ります。書式設定を適切に行うため、各行に各パラメーターを単独で配置すると便利です。次に例を示します。

param (
  [string]$computername,
  [string]$logfile,
  [int]$attemptcount = 5
)

この例では、3 つのパラメーターを定義しています。このスクリプトでは、これらのパラメーターは単に変数のように使用されます。4 行目では、$attemptcount パラメーターに既定値を割り当てています。この既定値は、入力パラメーターによって上書きされますが、スクリプトの実行時に入力パラメーターが指定されなかった場合は、既定値が使用されます。

次に、スクリプトが実行される方法をいくつか示します。スクリプトは、Test.ps1 という名前で保存したとします。

./test -computername SERVER
./test -comp SERVER -log err.txt -attempt 2
./test SERVER err.txt 2
./test SERVER 2
./test -log err.txt -attempt 2 -comp SERVER

スクリプトでは、コマンドレットの場合とほぼ同じようにパラメーターを受け取ります。変数名は、Windows PowerShell のすべてのパラメーター名の前に付けられる通常のダッシュを付けて指定することで、パラメーター名として使用されています。これから、上記のコードのしくみについて説明します。

  • 1 行目では、パラメーターのうち 1 つしか指定していないので、$logfile 変数は空で、$attemptcount 変数の値が既定値の 5 になります。
  • 2 行目では、3 つすべてのパラメーターを指定していますが、短縮されたパラメーター名を使用しています。コマンドレットの場合と同様に、Windows PowerShell に指定したパラメーターを伝えるためには、パラメーター名が認識されるのに必要な長さのパラメーター名を入力するだけでかまいません。
  • 3 行目でも、3 つすべてのパラメーターを指定していますが、パラメーター名を使用せず、位置を利用しています。スクリプトでパラメーターが配置されている順番で値を指定すれば、問題なく動作します。
  • 4 行目では、注意を怠った場合の結果を示しています。ここでは、$computername 変数の値が SERVER になり、$logfile 変数の値が 2 になりますが、$attemptcount 変数の値が 5 になります。これは、意図する結果ではありません。パラメーター名を使用しないと、柔軟さがなくなり、作成者の意図を他者が解読するのが難しくなります。このため、他者が問題のトラブルシューティングを行うことも困難になります。
  • 5 行目は、改善された例です。ここでは、パラメーターをランダムに指定しましたが、パラメーター名を使用しているので問題ありません。個人的には、原則として、最大限の柔軟性を発揮するために、パラメーター名を常に使用するようにしています。そうすれば、パラメーターの順番を覚えておく必要はありません。

高度なスクリプト

Windows PowerShell では、パラメーターについての追加情報を指定する手法がサポートされています。このサポートにより、パイプラインから入力を受け取りながら、パラメーターを指定することを必須条件として宣言できます。この手法は、コマンドレットのバインドといいます。

スクリプトでパラメーターを使用する方法は変わりませんが、パラメーターについての追加情報がシェルに提供されます。この手法は関数でよく使用されるものですが、構文はスクリプトでも問題なく動作します。次に簡単な例を示します。

[CmdletBinding()]
param (
  [Parameter(Mandatory=$True)]
  [string]$computername,

  [Parameter(Mandatory=$True)]
  [string]$logfile,

  [int]$attemptcount = 5
)

ここでは、スクリプトの最初の実行可能なコード行として、[CmdletBinding()] という指示を追加しただけです。この指示の前には、コメントを追加してもかまいませんが、それ以外のものは追加できません。また、2 つのパラメーターに [Parameter()] という指示も追加しました。この指示では、これらのパラメーターが必須要素であることを示しています。これらのパラメーターを指定せずにスクリプトを実行すると、Windows PowerShell でメッセージが表示されるようになります。

最後のパラメーターには特別な指示はなく、3 つのパラメーターはコンマで区切られています (つまり、最初の 2 つのパラメーターの後にはコンマが続いています)。パラメーターには、他にも多数の指示を指定できます。詳細については、about_Functions_Advanced_Parameters ヘルプ トピックを参照してください。

このコラムでは、Windows PowerShell でスクリプトを作成するときに必要となる重要な概念をいくつか駆け足で説明しました。1 つでも学んでいただけたことがあれば、さいわいです。パラメーター化されたスクリプトを作成できると、Windows PowerShell のネイティブなコマンドレットのように動作するスクリプトを作成できるので、特に便利です。

Don Jones

Don Jones は、Microsoft MVP の受賞者で、『Learn Windows PowerShell in a Month of Lunches』(Manning Publications、2011 年) の著者でもあります。この書籍は、管理者が Windows PowerShell を効率的に使用できるようにすることを目的としています。また、一般ユーザーを対象にオンサイトの Windows PowerShell トレーニングも開催しています。Don に対するお問い合わせについては、彼の Web サイト (ConcentratedTech.com、英語) を参照してください。

関連コンテンツ