調べたいこと
PowerShellにおけるモジュールの詳細は以下の記事に極めて詳細かつ包括的に書かれている。
モジュールの種類は以下の4つがある。
- スクリプトモジュール
- バイナリモジュール
- マニフェストモジュール
- ダイナミックモジュール
実はマニフェスト(.psd1
)を使ってモジュールを作成したとしてもマニフェストモジュールになるとは限らない。
マニフェスト内のRootModule
やNestedModules
の設定に応じてスクリプトモジュールになったり、バイナリモジュールになったりする。
どういうルールでモジュールの種類が決まるのかネット上の記事をいろいろ見てみたが釈然としなかったので実際に調べてみた。
下準備
フォルダ構成
以下のようなモジュール(ManifestTest
)を作成し、Windowsの環境変数のPSModulePath
にManifestTest
の1つ上のフォルダを追加しておく。これで実験用のモジュールを簡単に参照できるようになる(必須ではないよ)。
ManifestTest ... モジュールのフォルダ
ManifestTest.psd1 ... マニフェスト(フォルダ名と同じファイル名)
ManifestTest.psm1 ... メインのスクリプト(フォルダ名と同じファイル名)
SubScript.psm1 ... サブのスクリプト
DllCmdlet.dll ... コマンドレットのDLL
ファイル
マニフェストファイル(ManifestTest.psd1
)は以下のように作成する。あえて余計な引数は加えずに作成する。
ファイル名はモジュールのフォルダ名と同じにする(これはお約束)。今回の検討には関係のない行は削除して見やすくしておく。
なんちゃらToExport
についてはすべてワイルドカード指定(あるものはすべてエクスポート)にしておく。
New-ModuleManifest -Path ManifestTest.psd1
@{
ModuleVersion = '0.0.1'
GUID = 'f875b3b4-248b-4258-8bd4-8870725f80d8'
Author = 'TheParkSider'
CompanyName = 'Unknown'
Copyright = '(c) TheParkSider. All rights reserved.'
# RootModule = ''
# NestedModules = @()
FunctionsToExport = '*'
CmdletsToExport = '*'
VariablesToExport = '*'
AliasesToExport = '*'
PrivateData = @{
PSData = @{}
}
}
メインのスクリプトファイル(ManifestTest.psm1
)には適当に1つだけ関数を追加しておく。
function Get-MessageFromManifestTest {
"Hi, Manifest test!"
}
サブのスクリプトファイル(SubScript.psm1
)にも適当に1つだけ関数を追加しておく。
function Get-MessageFromSubScript {
"Hey, Sub script!"
}
自作のコマンドレットをC#のDLLとして作っておく。
DllCmdlet
| DllCmdlet.sln
|
\---DllCmdlet
DllCmdlet.csproj
GetMessageFromDll.cs
プロジェクトのTargetFramework
は.NET Standard 2.0にした。これでWindows PowerShell、PowerShell(オープンソース版)のどちらからでも使用できるDLLになるらしい。
NuGetでPowerShellStandard.Libraryを取得しておく。これでコマンドレットをC#で作成できるようになる。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ApplicationIcon />
<OutputType>Library</OutputType>
<StartupObject />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile></DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PowerShellStandard.Library" Version="5.1.0" />
</ItemGroup>
</Project>
コマンドレットは以下のような感じに作る。引数なしで文字列だけを返すコマンドレットである。
using System;
using System.Management.Automation;
namespace DllCmdlet
{
[Cmdlet(VerbsCommon.Get, "MessageFromDll")]
[OutputType(typeof(string))]
public class GetMessageFromDll : PSCmdlet
{
protected override void ProcessRecord()
{
WriteObject("Hi, DLL!");
}
}
}
ソースコードはこちら。
そもそもマニフェストがなかったらどうなるのか?
マニフェストManifestTest.psd1
をいったん削除してからこのモジュールを読み込んだらどのように認識されるのか?
PowerShellのコンソールを起動して以下のコマンドを入力する。
Get-Module -ListAvailable | where { $_.Name -eq "ManifestTest" }
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 0.0 ManifestTest Get-MessageFromManifestTest
ModuleType
を見れば明らかなようにスクリプトモジュールとして認識されている。フォルダ名ManifestTest
と名称が同じスクリプトファイルManifestTest.pmd1
が参照されるようだ。
このモジュールのプロパティを見てみるとメタ情報はスッカラカン(たとえばバージョンやGUIDは全部ゼロ)だが、これで十分という人もいるのではないか?
つまり、超手っ取り早くモジュールを作りたかったらPSModulePath
の中に任意の名称のモジュールのフォルダを作成し、そのフォルダと同名のスクリプトファイル(.psm1
)をその中に入れればOKだ。
スクリプトファイルにメタ情報などを加えてみる
さすがにバージョンや著作権情報もないのは困るのでマニフェストManifestTest.psd1
をモジュールのフォルダ上に戻してみよう。
マニフェストの中身は前述したとおりまったくのデフォルト状態である。
PowerShellのコンソールを再起動してモジュールの情報を再読み込みしてみる(こうしないとPSModulePath
が再読み込みされないはず)。
Get-Module -ListAvailable | where { $_.Name -eq "ManifestTest" }
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 0.0.1 ManifestTest
マニフェストモジュールとして読み込まれ、バージョンやGUID、著作権情報にマニフェストで指定した情報が書き込まれている。
ところがExportedCommands
が空になっている。マニフェストを使用する場合はどのスクリプトファイルの関数をエクスポートするかを指定しなければいけないようだ。
マニフェストがないときはモジュールと同名のスクリプトファイル(.psm1
)を暗黙的に参照してくれるのになあ。
ではマニフェストを以下のように変更し、RootModule
にメインのスクリプトファイルManifestTest.psm1
を指定してみる。
@{
(略)
Copyright = '(c) TheParkSider. All rights reserved.'
RootModule = 'ManifestTest.psm1' # ここが変わったよ
# NestedModules = @()
(略)
}
モジュールの情報を再読み込みしてみる。
Get-Module -ListAvailable | where { $_.Name -eq "ManifestTest" }
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 0.0.1 ManifestTest Get-MessageFromManifestTest
RootModule
に指定したスクリプトファイルにあった関数がきちんとエクスポートされるようになった。
ところがModuleType
がScript
になっている!RootModule
を指定する前はマニフェストモジュールだったのに。
バージョン情報などはマニフェストで指定したものだ。マニフェスト使っているけどマニフェストモジュールじゃないものが作られている。
冒頭で紹介した記事にも以下の記載があるので公知の事実のようだ。そういうものだと考えるしかないのだろうか。
RootModule
このパラメータにバイナリモジュール(.dll)やモジュールファイル(.psm1)の名前(.psd1の同一パスに.ps1mが存在することが同一パスとなる)を指定することで、対象のモジュールをモジュールファイルの記述(あるいバイナリモジュールの記述)通りに読み込んでからマニフェスト(.psd1)の記述で制御します。
このパラメータで モジュールファイル(.psm1) を指定したとき、Import-Module で読み込まれるモジュールタイプは スクリプトモジュールとなります。
PowerShell のモジュール詳解とモジュールへのコマンドレット配置手法を考えるより引用
マニフェストを使ったマニフェストモジュールはどうやって作る?
マニフェストを使ったスクリプトモジュールではなく、マニフェストを使ったマニフェストモジュールを作りたい。
以下のようにやってみよう。
@{
(略)
Copyright = '(c) TheParkSider. All rights reserved.'
RootModule = '' # ここが変わったよ
NestedModules = @('ManifestTest.psm1') # ここが変わったよ
(略)
}
Get-Module -ListAvailable | where { $_.Name -eq "ManifestTest" }
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 0.0.1 ManifestTest Get-MessageFromManifestTest
マニフェストモジュールになった。
ではこうしたらどうだろう。RootModule
とNestedModules
にそれぞれ異なるスクリプトファイルを指定する。
@{
(略)
Copyright = '(c) TheParkSider. All rights reserved.'
RootModule = 'ManifestTest.psm1' # ここが変わったよ
NestedModules = @('SubScript.psm1') # ここが変わったよ
(略)
}
Get-Module -ListAvailable | where { $_.Name -eq "ManifestTest" }
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 0.0.1 ManifestTest {Get-MessageFromManifestTest, Get-MessageFromSubScript}
今度はスクリプトモジュールになった。つまり、RootModule
にスクリプトファイルを指定したらマニフェストを使っていてもスクリプトモジュールになってしまうことが分かった。
ではさらにこうしたらどうだろう。NestedModules
は配列なので複数要素を追加できるから、メインとサブのスクリプトファイルを指定してみる。
@{
(略)
Copyright = '(c) TheParkSider. All rights reserved.'
RootModule = '' # ここが変わったよ
NestedModules = @('ManifestTest.psm1', 'SubScript.psm1') # ここが変わったよ
(略)
}
Get-Module -ListAvailable | where { $_.Name -eq "ManifestTest" }
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 0.0.1 ManifestTest {Get-MessageFromManifestTest, Get-MessageFromSubScript}
なるほど。NestedModules
だけにスクリプトファイルを指定した場合はマニフェストモジュールになることがわかった。
直近の2つの例はモジュールの種類は違うものの、エクスポートされたコマンドやモジュールのメタ情報は基本的に同じである。
どうやって使い分けるんだろうなあ。
バイナリモジュールはどう作る?
バイナリモジュールを作る場合はRootModule
にバイナリファイルを指定するらしい。
@{
(略)
Copyright = '(c) TheParkSider. All rights reserved.'
RootModule = 'DllCmdlet.dll' # ここが変わったよ
NestedModules = @() # ここが変わったよ
(略)
}
Get-Module -ListAvailable | where { $_.Name -eq "ManifestTest" }
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Binary 0.0.1 ManifestTest Get-MessageFromDll
たしかにバイナリモジュールになっている。RootModule
にスクリプトファイルを指定したときのようにスクリプトモジュールになることはなかった。
さらにNestedModules
にスクリプトファイルを追加してバイナリとスクリプトの合わせ技のモジュールを作ってみる。
@{
(略)
Copyright = '(c) TheParkSider. All rights reserved.'
RootModule = 'DllCmdlet.dll'
NestedModules = @('ManifestTest.psm1', 'SubScript.psm1') # ここが変わったよ
(略)
}
Get-Module -ListAvailable | where { $_.Name -eq "ManifestTest" }
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Binary 0.0.1 ManifestTest {Get-MessageFromDll, Get-MessageFromManifestTest, Get-Mess...
バイナリモジュールのままである。
ではこれはどうだろう。RootModule
はスクリプト、NestedModules
にバイナリを入れてみる。
@{
(略)
Copyright = '(c) TheParkSider. All rights reserved.'
RootModule = 'ManifestTest.psm1' # ここが変わったよ
NestedModules = @('DllCmdlet.dll', 'SubScript.psm1') # ここが変わったよ
(略)
}
Get-Module -ListAvailable | where { $_.Name -eq "ManifestTest" }
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 0.0.1 ManifestTest {Get-MessageFromDll, Get-MessageFromManifestTest, Get-Mess...
スクリプトモジュールになっている。つまりバイナリを含むモジュールであってもRootModule
にスクリプトファイルが指定されていたらスクリプトモジュールになってしまうのだ。なんかもうよくわかんねえな。
最後にこれはどうだ。RootModule
は空、NestedModules
にバイナリとスクリプトを入れてみる。
@{
(略)
Copyright = '(c) TheParkSider. All rights reserved.'
RootModule = '' # ここが変わったよ
NestedModules = @('DllCmdlet.dll', 'ManifestTest.psm1') # ここが変わったよ
(略)
}
Get-Module -ListAvailable | where { $_.Name -eq "ManifestTest" }
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 0.0.1 ManifestTest {Get-MessageFromDll, Get-MessageFromManifestTest}
マニフェストモジュールになっている。RootModule
が空ならばNestedModules
がバイナリだろうとスクリプトだろうとマニフェストモジュールになるようだ。
まとめ
まとめると以下のようなフローチャートでモジュールの種類が決定される。
NestedModule
にもいろいろなものを入れてみたが、結局RootModule
の指定が重要であることがわかった。
"スクリプトモジュールは〇〇ができない"というようなモジュールの種類による機能制限はないように思うので、そもそもモジュールの種類はあまり意識する必要はないかも(違っていたら教えてください)。