⚙️

PowerShell モジュールで Application Insights を使用する

2022/09/15に公開

前提条件としてこちらの記事を読むことをおすすめします。

https://docs.microsoft.com/ja-jp/powershell/scripting/dev-cross-plat/resolving-dependency-conflicts?WT.mc_id=M365-MVP-5002941

C# で開発した PowerShell モジュールに Application Insights を導入するとします。PowerShell はコンソール アプリと考えることができるので Application Insights について以下の記事の手順にしたがって導入すればよさそうです。

https://docs.microsoft.com/ja-jp/azure/azure-monitor/app/worker-service?WT.mc_id=M365-MVP-5002941

コードとしてはこんな感じになります。

[Cmdlet("Invoke", "SampleApplication")]
public class SampleApplicationCommand : PSCmdlet
{

    private readonly ServiceProvider ServiceProvider = new ServiceCollection()
        .AddApplicationInsightsTelemetryWorkerService("{{instrumentation-key}}")
        .BuildServiceProvider();

    protected override void ProcessRecord()
    {
        var telemetryClient = ServiceProvider.GetService<TelemetryClient>();
        try
        {
            telemetryClient.TrackEvent(nameof(SampleApplicationCommand));
        }
        catch (Exception ex)
        {
            telemetryClient.TrackException(ex);
        }
        finally
        {
            telemetryClient.Flush();
        }
    }

}

ここで気になるのが Application Insights のバージョンについて。最初の記事を読んでいただければわかるのですが、PowerShell モジュールが読み込む外部 dll のバージョンは PowerShell 本体 (System.Management.Automation.dll) に依存します。PowerShell 7 からは Application Insights を使ってテレメトリを送信する機能が組み込まれており、当然ながら Application Insights の dll も PowerShell 本体によって読み込み済みであるため、新しいバージョンの dll を読み込もうとすると失敗します。特に公開情報はないので、リリース情報から調べてみると、Application Insights のバージョンは 2.13.1 です。

https://github.com/PowerShell/PowerShell/blob/v7.0.0/src/System.Management.Automation/System.Management.Automation.csproj

PowerShell 6 については、Application Insights を使っておらず、なによりすでにサポート切れなので、考慮しなくていいでしょう。よって 2.13.1 を使えば解決しそうです。

<ItemGroup>
    <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.13.1" />
</ItemGroup>

と、ここまでが .NET Core ベースの PowerShell の話。ご存じのこととは思いますが PowerShell には Windows にバンドルされた Windows PowerShell (5.1) があります。Application Insights 2.13.1 で作った PowerShell モジュールを Windows PowerShell で実行するとエラーになります。

Invoke-SampleApplication : ファイルまたはアセンブリ 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'、またはその依存関係の 1 つが読み込めませんでした。指定されたファイルが見つかりません。
発生場所 行:1 文字:1
+ Invoke-SampleApplication
+ ~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], FileNotFoundException
    + FullyQualifiedErrorId : System.IO.FileNotFoundException

仕方がないので Microsoft.ApplicationInsights.WorkerService のバージョンを下げていくしかないのですが、NuGet で公開されているもっとも古いバージョンでも解決できませんでした。掘り下げていくと AddApplicationInsightsTelemetryWorkerService メソッドを使うのが駄目ということがわかりました。なので Microsoft.ApplicationInsights.WorkerService そのものを諦めるしかないです。幸いコードは GitHub に公開されているので同じことをやればいいだけの気がします。

https://github.com/microsoft/ApplicationInsights-dotnet/blob/main/NETCORE/src/Microsoft.ApplicationInsights.WorkerService/ApplicationInsightsExtensions.cs

先ほどのコードを修正します。

[Cmdlet("Invoke", "SampleApplication")]
public class SampleApplicationCommand : PSCmdlet
{

    private readonly ServiceProvider ServiceProvider = new ServiceCollection()
        .AddSingleton(provider => new TelemetryConfiguration("{{instrumentation-key}}"))
        .AddSingleton<ITelemetryChannel, InMemoryChannel>()
        .AddSingleton<TelemetryClient>()
        .BuildServiceProvider();

    protected override void ProcessRecord()
    {
        var telemetryClient = ServiceProvider.GetService<TelemetryClient>();
        try
        {
            telemetryClient.TrackEvent(nameof(SampleApplicationCommand));
        }
        catch (Exception ex)
        {
            telemetryClient.TrackException(ex);
        }
        finally
        {
            telemetryClient.Flush();
        }
    }

}

念のため Microsoft.ApplicationInsights.WorkerService ではなく Microsoft.ApplicationInsights を参照するようにします。

<ItemGroup>
    <PackageReference Include="Microsoft.ApplicationInsights" Version="2.13.1" />
</ItemGroup>

本来であれば PowerShell モジュールごとに任意のバージョンの dll が呼び出せればいいのですが、.NET Core ではアプリケーション ドメインの分離がサポートされません。よって PowerShell のこの仕様はやむを得ないところはあるのですが、正直めちゃくちゃしんどいです。

Discussion