【PowerShell】MVA対策講座 Windows PowerShell 3.0 を基礎から学ぶ ‐ Part4 高度な関数 編


正直言って、今回は難しいです。どこから説明してよいかわからないくらいです。。。orz
Microsoft Virtual Academy で公開されているオンライントレーニングコース「PowerShell 3.0 を使用した高度なツールとスクリプト」のテスト解説シリーズ、第4回目です。

テストは以下から受けられます!

Microsoft Virtual Academy - PowerShell 3.0 を使用した高度なツールとスクリプト
http://www.microsoftvirtualacademy.com/training-courses/advanced-tools-and-scripting-with-powershell-3-jump-start-japanese#?fbid=pJeqjxv8UId

前回までの投稿は以下より。

さて、今回のタイトルは 「高度な関数」なのですが、「高度」と言われても何のことやら?ですよね。

これ、英語版だと「Advanced Function」と書かれています。 「Advanced」の訳って難しいですよね。個人的にはあえて訳さなくてもよいと思うんですけど。ちなみに「Advanced Windows Firewall」は「セキュリティが強化された Windows Firewall」と訳されていますね。ローカライズチームも苦労しているのだと思います。。。

PowerShell を使用すると、スクリプトだけで関数を定義できることはご存知の通りなのですが、「高度な」とつくからには単に Function ブロックで括る以上の「ナニカ」が無ければなりません。

解説を始める前に、関数とは何かについておさらいしておきましょう。

以下のようなスクリプト hello.ps1 があったとします。Param ブロック内に定義されている $a は引数です。

param($a="Hello!")
Write-Output $a

これを実行するには、PSコンソールで以下のように入力する必要があります。

image

このスクリプトを再度呼び出したいときや、別のスクリプトから呼び出したいときにも、同様にスクリプト hello.ps1 ごと呼び出す必要があります。

もちろんそれでもよいのですが、もうすこし使い勝手を良くするには、これを関数化します。

関数化するには、以下のように Function ブロックで括ります。

Function Echo-Something {
    param($a="Hello!")
    Write-Output $a
    }

同じファイル名 hello.ps1 で保存し、以下のように ドット ソース モード(Part3 参照)で実行すれば、スクリプト名を指定することなく、「Echo-Something」という名前で呼び出すことが可能です。

image 

ちなみに、ドット ソース でスクリプトを実行したあと、以下のように Function: ドライブに移動してみてください。Dir コマンドを使用すると、Echo-Somethig 関数が登録されていることが確認できます。

image

じゃ、Echo-Something 関数を呼び出すために毎回ドット ソース モードでスクリプトを呼び出す必要があるのかといえば、そうではありません。

関数を永続化する方法がいくつかあります。

もっとも簡単なのは、PowerShell のプロファイルスクリプトに 「 . c:\tmp\hello.ps1」を記述しておく方法です。

PowerShell コンソールが起動するたびに実行されるので、毎回 hello.ps1 を実行する手間が省けます。

話が複雑になるので要点だけ書くと、プロファイル用スクリプト(の一つ)は以下の通りです。

C:\Users\<ユーザー名>\Documents\WindowsPowerShell\profile.ps1

プロファイルについては改めて別の投稿で解説します。

(参考)Understanding and Using PowerShell Profiles
http://blogs.technet.com/b/heyscriptingguy/archive/2013/01/04/understanding-and-using-powershell-profiles.aspx

ってことで、関数については理解していただけましたでしょうか?

 

では、解説をはじめましょう。 あー憂鬱だ。。。

PowerShell コマンドレットの構造はどれですか

  1. コマンド ライン – ステートメント
  2. コマンド –メッセージ
  3. 名詞 - 動詞
  4. 動詞 - 名詞

Windows PowerShell のコマンドレットの構造にはキマリがあります。それは、かならず「動詞-名詞」という形になっているということです。

コマンドレットの一覧を参照するには Get-Command コマンドレットを使用しますが、これも「動詞ー名詞」という構造になっていることがわかります。

以下はコマンドの例です。コマンドレット名を見ると、およそ何をしたいコマンドなのかが予測できるようになっています。

Function        Update-StorageProviderCache                        Storage               
Function        Write-DtcTransactionsTraceSession                  MsDtc                 
Function        Write-PrinterNfcTag                                PrintManagement       
Cmdlet          Add-ADCentralAccessPolicyMember                    ActiveDirectory       
Cmdlet          Add-ADComputerServiceAccount                       ActiveDirectory       
Cmdlet          Add-ADDomainControllerPasswordReplicationPolicy    ActiveDirectory       
Cmdlet          Add-ADFineGrainedPasswordPolicySubject

逆に、自分でコマンドレットを作成する場合にもこの規則に沿って命名することをお勧めします。

例えば、「新入社員を人事データベースから抽出する」コマンドを作成するのであれば、その名称は Get-FreshmanFromHRDB とでもすれば、他の方がこのコマンドを呼び出すときにも便利です。

高度な関数の用途はどれですか

  1. コンパイル済みコマンドレットを作成する
  2. コマンドレットと同様に動作する、再利用可能なスクリプトを作成する
  3. 常駐型のメモリ内呼び出しを行う
  4. モジュールの作成を自動化する

「高度な関数」の「高度な」の意味が問われています。

答えは「2.コマンドレットと同様に動作する、再利用可能なスクリプトを作成する」ことです。

スクリプトでコマンドレット(のようなもの)を定義するという意味で、スクリプトコマンドレットと呼ばれることもあります。

では「コマンドレットと同様に」とはどういう意味でしょう?

単に「コンソールからコマンドレットのように呼び出せる」ということではありません。これだけならば、VBSript だって似たようなことができなくもありません。

バイナリで作成されたコマンドレットにはPowerShell特有のオプションが実装されています。

例えば、-confirm オプションがその1つです。

以下の例では Remove-Item コマンドレットを使用して sample フォルダ配下を削除しようとしていますが、-Confirm オプション指定することで、削除の確認メッセージが表示できます。

image

(参考)about_Functions_CmdletBindingAttribute
http://technet.microsoft.com/en-us/library/hh847872.aspx

ただの関数であれば、このような機能を実装するには関数内に別途スクリプトを記述する必要がありますが、「コマンドレット」と同様に動作させることで、コマンドレットが本来持っている機能を関数にも実装できるのです。じゃ、関数を高度化するにはどうすればよいかについては後述します。

PowerShell ISE でスクリプト テンプレートを簡単に呼び出すキーボード コマンドはどれですか

  1. Ctrl+Z
  2. Ctrl+I
  3. Ctrl+J
  4. Ctrl+T

正直、私はあまり使わないのですが、PowerShell ISE(統合スクリプト環境。要はインテリジェントなエディターです)を使用すると、事前に定義されている構文テンプレートを呼び出すことができます。

そのときに使用するショートカットが Ctrl + J です。Ctrl +J を押すと、以下のような画面が表示されるので、ここから使いたいテンプレートを選択します。

image

上記の中に「Cmdlet(高度な関数)」「Cmdlet(高度な関数)- 完了」という2つのテンプレートがありますが、これが関数を定義する際のテンプレートです。

完了」と書かれているのは、おそらく翻訳ミスで「完全」のことだと思われます。。。ほんとすんまそん。。。

つまり、「簡易版テンプレート」と「完全版テンプレート」という意味なのでしょう。実際、中身もそうなっています。

簡易版テンプレートを選択すると、以下のようなスクリプトが展開されます。

<#
.Synopsis
   短い説明
.DESCRIPTION
   詳しい説明
.EXAMPLE
   このコマンドレットの使用方法の例
.EXAMPLE
   このコマンドレットの使用方法の別の例
#>
function Verb-Noun
{
    [CmdletBinding()]
    [OutputType([int])]
    Param
    (
        # パラメーター 1 のヘルプの説明
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        $Param1,

        # パラメーター 2 のヘルプの説明
        [int]
        $Param2
    )

    Begin
    {
    }
    Process
    {
    }
    End
    {
    }
}

スクリプトを単純に Function ブロックで括っても関数化することはできますが、上記を見ると他にも様々なブロックが用意されていることがわかります。

それぞれのブロックについての説明は、後の問題で。

PowerShell のドキュメントに使用するコメント ブロックを定義するにはどうすればよいですか

  1. "<!--" と "-->" でブロックを囲む
  2. "\*" と "*/" でブロックを囲む
  3. "<#" と "#>" でブロックを囲む
  4. "<comment>" と "</comment>" でブロックを囲む

前問のテンプレートにも書かれている通り、コメントは <# ~ #> で括ります。

1行だけならば、先頭に # をつけるだけでOKです。

正誤問題: PowerShell ISE には、関数やスクリプトの作成に推奨されるプラクティスやスタイルに従ったテンプレートが用意されている

既に書きましたね。もちろん○です。

正誤問題: 高度な関数は、C# と .NET Framework を使用してのみ作成できる

これも大丈夫ですよね。×です。

C#やその他の言語を使用すればバイナリーなコマンドレットを作成することができますが、スクリプトだけでもコマンドレットのように扱える「高度な」関数を作成することができます。

関数における CmdletBinding 属性の機能は何ですか

  1. 任意の実行ポリシー コンテキストで関数を実行する
  2. ドット コンテキストで関数を読み込む
  3. 関数の動作をコンパイル済みコマンドレットのようにする
  4. 関数を管理者コンテキストに昇格する

数問前で「高度な関数」とは「コマンドレットのように動作する関数」であると書きましたが、それを実現しているのが関数内に定義する「CmdletBinding」属性です。

この属性が定義されていると、バイナリのコマンドレットが持っているようなオプションを有効化できます。

その一つが -Confirm であることは既に書いた通りです。他にも CmdletBinding によって有効化できる機能がありますので、紹介しておきます。

CmdletBinding 属性のいくつかの機能を有効にした関数例を以下に示します。

function Echo-Something
{
    [CmdletBinding(
        ConfirmImpact="High",
        SupportsShouldProcess=$True,
        DefaultParameterSetName="Set2"
        )]

    Param
    (
        [Parameter(Mandatory=$True,ParameterSetName="Set1")]
        [string[]]$Param1,

        [Parameter(Mandatory=$True,Position=1,ParameterSetName="Set2")]
        [string[]]$Param2,
        [Parameter(Mandatory=$True,Position=2,ParameterSetName="Set2")]
        [string[]]$Param3
    )

    Begin
    {
    }
    Process
    {
        if ($PSCmdlet.ShouldProcess("ほげほげ")) {
            Write-Output "1 $Param1"
            Write-Output "2 $Param2"
            Write-Output "3 $Param3"
            }
    }
    End
    {
    }
}

SupportsShouldProcess = $True は -Confirm と -Whatif オプションを有効にするための設定です。

これにより、関数の実行時に以下のように確認メッセージを表示することができます。

image

DefaultParameterSetName はとても使い勝手の良い機能で、複数の引数パターンのうち既定のパターンをどれにするか指定できます。

Paramブロックの中を見てください。ParameterSetName という属性が定義されていることがわかります。これは、引数がどのパターンに属するのかを示したものです。

今回は DefaultParameterSetName = ”Set2” と指定しているので、引数を何も指定しなければ以下のように パターン Set2 を使うものとして、値を聞いてくれます。

image

このほか、CmdletBinding を定義するだけで以下の共通オプションが使えるようになります。これらにより、デバッグに便利な Write-Debug コマンドや Write-Verbose コマンドも使えるようになるので、関数を定義するときには CmdletBinding を定義しておくことをお勧めします。

  • -Verbose

  • -Debug

  • -WarningAction

  • -WarningVariable

  • -ErrorAction

  • -ErrorVariable

  • -OutVariable

  • -OutBuffer

(参考)Build Your Own PowerShell Cmdlet: Part 4 of 9
http://blogs.technet.com/b/heyscriptingguy/archive/2012/10/02/build-your-own-powershell-cmdlet-part-4-of-9.aspx

関数における Process ブロックの目的は何ですか

  1. 受け取った値を反復処理するたびに実行する
  2. パイプライン入力に関係なく、1 回だけ実行する
  3. 文字列以外の値が入力された場合のみブロックを実行する
  4. 文字列値が入力された場合のみブロックを実行する

答えは「1. 受け取った値を反復処理するたびに実行する」なのですが、唐突に「反復処理」と言われても意味が分からないですよね。

「反復処理」とは、「受け取った引数の数」だけ実行されるということです。「引数の数」というのは Param ブロックで定義した引数の数ではありません。

Windows PowerShell ではパイプを使って、前のコマンドの結果をオブジェクトとして繰り返し受け取ることができます。

例えば Get-Service というコマンドレットがあります。このコマンドレットの出力結果には、システム上の個々のサービスの情報が格納されています。以下は、Get-Service コマンドレットの出力結果例です。

image

これを踏まえ、以下のようなコマンドを実行したとします。

Get-Service | Echo-Something

これにより、Get-Service の結果が1回ずつ(上の出力結果例の1行ずつ)Echo-Something に引数として渡されます。

結果が Echo-Something に渡されると、そのたびに Process ブロック内の処理が実行されます。

例を示しましょう。以下のような関数を定義したとします。

function Echo-Something
{
    [CmdletBinding()]
    Param
    (
        [Parameter( ValueFromPipeline =$true)]
        $Param1
    )

    Begin
    {
        "----Begin----"
    }
    Process
    {
        $Param1.Name
    }
    End
    {
        "----End----"
    }
}

Process ブロック内では、引数として渡されたオブジェクトの Name プロパティを表示しています。

以下のようにして、この関数を実行してみます。

image

結果は一目瞭然ですね。

Begin ブロック内が一番最初に1回だけ実行されます。

その後、パイプラインから渡された結果が繰り返し Process ブロックに渡されて処理されています。

最後に End ブロックが1回だけ実行されて、処理は終了します。

関数における Begin ブロックの目的は何ですか

  1. パラメーターを受け取る
  2. 変数を初期化する
  3. Process ブロックと同じ回数実行する
  4. Process ブロックの実行をスキップする

前問の結果から、もうわかりますよね。答えは「2. 変数を初期化する」です。

上の例では何もしていませんが、本当はここで引数の値を確認したり、変数の初期値を設定したりします。

関数における End ブロックの目的は何ですか

  1. Process ブロックの実行をスキップする
  2. Process ブロックの実行を制限する
  3. クリーンアップ タスクを実行する
  4. パラメーターの使用を制限する

これもOKですね。

「3. クリーンアップ タスクを実行する」が正解です。

Begin ブロックで用意した変数を初期化したり、ファイルをクローズするなどの処理はここで行います。

ーーー

以上で終わりなのですが、ちょっと中途半端なのでパラメタ(引数)の扱いについてもう少し解説しておきたいと思います。

以下のように Echo-Something 関数を定義したとします。

function Echo-Something
{
    Param
    (
        [Parameter( Mandatory=$True,
                    Position=1,
                    ValueFromPipeline=$true)]
        [string]$Param1,

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

        [Switch]$EchoString
    )

    Begin
    {
    }
    Process
    {
            If($EchoString){
            Write-Output $Param1
            Write-Output $Param2
        }
    }
    End
    {
    }
}
 

Parameter 部分に注目してください。

この関数には3つの引数が定義されており、両方とも Mandatory=$True と指定されています。つまり、Param1 と Param2 は必須ということです。

なので、パラメタを指定せずに Echo-Something を実行すると、以下のように入力を求められます。

image

Position=1 というのは、引数として受け取る順番を意味しています。つまり「1番目の引数は Param1 用である」ということが宣言されています。

一方で、Param2 には Position が設定されていないため、引数は必ず名前付きで指定する必要があります。

つまり以下のように。

image

[Switch] として定義されている3つ目の引数 $EchoString はとても素敵な機能です。

以下のように、関数のパラメタとして指定された場合には、$EchoString には $True が格納されます。指定されない場合には $False が格納されます。つまり、この値の中身を判断することで、処理のオン/オフを切り替えられるわけです。

image

詳しくはまた別の機会に。

(参考)Windows PowerShell: パラメーターを定義する
http://technet.microsoft.com/ja-jp/magazine/jj554301.aspx

Comments (1)

  1. Anonymous より:

    Blogs - フィールドSEあがりの安納です - Site Home - TechNet Blogs

Skip to main content