やる気駆動型エンジニアの備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CI やたまに趣味に関するブログです

Azure DevOps拡張機能で自作アプリを呼び出せるかどうか試してみた

Azure DevOps 拡張機能PowerShell を使用して開発することができますが、ちょっと PowerShell 力が足りないときに .NET 使いたいなーと思うことがありました。 しかし、リファレンス上では .NET アプリを使う方法については記載されていません。なので、未対応なのかと思いましたが「ひょっとして、PowerShell から自作した .NET アプリを呼び出すことができるのではないか?」と思い、少し試すことにしました。

結論

結論から言うと「制限はあるものの Azure DevOps の拡張機能で自作アプリ(アセンブリ)を呼び出すことは可能」でした。 制限については後ほど説明します。 また、今回は Azure Pipelines タスクの拡張機能として作成しています。 もしかすると Web ページを表示するようなタイプの拡張機能だと、この方法では実現できない可能性はあります(未検証)。

開発環境

自作アプリは .NET Core 3.1 で作成したコンソールアプリです。 このコンソールアプリを PowerShell で呼び出すような想定で作りました。

フォルダ構成

今回のフォルダ構成は細かいファイル配置は省いて次のようになっています。

/CallDotNetExample
│  .gitignore
│  package-lock.json
│  package.json
│  vss-extension.json
│  
├─console
│  │  console.csproj
│  └  Program.cs
│                      
└─src
    │  Startup.ps1
    │  task.json
    │  
    ├─dotnet
    │      console.exe
    │      console.pdb
    │      
    ├─node_modules
    │      
    │          
    └─ps_modules
        └─VstsTaskSdk

重要なのはconsole/Program.cs, src/Starup.ps1, dotnetフォルダです。

console/Program.csは、自作したコンソールアプリです。src/Startup.ps1から呼び出されることを想定しています。 src/Startup.ps1は、自作したコンソールアプリを呼び出します。この PowerShell は、Azure DevOps 拡張機能が実行された際に最初に呼ばれるエントリポイントです。 dotnetフォルダは、ビルドした自作コンソールアプリの出力先フォルダです。src/Startup.ps1は、このフォルダ配下にあるアセンブリを実行するようにしています。

実装したコード

それぞれのコードは以下のようになっています。

using System;

namespace console
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Hello World from dotnet console app!");
                System.IO.File.WriteAllText(System.IO.Path.Combine(Environment.CurrentDirectory, "example.txt"), "Hello World from dotnet console app!");
            }
            catch (System.Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}
[CmdletBinding()]
param()

try {
    $samplestring = Get-VstsInput -Name "samplestring"

    Write-Host "Hello world from Starup.ps1."

    Start-Process -FilePath "${PSScriptRoot}\dotnet\console.exe" -Wait -NoNewWindow
    
} finally {
    Trace-VstsLeavingInvocation $MyInvocation
}

Start-Process-Waitにしているのは、自作コンソールアプリを呼び出したあとに処理を待つため。 そして、-NoNewWindowにしないとコンソール出力の結果が Azure Pipelines のログに出力されないためです。

実行結果

これを実行すると次のようになりました。 期待したとおり、PowerShellHello worldC#Hello world が出力されています。

f:id:iyemon018:20210228225336p:plain

制限

今回調査した限りだと次のような制限事項があります。

アップロードサイズ

Azure DevOps 拡張機能ではパッケージしたファイルのサイズに制限があります。 試しに .NET アプリをシングルファイルにしてアップロードしようとしたところ以下のようなメッセージが表示されました。

Upload Error

The extension package size 'XXX bytes' exceeds the maximum package size '26214400 bytes'

なので、アップロード可能なサイズは26.2 MBまで。 Hello world だけのアプリをシングルファイルで生成しても 30 MB 程度はあったので、.NET のシングルファイル生成によるパッケージは不可能です。

また、アップロードファイルのサイズについて、削減する方法をまとめてくれているブログがありました。といってもこっちは正規の方法(Node.js + TypeScript)で作られているようなので、本記事とは関係は薄いです。

levelup.gitconnected.com

環境依存

上記にも関連しますが、シングルファイル生成によるパッケージが不可能ということは、拡張機能で呼び出す .NET アプリが環境に依存してしまうということです。 Azure Pipelines の特性上、Microsoftホスティングしている環境上で実行される場合は .NETフレームワークバージョンに制限があります。というより LTS でない古いバージョンは使えません(例えば、現状では .NET Core 3.0 などは Microsoftホスティングしている環境にはインストールされていない)。 この対策としてシングルファイル生成によるパッケージングができればどの環境でも動作できるのでは?と考えましたが、結果は上記のとおりでした。

他の対策は「常に .NET アプリ側を常に最新バージョンに対応させる」か、「プライベートホストを使って環境を固定する」かでしょうか。 どちらが良いかはプロダクトによるでしょうが、いずれにしろコストは掛かります。 このコストと C# / .NET が使えるメリットは考慮しておく必要があります。

検証していないこと

管理者権限で実行する

多分無理ですが、管理者権限でアプリを実行することは検証していません。 うろ覚えですが、たしか Azure DevOps 拡張機能自体、管理者権限では実行されなかったと記憶しているのでアプリ側も同じく不可能かと。

といっても管理者権限で実行したいようなケースは無いとは思いますが。

どこまでの機能が使えるか

少なくとも拡張機能の実行フォルダにファイルを出力することはできます。 しかし、それ以外の機能、例えば HttpClient で Web API を呼び出すなどのようなことが可能かどうかまでは検証できていません。 OSS ライブラリを使う場合、ものによっては Azure DevOps 側で制限がかけられる機能を使っている可能性もゼロではありません。 アプリによってはここがボトルネックになることが多いでしょうから予め検証は必要です。

まとめ

実現可能とはいえ、普通なら PowerShell でいいじゃんと多くの人が思うでしょうから、あまりにもニッチな需要かもしれません。 制限事項もあるので使い所も難しいケースもあるでしょう。 それでも使い慣れた言語が使えるというのはそれ単体では絶大なメリットだと思うので、どうしても…という場合には選択肢に追加しても良さそうです。