あまりこの現象に遭遇することはないと思いますが、遭遇してそれなりに原因究明に時間がかかったのでまとめておこうと思います。
AWSにはWindows環境でAPIを実行するツールとして、Tools for PowerShell と SDK for .NET があります。
AWS Tools for Windows PowerShell
AWS SDK for .NET | アマゾン ウェブ サービス(AWS 日本語)
さて、SDK for .NET を参照してC#で作成したPowerShell のCmdletバイナリを作った上で、PowerShellスクリプトの中で Tools for PowerShell と作成したCmdletの両方をインポートしてCmdletで定義したコマンドを実行していました。すると、なんということでしょう!MissingMethodException
が出るようになってしまったのです。
MissingMethodException
というのは、つまりビルドしたときの参照DLLと実行時の参照DLLが異なっているから起きているわけですが、そのCmdletはビルドしてできたDLL群をそのまま配置して実行していたため、最初は「???」という状態でした。
いろいろ調べていくうちに原因がわかりました。
AWS Tools for Windows PowerShell は AWS SDK for .NET の.NET 3.5版と思われるバイナリを参照する
AWS Tools for Windows PowerShell はインストーラーとして提供されていますが、インストールするとC:\Program Files (x86)\AWS Tools\PowerShell\AWSPowerShell\
以下に参照するDLL群を配置します。このDLLなんですが、どうやらAWS SDK for .NET と同じアセンブリ名をつけており、AssemblyDescription
を見るに.NET 3.5向けのバイナリのようです*1。
コード*2
var assembly = Assembly.LoadFrom(@"D:\tmp\AWSSDK.ElasticLoadBalancing-toolsforps.dll"); assembly.FullName.Dump(); assembly.GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false) .Cast<AssemblyDescriptionAttribute>().FirstOrDefault()?.Description.Dump();
AWS SDK for .NET は .NET 4.5を参照している
これはCmdletクラスを作成するときのTarget Frameworkが.NET 4.5以上にしていますし、実行環境も.NET 4.5以上なのでまあ当たり前です。で、こちらのDLLの情報見るとアセンブリバージョンが完全に一致していることがわかります。
.NET 3.5 では(当然のごとく)Async系のメソッドが定義されていない
まあ、これはそうですよね。ソースコードレベルでメソッドが定義されていないか確認できればいいのですが、Tools for PowerShellの方はコードが提供されていないので*3実際に参照してコードを書いてみるとコード補完されないことがわかるでしょう。
つまり混ぜるな危険
SDK for .NET を参照して作ったCmdletクラスが.NET 4.5以上向けであって、Async系のメソッドを利用している場合を考えます。
- Cmdletは普通にビルドできて配置できる
Imrpot-Module AWSPowerShell
すると.NET 3.5向けのDLLが先に読み込まれるImport-Module MyCmdlet.dlll
すると*4、AWS SDK のアセンブリをロードしようとするが、先に同じ名前*5で.NET 3.5向けのDLLが読み込まれているため、.NET 4.5向けのDLLは読み込まれない- MyCmdletの中で.NET 4.5向けでしか存在しないメソッド(Async系など)を呼び出している部分を実行しようとしたとき、実際にそのときに読み込まれているDLLは.NET 3.5向けのDLLなのでメソッドが存在せず
MissingMethodException
がスローされる
という流れで起きていました。
対策は?
一時しのぎの対策はいくつかあると思うんですが、本質的な対策をどうするか?というのは割と悩んでいます。例えば Tools for PowerShell が参照するDLLのアセンブリ名がSDK for .NET と違えば問題ないんですが、本当に SDK for .NET の.NET 3.5向けのアセンブリをそのまま利用しているのであれば、そのアセンブリ名を変更してほしいというのもどうだろう?という気がします*6。
かといって、今更Cmdletを.NET 3.5に落として書くのも苦痛です*7。
きれいな対策はまだ思いついていないのですが、もし同じ問題が起きている人がいたら参考になればと思いまとめておきました。
おまけ
このブログは結果が分かってから書いたので、この順番になっていますが、実際原因探っているときはまったくわけわからん状態でした。で、MissingExceptionが出ているということはきっと参照しているDLLが想定しているものとは違うはずだ!ということでこのコードを埋め込んで、DLLがTools for PowerShell由来であることを確かめました、
var assembly = typeof(AmazonElasticLoadBalancingClient).Assembly; Console.WriteLine($"{assembly.Location} {assembly.FullName}");