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を利用する方法を参考にしていただければ幸いです。
目次
- 目次
- なぜモジュールを利用するのか
- Windows PowerShell Module Concepts
- I. Script Module
- II. Binary Modules
- III. Manifest Modules
- IV. Dynamic Modules
- モジュールへのコマンドレット配置手法
- まとめ
なぜモジュールを利用するのか
とあるPowerShellerの思い
少し PowerShell を触るとわかりますが、 PowerShellコードは再利用性を持って記述することが容易です。そのため自作のファンクションをサクサク書いて利用するようになると、PowerShell.exe や PowerShell_Ise.exe を起動したときに自動的に読み込まれてほしいと思うようになります。
毎回書くとか人間のすることではありません。
簡易的な自動的なfunction読み込み : $Profile
簡易的なファンクション自動読み込み方法として、 $Profile にファンクションを記述するやり方があります。$Profileは、PowerShell.exe や PowerShell_Ise.exe 起動時に自動的に読み込まれるため、$Profileに記述したファンクションも自動的に実行され任意のタイミングで利用できるようになります。
$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同士では参照しあえます。(例えスクリプトファイル(.ps1)ごとに分離させてもドットソース読み込みしていれば同様です。)
- public のイメージ : Export-ModuleMember で出力することで、モジュール利用者が自由にコマンドレットを実行できます
つまり、モジュール内部で他のプログラミング言語のように用途に応じて細かくファンクションを作っても、外部に公開するファンクション や 変数は選べるということです。
通常のスクリプトファイル(.ps1) で 特定のファンクションを公開したくない場合、function{}内部にそのfunction{}をネストして記述するなどして隠す必要がありますが、モジュールではExport-ModuleMemberで制御できます。
- すべてのファンクションを公開する場合は、 ワイルドカード '*' を指定します
Export-ModuleMember -Function *
3. モジュールメンバーの実行 と 暗黙的なモジュールの読み込み
モジュールフォルダとモジュールファイル(.psm1)を同一名で作成を終えたら、PowerShell.exe か PowerShell_Ise.exe を起動して次のコマンドを入力してみてください。
write-hosthoge
結果、hogeと出力されます。つまり、hogeモジュールが自動的に読み込まれwrite-hosthogeコマンドレットが実行されているのです。
PS> write-hosthoge hoge
PowerShell 3.0以降は、モジュールメンバーは明示的なモジュールファイル読み込みをすることなく実行可能です。
4. モジュール読み込みの確認
モジュールの読み込み候補
PowerShell3.0 以降において、モジュールに対して暗黙的な読み込みが実装されました。
これにより、PowerShellでインテリセンス実行時に 自動的に 環境変数$Env:PSModulePath
を探索し、正しくモジュールと解釈できるモジュールのファンクションを読み込みます。
デフォルトの環境変数は以下の状態です。
C:\Users\UserName\Documents\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\
以下の3つのパスです。
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 -ComputerName
やNew-PSSession
などでWinRM経由で接続したときには、接続時にはこれらのモジュールが読み込まれています。
特にリモートセッションで特定のユーザーごとにモジュールを持たせるのが著しく手間であり、信用できるスクリプトの場合は大きく手間が省けることになります。
ユーザーレベルパス
以下が該当します。が、ドキュメントフォルダを移動している場合は、その限りではありません。
$env:USERPROFILE\Documents\WindowsPowerShell\Modules
ユーザーレベルパスは、現在のユーザーのPowerShellセッションでしか参照されません。
そのため、 ユーザー hoge さんで接続しているときに、ユーザーfugaさんのユーザーモジュールパスが参照されることはありません。
これは、ユーザーごとのモジュール選択可能性を可能にしており、PSリモーティングでも接続に利用したユーザーのユーザーフォルダしか参照されることはありません。
明示的なモジュールの読み込み
前述の通り、PowerShell 3.0以降は明示てきなモジュールの読み込みことなく暗黙的にモジュールの探索が行われ、コマンドレット実行時に モジュールの読み込みが自動的になされます。
ただ開発中の場合は、モジュールの読み込みが確認できれば、Export-ModuleMember で出力したメンバーを確認することもできるでしょう。 もし、コマンドレット実行前にモジュールを事前に読み込みたい、あるいはPowerShell 2.0を使っている場合は、明示的にモジュールを読み込んでみましょう。
Import-Module hoge -PassThru
ModuleType を見ることで、psm1読み込みによる Script Module であることがわかります。
PS> Import-Module hoge -PassThru ModuleType Version Name ExportedCommands ---------- ------- ---- ---------------- Script 0.0 hoge write-hosthoge
これでhogeモジュールが明示的に読み込まれ、write-hosthoge
ファンクションやモジュールに含まれる変数が読み込まれます。
もしモジュール読み込み時に、読み込まれたモジュール結果出力がいらない場合は、-PassThruスイッチを外します。
Import-Module hoge
このように読み込まれたモジュール結果が出力されません。
PS> Import-Module hoge -PassThru PS>
読み込まれるモジュールメンバーの確認
Import-Module 時に、-Verboseスイッチを付けることでモジュール読み込み時にインポートされたファンクション/変数が確認できます。
Import-Module hoge -PassThru -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つとして紹介にとどめますが、非常に参考になるセッションでした。
さて、バイナリモジュールにおける .dll 生成と モジュール読み込みまでを見てみましょう。
1. Visual Studioで.dllを生成する
PowerShellのバイナリモジュールは C#/VB でコーディング可能です。書くなら断然 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! と出力されます
Get-string
hogehoge!
簡単な流れでしたが、C# で Cmdletを記述しビルドした.dll をPowerShellで読み込み、実行できます。
VSと C# のサポートをフルに受けれるので、開発としては非常に楽なのは確かです。
ここまで詳解したスクリプトモジュール(.psm1) やバイナリモジュール(.dll) ですが、モジュール読み込み時に マニフェスト(.psd1) を利用することで「モジュールバージョン」や「作者情報」、「出力する Cmdletや変数」などの制御が可能です。
さっそくマニフェストモジュールによる、モジュール制御を見てみましょう。
III. Manifest Modules
マニフェストモジュールは、 先述したスクリプトモジュールやバイナリモジュールをマニフェスト(.psd1)を用いてきめ細かく制御します。
例えば、「モジュールのバージョン」や「作者情報」を考えても、スクリプトモジュール(.psm1)やバイナリモジュール(.dll)では、Import-Module でモジュールを読み込んだ時にバージョンなどを指定できず、またモジュール内部で仕込む場所もありません。*2。
他にもマニフェストファイル(.psd1) を利用することで、複数のスクリプトモジュールをまとめて1つのモジュールとして読み込ませたりも可能になります。(Nested Module)
PowerShell 4.0 からは、マニフェストに記述したバージョンを Get-Module や Import-Module で指定できるようになったため一層使いやすくなりました。
では具体的に、マニフェストファイルの生成から内容まで見ていきましょう。
マニフェストの生成
マニフェストの生成には、専用のCmdlet が用意されています。
New-ModuleManifest
まずはサンプルとして空のマニフェストを作ってみてみます。
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)で指定され、かつマニフェストのこのパラメータで指定されたファンクションのみがモジュールファンクションとして出力されます。
ToExport は基本的に同様に働き、変数やAliasも制限が可能です。
他にも 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を付けてくれます。
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();
実行してみましょう。
$hoge.hoge("guitarrapc")
メソッド実行できていますね。
Hello, guitarrapc
ダイナミックモジュール内部でfunctionを実行して結果だけを返す
最後に、-ReturnResultとすることで、スクリプトブロックの実行内容を返します。
New-Module -ScriptBlock {function test {"test!"}; test} -returnResult
結果、test ファンクションが実行された結果が変えります。
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つ一つスクリプトファイル(.ps1)に分散させ、モジュールファイル(.psm1)にはフォットソースで読み込む手法です。
この手法で開発されている有名なライブラリとしては、Pesterやchocolateyがあります。
また、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さんです。がんばってください!