LoginSignup
1
4

More than 1 year has passed since last update.

PowerShellのモジュールの種類はどう決まる?

Last updated at Posted at 2022-01-09

調べたいこと

PowerShellにおけるモジュールの詳細は以下の記事に極めて詳細かつ包括的に書かれている。

モジュールの種類は以下の4つがある。

  1. スクリプトモジュール
  2. バイナリモジュール
  3. マニフェストモジュール
  4. ダイナミックモジュール

実はマニフェスト(.psd1)を使ってモジュールを作成したとしてもマニフェストモジュールになるとは限らない
マニフェスト内のRootModuleNestedModulesの設定に応じてスクリプトモジュールになったり、バイナリモジュールになったりする。
どういうルールでモジュールの種類が決まるのかネット上の記事をいろいろ見てみたが釈然としなかったので実際に調べてみた。

下準備

フォルダ構成

以下のようなモジュール(ManifestTest)を作成し、Windowsの環境変数のPSModulePathManifestTest1つ上のフォルダを追加しておく。これで実験用のモジュールを簡単に参照できるようになる(必須ではないよ)。

ManifestTest             ... モジュールのフォルダ
   ManifestTest.psd1     ... マニフェスト(フォルダ名と同じファイル名)
   ManifestTest.psm1     ... メインのスクリプト(フォルダ名と同じファイル名)
   SubScript.psm1        ... サブのスクリプト
   DllCmdlet.dll         ... コマンドレットのDLL

ファイル

マニフェストファイル(ManifestTest.psd1)は以下のように作成する。あえて余計な引数は加えずに作成する。
ファイル名はモジュールのフォルダ名と同じにする(これはお約束)。今回の検討には関係のない行は削除して見やすくしておく。
なんちゃらToExportについてはすべてワイルドカード指定(あるものはすべてエクスポート)にしておく。

New-ModuleManifest -Path ManifestTest.psd1
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つだけ関数を追加しておく。

ManifestTest.psm1
function Get-MessageFromManifestTest {
    "Hi, Manifest test!"
}

サブのスクリプトファイル(SubScript.psm1)にも適当に1つだけ関数を追加しておく。

SubScript.psm1
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#で作成できるようになる。

DllCmdlet.csproj
<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>

コマンドレットは以下のような感じに作る。引数なしで文字列だけを返すコマンドレットである。

GetMessageFromDll.cs
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を指定してみる。

ManifestTest.psd1
@{
    (略)
    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に指定したスクリプトファイルにあった関数がきちんとエクスポートされるようになった。
ところがModuleTypeScriptになっている!RootModuleを指定する前はマニフェストモジュールだったのに。
バージョン情報などはマニフェストで指定したものだ。マニフェスト使っているけどマニフェストモジュールじゃないものが作られている。

冒頭で紹介した記事にも以下の記載があるので公知の事実のようだ。そういうものだと考えるしかないのだろうか。

RootModule
このパラメータにバイナリモジュール(.dll)やモジュールファイル(.psm1)の名前(.psd1の同一パスに.ps1mが存在することが同一パスとなる)を指定することで、対象のモジュールをモジュールファイルの記述(あるいバイナリモジュールの記述)通りに読み込んでからマニフェスト(.psd1)の記述で制御します。

このパラメータで モジュールファイル(.psm1) を指定したとき、Import-Module で読み込まれるモジュールタイプは スクリプトモジュールとなります。

PowerShell のモジュール詳解とモジュールへのコマンドレット配置手法を考えるより引用

マニフェストを使ったマニフェストモジュールはどうやって作る?

マニフェストを使ったスクリプトモジュールではなく、マニフェストを使ったマニフェストモジュールを作りたい。
以下のようにやってみよう。

ManifestTest.psd1
@{
    (略)
    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

マニフェストモジュールになった。

ではこうしたらどうだろう。RootModuleNestedModulesにそれぞれ異なるスクリプトファイルを指定する。

ManifestTest.psd1
@{
    (略)
    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は配列なので複数要素を追加できるから、メインとサブのスクリプトファイルを指定してみる。

ManifestTest.psd1
@{
    (略)
    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にバイナリファイルを指定するらしい。

ManifestTest.psd1
@{
    (略)
    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にスクリプトファイルを追加してバイナリとスクリプトの合わせ技のモジュールを作ってみる。

ManifestTest.psd1
@{
    (略)
    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にバイナリを入れてみる。

ManifestTest.psd1
@{
    (略)
    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にバイナリとスクリプトを入れてみる。

ManifestTest.psd1
@{
    (略)
    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の指定が重要であることがわかった。
PowerShellModule.png

"スクリプトモジュールは〇〇ができない"というようなモジュールの種類による機能制限はないように思うので、そもそもモジュールの種類はあまり意識する必要はないかも(違っていたら教えてください)。

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4