【PowerShell】クラス内で.NETアセンブリを利用する際の注意点

### 前置き
PowerShell を使っていたら「型[～]が見つかりません。」というエラーが出たので、どう対応したかを纏めさせていただきました。
今回は`Microsoft.Office.Interop.Excel.XlDirection`というアセンブリを題材で話を進めさせていただきます。
サンプルプログラムはExcelが入っていないと動作しませんが、動作させなくても分かるように頑張って記載していきたいと思います。
※ `Microsoft.Office.Interop.Excel.XlDirection`はExcelの`Ctrl` + `十字キー`を押したときの動作をしてくれます

`XlDirection`は、リストの最終行を取得する際によく使ったりしますが、今回はエラーの解消方法について明記したいため、サンプルプログラムではExcelの最終行を`1048576`をコンソール出力するだけとなっています。


### 環境
Windows PowerShell 5.1
Excel 2016

### 確認したエディタ
Windows PowerShell ISE
Visual Studio Code

### クラス内で.NETアセンブリを利用する場合
このパターンで今回のエラーを確認しました。
`[Microsoft.Office.Interop.Excel.XlDirection]`に赤波線が付きます。

```powershell:HogeClass.ps1
class HogeClass {
    [void]hogeClassMethod() {
        $excel = New-Object -ComObject Excel.Application
        $book = $excel.Workbooks.Add()
        # 「型[Microsoft.Office.Interop.Excel.XlDirection]が見つかりません。」と赤波線でエラーが発生
        Write-Host $book.sheets(1).Range("A1").End([Microsoft.Office.Interop.Excel.XlDirection]::xlDown).row()
        $excel.Quit()
        $excel = $null
        [GC]::Collect()
    }
}
$hogeClass = New-Object HogeClass
$hogeClass.hogeClassMethod()
```

### メソッドで.NETアセンブリを利用する場合
このパターンはエラーが**出ません**でした。
メソッドに定義している場合は、実行前にコードのチェックが行われていないと推測しています。

```powershell:HogeMethod.ps1
function hogeMethod {
    $excel = New-Object -ComObject Excel.Application
    $book = $excel.Workbooks.Add()
    Write-Host $book.sheets(1).Range("A1").End([Microsoft.Office.Interop.Excel.XlDirection]::xlDown).row()
    $excel.Quit()
    $excel = $null
    [GC]::Collect()
}
hogeMethod
```

### コードのチェックでなぜ引っかかるのか
下記のコマンドで読み込まれているアセンブリを確認したところ、Powershellを起動した時点では`Microsoft.Office.Interop.Excel`が存在しないため、コードのチェックでエラーとして検知されいると推測しております。

``` powershell
 [System.AppDomain]::CurrentDomain.GetAssemblies() | ForEach-Object { $_.GetName().Name }
```

### 解消方法１
個人的にこの方法は非推奨です。
別ファイルで事前にアセンブリを読み込む方法です。

```powershell:Main.ps1
Set-Location (Split-Path -Parent $MyInvocation.MyCommand.Path)

# アセンブリを読み込む
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Interop.Excel")
# Add-Type -AssemblyName Microsoft.Office.Interop.Excel ← でも同じ動作します

.".\Hoge.ps1"
$hogeClass = New-Object HogeClass
$hogeClass.hogeClassMethod()
```
```powershell:HogeClass.ps1
class HogeClass {
    [void]hogeClassMethod() {
        $excel = New-Object -ComObject Excel.Application
        $book = $excel.Workbooks.Add()
        # エディタ上では「型[Microsoft.Office.Interop.Excel.XlDirection]が見つかりません。」の赤波線が残ったまま
        Write-Host $book.sheets(1).Range("A1").End([Microsoft.Office.Interop.Excel.XlDirection]::xlDown).row()
        $excel.Quit()
        $excel = $null
        [GC]::Collect()
    }
}
```
###### 非推奨理由
同じファイル内に記載するとコードのチェックのエラーが先に引っかかってエラーは解消されません。
そのため、使用例ではMain.ps1にアセンブリを読み込むコードを記載いています。

あと、赤波線は一度実行して、Hoge.ps1ファイルを開きなおさないと消えませんでした。
なので、テキストエディタ自体を閉じて、また開くと赤波線は復活します。

### 解消方法２
個人的にこの方法を推奨してます。
同一のファイル内でアセンブリを読み込みを行い、文字列で指定した値を`-as [type]`で.NET型に変換しています。

~~ただ今回のサンプルでは、`New-Object -ComObject Excel.Application`で
・Microsoft.Office.Interop.Excel
・office
・Microsoft.PowerShell.Commands.Utility.resources
の３つのアセンブリが読み込まれるため、別でアセンブリを読み込むコードは不要となってます。~~
Excel2013の環境で試したところ`New-Object -ComObject Excel.Application`利用時に新しくアセンブリが追加されなかったため、読み込むコードは記載する方が無難です。

```powershell:Hoge.ps1
# アセンブリを読み込む
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Interop.Excel")
# Add-Type -AssemblyName Microsoft.Office.Interop.Excel ← でも同じ動作します

class HogeClass {
    [void]hogeClassMethod() {

        $excel = New-Object -ComObject Excel.Application
        $book = $excel.Workbooks.Add()

        # 追記箇所
        $xlDirection = "Microsoft.Office.Interop.Excel.XlDirection" -as [type]

        # [Microsoft.Office.Interop.Excel.XlDirection] ⇒ 上で定義した「$xlDirection」に変更
        Write-Host $book.sheets(1).Range("A1").End($xlDirection::xlDown).row()
        $excel.Quit()
        $excel = $null
        [GC]::Collect()
    }
}
$hogeClass = New-Object HogeClass
$hogeClass.hogeClassMethod()
```
###### 推奨理由
・１つのファイルで完結する点
・赤波線がつかない点
・コードを変数に定義することで短く記載できる点
でこちらの方が使いやすいと感じました。

###### いまいちなところ
`$xlDirection = "Microsoft.Office.Interop.Excel.XlDirection" -as [type]`で誤字脱字があった場合、実行するまで気づけない。

### まだもやもやしているところ
本文中では「コードのチェック」と記載させていただいておりますが、「クラス定義にしたらなぜチェックができるのか？」というところが疑問です。
静的解析機能がクラス内では適用されるとかの表現が正しいのでしょうか？

どなたかご存知でしたらコメント欄で教えてください。

### 終わりに
今回、解消方法１の方は調べたらすぐに見ったのですが、「微妙やな～」って思いながら粘って探したら解消方法２を偶然見つけれたのでラッキーでした。
ただ、一番いいのはアセンブリの読み込みをするコードの記述があったら、それをくみ取ってコードのチェックをしてほしいですね。試してないですが、PowerShell Coreだとできるんですかね。

ということで、ここまで読んでくださいまして、ありがとうございました。
この記事を見ていただいた方に少しでもお役に立てたなら嬉しいです。


### 参考元URL
[PowerShell でロード済みのアセンブリを確認する](https://www.vwnet.jp/Windows/PowerShell/CheckAssemblis.htm)
[-asのヒントになった記事](http://kamifuji.dyndns.org/PS-Support/PS_Excel/index.html)