ClickOnceアプリを引数付きで起動したい話

職場で作っているアプリを、他の自社製アプリから起動させる時に引数を付けたいな～...

...という事で、**ClickOnceアプリを引数を付けて起動する話**ですが、
<font color="Red">**前提条件が弊社環境依存でキツ目**</font>なので、汎用性としてはイマイチかもしれません...(;^_^A

##実現したいこと

- 外部アプリから引数を付けて起動できる事
- 新バージョンが存在する場合も<font color="Red">**バージョンアップ後に起動して引数を受け取れる事**</font>

##今回の前提条件

- .NET Frameworkのアプリ　※WinForms/4.7.2で確認
- ClickOnce発行のアプリで、
    - 公開のインストールモードが"アプリケーションはオフラインでも利用できる"である事
    - マニフェスト"デスクトップショートカットを作成する"がON
    - 更新オプション"アプリケーションの更新プログラムを確認する"がONで、"アプリケーションの開始前に行う"を選択

※"URLパラメータをアプリケーションに渡す事を許可する"はOFFで大丈夫です。

##実装

※VisualStudio2019で検証しています。

先ずは.NET FrameworkのWinFormsアプリを作成。　※"FWWinFormsClickOnce1"としています。
Form1にListBoxを1つ配置(listBox1)、Form1のLoadイベントを下記の通り記述します。

```C#:Form1_Load
////usingは下記が必要
//using System.Deployment.Application; //必要
//using System.Diagnostics; //バージョン取得に必要
//using System.Reflection; //バージョン取得に必要

private void Form1_Load(object sender, EventArgs e)
{
    //バージョン情報取得＆表示
    //(後に、ちゃんとバージョンアップされたかの確認用)
    FileVersionInfo ver = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location);
    listBox1.Items.Add($"<Ver.{ver.ProductVersion}>"); 

    if (ApplicationDeployment.IsNetworkDeployed)
    {
        //ClickOnceで起動した場合
        listBox1.Items.Add("<<< ClickOnceで起動 >>>");

        if (AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData != null
            && AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData.Count() > 0)
        {
            //(第2引数以降は来ない模様)
            listBox1.Items.AddRange(AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData);
        }
    }
    else
    {
        //EXEを直接起動した場合(デバッガで動かしている時とか)
        listBox1.Items.Add("<<< EXEを直接起動 >>>");
        string[] args = Environment.GetCommandLineArgs();
        listBox1.Items.AddRange(args);
    }
}
```

上記が実行できる事を確認("EXEを直接起動"が動いて表示されるハズ)して、アプリを公開します。

※アプリケーションのアセンブリ情報を公開するバージョンと合わせておきます。
　その他"今回の前提条件"に内容を揃えます。

公開後、公開フォルダに作成された"setup.exe"を実行してインストールします。
　→上手く行けばデスクトップにショートカット"FWWinFormsClickOnce1.appref-ms"が作成されます。

##実行＆検証

さて、先ずはデスクトップのショートカットで起動してみましょう。
　→バージョン情報と"ClickOnceで起動"だけが表示されているはずです。

続いて引数を付けて起動して見ましょう。
お手軽にコマンドプロンプトを開いて、ショートカットを落として引数2つを足しましょう。

```:コマンドプロンプト
C:\Users\xxxxx>C:\MyDesktop\FWWinFormsClickOnce1.appref-ms hoge1 hoge2
```

![20201222_qiita_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/954879/db6468bc-15eb-5892-f071-edb5a2df8f10.png)

第1引数は無事に表示されました。
(もうソースのコメントには書いちゃっていますがｗ)
しかしAppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData には<font color="Red">**第2引数以降は入らない**</font>ようです。

そこは工夫で何とかするという事で、空白を含めたい場合は呼び出し側で引数をダブルクォーテーションで括り、(ここでは扱いませんが)受け取り側で都合の良いように加工します。

```:コマンドプロンプト
C:\Users\xxxxx>C:\MyDesktop\FWWinFormsClickOnce1.appref-ms "hoge1 hoge2"
```

![20201222_qiita_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/954879/47a20d69-6418-7ce5-1f71-fbd00bc2b184.png)

残るは<font color="Red">**バージョンアップ後に起動して引数を受け取れる事**</font>の確認。

アプリケーションのアセンブリ情報のバージョンを上げて、もう一度"公開"します。
(※手元の環境では公開バージョンを自動インクリメントにしています)

公開後に再度、コマンドプロンプトから以下を実行します。

```:コマンドプロンプト
C:\Users\xxxxx>C:\MyDesktop\FWWinFormsClickOnce1.appref-ms "hoge1 hoge2"
```

![20201222_qiita_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/954879/db8488f4-2bc4-f272-618b-5c4cf05f2d86.png)

アプリケーションのバージョンアップと共に、無事に引数も渡せました！

最後に、呼び元のアプリケーション側のコードです。

```C#:呼び元アプリケーション側コード
//ターゲットアプリ起動
string app = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\FWWinFormsClickOnce1.appref-ms";

if (!File.Exists(app))
{
    //インストールされていない
    MessageBox.Show("アプリをインストールして下さい");
    return;
}

Process.Start(app, "\"hoge1 hoge2 hoge3!\"");
```

こちらは単純に、デスクトップのショートカットの存在有無でインストール判定、存在すれば引数を付けて実行します。

以上、使える前提は厳しめですが上手くハマるケースなら比較的お手軽に引数付き実行可能でした。
少しでも参考になれば幸いです！

##参考サイト

ApplicationDeployment.IsNetworkDeployedについて、[Kazzz's diary](http://kazzz.hatenablog.com/entry/20060708/p1)さんの記事を、
スペシャルフォルダの取得方法について、[DOBON.NET](https://dobon.net/vb/dotnet/file/getfolderpath.html)さんのページ
...を参考にさせて頂きました。多謝！