からめもぶろぐ。

CSOM 完全に理解した

PowerShell Core で Binary Module (C#) を開発するときの注意点について

この記事は「PowerShell Advent Calendar 2018」の参加記事です。

qiita.com

サンプルとして簡単な JSON を返すコマンドレットを持つモジュールを作成します。対象の PowerShell Core のバージョンは 6.1.0 です。

サンプル コード

SampleModule.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
    <PackageReference Include="System.Management.Automation" Version="6.1.0" />
  </ItemGroup>
  <ItemGroup>
    <None Update="SampleModule.psd1">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

SampleModule.psd1

@{
    RootModule = 'SampleModule.dll'
    ModuleVersion = '1.0.0'
    CmdletsToExport = "*"
}

WriteHelloWorldCommand.cs

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

namespace SampleModule
{
    [Cmdlet("Write", "HelloWorld")]
    public class WriteHelloWorldCommand : PSCmdlet
    {
        protected override void ProcessRecord()
        {
            this.WriteObject(JsonConvert.SerializeObject(new { Message = "Hello World" }));
        }
    }
}

実行結果

dotnet publish してから Import-Module でモジュールを読み込んで実行してみますがエラーになります。

C:\SampleModule> dotnet publish
C:\SampleModule> Import-Module "C:\SampleModule\bin\Debug\netcoreapp2.1\publish\SampleModule.psd1"
C:\SampleModule> Write-HelloWorld
Write-HelloWorld : Could not load file or assembly 'Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'. Could not find or load a specific file. (Exception from HRESULT: 0x80131621)
At line:1 char:2
+  Write-HelloWorld
+  ~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [Write-HelloWorld], FileLoadException
+ FullyQualifiedErrorId : System.IO.FileLoadException,SampleModule.WriteHelloWorldCommand

何が起こっているのか?

PowerShell Core は自身が Json.NET をライブラリとして使っています。PowerShell Core 6.1 系では Json.NET 11.0.2 が同梱されています。PowerShell Core を起動した時点で 11.0.2 がロードされてしまっているので、インポートしたモジュールで異なるバージョンのアセンブリを読もうとするとエラーになってしまいます。この問題を解決するには、モジュールが使う Json.NET のバージョンを常に PowerShell Core が使っている Json.NET のバージョンと合わせる必要があります。

根底としては PowerShell Core の問題ではなく .NET Core の問題 (AppDomain をサポートしていないことによる) なのですが、こんな時代になっても DLL 地獄に悩まされるのはどうなのかなあと思ってしまいます。

なお GitHub でもだいぶ前から Issue は上がっていますが Open のままとなっています。ちなみにこちらでは、異なるモジュールをインポートしたときにそれぞれが異なるバージョンのアセンブリを使っているとエラーになるよという内容ですが、どちらかというとこちらの問題のほうがエグいですね。

github.com