これは、Add-Typeの方が[reflection.assembly]::LoadWithPartialName()より便利という記事です。
多くの場合Add-Typeはイイ感じに動作します。しかしAdd-Typeは気になる動作もします。
- [Reflection.Assembly]
- Add-Type
- Add-Type の失敗例
- Add-Type ではフルネームが必要になる場合がある
- PowerShell の Type Conversion
- 結論
- まとめ
- 余談
[Reflection.Assembly]
Windowsは、 .Netを利用することで快適に使えます。ただ.Netは膨大な規模なのですけど、PowerShellのデフォルトではコアになる部分しか読み込まれていません。
PowerShellでデフォルト参照されていない.NETアセンブリを利用したい。わけで、.Netアセンブリをメモリに読み込む機会も多々あります。*1
たとえば、.Net 4.5で追加されたHttpClientを利用しようとすると
[System.Net.Http.HttpClient]
参照できずエラーがでます。
Unable to find type [System.Net.Http.HttpClient]. Make sure that the assembly that contains this type is loaded.
At line:1 char:1
+ [System.Net.Http.HttpClient]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.Http.HttpClient:TypeName) [], RuntimeException
+ FullyQualifiedErrorId : TypeNotFound
PowerShell 1.0では、明示的に参照するために[System.Reflection.Assembly]のStaticメソッドで読み込みをしました。
なお、Systemは省略可能なので、[Reflection.Assembly]と書けます。
Load メソッド
まずはLoad() メソッドでしょうか。え、使わない? ソウデスネ。Fullnameを求めるので使ってられません。そんなの覚えてられるか。書きたくない!
[Reflection.Assembly]::Load("System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
LoadFrom メソッド
それならLoadFrom() って? C# のように参照に追加してあとはさくっと名前で参照とかできないのでツラいですね。
[Reflection.Assembly]::LoadFrom("C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5.1\System.Net.Http.dll")
LoadWithPartialName() メソッド
で、良く出てきているのがこれです。
[Reflection.Assembly]::LoadWithPartialName("System.Net.Http")
気分的にはこっちが見慣れたフルネームですし、うまく動いていますね。しかし、Stringなのでタブ補完効かないしめんどくさい。
[System.Net.Http.HttpClient]
IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False HttpClient System.Net.Http.HttpMessageInvoker
Add-Type
PowerShell 2.0で、 PowerShellは改善策を打ってきました。それが、Add-Type Cmdletです。
たとえば、先ほどの
[Reflection.Assembly]::LoadWithPartialName("System.Net.Http")
は、Add-Typeでこう書けます。
Add-Type -AssemblyName System.Net.Http
これなら、タブ補完も効くしいい感じですね! やった! だいたいの場合は。
問題は、Partial Nameで判別ができない場合です。
複数バージョンのアセンブリがインストールされている場合が問題
しかし、 Add-Typeは、これとは別の内部テーブルを使っているようで、 [Reflection.Assembly]::LoadWithPartialName()とは違う結果がもたらされます。
もし複数のアセンブリが異なるバージョンでインストールされている場合、 Add-Type -AssembryNameは判別する方法を持ちません。で、だいたいの場合は古いバージョンが参照されて、新しいバージョンを参照しているスクリプトは失敗します。
Add-Type の失敗例
例えばSQLServer,SMOは、厄介です。
v10.0.0.0とv11.0.0.0などがありますが、Add-Typeで最新が参照されるでしょーっと気にせず追加しようとすると...。
Add-Type -AssemblyName Microsoft.SqlServer.SMO
PowerShellエンジンは古いバージョンを読み込むどころか、アセンブリの読み込みに失敗します。v9.0.242.0って、やだー。ないよそんなのれがしー。
Add-Type : Could not load file or assembly 'Microsoft.SqlServer.Smo, Version=9.0.242.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91' or one of its dependencies. The system cannot find the file specified.
At line:1 char:1
+ Add-Type -AssemblyName Microsoft.SqlServer.SMO
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Add-Type], FileNotFoundException
+ FullyQualifiedErrorId : System.IO.FileNotFoundException,Microsoft.PowerShell.Commands.AddTypeCommand
Add-Type ではフルネームが必要になる場合がある
この場合、バージョンを的確に指定する、つまりフルネームならいけます。
例えば私の環境*2の場合は、v11.0.0.0を指定すればokです。
Add-Type -AssemblyName "Microsoft.SqlServer.SMO, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"
しかし、もう1回言いますがフルネームはない。
[Reflection.Assembly]::LoadWithPartialName() なら大丈夫
Microsoft.SqlServer.Smoだけで読み込んで欲しいじゃないですか。で、これならok。
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
はい、ok。
GAC Version Location --- ------- -------- True v2.0.50727 C:\Windows\assembly\GAC_MSIL\Microsoft.SqlServer.SMO\11.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.SMO.dll
ただし、この場合はどのバージョンが読み込まれるかは保証できません、が、とりあえず読んでくれる。やった。Add-Typeどの~。
PowerShell の Type Conversion
この問題には、 PowerShellの型変換システムが絡んでいます。
[Reflection.Assembly]::LoadWithPartialName()メソッドは裏でうまくアセンブリを判別し、またPowerShellの型変換アルゴリズムを使ってくれています。素晴らしい。*3
PowerShellの10ある型変換アルゴリズムはここを見てください。
1. **Direct assignment.** If your input is directly assignable, simply cast your input to that type - **Language-based conversion.** These language-based conversions are done when the target type is void, Boolean, String, Array, Hashtable, PSReference (i.e.:[ref]), XmlDocument (i.e.:[xml]). Delegate (to support ScriptBlock to Delegate conversions), and Enum - **Parse conversion.** If the target type defines a Parse() method that takes that input, use that - **Static Create conversion.** If the target type defines a static ::Create() method that takes that input, use that - **Constructor conversion.** If the target type defines a constructor that takes your input, use that - **Cast conversion.** If the target type defines a implicit or explicit cast operator from the source type, use that. If the source type defines an implicit or explicit cast operator to the target type, use that - **IConvertible conversion.** If the source type defines an IConvertible implementation that knows how to convert to the target type, use that - **IDictionary conversion.** If the source type is an IDictionary (i.e.: Hashtable), try to create an instance of the destination type using its default constructor, and then use the names and values in the IDictionary to set properties on the source object - **PSObject property conversion.** If the source type is a PSObject, try to create an instance of the destination type using its default constructor, and then use the property names and values in the PSObject to set properties on the source object. . If a name maps to a method instead of a property, invoke that method with the value as its argument - **TypeConverter conversion.** If there is a registered TypeConverter or PSTypeConverter that can handle the conversion, do that. You can register a TypeConverter through a types.ps1xml file (see: $pshome\Types.ps1xml), or through Update-TypeData
しかし、Add-Typeはこの型変換を使わず、内部に変換テーブルを持っているようです。で、失敗すると。
なお、Add-Typeがあるためか[Reflection.Assembly]::LoadWithPartialName()は廃止予定とPowerShell Teamは言ってます。*4
結論
もし、 Add-Typeでアセンブリが読める + 読まれたバージョンで問題がないなら
Add-Type -AssemblyName System.Net.Http
もし、Add-Typeの内部テーブルにアセンブリがなく読み込めない、バージョンがわからないけどとりあえずどれが読まれてもいいなら、
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
もし、アセンブリのバージョンを指定したい、または将来上の2つが使えなくなった場合は、
Add-Type -AssemblyName "Microsoft.SqlServer.SMO, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"
がいいでしょう。
まとめ
Add-Typeかわいいよ。もっと賢くなるともっと好き。
余談
来月2014年04月12日第1回PowerShell勉強会@大阪でセッションをします。
Japan PowerShell User Group (JPPOSH)の第2回としてとかなんとか。前回のJPPOSH第1回は東京での開催で、今回が初の地方で大阪です。
私は、 PowerShellを使ってて本当に役に立ってる! 隙間的な役割を十全に果たせているのはどこか、と聞かれると「デプロイ」だと思っています。そこで、今回は DSC と デプロイ に関してセッションしようかと。
valentiaをはじめとしてAppVeyor - AppRollaなども自作のPowerShellによるデプロイを持っています。
DSCと、これらはどう違うのか。何を目指しているのか。
まだまだ席があるようなので、ぜひよろしくお願いします。