◆ ClearScript で V8 を使う
◆ V8 経由で COM の Excel を操作する
◆ .NET は COM を自動で解放してくれないみたい

npm には ExcelJS のようなライブラリがありますが これらは XML から xlsx ファイルを作るものです
Excel そのものを操作するより高速ですが 見た目に拘るようなものの場合 互換性的に問題が出ることもあります
Python には xlwings のようなライブラリがありますし npm にも Excel を使って操作するものは何か無いかと探してみましたが いいものはなさそうでした

一応 Windows には JScript なるものがあり ActiveXObject で Excel.Application のインスタンスを作って操作できたりはします……が やりたくないです
モダンな JavaScript がいいです

ということで C# の ClearScript 経由でやってみました

V8 エンジンの JavaScript で Excel を操作

ClearScript を依存関係に入れて Program.cs をこうしてビルドします

using Microsoft.ClearScript.V8;

using (var engine = new V8ScriptEngine())
{
engine.AddHostType("Console", typeof(Console));
engine.AddCOMType("Excel", "Excel.Application");

var source = File.ReadAllText(args[0]);

engine.Execute(source);
}

エラー処理等は手抜きですが 引数で受け取ったファイルを JavaScript として実行するものです

あとは引数に渡す JavaScript ファイルをこんな感じにします

const excel = new Excel()
const book = excel.Workbooks.Add()
const sheet = book.Worksheets.Item(1)

sheet.Range("A1").Value = 100

book.SaveAs("out1.xlsx")
excel.Quit()

これで動きました
A1 に 100 を入力した out1.xlsx ファイルが作れます

C# で exe を作らないといけない手間はありますが 全体としては結構簡単にできました

API

ところで ActiveXObject 経由で使う場合と少し API が異なるようです
どちらも Excel.Application の COM を使うはずなのですけど

以前 JScript で Excel を操作したときのコードを見るとワークシートへのアクセスは関数形式でした

book.Worksheets(1)

今回の方法だと Item メソッドが必要になります

book.Worksheets.Item(1)

Worksheets を関数のように使おうとしたら 関数じゃないのでエラーでした
ググると Worksheets をオブジェクトのように [] でアクセスする例もあって いまいちよくわからない挙動です

COM の解放

今回 終了しても Excel プロセスが残り続ける問題が起きました
COM を解放してないとこうなりますが .NET の場合はフレームワークが管理してるので GC のタイミングでリリースされるはずです
実際これまでも C# を使う場合は解放してなくてもプロセスは終了していました

もしかすると ClearScript で COM を追加すると .NET を通さず V8 が操作できるような仕組みがあるとかなんでしょうか?
一旦 V8 を介さず C# コードで直接こんな感じに置き換えました

var type = Type.GetTypeFromProgID("Excel.Application");
dynamic excel = Activator.CreateInstance(type);

dynamic book = excel.Workbooks.Add();
dynamic sheet = book.Worksheets.Item(1);

sheet.Range("A1").Value = 100;

book.SaveAs("out1.xlsx");
book.Close();
excel.Quit();

これでも発生してました

PowerShell でもやってみました
内容は上の C# コードを PowerShell にそのまま置き換えただけです
なのに PowerShell だと Excel プロセスが終了しました

何が違うのでしょうか
PowerShell だけ特殊なことなんてなさそうなのに……と思ったあたりで気づきました
この PowerShell は Windows に組み込みのレガシーな PowerShell です

ということは .NET Framework と .NET の違いがあります
C# でも .NET Framework を使えば COM を解放してくれそうです

試してみると .NET Framework だと Excel プロセスが終了しました
なぜかはよくわからないですが .NET だと COM の自動解放をやってくれないみたいです

さすがに手動で COM の全解放は辛いです
すべての操作でプロパティやメソッドをチェーンで書けないです
上の例でいうと Range は参照を保存せず直接 Value プロパティを取得しています
これだと Range の COM が解放漏れになるので 一旦変数に入れて後から解放する必要があります
これをやると面倒な上にコードがとても読みづらくなります
手作業だと漏れが出ることを考えると 全部の操作をラップして destructor で解放とかにすれば 見た目はマシになりそうですが 準備が面倒すぎて軽い気持ちで使えません

COM 操作する部分は .NET Framework を使ったほうが良さそうですね