標準ライブラリ バイナリ モジュールを作成する方法

私は最近、バイナリモジュールとして実装したいモジュールのアイデアを持っていました。 PowerShell 標準ライブラリ を使用して作成していないので、これは良い機会のように感じました。 私は、クロスプラットフォームバイナリモジュールの作成 ガイドを使用して、このモジュールをロードブロックなしで作成しました。 私たちは同じプロセスを歩くつもりです、そして私は道に沿って少し余分な解説を追加します。

手記

この記事の 元のバージョン は、@KevinMarquetteによって書かれたブログに登場しました. PowerShell チームは、このコンテンツを Microsoft と共有してくれた Kevin に感謝します。 PowerShellExplained.comで彼のブログをチェックしてください.

PowerShell 標準ライブラリとは

PowerShell 標準ライブラリを使用すると、PowerShell と Windows PowerShell 5.1 の両方で動作するクロス プラットフォーム モジュールを作成できます。

バイナリ モジュールの理由

C# でモジュールを記述する場合は、PowerShell コマンドレットと関数に簡単にアクセスできます。 ただし、他の多くの PowerShell コマンドに依存しないモジュールを作成する場合は、パフォーマンス上の利点が大きくなる可能性があります。 PowerShell は、コンピューターではなく管理者向けに最適化されました。 C# に切り替えることで、PowerShell によって追加されるオーバーヘッドを排除できます。

たとえば、JSON とハッシュテーブルで多くの作業を行う重要なプロセスがあります。 PowerShell はできるだけ最適化しましたが、プロセスが完了するまでに 12 分かかります。 モジュールには既に多くの C# スタイルの PowerShell が含まれていました。 これにより、バイナリ モジュールへの変換がクリーンで簡単になります。 バイナリ モジュールに変換することで、処理時間を 12 分以上から 4 分以下に短縮しました。

ハイブリッド モジュール

バイナリ コマンドレットと PowerShell の高度な関数を混在させることができます。 スクリプト モジュールに関する知識はすべて同じように適用されます。 空の psm1 ファイルが含まれているため、後で他の PowerShell 関数を追加できます。

私が作成したコンパイル済みのコマンドレットのほとんどすべてが、最初に PowerShell 関数として開始されました。 私たちのバイナリモジュールはすべて本当にハイブリッドモジュールです。

スクリプトをビルドする

私はここでビルドスクリプトをシンプルにしました。 通常、CI/CD パイプラインの一部として大きな Invoke-Build スクリプトを使用します。 Pester テストの実行、PSScriptAnalyzer 実行、バージョン管理、PSGallery への公開など、より多くのマジックが行われます。 モジュールのビルド スクリプトを使用し始めたら、それに追加するものをたくさん見つけることができました。

モジュールの計画

このモジュールの計画は、C# コード用の src フォルダーを作成し、スクリプト モジュールの場合と同様に残りの部分を構成することです。 これには、ビルド スクリプトを使用してすべてを Output フォルダーにコンパイルすることが含まれます。 フォルダー構造は次のようになります。

MyModule
├───src
├───Output
│   └───MyModule
├───MyModule
│   ├───Data
│   ├───Private
│   └───Public
└───Tests

はじめに

まず、フォルダーを作成し、Git リポジトリを作成する必要があります。 モジュール名のプレースホルダーとして $module を使用しています。 これにより、必要に応じてこれらの例を再利用しやすくなります。

$module = 'MyModule'
New-Item -Path $module -Type Directory
Set-Location $module
git init

次に、ルート レベルのフォルダーを作成します。

New-Item -Path 'src' -Type Directory
New-Item -Path 'Output' -Type Directory
New-Item -Path 'Tests' -Type Directory
New-Item -Path $module -Type Directory

バイナリ モジュールのセットアップ

この記事はバイナリモジュールに焦点を当てているので、そこから始めます。 このセクションでは、「クロスプラットフォーム バイナリ モジュールの作成」 ガイドの例を取り上げます。 詳細が必要な場合や問題がある場合は、そのガイドを確認してください。

まず、インストールした dotnet core SDK のバージョンを確認します。 2.1.4 を使用していますが、続行する前に 2.0.0 以降が必要です。

PS> dotnet --version
2.1.4

私はこのセクションの src フォルダから作業しています。

Set-Location 'src'

dotnet コマンドを使用して、新しいクラス ライブラリを作成します。

dotnet new classlib --name $module

これにより、ライブラリプロジェクトがサブフォルダーに作成されましたが、その追加レベルの入れ子は望んでいません。 私はそれらのファイルをレベル上げするつもりです。

Move-Item -Path .\$module\* -Destination .\
Remove-Item $module -Recurse

プロジェクトの .NET Core SDK バージョンを設定します。 私は2.1 SDKを持っているので、私は 2.1.0を指定するつもりです. 2.0 SDK を使用している場合は、2.0.0 を使用します。

dotnet new globaljson --sdk-version 2.1.0

PowerShell Standard LibraryNuGet パッケージ をプロジェクトに追加します。 必要な互換性レベルで使用できる最新バージョンを使用してください。 私はデフォルトで最新バージョンですが、このモジュールは PowerShell 3.0 より新しい機能を利用しているとは思いません。

dotnet add package PowerShellStandard.Library --version 7.0.0-preview.1

次のような src フォルダーが必要です。

PS> Get-ChildItem
    Directory: \MyModule\src

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        7/14/2018   9:51 PM                obj
-a----        7/14/2018   9:51 PM             86 Class1.cs
-a----        7/14/2018  10:03 PM            259 MyModule.csproj
-a----        7/14/2018  10:05 PM             45 global.json

これで、独自のコードをプロジェクトに追加する準備ができました。

バイナリ コマンドレットのビルド

このスターター コマンドレットを含む src\Class1.cs を更新する必要があります。

using System;
using System.Management.Automation;

namespace MyModule
{
    [Cmdlet( VerbsDiagnostic.Resolve , "MyCmdlet")]
    public class ResolveMyCmdletCommand : PSCmdlet
    {
        [Parameter(Position=0)]
        public Object InputObject { get; set; }

        protected override void EndProcessing()
        {
            this.WriteObject(this.InputObject);
            base.EndProcessing();
        }
    }
}

クラス名と一致するようにファイルの名前を変更します。

Rename-Item .\Class1.cs .\ResolveMyCmdletCommand.cs

その後、モジュールをビルドできます。

PS> dotnet build

Microsoft (R) Build Engine version 15.5.180.51428 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

Restore completed in 18.19 ms for C:\workspace\MyModule\src\MyModule.csproj.
MyModule -> C:\workspace\MyModule\src\bin\Debug\netstandard2.0\MyModule.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:02.19

新しい dll の Import-Module を呼び出して、新しいコマンドレットを読み込むことができます。

PS> Import-Module .\bin\Debug\netstandard2.0\$module.dll
PS> Get-Command -Module $module

CommandType Name                    Version Source
----------- ----                    ------- ------
Cmdlet      Resolve-MyCmdlet        1.0.0.0 MyModule

システムでインポートが失敗した場合は、.NET を 4.7.1 以降に更新してみてください。 クロスプラットフォーム バイナリ モジュールの作成 ガイドでは、.NET のサポートと以前のバージョンの .NET の互換性について詳しく説明します。

モジュール マニフェスト

dllをインポートして作業モジュールを持つことは素晴らしいですね。 私はそれに進み続け、モジュールマニフェストを作成するのが好きです。 後で PSGallery に発行する場合は、マニフェストが必要です。

プロジェクトのルートから、このコマンドを実行して、必要なモジュール マニフェストを作成できます。

$manifestSplat = @{
    Path              = ".\$module\$module.psd1"
    Author            = 'Kevin Marquette'
    NestedModules     = @('bin\MyModule.dll')
    RootModule        = "$module.psm1"
    FunctionsToExport = @('Resolve-MyCmdlet')
}
New-ModuleManifest @manifestSplat

また、将来の PowerShell 関数用に空のルート モジュールを作成する予定です。

Set-Content -Value '' -Path ".\$module\$module.psm1"

これにより、通常の PowerShell 関数とバイナリ コマンドレットの両方を同じプロジェクトに混在できます。

完全なモジュールのビルド

私はすべてをまとめて出力フォルダにコンパイルします。 そのためには、ビルド スクリプトを作成する必要があります。 私は通常、これを Invoke-Build スクリプトに追加しますが、この例では単純に保つことができます。 これをプロジェクトのルートにある build.ps1 に追加します。

$module = 'MyModule'
Push-Location $PSScriptRoot

dotnet build $PSScriptRoot\src -o $PSScriptRoot\output\$module\bin
Copy-Item "$PSScriptRoot\$module\*" "$PSScriptRoot\output\$module" -Recurse -Force

Import-Module "$PSScriptRoot\Output\$module\$module.psd1"
Invoke-Pester "$PSScriptRoot\Tests"

これらのコマンドにより、DLL がビルドされ、output\$module\bin フォルダーに配置されます。 その後、他のモジュール ファイルが所定の場所にコピーされます。

Output
└───MyModule
    ├───MyModule.psd1
    ├───MyModule.psm1
    └───bin
        ├───MyModule.deps.json
        ├───MyModule.dll
        └───MyModule.pdb

この時点で、psd1 ファイルを使用してモジュールをインポートできます。

Import-Module ".\Output\$module\$module.psd1"

ここから、.\Output\$module フォルダーを $Env:PSModulePath ディレクトリにドロップし、必要なときにいつでもコマンドを自動読み込みできます。

更新: dotnet new PSModule

dotnet ツールには PSModule テンプレートがあることを学びました。

上記で説明したすべての手順は引き続き有効ですが、このテンプレートによって多くの手順が省略されています。これはまだかなり新しいテンプレートで、さらに洗練が加えられているところです。 ここから良くなるのを期待してください。

PSModule テンプレートをインストールして使用する方法を次に示します。

dotnet new -i Microsoft.PowerShell.Standard.Module.Template
dotnet new psmodule
dotnet build
Import-Module "bin\Debug\netstandard2.0\$module.dll"
Get-Module $module

この最小限に実行できるテンプレートは、.NET SDK、PowerShell 標準ライブラリの追加を処理し、サンプル クラスをプロジェクトに作成します。 すぐにビルドして実行できます。

重要な詳細

この記事を終了する前に、説明する価値のあるその他の詳細をいくつか紹介します。

DLL のアンロード処理

バイナリ モジュールが読み込まれたら、実際にアンロードすることはできません。 DLL ファイルは、アンロードするまでロックされます。 これは、変更を加えてビルドするたびにファイルがロックされることが多いため、開発時に煩わしい場合があります。 これを解決する唯一の信頼性の高い方法は、DLL を読み込んだ PowerShell セッションを閉じる方法です。

VS Code のウィンドウの再読み込みアクション

私はほとんどの PowerShell 開発作業を VS Codeで行います。 バイナリ モジュール (またはクラスを含むモジュール) で作業している場合、ビルドするたびに VS Code を再読み込みする習慣が身に付きました。 Ctrl+Shift+P コマンド ウィンドウがポップされ、Reload Window は常にリストの一番上にあります。

入れ子になった PowerShell セッション

もう 1 つの選択肢は、適切な Pester テスト カバレッジを使用することです。 その後、build.ps1 スクリプトを調整して、新しい PowerShell セッションを開始し、ビルドを実行し、テストを実行して、セッションを閉じます。

インストールされているモジュールの更新

ローカルにインストールされているモジュールを更新しようとすると、このロックが煩わしい場合があります。 これがいずれかのセッションで読み込まれている場合は、それを見つけて閉じる必要があります。 モジュールのバージョン管理では新しいものが別のフォルダーに配置されるため、PSGallery からインストールする場合の問題は少なくなります。

ローカルの PSGallery を設定し、ビルドの一部としてそれに発行することができます。 その後、その PSGallery からローカル インストールを行います。 これは多くの作業のように聞こえますが、これは Docker コンテナーを開始するのと同じくらい簡単です。 PSRepository 用に NuGet サーバーを使用するに関する投稿で、これを行う方法が説明されています。

最終的な考え

コマンドレットを作成するための C# 構文については触れていませんが、Windows PowerShell SDKには多くのドキュメントがあります。 それは間違いなく、より深刻なC#への足がかりとして試してみる価値があります。