以下の内容はhttps://tech.guitarrapc.com/entry/2013/12/03/014013より取得しました。


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

PowerShell Advent Calendar 2013に参加させていただいています。これは2日目の記事となります。

今回は、Windows PowerShellのモジュール機構を利用するにあたり以下の2つに関して考えてみましょう。

  • 4つあるモジュール各種の詳解
  • モジュールへのコマンドレット配置手法

本記事はPowerShell 3.0以上 (私の環境はPowerShell 4.0)をベースとしています。PowerShell 2.0では一部パラメータ名称が異なったりします。 以前書いたWindows 8.1 や Windows Server 2012 R2 以外で Windows PowerShell 4.0を利用する方法を参考にしていただければ幸いです。

なぜモジュールを利用するのか

少しPowerShellを触るとわかりますが、 PowerShellコードは再利用性を持って記述することが容易です。そのため自作のファンクションをサクサク書いて利用するようになると、PowerShell.exeやPowerShell_Ise.exeを起動したとき、自動的に読み込まれてほしいです。毎回書くとか人間のすることではありません。

$Profileの限界とモジュール機構

簡易的なファンクション自動読み込み方法として$Profileにファンクションを記述するやり方があります。$Profileを使うとPowerShellやISE起動時に$Profileへ記述した処理を自動的に読み込みます。

せっかく作った一連のPowerShell Scriptを$Profileに書き連ねるのは限界があります。例えば、自作/他の人が書いたファンクションを50個も$Profileに書いておいたとして、何が含まれるか管理するのは困難です。読み込ませたくないものを制御することも難しいです。$Profileでは例えGet-Commandを用いてもどのファンクションが$Profileに含まれたものなのか判断が困難であり、自作コマンドレットが秩序なく散らばる要因となりねません。

この問題に対して、PowerShell 2.0からはモジュール機構が実装されました。

モジュールは何をできるの

モジュールは、HDDやSSDといった記憶媒体に保持したPowerShellファンクション/スクリプトの参照、読み込み、保持機構です。スナップイン(snap-in)とは異なりモジュールは、メンバーとしてコマンドレット、プロバイダ、ファンクション、変数、エイリアスなどを持ることができます。

モジュール機構を利用することで、一連のスクリプトやファンクションをモジュールとしてパッケージ化、読み込みできるようになります。特にPowerShell3.0からはモジュールに含まれるコマンドレットは自動的に探索され、インテリセンスでも候補に挙がります。さらにそのコマンドレットを実行した時に、コマンドレットを含むモジュールも自動的に読み込まれます。

もちろんGet-CommandでもModuleNameプロパティでどのモジュールにそのファンクションが含まれるのか、あるいはそのモジュールに含まれるファンクションが何かを判断できます。

Windows PowerShell Module Concepts

Windows PowerShellにおいてモジュールのコンセプトはタイプごとに4つあります。

モジュールタイプ 概要
Script Modules スクリプトファイル(.psm1) に含まれたPowerShell コードを指します。開発者や管理者はモジュールメンバーのファンクションや変数などを利用できます。
Binary Modules .NET Framework アセンブリ(.dll)に含まれたコンパイルされたコードを指します。開発者はこのモジュールにCmdletやプロバイダ、あるいは既存のSnap-inも利用できます。
Manifest Modules コンポーネントをマニフェスト(.psd1)で定義し、単一、あるいは複数のルートモジュールファイル(.psm1)読み込みをマニフェストで記述制御でき、アセンブリ、型、フォーマットなどを読み込めます
Dynamic Modules ディスク(ファイル)として保持されていないものを指します。オンデマンドに、ディスク保持する必要がないモジュールを可能にし、New-Module で作成、利用できます。

それぞれを細かく見ていきましょう。

I. Script Module

簡単にいうと、モジュールファイル(.psm1) で各種function/Workflow/filter/変数/エイリアスを定義したものを指します。サンプルをPowerShell_ise.exeでコマンド実行しながら考えましょう。

1. モジュールパスにモジュールフォルダを作成する

まず、ユーザーモジュールパス($env:USERPROFILE\Documents\WindowsPowerShell\Modules)にhogeフォルダを作ります。

mkdir $env:USERPROFILE\Documents\WindowsPowerShell\Modules\hoge

2. モジュールフォルダにモジュールファイル(.psm1)を作成する

次にモジュールファイルであるhoge.psm1をモジュールフォルダ直下に作成します。

モジュールファイル(.psm1)は必ずモジュールフォルダと同一名称にしてください。

function write-hosthoge
{
    Write-Host "hoge"
}

function Not-ExportFunction
{
    "出力しないもん!"
}

Export-ModuleMember -Function write-hosthoge

.psm1記述内容について

ここでやっていることはいたって簡単です。

write-hosthogeというfunctionを定義して、Export-ModuleMemberコマンドレットでwrite-hosthogeファンクションのみ「モジュールの公開するファンクション」として出力しています。

Export-ModuleMember による公開するファンクションの選択

このExport-ModuleMemberですが、「公開するfunction = public を指定している」 と考えれば性質がわかりやすいでしょう。

  • privateのイメージ: Export-ModuleMemberで出力せずとも、.psm1内部のfunction同士参照可能
  • publicのイメージ : Export-ModuleMemberで出力することで、モジュール利用者が自由にコマンドレットを実行可能

つまり、モジュール内部で他のプログラミング言語のように用途に応じて細かくファンクションを作っても、外部に公開するファンクションや変数は選べるということです。

通常のスクリプトファイル(.ps1) で特定のファンクションを公開したくない場合、function{}内部にそのfunction{}をネストして記述するなどして隠す必要がありますが、モジュールではExport-ModuleMemberで制御できます。

  • すべてのファンクションを公開する場合はワイルドカード*を指定
Export-ModuleMember -Function *

3. モジュールメンバーの実行 と 暗黙的なモジュールの読み込み

モジュールフォルダとモジュールファイル(.psm1)を同一名で作成を終えたら、PowerShell.exeかPowerShell_Ise.exeを起動して次のコマンドを入力してみてください。結果はhogeと出力されます。つまり、hogeモジュールが自動的に読み込まれwrite-hosthogeコマンドレットが実行されているのです。

PS> write-hosthoge
hoge

PowerShell 3.0以降は、モジュールメンバーは明示的なモジュールファイル読み込みをすることなく実行可能です。

4. モジュール読み込みの確認

モジュールの読み込み候補

PowerShell3.0以降において、モジュールに対して暗黙的な読み込みが実装されました。

これにより、PowerShellでインテリセンス実行時に自動的に環境変数$Env:PSModulePathを探索し、正しくモジュールと解釈できるモジュールのファンクションを読み込みます。

デフォルトの環境変数は以下の状態です。

PS> $Env:PSModulePath
C:\Users\UserName\Documents\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\

PS> $Env:PSModulePath -split ';'
C:\Users\UserName\Documents\WindowsPowerShell\Modules;
C:\Program Files\WindowsPowerShell\Modules;
C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\;

これらのパスに存在するモジュールは、明示的にモジュールを読み込ませずともインテリセンスの候補に上がり、コマンド実行時にImport-Moduleがバックグラウンドで実行されモジュールが読み込まれます。

システムレベルとユーザーレベルのモジュールパス

モジュールパスは、システムレベルとユーザーレベルでパスを保持しています。

システムレベルとユーザ-レベルでは大きく挙動が変わります。

システムレベルパス

特に以下が該当します。

C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\;

また、x64環境の場合、 PowerShell x86は、以下のパスがシステムレベルパスになります。

C:\Windows\SysWOW64\WindowsPowerShell\v1.0\Modules

PowerShellのデフォルトのモジュールはすべてここに含まれていることがわかります。

PS> ls C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\ | fw


    ディレクトリ: C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules



[AppBackgroundTask]                                         [AppLocker]
[Appx]                                                      [AssignedAccess]
[BitLocker]                                                 [BitsTransfer]
[BranchCache]                                               [CimCmdlets]
[Defender]                                                  [DirectAccessClientComponents]
[Dism]                                                      [DnsClient]
[Hyper-V]                                                   [International]
[iSCSI]                                                     [ISE]
[Kds]                                                       [Microsoft.PowerShell.Diagnostics]
[Microsoft.PowerShell.Host]                                 [Microsoft.PowerShell.Management]
[Microsoft.PowerShell.Security]                             [Microsoft.PowerShell.Utility]
[Microsoft.WSMan.Management]                                [MMAgent]
[MsDtc]                                                     [MSOnline]
[MSOnlineExtended]                                          [NetAdapter]
[NetConnection]                                             [NetEventPacketCapture]
[NetLbfo]                                                   [NetNat]
[NetQos]                                                    [NetSecurity]
[NetSwitchTeam]                                             [NetTCPIP]
[NetWNV]                                                    [NetworkConnectivityStatus]
[NetworkTransition]                                         [PcsvDevice]
[PKI]                                                       [PrintManagement]
[PSDesiredStateConfiguration]                               [PSDiagnostics]
[PSScheduledJob]                                            [PSWorkflow]
[PSWorkflowUtility]                                         [ScheduledTasks]
[SecureBoot]                                                [SmbShare]
[SmbWitness]                                                [StartScreen]
[Storage]                                                   [TLS]
[TroubleshootingPack]                                       [TrustedPlatformModule]
[VpnClient]                                                 [Wdac]
[WindowsDeveloperLicense]                                   [WindowsErrorReporting]
[WindowsSearch]

これらのモジュールは、ユーザーモジュールパスと異なり、どのユーザーであってもPowerShellエンジンの起動時に自動的にImport-Moduleされています。

つまり、ローカルのPowerShell.exeを起動時はインポートされていませんが、Invoke-Command -ComputerNameNew-PSSessionなどでWinRM経由で接続したときには、接続時にはこれらのモジュールが読み込まれています。

特にリモートセッションで特定のユーザーごとにモジュールを持たせるのが著しく手間であり、信用できるスクリプトの場合は大きく手間が省けることになります。

ユーザーレベルパス

以下が該当します。が、ドキュメントフォルダを移動している場合は、その限りではありません。

$env:USERPROFILE\Documents\WindowsPowerShell\Modules

ユーザーレベルパスは、現在のユーザーのPowerShellセッションでしか参照されないので、ユーザー hogeさんで接続しているときに、ユーザーfugaさんのユーザーモジュールパスが参照されることはありません。 これは、ユーザーごとのモジュール選択可能性を可能にしており、PSリモーティングでも接続に利用したユーザーのユーザーフォルダしか参照されることはありません。

明示的なモジュールの読み込み

前述の通り、PowerShell 3.0以降は明示てきなモジュールの読み込みことなく暗黙的にモジュールの探索が行われ、コマンドレット実行時にモジュールの読み込みが自動的になされます。

ただ開発中の場合は、モジュールの読み込みが確認できれば、Export-ModuleMemberで出力したメンバーを確認できます。 もし、コマンドレット実行前にモジュールを事前に読み込みたい、あるいはPowerShell 2.0を使っている場合は、明示的にモジュールを読み込んでみましょう。

ModuleType を見ることで、psm1読み込みによるScript Moduleであることがわかります。hogeモジュールが明示的に読み込まれ、write-hosthogeファンクションやモジュールに含まれる変数が読み込まれていることがわかります。

PS> Import-Module hoge -PassThru

ModuleType Version    Name                                ExportedCommands
- -------    ----                                -------
Script     0.0        hoge                                write-hosthoge

読み込まれるモジュールメンバーの確認

Import-Module時に、-Verboseスイッチを付けることでモジュール読み込み時にインポートされたファンクション/変数が確認できます。

PS> Import-Module hoge -PassThru -Verbose
VERBOSE: Importing function 'write-hosthoge'.

ModuleType Version    Name                                ExportedCommands
- -------    ----                                -------
Script     0.0        hoge                                write-hosthoge

読み込まれたモジュールの確認

現在のPowerShellプロセスで実行されているRunSpaceに読み込まれたモジュールを確認するには以下のようにします。

Get-Module

ここでモジュール名を指定することで、そのモジュールが存在するかわかります。

Get-Module hoge

あるいは、エラーを出さずに判定するならWhere-Objectコマンドレットを使います。

Get-Module | Where Name -eq "hoge"     # PowerShell 3.0のWhere-Object 省略記法
(Get-Module).Where{$_.Name -eq "hoge"} # PowerShell 4.0のWhereメソッド

読み込まれたモジュールメンバーの確認

一度モジュールが読み込まれた後に、PowerShellコマンド全体から指定したモジュールメンバーのコマンドレットを取得できます。

Get-Command -Module hoge

今回の場合は、以下のように出力されます。

PS> Get-Command -Module hoge

CommandType     Name                                               ModuleName
--     ----                                               -
Function        write-hosthoge                                     hoge

5. モジュールの破棄と再読み込み

読み込んだモジュールの破棄

一度読み込んだモジュールをセッションから取り除くなら以下のようにします。

Remove-Module hoge

一度モジュールを読み込んだセッションでモジュールファイルを変更して読み込み直す

PowerShellは、その起動ごとにRunSpace内で変数やファンクション、読み込んだモジュールなど各セッション情報を保持しています。つまり、PowerShellを起動しなおすことで新しいRunSpaceでセッションが実行されます。

一方で、一度読み込んだモジュールを現在のRunSpaceを維持したままImport-Moduleしても読み込んだモジュールのまま維持してしまい、新たなモジュール設定を読み込み直すことはありません。この場合、一度読み込んだモジュールを破棄して再度モジュールを読み込み直すか、新しいRunSpaceでモジュールを読み込む必要があります。

PowerShell_Ise.exeの場合は、Ctrl+Tで新たなRunSpaceタブを作ることで、iseを閉じることなく新しいセッションを試すことができます。

もし同一RunSpaceでモジュールの再読み込みを行うには、-Forceスイッチを付けてImport-Moduleします。

Import-Module hoge -Force

-Verboseスイッチを付けることで、一度現在のRunSpaceから一度ファンクションが取り除かれてから再度読み込まれていることがわかります。

PS> Import-Module hoge -PassThru -Verbose -Force
VERBOSE: Removing the imported "write-hosthoge" function.
VERBOSE: Loading module from path 'C:\Users\acquire\Documents\WindowsPowerShell\Modules\hoge\hoge.psm1'.
VERBOSE: Importing function 'write-hosthoge'.

ModuleType Version    Name                                ExportedCommands
- -------    ----                                -------
Script     0.0        hoge                                write-hosthoge

ここまでが、スクリプトモジュールのおおよその概要になります。続いてバイナリモジュールに関して見てみましょう。

II. Binary Modules

Binary Moduleは、C# でPowerShellアセンブリ(.dll) 形式を生成し、PowerShellにモジュールとして読み込ませる方式を指します。

PowerShell V1の頃に現在のような「高度な関数」といわれるPowerShellスクリプト単独での細かな制御をファンクションが持ちえなかった頃や、C# の強力な機能を利用する際に作成します。

Microsoft MVP for PowerShellの牟田口大介さんがバイナリモジュールを用いたモジュールに関するセッションを2013/5/11のCommunity Open Day 2013にて行われています。

デモが中心だったため、資料の1つとして紹介にとどめますが、非常に参考になるセッションでした。

運用自動化に役立つPowerShellモジュールの作成方法

さて、バイナリモジュールにおける .dll生成とモジュール読み込みまでを見てみましょう。

1. Visual Studioで.dllを生成する

PowerShellのバイナリモジュールはC#/Visual Basicでコーディング可能です。書くなら断然C# が楽です。

ということで、みなさん大好きVisual Studioで開発する流れを順を追ってみてみましょう。 *1

1.1 新規プロジェクトと参照の追加

新規プロジェクトで、C# > クラスライブラリを選択します。

参照にC:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dllを追加します。

これでアセンブリ > フレームワークにてSystem.Management.Automationを追加できます。

1.2 コード記述

usingにusing System.Management.Automation;using System.Management;を加えて、以下のサンプルコードを書きます。hogehoge! と出力されるだけの意味のないものです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Management;
using System.Management.Automation;

namespace PowerShellClass1
{
    [Cmdlet(VerbsCommon.Get, "string")]
    public class GetString : Cmdlet
    {
        protected override void EndProcessing()
        {
            WriteObject("hogehoge!");
        }
    }
}

1.3 ビルド

ビルドします。

ここではPowerShell 4.0で動作させているので、対象フレームワークは .NET Framework 4.5としていますが、PowerShell3.0なら .NET Framework 4.0となります。

1>------ すべてのリビルド開始: プロジェクト:PowerShellClass1, 構成:Debug Any CPU ------
1>  PowerShellClass1 -> D:\visual studio 2013\Projects\ClassLibrary1\ClassLibrary1\bin\Debug\PowerShellClass1.dll
========== すべてリビルド: 1 正常終了、0 失敗、0 スキップ ==========

1.4 PowerShellでバイナリからモジュールを読み込む

PowerShellからdllをパス指定してImport-Moduleで読み込みます。

Import-Module PowerShellClass1.dll

ModuleType Version    Name                                ExportedCommands
- -------    ----                                -------
Binary     1.0.0.0    PowerShellClass1                    Get-string

1.5 読み込んだコマンドを実行する

  • 実行してみると、記述通りhogehoge! と出力
PS> Get-string

hogehoge!

簡単な流れでしたが、C# でCmdletを記述しビルドした.dllをPowerShellで読み込み、実行できます。VSとC#サポートをフルに受けられるので、開発としては非常に楽なのは確かです。 ここまで詳解したスクリプトモジュール(.psm1) やバイナリモジュール(.dll) ですが、モジュール読み込み時にマニフェスト(.psd1) を利用することで「モジュールバージョン」や「作者情報」「出力するCmdletや変数」などの制御が可能です。

さっそくマニフェストモジュールによる、モジュール制御を見てみましょう。

III. Manifest Modules

マニフェストモジュールは、 先述したスクリプトモジュールやバイナリモジュールをマニフェスト(.psd1)を用いてきめ細かく制御します。

例えば、「モジュールのバージョン」や「作者情報」を考えても、スクリプトモジュール(.psm1)やバイナリモジュール(.dll)では、Import-Moduleでモジュールを読み込んだ時にバージョンなどを指定できません。またモジュール内部で仕込む場所もありません。

※ バイナリモジュールはdllのバージョンとして保持できますが、それは一端置いておきましょう。

他にもマニフェストファイル(.psd1) を利用することで、複数のスクリプトモジュールをまとめて1つのモジュールとして読み込ませたりも可能になります。(Nested Module)

PowerShell 4.0からは、マニフェストに記述したバージョンをGet-ModuleやImport-Moduleで指定できるようになったため一層使いやすくなりました。

では具体的に、マニフェストファイルの生成から内容まで見ていきましょう。

マニフェストの生成

マニフェストの生成には、専用のCmdletが用意されています。まずはサンプルとして空のマニフェストを作ってみてみます。

PS> New-ModuleManifest -path manufesttest.psd1

#
# モジュール 'manufesttest' のモジュール マニフェスト
#
# 生成者: guitarrapc
#
# 生成日: 2013/12/02
#

@{

# このマニフェストに関連付けられているスクリプト モジュール ファイルまたはバイナリ モジュール ファイル。
# RootModule = ''

# このモジュールのバージョン番号です。
ModuleVersion = '1.0'

# このモジュールを一意に識別するために使用される ID
GUID = '7a12ba32-5e12-4ee1-8685-948ecc9246df'

# このモジュールの作成者
Author = 'guitarrapc'

# このモジュールの会社またはベンダー
CompanyName = '不明'

# このモジュールの著作権情報
Copyright = '(c) 2013 guitarrapc. All rights reserved.'

# このモジュールの機能の説明
# Description = ''

# このモジュールに必要な Windows PowerShell エンジンの最小バージョン
# PowerShellVersion = ''

# このモジュールに必要な Windows PowerShell ホストの名前
# PowerShellHostName = ''

# このモジュールに必要な Windows PowerShell ホストの最小バージョン
# PowerShellHostVersion = ''

# このモジュールに必要な Microsoft .NET Framework の最小バージョン
# DotNetFrameworkVersion = ''

# このモジュールに必要な共通言語ランタイム (CLR) の最小バージョン
# CLRVersion = ''

# このモジュールに必要なプロセッサ アーキテクチャ (なし、X86、Amd64)
# ProcessorArchitecture = ''

# このモジュールをインポートする前にグローバル環境にインポートされている必要があるモジュール
# RequiredModules = @()

# このモジュールをインポートする前に読み込まれている必要があるアセンブリ
# RequiredAssemblies = @()

# このモジュールをインポートする前に呼び出し元の環境で実行されるスクリプト ファイル (.ps1)。
# ScriptsToProcess = @()

# このモジュールをインポートするときに読み込まれる型ファイル (.ps1xml)
# TypesToProcess = @()

# このモジュールをインポートするときに読み込まれる書式ファイル (.ps1xml)
# FormatsToProcess = @()

# RootModule/ModuleToProcess に指定されているモジュールの入れ子になったモジュールとしてインポートするモジュール
# NestedModules = @()

# このモジュールからエクスポートする関数
FunctionsToExport = '*'

# このモジュールからエクスポートするコマンドレット
CmdletsToExport = '*'

# このモジュールからエクスポートする変数
VariablesToExport = '*'

# このモジュールからエクスポートするエイリアス
AliasesToExport = '*'

# このモジュールに同梱されているすべてのモジュールのリスト
# ModuleList = @()

# このモジュールに同梱されているすべてのファイルのリスト
# FileList = @()

# RootModule/ModuleToProcess に指定されているモジュールに渡すプライベート データ
# PrivateData = ''

# このモジュールの HelpInfo URI
# HelpInfoURI = ''

# このモジュールからエクスポートされたコマンドの既定のプレフィックス。既定のプレフィックスをオーバーライドする場合は、Import-Module -Prefix を使用します。
# DefaultCommandPrefix = ''

}

マニフェストにRootModule(モジュールファイル .psm1や.dllを指定する)やModuleVersion(モジュールバージョンの指定)、FunctionsToExport(出力するファンクションを指定)を記述することで、Import-Moduleでモジュールが読み込まれるときに、読み込まれるモジュールを制御します。

マニフェストファイルを出力する

マニフェストファイル(.psd1)は、モジュールファイル(.psm1)同様、対象のモジュールフォルダ直下に配置します。(valentiaならvalentia\valentia.psm1とvalentia\valaneita.psd1がモジュールフォルダ直下に存在する)

サンプルとしてvalentiaのマニフェストファイルを見てみましょう。

まず、マニフェストファイル(.psd1)を次のコマンドレットで出力しています。

$script:module = "valentia"
$script:moduleVersion = "0.3.3"
$script:description = "PowerShell Remote deployment library for Windows Servers";
$script:copyright = "28/June/2013 -"
$script:RequiredModules = @()
$script:clrVersion = "4.0.0.0" # .NET 4.0 with StandAlone Installer "4.0.30319.1008" or "4.0.30319.1" , "4.0.30319.17929" (Win8/2012)

$script:functionToExport = @(
        "ConvertTo-ValentiaTask",
        "Edit-ValentiaConfig",
        "Get-ValentiaCredential",
        "Get-ValentiaFileEncoding",
        "Get-ValentiaGroup",
        "Get-ValentiaRebootRequiredStatus",
        "Get-ValentiaTask",
        "Initialize-ValentiaEnvironment",
        "Invoke-Valentia",
        "Invoke-ValentiaAsync",
        "Invoke-ValentiaClean",
        "Invoke-ValentiaCommand",
        "Invoke-ValentiaCommandParallel",
        "Invoke-ValentiaDeployGroupRemark",
        "Invoke-ValentiaDeployGroupUnremark",
        "Invoke-ValentiaDownload",
        "Invoke-ValentiaParallel",
        "Invoke-ValentiaSync",
        "Invoke-ValentiaUpload",
        "Invoke-ValentiaUploadList",
        "New-ValentiaCredential",
        "New-ValentiaGroup",
        "New-ValentiaFolder",
        "Set-ValentiaHostName",
        "Set-ValentiaLocation",
        "Show-ValentiaConfig",
        "Show-ValentiaGroup",
        "Show-ValentiaPromptForChoice"
)

$script:variableToExport = "valentia"
$script:AliasesToExport = @("Task",
    "Valep","CommandP",
    "Vale","Command",
    "Valea",
    "Upload","UploadL",
    "Sync",
    "Download",
    "Go",
    "Clean","Reload",
    "Target",
    "ipremark","ipunremark",
    "Cred",
    "Rename",
    "Initial"
)

$script:moduleManufest = @{
    Path = "$module.psd1";
    Author = "guitarrapc";
    CompanyName = "guitarrapc"
    Copyright = "";
    ModuleVersion = $moduleVersion
    Description = $description
    PowerShellVersion = "3.0";
    DotNetFrameworkVersion = "4.0";
    ClrVersion = $clrVersion;
    RequiredModules = $RequiredModules;
    NestedModules = "$module.psm1";
    CmdletsToExport = "*";
    FunctionsToExport = $functionToExport
    VariablesToExport = $variableToExport;
    AliasesToExport = $AliasesToExport;
}

New-ModuleManifest @moduleManufest

出力された結果です。

#
# Module manifest for module 'valentia'
#
# Generated by: guitarrapc
#
# Generated on: 2013/12/02
#

@{

# Script module or binary module file associated with this manifest.
# RootModule = ''

# Version number of this module.
ModuleVersion = '0.3.3'

# ID used to uniquely identify this module
GUID = '87410785-c365-476c-9b56-ebe8ca259837'

# Author of this module
Author = 'guitarrapc'

# Company or vendor of this module
CompanyName = 'guitarrapc'

# Copyright statement for this module
Copyright = '(c) 2013 guitarrapc. All rights reserved.'

# Description of the functionality provided by this module
Description = 'PowerShell Remote deployment library for Windows Servers'

# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '3.0'

# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''

# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''

# Minimum version of Microsoft .NET Framework required by this module
DotNetFrameworkVersion = '4.0'

# Minimum version of the common language runtime (CLR) required by this module
CLRVersion = '4.0.0.0'

# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''

# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()

# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()

# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()

# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()

# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()

# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
NestedModules = @('valentia.psm1')

# Functions to export from this module
FunctionsToExport = 'ConvertTo-ValentiaTask', 'Edit-ValentiaConfig',
               'Get-ValentiaCredential', 'Get-ValentiaFileEncoding',
               'Get-ValentiaGroup', 'Get-ValentiaRebootRequiredStatus',
               'Get-ValentiaTask', 'Initialize-ValentiaEnvironment',
               'Invoke-Valentia', 'Invoke-ValentiaAsync', 'Invoke-ValentiaClean',
               'Invoke-ValentiaCommand', 'Invoke-ValentiaCommandParallel',
               'Invoke-ValentiaDeployGroupRemark',
               'Invoke-ValentiaDeployGroupUnremark', 'Invoke-ValentiaDownload',
               'Invoke-ValentiaParallel', 'Invoke-ValentiaSync',
               'Invoke-ValentiaUpload', 'Invoke-ValentiaUploadList',
               'New-ValentiaCredential', 'New-ValentiaGroup', 'New-ValentiaFolder',
               'Set-ValentiaHostName', 'Set-ValentiaLocation', 'Show-ValentiaConfig',
               'Show-ValentiaGroup', 'Show-ValentiaPromptForChoice'

# Cmdlets to export from this module
CmdletsToExport = '*'

# Variables to export from this module
VariablesToExport = 'valentia'

# Aliases to export from this module
AliasesToExport = 'Task', 'Valep', 'CommandP', 'Vale', 'Command', 'Valea', 'Upload', 'UploadL',
               'Sync', 'Download', 'Go', 'Clean', 'Reload', 'Target', 'ipremark', 'ipunremark',
               'Cred', 'Rename', 'Initial'

# List of all modules packaged with this module
# ModuleList = @()

# List of all files packaged with this module
# FileList = @()

# Private data to pass to the module specified in RootModule/ModuleToProcess
# PrivateData = ''

# HelpInfo URI of this module
# HelpInfoURI = ''

# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''

}

マニフェストの注意点

各パラメータがハッシュテーブル形式で記述されていることがわかります。多くのパラメータは、読んでそのままですが注意を要する項目に触れておきましょう。

RootModule

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

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

NestedModule

このパラメータは、モジュールファイル(.psm1)を複数指定することで、対象のモジュールをまとめてモジュールファイルの記述(あるいバイナリモジュールの記述)通りに読み込んでからマニフェスト(.psd1)の記述で制御します。

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

FunctionToExport

モジュールファイルなどで、Export-ModuleMemberで指定したファンクションの制御に利用します。

マニフェストファイル(.psd1)が存在するとき、モジュールファイル(.psm1)で指定され、かつマニフェストのこのパラメータで指定されたファンクションのみがモジュールファンクションとして出力されます。

他にもFormatsToProcessでの書式設定ファイル(.formats.ps1xml)指定もありますが、ここでは割愛します。

マニフェストでのモジュール読み込み

マニフェストであっても、モジュールの読み込み方法は同様です。必ず、モジュールフォルダと同一の名称を付けて出力します。(valentiaの場合は、valentia.psd1)

この上で、モジュールファイル(.psm1)やバイナリモジュール(.dll)とともに配置されていることを確認してImport-Moduleすることでモジュールがインポートされます。

IV. Dynamic Modules

最後は、Dynamic Moduleです。

ダイナミックモジュールは、これまであげたモジュールと1つ大きな違いがあります。

現在のPowerShell セッション内部/インメモリにモジュール生成する

PowerShellセッションが切れるとダイナミックモジュールも消えます。この恒久的ではなく一時的なモジュールという性質が他と大きく異なる点です。

まずはダイナミックモジュールの生成を見てみましょう。

New-Moduleによるモジュール生成

ダイナミックモジュールを生成するには、New-Moduleコマンドレットを利用します。

functionとしてのダイナミックモジュール

例えば、以下のようなHelloと出すだけのダイナミックモジュールを実行するとWindowsで勝手にNameを付けてくれます。

PS> New-module -Scriptblock {function Hello {"Hello!"}}

ModuleType Version    Name                                ExportedCommands
- -------    ----                                -------
Script     0.0        __DynamicModule_c3a44fd4-d78c-41... Hello

functionとしてのダイナミックモジュールに名称をつける

もしモジュールに名前を付けたい場合は、-Nameパラメータで指定します。

PS> New-module -Scriptblock {function test {"Hello!"}} -Name hoge

ModuleType Version    Name                                ExportedCommands
- -------    ----                                -------
Script     0.0        hoge                                test

CustomObjectとしてのダイナミックモジュール

あるいは、-AsCustomObjectとすることでメソッドの追加も可能です。

$hoge = New-Module -Scriptblock {function hoge ($name) {"Hello, $name"}; function hogehoge ($name) {"Goodbye, $name"}} -AsCustomObject
$hoge | gm

   TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        -   -
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
hogehoge    ScriptMethod System.Object Goodbye();
hoge        ScriptMethod System.Object Hello();

メソッド実行できていますね。

PS> $hoge.hoge("guitarrapc")

Hello, guitarrapc

ダイナミックモジュール内部でfunctionを実行して結果だけを返す

最後に-ReturnResultを指定して、スクリプトブロックの実行内容を返します。testファンクションの実行結果が出力されています。

PS> New-Module -ScriptBlock {function test {"test!"}; test} -returnResult

test!

ダイナミックモジュールは、その場限りのモジュールなため、私自身はそれほど使いませんが、繰り返し実行にはいいかも知れませんね。

モジュールへのコマンドレット配置手法

PowerShellスクリプト単独でモジュールを作る場合、モジュールのファンクションや動作をモジュールファイル(.psm1)で定め、マニフェスト(.psd1)で公開する情報を制御するマニフェストモジュールが最も利用されることでしょう。

モジュールファイルで複数のファンクションを読み込ませるにあたり、どのように配置するべきかを考えるとおよそ2つあります。

手法 概要 メリット デメリット
.psm1に直接書き込む .psm1内部でファンクションを直接記述 変数の受け渡しが容易 ファンクションが増加すると著しく可読性、保守性が落ちる
.ps1分散/.psm1にドットソース読込 .ps1に各ファンクションを記述し.psm1でドットソースで読み込む 機能別に分離しており可読性、保守性が良い 変数の受け渡しで工夫を要する

それぞれのメリットとデメリットを見てみましょう。

1. モジュールファイル(.psm1)に直接書き込む

モジュールファイル内部で、利用するファンクションを直接記述し、モジュールファイル(.psm1)の最後でExport-ModuleMemberにて制御する方法です。

この手法で開発されている有名なライブラリとしては、psakeがあります。

また、guitarrapc / PS-SumoLogicAPIもこの手法で記述しています。

メリット

非常に直観的で、変数などのインテリセンスを何も考えずとも.psm1さえ開いていれば効くため、書いている時は楽です。

デメリット

すべてのファンクションが1つのファイルに存在するのは、可読性を著しく下げます。 特にGitHubのようなバージョン管理ツールと相性が悪く、一部の機能でもファイル全体の更新という結果になります。

切り替えタイミング

3,4個のファンクションであったり、さくっと書いただけというかき捨てならばともかく、モジュールがある程度の規模になった場合は、「.ps1へ分散」を検討するべきです。

2. スクリプトファイル(.ps1)分散/モジュールファイル(.psm1)にドットソース読込

モジュールで利用するファンクションを、1つ1つスクリプトファイル(.ps1)に分散させ、モジュールファイル(.psm1)にはフォットソースで読み込む手法です。

この手法で開発されている有名なライブラリとしては、Pesterchocolateyがあります。

また、guitarrapc / valentiaもこの手法で開発しています。

メリット

機能別に分離しており可読性、保守性が高くなります。 また、GitHubのようなバージョン管理ツールでも、変更があった箇所のみの更新となります。

デメリット

変数の受け渡しや、インテリセンスが .psm1に直接すべて記述するのと比べて難があります。

が、大した手間ではない上に、それにも勝る保守性の良さがあります。

スクリプトファイルの読み込み

さて、スクリプトファイル(.ps1) を分散させている場合、今度はその読み込みをどのように簡略化するか考える必要があります。

よく見かけるこれは、非推奨です。

. $psScriptRoot\hoge.ps1
. $psScriptRoot\fuga.ps1
. $psScriptRoot\piyo.ps1
. $psScriptRoot\foo.ps1
. $psScriptRoot\bar.ps1

なぜなら、スクリプトファイルが増えるごとに毎回メンテナンスが必要です。

functionを格納するフォルダを生成し、フィルタして自動的に読み込ませましょう。

Pesterの場合は、このようにしています。

Get-ChildItem $pester.fixtures_path -Include "*.ps1" -Recurse |
    ? { $_.Name -match "\.Tests\." } |
    % { & $_.PSPath }

valentiaの場合も、似たような形式です。

Resolve-Path (Join-Path $valentia.modulePath $valentia.helpersPath) |
    where { -not ($_.ProviderPath.Contains(".Tests.")) } |
    % { . $_.ProviderPath }

まとめ

想定外(遅刻した! ) の長さになってしまいましたが、おおよそモジュールに関する説明となりました。

プロジェクトの規模に応じて、スクリプトモジュール、マニフェストモジュールを選択し、メンテナンス性を考えてスクリプトファイル(.ps1)を配置、開発できるとよいでしょう。

明日は、@rbtnnさんです。がんばってください!

*1:私はVisual Studio 2013で行っていますが、VS 2010/2012でも可能です。




以上の内容はhttps://tech.guitarrapc.com/entry/2013/12/03/014013より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14