目的
VisualStudioを利用して作成したローカルアプリをウェブブラウザのJavascriptから呼び出して起動する、この様な条件のインストーラーを作成し配布可能な状態にする事が目的です。
使用するもの
- Visual Studio 2022
- 自作のApp (VSで作った今回起動させたいアプリ、Releaseまで出力しておく)
- Microsoft Visual Studio Installer Project 2022
(Winでよく見かけるインストーラーを作成する機能 VS拡張)
HowTo
ローカルApp
起動させたいAppを作成しておきます。今回はブラウザからの起動時にクエリパラメータをAppが受け取りたいので以下のようなArgsを取得するコードを入れています。
Appの名前は私が作っていたものそのままの CMMScannerとなっています。以下でCMMScannerの名前等は自分のプロジェクト名によしなに変えておいてください。
// WPFアプリなのでこんなの Formのレガシーで随分時間が止まっていたのでちょっと新鮮
public partial class App : Application
{
/// <summary>
/// 起動時に渡されたパラメータ
/// </summary>
public NameValueCollection WakeupArgs { get; private set; } = new NameValueCollection();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
if (e.Args.Length > 0)
{
string url = e.Args[0]; // 例: CMMScanner://start?port=12345&token=abcdef
Uri uri = new Uri(url);
WakeupArgs = HttpUtility.ParseQueryString(uri.Query);
foreach (var key in WakeupArgs.AllKeys)
{
Debug.WriteLine($"{key}: {WakeupArgs[key]}");
}
}
else
{
MessageBox.Show("不正な起動パラメータ");
Application.Current.Shutdown();
}
}
}
ブラウザから期待する入力としては CMMScanner://start?port=12345&token=abcdefでこの portとtokenをAppが受け取ります。
拡張機能のインストール
Microsoft Visual Studio Installer Projectは VSを起動して 拡張機能>拡張機能の管理 にてインストールできます。
Visual Studio Installerの変更などに無いかしばらく見ていたのは内緒です。そこにはありません。
Installer Projectの作成
新しいプロジェクトの作成を選択しSetup Projectを選択します。制作してあるAppと同じ場所、ソリューション名でAppを指定します。 正直、この様なInstallerプロジェクトを新規作成するなどの仕組みに、そこはかとなくレガシーな無駄感を感じますが。
Appのbin/Releaseにある作成されたAppファイルをコピーしておき、vsのInstaller ProjectのApplication Folderに右クリック>貼り付けでファイルを登録します。 D&Dなどでは登録できません。
ソリューションエクスプローラーで Deployment Project(Installer Project名の所)を選択し、プロパティからManufacturer及びProductNameを変えます。 これを変更しないと、アプリに保存されるパスが C:/Program Files/Default Company Name/CMMScanner_installer/..とかのダサいことになります。

レジストリへの登録
ブラウザのJavascriptから自作アプリを起動するためにはレジストリにMyApp.exeのパスを登録しておく必要があります。
InstallerProjectのレジストリエディタを開くためには 表示>Editor>レジストリ を選択します。こんな感じの画面が表示されるはずです。 (Classes以下は後述する自作する部分です)

Webから起動させる際のレジストリの登録先としてはHKEY_CLASSES_ROOT、HKEY_CURRENT_USER及びHKEY_LOCAL_MACHINEがあります。違いとしてはROOTは名の通り最高優先度で全ユーザに登録されます。名前にCLASSESと入っている通りMACHINEやUSERは ClassesキーがSoftware以下に入りますが、ROOTは直下に入っています。レジストリエディタを覗いてみると言葉で説明するより明確にわかると思いますが。MACHINEはローカルマシン以下の全ユーザー、USERは現在のユーザーへの適用となります。ちなみにMACHINEとUSERが被った場合はUSERが優先されるようです。
ではどれに入力すれば良いのかというと、とりあえず現ユーザーに優先して使われるUSERが良いと思います。
Class以下のKeyを作成していきます。構造としては上記の画像の通り ./Classes/MyAppName/shell/open/commandまでを作成しておきます。
MyAppName キー内に
- Default:
説明(内容は問わないが慣例として URL: MyAppName) - URL Protocol:
(Valueは空、URLスキームとして認識しない)
ちなみにValueはプロパティウィンドウからのみ編集できます。名前の編集は右クリックで直接できるのに:(
あと、Name Defaultは文字列の名前の入力を空にすると勝手にそうなります。
command キー内に
- Default:
[TARGETDIR]MyApp.exe %1
TARGETDIRは予約語でAppのインストールディレクトリのパスを表します。
%1をつけることによってブラウザから渡されるパスが取得できます。
これでInstallerの方は完了です。
TEST
制作したInstallerでAppをインストールできたら以下のJsでテストを行いました。
<button id="launch">アプリを起動</button>
<script>
document.getElementById('launch').addEventListener('click', () => {
const token = encodeURIComponent('abc123'); // 固定トークン(テスト用)
const port = 12345; // 必要ならポートなど
const url = `CMMScanner://start?token=${token}&port=${port}`;
window.location.href = url; // カスタムURLで起動
});
</script>
ブラウザからのAppの起動を確認しましたら完成です。お疲れ様でした。