PowerShell DSC実践活用(2)
本番で使えるPowerShell DSCリソース作成入門
Windows Serverの構成管理を自動化できるツール「PowerShell DSC」を使いこなそう。PowerShellでDSCのリソースを自作する方法とは?
PowerShell DSC(Desired State Configuration)では、標準で提供されているリソースだけでなく、独自のリソースも作成できる。本稿では、現在、リリースされているPowerShell v4でリソースを自作する方法を解説する。
リソースとは
前回の記事では、Configuration構文キーワードを用いて「構成を適用する流れ」を示した。コンフィギュレーション(Configuration)が「どんな構成を適用するかの宣言」だとすれば、リソース(Resource)は「実際に構成を適用するロジック」である。
コンフィギュレーションは、対応するリソースがあって初めて利用できるため、何かを構成したいときには、その構成を実現可能なリソースを用意する必要がある。逆にいえば、リソースさえあれば、コンフィギュレーションを書くだけで容易に利用可能だ。
既存リソースの利用
Windows 8.1やWindows Server 2012 R2には、標準でリソースが入っている(ビルトイン・リソース)。
また、ビルトイン・リソース以外にも、PowerShellチームが追加リソースを公開しており、コミュニティでもリソースを公開している(カスタム・リソース)。それらのカスタム・リソースで事足りる場合は、自作せずに利用することを勧めたい。
実施したい構成を可能とするリソースがなかったときが、リソースを書くときになるだろう。一見、リソースを書くのは難しそうに見えるかもしれないが、容易に書けるので安心してほしい。今回は、DSCリソースを実際に自作する流れを説明しよう。
リソースについての予備知識
リソースを書くときにいくつか押さえておくべきことがあるので、先に説明する。
リソースで実装される関数
リソースには、大きく3つの機能が所定の関数名で実装されている必要がある。
- 正しく構成されているかをテスト:
Test-TargetResource
- 構成を適用:
Set-TargetResource
- 現在の構成状態を取得:
Get-TargetResource
リソースが公開する関数はこの3つだけで、リソース内部ではあとは自由に関数などを記述可能だ。
冪等性(べき等性)
「PowerShell DSCは冪等性を備えている」といわれることが多いが、その冪等性はリソースが担保している。DSCが自動的にすでに構成済みかどうかを判断して冪等性を担保するわけではない。リソースを自作するときは、冪等性を担保するように考える必要がある。
具体的には、Test-TargetResource
関数の作りが冪等性に直結するため、リソースを書くときに最も気を遣うべきだろう。
リソースの構成
リソースは、所定のフォルダー構造である必要がある。またリソースは、MOF(Managed Object Format)といわれるスキーマ構造とPowerShellモジュールを結び付ける必要がある。
とはいえ、面倒くさいことはなく、PowerShellチームから公開されているxDSCResourceDesignerを使えば容易に出力できるので安心してほしい。本記事でもxDSCResourceDesignerを使った手順を紹介する。
リソースの種類
ひと言にリソースといっても、(すでに少し説明したものもあるが)大きく3種類存在する。
- ビルトイン・リソース: Windowsに標準で組み込まれたリソース
- カスタム・リソース: 標準リソース以外で、構成ロジックを組み上げて作ったリソース
- コンポジット・リソース: 既存コンフィギュレーションを再利用したリソース
一般に「リソース」というと、ビルトイン・リソースとカスタム・リソースを指す。「では、コンポジット・リソースは?」というと、一度書いたコンフィギュレーションをリソースとして再利用する仕組みだ。
実際に筆者が作成し、本番環境で利用しているコンポジット・リソースがリスト1だ。ご覧の通り、普通のコンフィギュレーションと何ら変わらない。
configuration cWebPILauncher
{
param
(
[Parameter(Mandatory = $false, helpMessage = "Install and search from Reg by Get-DSCModuleProductId")]
[string]$ProductId = "4D84C195-86F0-4B34-8FDE-4A17EB41306A",
[Parameter(Mandatory = $false)]
[string]$InstallerUri = "http://go.microsoft.com/fwlink/?LinkId=255386"
)
function Get-RedirectedUrl ([String]$URL)
{
$request = [System.Net.WebRequest]::Create($InstallerUri)
$request.AllowAutoRedirect = $false
$response = $request.GetResponse()
$response | where StatusCode -eq "Found" | % {$_.GetResponseHeader("Location")}
}
$name = "Web Platform Installer"
$DownloadPath = Get-RedirectedUrl $InstallerUri
Package InstallWebPILauncher
{
Name = $name
Path = $DownloadPath
ReturnCode = 0
ProductId = $productId
}
}
|
コンポジット・リソースを使うシーンとして、そのコンフィギュレーションがさまざまなサーバーで共通して使われる場合に、「コンポジット・リソース」とすれば同じコンフィギュレーションを何度も書かずに済むため、便利だろう。
本記事ではカスタム・リソースの作成にのみ触れるが、リソースによってファイル構成が以下のように異なっている。
- ビルトイン/カスタム・リソース: .schema.mof(スキーマMOF)ファイル+.psm1(モジュール)ファイル
- コンポジット・リソース: .psd1(モジュールマニフェスト)ファイル+.psm1ファイル(=コンフィギュレーション)
カスタム・リソースのひな型を作成する
PowerShell v4では、C#かPowerShellのいずれかでDSCリソースを記述できる。ここではPowerShellを用いて簡単なリソースを作ってみよう。
なお、PowerShell v5が現在、プレビュー版として公開されている。v5では、リソース記述の効率化を主眼にして、クラス構文やDSC用の属性が導入される予定だ。プレビューだけあって、リリースごとに仕様が大きく変わっており、ここでの紹介は避けるが、v5が正式にリリースされた際にはより効率のいいリソースの記述方法を紹介したい。
ベストプラクティス
指針がない状況でリソースを作成するのは不安も大きいだろう。PowerShellチームからリソースを書くときのベストプラクティスが公開されているので、ぜひ参考にしてほしい。本記事でもこれを注釈に挟みつつ説明する。
作成するリソース
サンプルとして、非常にシンプルなリソースを作成してみよう。今回は、特定のフォルダーに対象ノードのOS情報をJSONフォーマットのファイルで出力するリソースを作成する。
あるべきJSONファイルに対しては、
- CIMクラス名(=
CimClass
)(詳細後述)が「OS」として適切に出力されていること - 出力されたJSONに記述されたOSのコンピューターシステム名が、そのノードの名前(=ホスト名)であること
をもって、「あるべき状態(Desired State)で出力されているか」を判定しよう。
ファイルの生成だけならビルトイン・リソースのFile
リソースで容易に作成できるのだが、あえて簡単なサンプルとして作成することとする。
では、リソースで必要な要素(=プロパティ)を考えてみよう。今回はシンプルに表1に示す2つのプロパティを設定可能にしよう。
プロパティ名 | 型 | 一意性 | 必須 | 概要 |
---|---|---|---|---|
Ensure | System.String | なし | あり | ファイルを存在させる(Present)、させない(Absent)の2種類から選択 |
Path | System.String | あり | あり | ファイルの出力先パスを指定する |
リソースには、同じリソースを用いても必ず一意に特定できるプロパティ(=「Key
属性」と呼ぶ)を1つ定義する必要がある。今回はPath
プロパティがKey
属性で(=[一意性]列を「あり」としている)、同一リソースを用いてもPath
は必ず重複しないことを前提に作成する。
リソースで利用するプロパティを決めたので、次に「スキーマMOFとモジュール」のひな型を作ってみよう。
xDSCResourceDesignerの導入
リソースを書くときの手間を大きく軽減してくれるのが、PowerShellチームから公開されているxDSCResourceDesignerモジュール(Module)だ。このモジュールを使うことで、リソースとMOFスキーマの整合性テストや、「スキーマMOFファイル(=.schema.mofファイル)とモジュールファイル(=.psm1ファイル)」のひな型の生成ができる。今回もひな型の生成に用いてみよう。
まずは、xDSCResourceDesinerモジュールをダウンロードしてほしい。
ダウンロードした.zipファイルを解凍して、モジュールフォルダーに配置すれば準備は完了だ(図1)。モジュールフォルダーには「%ProgramFiles%\WindowsPowerShell\Modules」と「%UserProfile%\Documents\WindowsPowerShell\Modules」の2種類があり、このどちらに配置しても構わない(※筆者は、「%UserProfile%」配下のモジュールフォルダーに配置することで、モジュールの利用者をユーザーに限定している。「%ProgramFiles%」配下のモジュールフォルダーにした場合は全ユーザーで利用できるので、用途に応じて使い分けてほしい)。
このダウンロードから配置までの処理は、もちろん自動化可能だ。筆者も実際は自動化している。しかし、MicrosoftスクリプトセンターはxDSCResourceDesignerのバージョンが変わるたびに参照先が変わるため、自動化の方法の説明については割愛する。
さて、v5からは(Microsoftスクリプトセンターではなく)PowerShell Gallery(現在、限定プレビューとして利用可能)に公開されたモジュールは、Linuxのapt-get
コマンドのように、Install-Module
という1つのCmdlet(コマンドレット)で簡単にインストール可能になる。実際に試すと、現在は限定的な利用となるが、モジュールのインストールが各段に容易に可能となっているのが確認できる。もしWindows Management Framework 5.0 Previewの最新版をインストールしているならば、管理者として起動したPowerShellでリスト2のコマンドを試してほしい(執筆時点でNovember Previewが最新)。
Install-Module xDSCResourceDesigner -Scope AllUsers
|
このコマンド1つで、xDSCResourceDesignerが「%ProgramFiles%」配下のモジュールフォルダーにインストールされる(図2)。
xDSCResourceDesignerを利用したひな型の生成
xDSCResourceDesignerを導入したら、PowerShell ISEを管理者として起動してほしい(図3)。これは、xDSCResourceDesignerが管理者として実行を求めるためだ。
起動したら、リスト3を実行してモジュールを読み込もう。
Import-Module xDSCResourceDesigner
|
それでは、xDSCResourceDesignerを使って、コンフィギュレーションで利用する各種プロパティを定義して、.schema.mofファイルと.psm1ファイルのひな型を生成する。
リスト4は、表1で定義したプロパティに基づいてプロパティ名と属性を定義し、モジュール名を「SampleResource」、リソース名を「cOSInfoJson」として、(スキーマMOFとモジュールの)ひな型を出力するコードだ。
Import-Module xDSCResourceDesigner
$property = @()
$property += New-xDscResourceProperty -Name Ensure -Type String -Attribute Required -ValidateSet Present, Absent
$property +=New-xDscResourceProperty -Name Path -Type String -Attribute Key
$param = @{
ModuleName = "SampleResource"
Name = "cOSInfoJson"
FriendlyName = "cOSInfoJson"
Property = $property
Path = "$env:ProgramFiles\WindowsPowerShell\Modules"
}
New-xDscResource @param
|
コードを簡単に説明しよう。
New-xDscResourceProperty
コマンドレットは、コンフィギュレーションで利用するプロパティを生成してくれる。-Name
パラメーターでプロパティ名、-Type
パラメーターでプロパティの型、-Attribute
パラメーターでスキーマMOFの属性、-ValidateSet
パラメーターで利用可能な文字列セットを与える。他にも-Description
パラメーターでスキーマMOF中にプロパティの概要を記述することもできる。
-Attribute
パラメーターについてもう少し説明しよう。設定可能な値は、Key
、Required
、Write
、Read
の4種類だ。それぞれ、一意性を持つプロパティにはKey
、必須性を持つプロパティにはRequired
、コンフィギュレーションで設定可能なプロパティにはWrite
、コンフィギュレーションには露出しないが状態取得時にのみ露出するプロパティにはRead
を与えてほしい。
リスト4のコードを実行すると、指定したパスに(.schema.mofファイルと.psm1ファイルの)ひな型が生成される(図4)。今回指定したパス「$env:ProgramFiles\WindowsPowerShell\Modules」は、カスタムDSCリソースを設置するパスでもある。自作、あるいは公開されているリソースは、ノードの当該パスに配置することでリソースとして利用できるようになる。
生成されたツリー構造を見てみよう(図5)。(param
オブジェクトの)ModuleName
に与えた「SampleResource」と同名のフォルダー配下に、「DSCResources」フォルダーが生成されている。そのフォルダーの配下を見ると、各種リソースは、Name
で与えた「cOSInfoJson」と同名のフォルダーの中に、「cOSInfoJson.schema.mof」ファイルと「cOSInfoJson.psm1」ファイルとして生成されたことが分かるだろう。
カスタム・リソースは、必ず「<リソース名>.psm1」ファイルと「<リソース名>.schema.mof」ファイルのセットになっている。
「<リソース名>.schema.mof」がMOFファイルの定義だが、xDSCResourceDesignerで生成した場合は、直接編集する機会はまずないだろう。リスト6は、今回デザイナーから生成されたcOSInfoJson.schema.mofファイルの内容だ。
[ClassVersion("1.0.0.0"), FriendlyName("cOSInfoJson")]
class cOSInfoJson : OMI_BaseResource
{
[Required, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
[Key] String Path;
};
|
「<リソース名>.psm1」は、実際にどのように動作させるかのロジックを記述するためのモジュールファイルだ。ここに冪等性を担保するように動作ロジックを記述していく。
リソース内部関数の利用タイミング
さっそく、生成されたcOSInfoJson.psm1ファイルを開いてみよう。PowerShell ISE上からは、リスト5のようにpsedit
関数を使って別タブにファイルを開くことができる。
psedit "$env:ProgramFiles\WindowsPowerShell\Modules\SampleResource\DSCResources\cOSInfoJson\cOSInfoJson.psm1"
|
.psm1ファイルを見てみると、すでにxDSCResourceDesignerで示したプロパティに沿ってリソースのひな型が作成できていることが分かる。
ひな型として.psm1ファイルに生成されたGet-TargetResource
/Set-TargetResource
/Test-TargetResource
は、DSCの動作で利用される関数だ。DSCのライフサイクルの中で、それぞれの関数がどのように利用されるかを説明しよう。
Get-TargetResource
関数は、Get-DSCConfiguration
コマンドレットで「ノードの現在の状態を取得する」ために呼び出される。そのため、Get-TargetResource
関数は実質的にノード状態のテストを行っていることになる。戻り値は[System.HashTable]
型で、スキーマMOFのKey
やRequired
に指定したプロパティは必ず返却される必要がある。Write
やRead
に関しては任意だが、筆者は極力返却するように書いている。
Set-TargetResource
関数は、コンフィギュレーションを適用するときに実行される内容だ。まさに状態をあるべき状態にする操作が記述される。あるべき状態にすることを書けばいいので最も記述しやすいだろう。戻り値は[System.Void]
型だ。
Test-TargetResource
関数は、現在のノード状態があるべき状態かどうかを判定するために実行される。戻り値は[System.Boolean]
型だ。コンフィギュレーションを適用するときにすでに適用済みかをテストする際と、Test-DSCConfiguration
コマンドレットで呼び出されるのがこの関数だ。Get/Set/Testで最も頻繁に呼ばれるため、最も軽い処理であることが望まれる。ベストプラクティスとして、Get-TargetResource
関数はテストを内包するため、Test-TargetResource
関数の内部ではGet-TargetResource
関数を単に呼び出すことが推奨されている。
リソースの記述
今回、リソースのEnsure
プロパティには「PresentかAbsent」のみが許容されるように定義した。リスト7のようにしてenum
(列挙体)を生成しておくと、Ensure
プロパティが扱いやすくなるので、お勧めだ。
# Enum for Ensure
Add-Type -TypeDefinition @"
public enum EnsureType
{
Present,
Absent
}
"@ -ErrorAction SilentlyContinue
|
(※2015/02/17 コードの一部を変更しました。詳しくは、本ページ最後の【更新履歴】をご参照ください。)
こうすることによって、Ensure
プロパティに指定すべき値を[EnsureType]::Present.ToString()
と呼び出せるため、文字列を直接書くことによる誤字を防ぎ、確実に返すべき値を取得できる。
現行のPowerShell v4では、enumの生成にC#コードをAdd-Typeコマンドレットでオンザフライにコンパイルする必要がある。v5からはEnum
キーワードが追加されてもっとシンプルに書けるようになるので楽しみにしていてほしい。(※2015/02/17追記)
Get-TargetResource関数の記述
それでは、Get-TargetResource
関数から書いていこう。Get-TargetResource
関数は、スキーマMOFファイルでKey
属性に指定したPath
プロパティと、Required
属性に指定したEnsure
プロパティを返す必要がある。
今回は、「JSONファイルが指定されたパスに存在しない場合」あるいは「存在していても、そのJSONデータにCIMクラス名とOSのコンピューター名が適切に入っていない場合(※いずれも詳細は後述する。その判定処理は、リスト9のIsCIMClassValid
/IsCSNameValid
関数で行っている)」は、Ensure
プロパティが「Absent」(=存在しない)として扱う。これは、リスト8のように記述できる。
function Get-TargetResource
{
[CmdletBinding()]
[OutputType([System.Collections.Hashtable])]
param
(
[parameter(Mandatory = $true)]
[ValidateSet("Present","Absent")]
[System.String]$Ensure,
[parameter(Mandatory = $true)]
[System.String]$Path
)
Write-Debug "Obtain Ensure status"
$Ensure = if (!(Test-Path -Path $Path))
{
Write-Verbose "Path not exist."
[EnsureType]::Absent.ToString()
}
else
{
Write-Debug "Read content and convert to JsonObject"
$jsonObject = GetJsonObject -Path $Path
Write-Debug "Check content is valid."
if ((IsCIMClassValid -JsonObject $jsonObject) -and (IsCSNameValid -JsonObject $jsonObject))
{
Write-Debug "Content detected valid."
[EnsureType]::Present.ToString()
}
else
{
Write-Debug "Content detected invalid."
[EnsureType]::Absent.ToString()
}
}
$returnValue = @{
Ensure = [System.String]$Ensure
Path = [System.String]$Path
}
return $returnValue
}
|
続いて、リソースで用いられている定石を簡単に説明する。
まず、Write-Verbose
コマンドレットとWrite-Debug
コマンドレットの使い分けだ。
コンフィギュレーションを使ってリソースを適用するとき、PUSHにおいてはStart-DSCConfiguration
コマンドレットに-Verbose
パラメーターを付けて途中経過を表示させながら実行することが常套(じょうとう)手段だ。そこで、より細かい状態遷移については、VerboseストリームではなくWrite-Debug
コマンドレットを使ってデバッグ実行時にのみ出力するように、PowerShellチームのリソースは組まれている。もちろん、伝えるべき進捗(しんちょく)はWrite-Verbose
コマンドレットを使ってVerboseストリームに出力するといいだろうが、Write-Debug
コマンドレット実行時により詳細の進捗が分かることは望ましいだろう。
今回、Get-TargetResource
関数で利用する、3つの関数「GetJsonObject」「IsCIMClassValid」「IsCSNameValid」を切り出した(リスト9)。この程度ならば、わざわざ関数として切り出さなくても問題ない。しかし、リソースの規模が大きくなってきたら、関数を分離して、その処理の影響範囲を関数内に封じ込めた方が制御しやすいだろう。今回は「ファイルが必ずJSON形式である」という前提で記述しているが、より正確に制御したければ、リスト9に示すようにGetJsonObject
関数で判定すればいいだろう。
function GetJsonObject
{
[CmdletBinding()]
[OutputType([PSCustomObject])]
param
(
[parameter(Mandatory = $true)]
[System.String]$Path
)
$json = Get-Content -Path $Path -Encoding UTF8 -Raw | ConvertFrom-Json
return $json
}
function IsCIMClassValid
{
[CmdletBinding()]
[OutputType([System.Boolean])]
param
(
[parameter(Mandatory = $true)]
[PSCustomObject]$JsonObject
)
return $JsonObject.CimClass.CimSuperClassName -eq "CIM_OperatingSystem"
}
function IsCSNameValid
{
[CmdletBinding()]
[OutputType([System.Boolean])]
param
(
[parameter(Mandatory = $true)]
[PSCustomObject]$JsonObject
)
return $JsonObject.CSName -eq [System.Net.DNS]::GetHostName()
}
|
リスト9のIsCIMClassValid
関数では、CIMクラス名(=CimClass
オブジェクトのスーパークラス名)が、「CIM_OperatingSystem」(OS)として適切に出力されているかを判定している。
またIsCSNameValid
関数では、ノード名(=そのCIM_OperatingSystem
オブジェクトのCSName
プロパティとして取得できるコンピューターシステム名)が、ホスト名(=[System.Net.DNS]::GetHostName
メソッドの戻り値)と一致しているかを判定している。
Test-TargetResource関数の記述
リソースの3つの関数を書く順番は自由だ。普段筆者は、最初にGet-TargetResource
関数を書いて、次にTest-TargetResource
関数を書くことにしている。というのも、Get-TargetResource
関数で「現在の状態があるべき状態であるかの判定」を行っているので、Test-TargetResource
関数はその結果を取得するだけでよいからだ。
今回の場合、現在の状態がEnsure
プロパティに与えた「Present/Absent」と一致しているかで表現できるため、リスト10のように非常にシンプルなコードとなる。
function Test-TargetResource
{
[CmdletBinding()]
[OutputType([System.Boolean])]
param
(
[parameter(Mandatory = $true)]
[ValidateSet("Present","Absent")]
[System.String]
$Ensure,
[parameter(Mandatory = $true)]
[System.String]
$Path
)
[System.Boolean]$result = (Get-TargetResource -Ensure $Ensure -Path $Path).Ensure -eq $Ensure
return $result
}
|
いかがだろうか。先述したようにTest-TargetResource
関数は、Get-TargetResource
関数を呼び出すようにして作成するのも定石であり、ロジックの分散を招かないための有用な手法である。
Set-TargetResource関数の記述
最後にSet-TargetResource
関数で、あるべき状態にするためのロジックを記述する。普段、PowerShellスクリプト関数を書いている人にとっては、一番書きやすいのではないだろうか。
今回は、Ensure
プロパティが「Absent」の場合は、Path
プロパティに指定したJSONファイルが存在しなければ正常だ。「Present」の場合は、Path
プロパティに指定されたファイルに、Win32_OperatingSystem
オブジェクトの内容をJSON形式で出力する。これを記述したのが、リスト11である。
function Set-TargetResource
{
[CmdletBinding()]
[OutputType([Void])]
param
(
[parameter(Mandatory = $true)]
[ValidateSet("Present","Absent")]
[System.String]
$Ensure,
[parameter(Mandatory = $true)]
[System.String]
$Path
)
#Ensure = "Absent"
if ($Ensure -eq [EnsureType]::Absent.ToString())
{
if (Test-Path -Path $Path)
{
Write-Debug ("File found at '{0}'. Removing file." -f $Path)
Remove-Item -Path $Path -Force > $null
return;
}
}
# Ensure = "Present"
$json = Get-CimInstance -ClassName Win32_OperatingSystem | ConvertTo-Json
$parent = Split-Path -Path $Path -Parent
if (!(Test-Path -Path $parent))
{
New-Item -Path $parent -ItemType Directory -Force > $null
}
Write-Debug ("Output Win32_OperatingSystem information to Path '{0}'." -f $Path)
Out-File -InputObject $json -FilePath $Path -Encoding utf8 -Force
}
|
*-TargetResource関数のエキスポート
「<リソース名>.psm1」(モジュールファイル)の最後で、Get-TargetResource
、Set-TargetResource
、Test-TargetResource
の3関数をエキスポートして作成完了だ。
xDSCResourceDesignerで「<リソース名>.psm1」ファイルを生成すると、リスト12のように「*-TargetResource」とワイルドカード(*)で関数を指定して出力するように書かれているので、特に追記する必要はない。
Export-ModuleMember -Function *-TargetResource
|
最後に、cOSInfoJson.psm1ファイルに記述したコードの全体像をリスト13に示す。
# Enum for Ensure
Add-Type -TypeDefinition @"
public enum EnsureType
{
Present,
Absent
}
"@ -ErrorAction SilentlyContinue
function Get-TargetResource
{
[CmdletBinding()]
[OutputType([System.Collections.Hashtable])]
param
(
[parameter(Mandatory = $true)]
[ValidateSet("Present","Absent")]
[System.String]$Ensure,
[parameter(Mandatory = $true)]
[System.String]$Path
)
Write-Debug "Obtain Ensure status"
$Ensure = if (!(Test-Path -Path $Path))
{
Write-Verbose "Path not exist."
[EnsureType]::Absent.ToString()
}
else
{
Write-Debug "Read content and convert to JsonObject"
$jsonObject = GetJsonObject -Path $Path
Write-Debug "Check content is valid."
if ((IsCIMClassValid -JsonObject $jsonObject) -and (IsCSNameValid -JsonObject $jsonObject))
{
Write-Debug "Content detected valid."
[EnsureType]::Present.ToString()
}
else
{
Write-Debug "Content detected invalid."
[EnsureType]::Absent.ToString()
}
}
$returnValue = @{
Ensure = [System.String]$Ensure
Path = [System.String]$Path
}
return $returnValue
}
function Set-TargetResource
{
[CmdletBinding()]
[OutputType([Void])]
param
(
[parameter(Mandatory = $true)]
[ValidateSet("Present","Absent")]
[System.String]
$Ensure,
[parameter(Mandatory = $true)]
[System.String]
$Path
)
#Ensure = "Absent"
if ($Ensure -eq [EnsureType]::Absent.ToString())
{
if (Test-Path -Path $Path)
{
Write-Debug ("File found at '{0}'. Removing file." -f $Path)
Remove-Item -Path $Path -Force > $null
return;
}
}
# Ensure = "Present"
$json = Get-CimInstance -ClassName Win32_OperatingSystem | ConvertTo-Json
$parent = Split-Path -Path $Path -Parent
if (!(Test-Path -Path $parent))
{
New-Item -Path $parent -ItemType Directory -Force > $null
}
Write-Debug ("Output Win32_OperatingSystem information to Path '{0}'." -f $Path)
Out-File -InputObject $json -FilePath $Path -Encoding utf8 -Force
}
function Test-TargetResource
{
[CmdletBinding()]
[OutputType([System.Boolean])]
param
(
[parameter(Mandatory = $true)]
[ValidateSet("Present","Absent")]
[System.String]
$Ensure,
[parameter(Mandatory = $true)]
[System.String]
$Path
)
[System.Boolean]$result = (Get-TargetResource -Ensure $Ensure -Path $Path).Ensure -eq $Ensure
return $result
}
function GetJsonObject
{
[CmdletBinding()]
[OutputType([PSCustomObject])]
param
(
[parameter(Mandatory = $true)]
[System.String]$Path
)
$json = Get-Content -Path $Path -Encoding UTF8 -Raw | ConvertFrom-Json
return $json
}
function IsCIMClassValid
{
[CmdletBinding()]
[OutputType([System.Boolean])]
param
(
[parameter(Mandatory = $true)]
[PSCustomObject]$JsonObject
)
return $JsonObject.CimClass.CimSuperClassName -eq "CIM_OperatingSystem"
}
function IsCSNameValid
{
[CmdletBinding()]
[OutputType([System.Boolean])]
param
(
[parameter(Mandatory = $true)]
[PSCustomObject]$JsonObject
)
return $JsonObject.CSName -eq [System.Net.DNS]::GetHostName()
}
Export-ModuleMember -Function *-TargetResource
|
(※2015/02/17 コードの一部を変更しました。詳しくは、本ページ最後の【更新履歴】をご参照ください。)
リソースのテスト
先述したPowerShell Teamが公開しているベストプラクティスにもあるが、リソースが書けたら、可能な限りテストを行ってほしい。テスト手法については以下の手順をパスすることが望ましい。
- モジュールファイルに記述した
Get/Test/Set-TargetResource
関数を直接呼び出して意図した動作をすること - 付随する関数が意図した動作をすること
Import-Module
コマンドレットでリソースのモジュールが正常に読み込めることGet-DscResource
コマンドレットでリソースが取得できること- コンフィギュレーションを記述して
Start-DSCConfiguration
コマンドレットで実行したときに、エラーなく、あるべき状態になること - あるべき状態になったときに、
Test-DSCConfiguration
コマンドレットで状態が「true」として取得できること - あるべき状態でなくなったときに、
Test-DSCConfiguration
コマンドレットで状態が「false」として取得できること - 構成の適用後、
Get-DSCConfiguration
コマンドレットでGet-TargetResource
関数に記述した通りに現在のノード状態が取得できること
テストには、テスティングフレームワークの「Pester」を用いることが、PowerShellチームからも推奨されており、デファクトスタンダードといえる。本記事では、リソースのテスト詳細については扱わないが、リソースは公開されて多くの利用者の環境で動作するため、テストを十分に行うことが望ましいだろう。筆者自身、テストが不十分だったために意図しない挙動を招いたことがあったので、心より推奨したい。
コンフィギュレーションでリソースを呼び出す
リソースが書けてテストが全て通ったものとしよう。すでにリソースは「$env:ProgramFiles\WindowsPowerShell\Modules」フォルダーに配置されているので、さっそくコンフィギュレーションを書いてみよう。
まずは、リソースが正しく配置されて、スキーマMOFも正常に解釈されているかの確認だ。リスト14のGet-DscResource
コマンドレットを実行してほしい。
Get-DscResource -Name cOSInfoJson
|
図7のように、cOSInfoJsonリソースが読み込まれていれば正しい挙動だ。
読み込めていることが確認できたので、localhost(自分自身)に対してコンフィギュレーションを実施してみる。
EnsureプロパティをPresentとする
まずは、OS情報のJSONファイルを出力するように、コンフィギュレーションを書いてみよう。Configurationの書き方に関しては、前回の記事を参考にしてほしい。リスト15が、作成したコンフィギュレーションだ。
configuration osInfoPresent
{
Import-DscResource -ModuleName SampleResource
cOSInfoJson FilePresent
{
Ensure = "Present"
Path = "c:\hoge\test.json"
}
}
osInfoPresent
Start-DscConfiguration -Wait -Force -Verbose -Path osInfoPresent
|
Import-DscResource
キーワードを使って、作成したリソースが含まれるモジュール名「SampleResource」を指定している。これで、作成したリソース「cOSInfoJson」がコンフィギュレーションで利用できるようになる。
「cOSInfoJson」というリソースのフレンドリ名と定義名(この例では「FilePresent」)を指定したら、スキーマMOFで指定したプロパティ(Ensure
とPath
)を宣言しよう。図8のように、リソース名にカーソルを合わせて、Ctrl+スペースキーでIntelliSense(=入力候補の一覧ウィンドウ)に利用可能なプロパティが表示される。ここでは、「c:\hoge\test.json」にファイルが存在してほしい「Present」と宣言している。
「osInfoPresent」コンフィギュレーションを実行することで、MOFファイルが現在のディレクトリに生成される。あとは、Start-DscConfiguration
コマンドレットで自分自身に適用してみよう。
図9が実行結果だ。水色の6行目の中ほどにある「開始 テスト」という行が、Test-TargetResource
関数が実行されたことを示す。「開始 設定」という行は、Set-TargetResource
関数が実行されたことを示す。初めて実行したので、テスト結果(=「Path not exist.」)にある通り、Path
プロパティに指定したファイルが存在しない。そのため、ファイルが生成されている。
生成されたファイルも、意図した通り、JSON形式で出力されている。
再実行でテストが通り、設定がスキップされることの確認
一度、ファイルが生成されたら、正しくCimClass
オブジェクトが「OS」で、そのCSName
プロパティが「ホスト名」と合致していれば、「それは望んだファイル」と判定するようにGet-TargetResource
関数で組んだ。
図11は、実際にコンフィギュレーションを再実行して、今度は設定がスキップされることを確認した様子だ。
Test-DscConfiguration
コマンドレットを使うと(図12)、あるべき状態かをテストした結果、「true」となっていることが分かる。
また、Get-DscConfiguration
コマンドレットを使うと(図13)、現在の状態が取得でき、ファイルパスと共にEnsure
プロパティが「Present」(存在する)と評価されていることが分かる。
EnsureプロパティをAbsentとする
リスト15では、コンフィギュレーション適用時にEnsure
プロパティを「Present」(存在する)として実行した。
リスト16は、Ensure
プロパティを「Absent」(存在しない)として適用するコンフィギュレーションだ。リソースが正しく動作すれば、Path
プロパティに指定したJSONファイルが削除されるはずだ。
configuration osInfoAbsent
{
Import-DscResource -ModuleName SampleResource
cOSInfoJson FileAbsent
{
Ensure = "Absent"
Path = "c:\hoge\test.json"
}
}
osInfoAbsent
Start-DscConfiguration -Wait -Force -Verbose -Path osInfoAbsent
|
図14が、その実行結果だ。指定したパスにすでにJSONファイルが存在したため、「開始 設定」として削除処理が起動したことが分かる。
図15は、再度実行した結果だ。すでにパスに存在したJSONファイルは削除済みのため、「スキップ 設定」と、処理がスキップされたことが分かる。
以上が、リソースを0から作成して、コンフィギュレーションで実際に適用するまでの流れだ。
■
いかがだっただろうか。すでにPowerShellを利用したことがあり、モジュールを書いたことがある人にとっては、新しく覚える必要のある概念はごく少なかったのではないだろうか。
多くの人にとって、本記事がリソースを書くときの参考となることを願っている。
【更新履歴】2015/02/17
リスト7とリスト13を以下の理由で修正しました。
Add-Type
コマンドレットでは、同じ型を追加すると、その(2度目以降の)追加時に「既に読み込み済み」のエラーが発生します。これを抑制するために、try{...}catch{...}
を使って回避していました。このコードをより自然に回避する手段として、-ErrorAction SilentlyContinue
によるコードに変更しました。
1. PowerShell DSCで導入された新しい構文キーワード
Windowsインフラ環境の構築を自動化できる「PowerShell DSC」とは? その使い方を紹介。DSCの構文をコードで示しながら、基本的な実践手順を説明する。
2. 【現在、表示中】≫ 本番で使えるPowerShell DSCリソース作成入門
Windows Serverの構成管理を自動化できるツール「PowerShell DSC」を使いこなそう。PowerShellでDSCのリソースを自作する方法とは?
3. PowerShell v5の新機能と、実戦で使ってほしい機能
Windows 10に標準搭載され、Windows 7/8.1/Server 2008/2012向けにもリリースされたWMF 5.0に同梱されるPowerShell 5.0の新機能と、PowerShellユーザーに特にお勧めの機能を紹介する。