ネットワーク上から取得したアセンブリを読み込む

ネットワーク上から取得したDLLファイルには、代替ストリームにどこから取得したかの情報が書き込まれ、それにより信頼できるか否かが自動的に判断される。
PowerShell等で信頼できないと判断されるDLLを読み込もうとすると例外が発生する。
(ファイルのプロパティからブロックを解除すると読み込めるようになるあれ)

"1" 個の引数を指定して "LoadFile" を呼び出し中に例外が発生しました: "ネットワーク上の場所からアセンブリを読み込もうとしました。これにより、
以前のバージョンの .NET Framework で、アセンブリがサンドボックス化された可能性があります。このリリースの .NET Framework では、
CAS ポリシーが既定で有効になっていないため、この読み込みは危険な場合があります。この読み込みがアセンブリのサンドボックス化を目的としない場合は、loadF
romRemoteSources スイッチを有効にしてください。詳細については、http://go.microsoft.com/fwlink/?LinkId=
155569 を参照してください。"
発生場所 C:\Path\To\Script\Which\Tries\To\Load\Untrusted\Assembly.psm1:19 文字:9
+         [Reflection.Assembly]::LoadFile($pathAssembly);
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : NotSupportedException

例えば企業イントラネットにおいて、ちゃんとローカルドメインを信頼できる場所として登録するのが正しいやり方だが、とは言ってもそれはインフラエンジニアの範疇になる。
アプリケーションエンジニアだけでどうにかするにはメモリ上にデータとして読み込み、それを後からアセンブリとして解釈して読み込ませてやる方法をとれば回避できる。
セキュリティリスクを抱えることになるので推奨はされないが、技術的にはできる。

もともとこんな感じのコードを

$asm = [Reflection.Assembly]::LoadFile($pathAssembly);

こうする。

# DLLファイルをメモリ上に読み込む関数を定義しておき
function Local:Read-LocalAssembly() {
    Param(
        [Parameter(Mandatory = $true, Position = 1)]
        [string] $FilePath
    )

    if(!(Test-Path -Path $FilePath)) {
        throw (New-Object -TypeName "System.IO.FileNotFoundException");
    }

    $name = ([regex]'^(.+)\.[^\.]+$').Match((Split-Path -Path $FilePath -Leaf)).Groups[1].Value;
    $asm = [System.Reflection.Assembly]::GetCallingAssembly();
    $ns = $asm.GetName().Name.ToString();
    $binr = $null;
    $str = New-Object -TypeName "System.IO.FileStream" -ArgumentList @($FilePath, [IO.FileMode]::Open, [IO.FileAccess]::Read);
    $data = $null;
    if($null -ne $str) {
        $binr = New-Object -TypeName "System.IO.BinaryReader" -ArgumentList @($str);
        $data = $binr.ReadBytes([Convert]::ToInt32($str.Length));
        $str.Close();
        $binr.Close();
    }

    return $data;
}


# ...


# 読み込む処理を書き換える
$data = Read-LocalAssembly -FilePath $pathAssembly;
$asm = [Reflection.Assembly]::Load([byte[]]$data);

メモリ上から読んでもモジュール名は維持されるので、従来通り[Assembly].GetName()からモジュール名を取得できる。

$nameAsm = "ICSharpCode.SharpZipLib";

$isLoaded = @([AppDomain]::CurrentDomain.GetAssemblies() | ? { $_.GetName().Name -eq $nameAsm }).Length -gt 0;

また副次的な効果として、通常は一度ロードされるとロックがかかるが、メモリ上のアセンブリをロードしているため元ファイルはプログラム実行中に移動・削除することができる。
例えば長時間稼働させるプログラムを止めずにアセンブリだけ再配置したい場合にはこの手法が取れる。
ただしその場合でもアセンブリ変更の反映にはプログラムの再起動が必要で、プログラムの再起動なしで反映させるにはアセンブリをロードするドメインを別にする必要がある。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です