PowerShellクラスの始め方

PowerShellはオブジェクト指向の言語です。コマンドを実行すると、その出力はオブジェクトとして表示されます。オブジェクトは空気から生じるものではありません。開発者が作成し、具体的にはクラスを使用してインスタンス化します。PowerShellのクラスは、それらのオブジェクトの定義またはスキーマを表します。

おそらく、New-Objectのようなコマンドを使用してオブジェクトを作成し、pscustomobjectタイプ アクセラレータを使用する方法には馴染みがあるかもしれませんが、これらは「新しい」オブジェクトではありません。これらのメソッドが生成するオブジェクトの種類は特定のタイプです。PowerShellのクラスはタイプを定義します。

このチュートリアルでは、PowerShellのクラスの基本を学びます。コンストラクタを持つ最初のクラスを作成し、そのクラスからオブジェクトを作成し、プロパティやメソッドでクラスをカスタマイズします。

オブジェクト、プロパティ、メソッドなどの用語については、ブログ投稿「基礎に戻る:PowerShellオブジェクトの理解」を参照してください。

前提条件

最初のクラスとオブジェクトの作成

PowerShellのクラスの詳細を学ぶ前に、まずはシンプルなクラスの作成を進めてみましょう。より高度なトピックについては後で学びます。

最初のクラスを作成すると、関数を作成する感じになります。基本的な構文は同じです。クラスは、関数と同様に定義から作成されます。ただし、最初の行はfunctionではなく、classで始まり、オブジェクトの型の名前が続きます。

以下には、studentというクラスの基本的な構造が示されています。

class student {

}

クラスには、そのクラスを説明する属性であるパラメータのようなプロパティがあります。以下の例では、studentというクラスにはFirstNameLastNameという2つのプロパティがあります。

プロパティを定義する際には、プロパティの値が保持できる具体的な「スキーマ」を設定する型を常に定義するべきです。以下の例では、両方のプロパティを文字列として定義しています。

プロパティの型を常に定義するべきです。後でその理由がわかります。

class student {
    [string]$FirstName
    [string]$LastName
}

クラスを定義したら、それからオブジェクトを作成するか、インスタンス化します。クラスからオブジェクトを作成する方法は複数ありますが、よく使われる方法の一つは、クラスを表すタイプアクセラレータ(例:[student])を使用し、それに続くクラスに付属するデフォルトのメソッドであるnew()を使用することです。

タイプアクセラレータのショートカットを使用することは、New-Objectコマンドを使用してオブジェクトを作成することと同じです。

New-Object -TypeName student

そのクラスからオブジェクトを作成したら、プロパティに値を割り当てます。以下の例では、TylerMuirFirstNameLastNameのプロパティに割り当てています。

class student {
    [string]$FirstName
    [string]$LastName
}
$student1 = [student]::new()
$student1.FirstName = 'Tyler'
$student1.LastName = 'Muir'
$student1

オブジェクトを作成し、プロパティに値を割り当てたら、以下のように割り当てた変数を呼び出してオブジェクトを検査します。

Creating an object from the student class.

クラスからオブジェクトを作成したので、PowerShellの他のオブジェクトと同様に、そのオブジェクトをGet-Member コマンドレットにパイプで渡して検査します。以下の例では、$student1変数に保持されているオブジェクトの型はstudentです。

クラス名は常にオブジェクトの型と関連しています。

Get-Memberが4つのメソッドと2つのプロパティを返すことに注目してください。プロパティはおそらく馴染みがあるかもしれませんが、メソッドはそうではないでしょう。PowerShellはデフォルトで特定のメソッドを追加しますが、独自のメソッドを追加したり、デフォルトのメソッドを変更したりすることもできます。

Showing members from custom student object.

メソッドの作成

前述の例では、オブジェクトにいくつかのデフォルトのメソッドがあることがわかりましたが、おそらく独自のメソッドを作成したいと思うでしょう。そのためには、クラス定義の内部に1つ以上のメソッドを定義する必要があります。

A method definition looks like below with an output type that defines what type of object is returned from this method, the name of the method, and the code to execute inside of a scriptblock.

[<output type>]<name>() {
	<code that runs when the method is executed>
}

名前の後ろにあるかっこ()に注目してください。ここで、メソッドパラメータ(後述)を定義することができます。メソッドパラメータを使用すると、関数パラメータと同様にメソッドの機能を変更することができます。

PowerShell関数を以前に書いたり実行したことがある場合、メソッドのスクリプトブロックは馴染みがあるはずですが、メソッドにはいくつかの特別なルールがあります。

returnは必須です。

PowerShell関数では、オブジェクトを単に関数内の任意の場所に配置するだけで返されます(以下の例を参照)。

function foo {
	$object = Get-Service
	$object ## オブジェクトをパイプラインに送信するだけ
}

ただし、関数とは異なり、メソッドがオブジェクトを返す場合、以下に示すようにreturn構文を使用する必要があります。

[string]GetName() {
	return 'foo'
}

$this変数の使用

メソッドには、関数とは異なり、$this変数があります。メソッド内で定義された$this変数は、現在のオブジェクトのプロパティや他のメソッドを参照します。

以下は、studentクラスに追加されたGetName()メソッドの例で、FirstNameLastNameの値を連結して返します。

class student {
    [string]$FirstName
    [string]$LastName
    
    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
}

これで、以下のようにドット表記を使用してGetName()メソッドを呼び出すことができます。以前にFirstNameLastNameに値を割り当てた場合、GetName()はそれらを返します。

Creating a class with a method and showing to output.

メソッドにパラメータを追加する

上記の例では、$student1.GetName()という行を実行すると、GetName()メソッドがそのまま呼び出されます。カッコ内では、関数と同様にパラメータを定義することができます。

GetName()メソッドは、FirstNameLastNameプロパティに設定された値を返します。しかし、GetName()取得できるプロパティを設定するメソッドがあればどうでしょうか?その場合、メソッドのパラメータを定義する必要があります。

メソッドのパラメータは、メソッドのパラメータの括弧内に、コンマで区切られた1つ以上のパラメータを含めることで定義します。以下に示すように。

[void]という出力タイプに注目してください。メソッドが何も出力しない場合、return構造を使用する必要はなく、出力タイプを[void]として定義することで、PowerShellにメソッドが何も返さないことを伝えます。

[void]SetName([string]$Name) {

}

例えば、SetName()メソッドがフルネーム(名前と姓)を受け入れる場合、スクリプトブロック内でこの文字列を分割し、それによって名前と姓を割り当てることができます。

SetName()メソッドをstudentクラスに挿入すると、以下のようになります。

class student {
    [string]$FirstName
    [string]$LastName
    
    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
    
    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }
}

これで、パラメータとしてフルネームをSetName()メソッドに渡すことができ、現在のオブジェクトのFirstNameLastNameプロパティが設定されます。

Creating a class with a void method and showing the output when it is run.

メソッドのオーバーロード

おそらく、メソッドに異なるパラメータセットを定義したいと思うかもしれません。関数やコマンドレットでパラメータセットが機能する方法と似ていますが、異なるパラメータの「コンテキスト」またはメソッドのシグネチャを定義することができます。

おそらく、FirstNameLastNameのパラメーターをSetName()メソッドにフルネームで渡すか、名前を個別に渡すことができるでしょう。選ぶ必要はありません。両方をメソッドのシグネチャで定義することができます。

クラス内で複数のメソッドシグネチャを定義することをオーバーロードと呼びます。

前のセクションの例を再利用して、SetName()メソッドに1つではなく2つの文字列を受け入れるオーバーロードを作成することができます。1つではなく2つの文字列を渡すと、SetName()メソッドは最初の文字列をFirstNameとし、2番目の文字列をLastNameとします。そのオーバーロードを使用すると、クラスは以下のようになります。

class student {
    [string]$FirstName
    [string]$LastName
    
    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
    
    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }
    
    [void]SetName([string]$FirstName,[string]$LastName) {
        $this.FirstName = $FirstName
        $this.LastName = $LastName
    }
}
Showing that the method overload works by providing either one or two strings.

クラスのコンストラクタ

new()メソッドまたは他の方法でオブジェクトをインスタンス化するときに、PowerShellにユーザー定義のコードであるコンストラクタを実行するように指示することができます。コンストラクタはメソッドのようなものですが、PowerShellがオブジェクトをインスタンス化する際に自動的に実行されます。

すべてのクラスにはデフォルトのコンストラクタがあります。このデフォルトのコンストラクタはあまり多くのことを行いません。オブジェクトのインスタンス化を担当するだけです。Newメソッドの出力を表示することでデフォルトのコンストラクタを確認できます。以下の行は単一のnew()メソッドを返します。

[student]::New
Default PowerShell constructor

コンストラクタのオーバーロード

おそらく、オブジェクトを作成するときに、通常のドット表記ではなく、FirstNameLastNameのプロパティに値を設定したいと思うかもしれません。その場合、SetName()を呼び出すパラメータを持つコンストラクタを作成することができます。

以下はstudentクラスのコンストラクタの例です。コンストラクタには特定の名前や出力タイプの前置詞はありません。コンストラクタは常にクラスと同じ名前を使用します。

コンストラクタ内で既存のメソッドを呼び出すことで、既に書かれた変数の設定を再利用することができます。

student([string]$Name) {
	$this.SetName($Name)
}

そして、studentクラスにそのコンストラクタを追加したのが以下です。

class student {
    [string]$FirstName
    [string]$LastName
    
    student([string]$Name) {
        $this.SetName($Name)
    }

    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
    
    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }

    [void]SetName([string]$FirstName,[string]$LastName) {
        $this.FirstName = $FirstName
        $this.LastName = $LastName
    }
}

新しいstudentオブジェクトをインスタンス化し、文字列パラメータを渡すと、オブジェクトのプロパティにはすぐに予想される値が設定されます。

Showing the output from using the constructor with a string

また、[student]::Newでオーバーロードされたコンストラクタを再度確認することができます。この場合、Nameパラメータで定義された新しいオーバーロードされたコンストラクタに注目してください。

Overloaded constructor

デフォルトとオーバーロードされたコンストラクタの定義

これでstudentクラスにオーバーロードされたコンストラクタがあるので、PowerShellはデフォルトのコンストラクタを上書きします。ただし、パラメータのないコンストラクタを手動で作成することで、デフォルトのコンストラクタを取り戻すことができます。

student() {}

以下はstudentクラスのデフォルトコンストラクタの例です。

class student {
    [string]$FirstName
    [string]$LastName

    student([string]$Name) {
        $this.SetName($Name)
    }

    student() {}

    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }
}

コンストラクタを再度確認してください。今度は両方のコンストラクタが表示されます。

[student]::New
Creating a default and custom constructor

クラスの継承

すべての他のオブジェクト指向言語と同様に、複数のクラスで階層的にPowerShellクラスを構築することができます。各クラスには「親」クラスと「子」クラスがあり、具体的でない、より一般的な目的から始まり、特化度を高めていきます。

例えば、私たちのstudentクラスは大学生(子/具体的)を表しています。その大学生は人(親/一般的)です。これらの2つの概念は関連しており、階層を形成しています。

A child class can inherit a parent class which means it can hold all properties and methods (members) defined via a parent class. We know that a person class may have properties like eye_color, height, and weight and perhaps a method called SetHeight().

学生が人である場合、その学生には依然としてそのプロパティとメソッドがあります。すでにpersonクラスに存在するこれらのメンバーをstudentクラスにも実装すると、重複した作業になります。クラスの継承を定義することで、studentクラスにpersonクラスのすべてのメンバーを自動的に定義することができます。

これが現時点では理解できない場合は、デモを実施する過程で理解できるようになります。

クラスの継承デモ

まず、以前に作成したstudentクラスのコピーを作成し、コンストラクタを削除してpersonクラスに名前を変更します。以下のようにpersonクラスが表示されるはずです。

A student, of course, has a first name and last name, but the class can be described more accurately by labeling it as a person. When creating a more “generic” class like this, you can create more specific “child” classes from it.

class person {
    [string]$FirstName
    [string]$LastName
    
    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
    
    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }

    [void]SetName([string]$FirstName,[string]$LastName) {
        $this.FirstName = $FirstName
        $this.LastName = $LastName
    }
}

次に、人を表すいくつかのクラスを作成しますが、より具体的な役割を持ちます。たとえば、以下のコードスニペットでは、teacherクラスとstudentクラスが作成されます。

class teacher {
    [int]$EmployeeId
}

class student {
    [int]$StudentId
}

現状では、teacherクラスとstudentクラスはpersonクラスとは相関関係がなく、personクラスのクラスメンバーを継承することはできません。それを変更しましょう。

今、teacherstudentクラスをpersonクラスの「子」クラスとして、継承によって階層を定義します。以下のように、クラス名の後にコロン(:)を追加し、親クラスの名前を続けて記述することで継承を定義できます。

class teacher : person {
    [int]$EmployeeId
}

class student : person {
    [int]$StudentId
}

以下は、クラスのスクリプト全体です。

class person {
    [string]$FirstName
    [string]$LastName
    
    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
    
    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }

    [void]SetName([string]$FirstName,[string]$LastName) {
        $this.FirstName = $FirstName
        $this.LastName = $LastName
    }
}

class teacher : person {
    [int]$EmployeeId
}

class student : person {
    [int]$StudentId
}

この時点で、teacherクラスまたはstudentクラスからオブジェクトをインスタンス化すると、どちらのクラスもpersonクラスと同じメンバーを持ちます。

Class Inheritance Demo

コンストラクタを継承する

先ほど見たように、クラスメソッドはクラス継承によって継承されますが、コンストラクタは継承されないため、子クラスごとに個別に定義する必要があります。

たとえば、以下のようにpersonクラスにオーバーロードされたコンストラクタを定義したが、teacherクラスにはコンストラクタを定義していない場合を考えてみます。

class person {
    [string]hidden $FirstName
    [string]$LastName
    
    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
    
    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }

    [void]SetName([string]$FirstName,[string]$LastName) {
        $this.FirstName = $FirstName
        $this.LastName = $LastName
    }

		person([string]$Name) {
			$this.SetName($Name)
		}
}

次に、teacherクラスのような子クラスを定義し、パラメータを持たないオブジェクトを作成しようとすると、以下のようにエラーが返されます。なぜなら、teacherクラス内にパラメータなしのコンストラクタが定義されていないからです。

No class defined for child class

A constructor is not necessary in a child class if you’re only using it as a template. Alternatively, if you want to use the parent class as its own standalone class and as a parent class you can include constructors. But you have to make sure that the parent class has a constructor that matches the ones in the child classes.

クラスメンバー属性

PowerShellのコマンドパラメータと同様に、クラスにはメンバー属性を持つことができます。これらの属性は、各メンバーの動作を変更します。

非表示メンバー

クラスメンバーを内部でのみ使用し、ユーザーが読み取ったり書き込んだりできないようにする場合は、メンバーを非表示に設定します。たとえば、他のメソッドによってのみ使用されるメソッドがあるかもしれません。そのメソッドをユーザーに公開する必要はありません。

非表示メンバーを定義するには、次に示すようにhidden属性を使用します。

class teacher {
    [int]hidden $EmployeeId
}

これにより、Get-Memberを使用してすべてのオブジェクトメンバーを検査すると、そのプロパティは表示されません。

Class Member Attributes

クラスメンバーを非表示に設定することは、値へのアクセスを制限するものではありません。値を表示から非表示にするだけです。機密データを保存するためにプロパティを非表示にするべきではありません。

静的メンバー

以前に説明したように、このチュートリアルでは「インスタンス化」という用語を使用してクラスからオブジェクトを作成することを説明しました。オブジェクトをインスタンス化すると、そのオブジェクトはクラスが定義するすべてのプロパティとメソッドを継承します。しかし、常にそのような場合ばかりではありません。

場合によっては、オブジェクト全体をインスタンス化するオーバーヘッドは不要で、代わりに単一のクラスメンバーを迅速に参照する必要があります。その場合、クラスメンバーを静的に設定できます。

hidden属性と同様に、次に示すようにstaticキーワードを使用してクラスメンバーを静的に定義します。

class student {
	[int]static $MaxClassCount = 7
}

通常のクラスメンバーとは異なり、PowerShellは静的クラスメンバーからプロパティとメソッドを作成しません。クラスメンバーを静的に定義すると、非表示メンバーと同様にGet-Memberを使用しても表示されません。

Static Members

例えば、studentクラスに大学のクラス名を関連付け、学生が参加できる大学のクラスの最大数を定義したいとします。そのために、Classesの配列メンバーとMaxClassCountメンバーを作成します。

ユーザーはほとんどMaxClassCountメンバーを変更する必要がないため、それを静的に定義することにしました。

最後に、AddClass()メソッドを作成し、それがMaxClassCountより少ない場合にのみ、学生のスケジュールにクラスを追加します。

class student {
    [string[]]$Classes = @()
    [int]static $MaxClassCount = 7
    
    [void]AddClass ([string]$Name) {
        if ($this.Classes.Count -lt [student]::MaxClassCount) {
            $this.Classes += $Name
        }
    }
}

これで、新しいstudentオブジェクトを作成し、それに多すぎる大学のクラスを割り当てようとすると、PowerShellは最大数の7つだけを割り当てます。

$student1 = [student]::new()
'PE','English','Math','History','Computer Science','French','Wood Working','Cooking' | ForEach-Object {
	$student1.AddClass($_)
}
$student1.Classes
Class Count

静的メンバーの値はいつでも変更できます。例えば、MaxClassCountメンバーを7ではなく5にしたい場合は、[student]::MaxClassCount = 5というコードを使用して値を変更します。この例では、値の変更は制限を超えるクラスを逆に取り除くわけではありません。

結論

PowerShellのクラスは、スクリプト言語とプログラミング言語の境界を曖昧にします。クラスはオブジェクトの関係を定義し、通常は特殊な関数を書くことしかできないオブジェクトとの対話やフォーマットの方法を追加するための素晴らしい方法です。

Source:
https://adamtheautomator.com/powershell-classes/